mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-30 19:22:10 +03:00
fix(inbounds): keep the stored client list and enable on inbound save
Invariant: saving an inbound's configuration never changes which clients it holds nor whether it is enabled; both have their own endpoints. The edit modal posts back the clients and the enable flag it loaded when it opened. A client added meanwhile (another admin, the bot, the API, LDAP) was detached and its stats deleted; a client deleted meanwhile came back with its credentials, restoring access that had been revoked; an inbound switched off meanwhile was switched back on. For every save but a master's node-sync push, UpdateInbound now takes the client list and enable from the row it re-reads inside the writer; this replaces the lifecycle-only carry from the previous commit. Client validation (renewal schedule, Hysteria auth, TUIC credentials) moves after that swap so it judges the clients actually saved: a protocol switch keeps the stored clients, and #6268's refusal must apply to them. The edit form no longer loads or sends clients, so neither the JSON editor nor validation sees a copy the server ignores, and the enable switch shows only when adding; the list toggle (/setEnable) covers existing inbounds. Tests that added or re-keyed clients through a panel inbound save pinned the old rule; they now drive the master-push path, where payload clients still apply.
This commit is contained in:
@@ -140,44 +140,22 @@ func mergeClientLists(base, ours, current []any) []any {
|
||||
return out
|
||||
}
|
||||
|
||||
// Client limits and state the inbound form never owns: the client endpoints and
|
||||
// traffic jobs change them, so a form opened earlier must not write them back.
|
||||
var storedClientLifecycleKeys = []string{"enable", "expiryTime", "totalGB", "reset", "resetDay", "resetWeekday", "resetMax"}
|
||||
|
||||
// keepStoredClientLifecycle copies those keys from the stored settings onto every
|
||||
// payload client the inbound already holds; new clients keep what they carry.
|
||||
func keepStoredClientLifecycle(payload, stored string) string {
|
||||
// keepStoredClients puts the stored client list back into an inbound save's
|
||||
// payload: clients change through the client endpoints, never this form.
|
||||
func keepStoredClients(payload, stored string) string {
|
||||
var payloadM, storedM map[string]any
|
||||
if json.Unmarshal([]byte(payload), &payloadM) != nil || json.Unmarshal([]byte(stored), &storedM) != nil {
|
||||
return payload
|
||||
}
|
||||
storedClients, _ := storedM["clients"].([]any)
|
||||
storedBy := indexClientsByEmail(storedClients)
|
||||
payloadClients, _ := payloadM["clients"].([]any)
|
||||
changed := false
|
||||
for _, entry := range payloadClients {
|
||||
p, email := clientEntryEmail(entry)
|
||||
s, known := storedBy[email]
|
||||
if !known {
|
||||
continue
|
||||
}
|
||||
for _, key := range storedClientLifecycleKeys {
|
||||
sv, inStored := s[key]
|
||||
pv, inPayload := p[key]
|
||||
if inStored == inPayload && reflect.DeepEqual(sv, pv) {
|
||||
continue
|
||||
}
|
||||
changed = true
|
||||
if inStored {
|
||||
p[key] = sv
|
||||
} else {
|
||||
delete(p, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
storedClients, has := storedM["clients"]
|
||||
if reflect.DeepEqual(payloadM["clients"], storedClients) {
|
||||
return payload
|
||||
}
|
||||
if has {
|
||||
payloadM["clients"] = storedClients
|
||||
} else {
|
||||
delete(payloadM, "clients")
|
||||
}
|
||||
b, err := json.MarshalIndent(payloadM, "", " ")
|
||||
if err != nil {
|
||||
return payload
|
||||
|
||||
Reference in New Issue
Block a user