mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 15:17:14 +00:00
fix(clients): flag the restart a partly-applied edit or delete still needs
63b46cd6made a multi-inbound client op apply its inbounds concurrently and stop aborting at the first failure, so an error can now come back together with needRestart=true: the inbounds that succeeded committed real changes and their Xray still needs the restart. That commit taught the two callers it converted — create and attach — to read the flag before the error check.d34ec97fthen routed Update, Delete, Detach and DeleteByEmail's record-less fallback through the same fanout but touched no caller, so on a master with several nodes a partly-applied edit or delete returned (true, err) into a handler that returned on err first. Xray was never flagged for the work that landed and notifyClientsChanged never fired, so the running config kept serving the pre-edit client set and every open panel showed stale rows until something else happened to trigger a restart. Read needRestart before the error check in update, delete and detach, and broadcast on needRestart || err == nil — the same shape create and attach have had since63b46cd6. The predicate is a strict superset of the old err == nil, and needRestart is only ever assigned after a runSerializedTx commit, so it firing genuinely means something landed. The three handlers are pinned by a new controller test each: one client on two inbounds, the second one's settings JSON corrupted so the op commits on one and fails on the other, asserting both the success:false response and the restart flag. All three fail without the change. The API docs for update, del and detach now describe the partial-application contract, as add and attach already did. Detach ends at the fanout so every one of its errors carries the inbound prefix; update and delete write the client record afterwards, and a failure there is reported without one.
This commit is contained in:
@@ -215,30 +215,42 @@ func (a *ClientController) update(c *gin.Context) {
|
||||
}
|
||||
inboundFilter := parseInboundIdsQuery(c.Query("inboundIds"))
|
||||
needRestart, err := a.clientService.UpdateByEmail(&a.inboundService, email, req.Client, req.LimitHwid, inboundFilter...)
|
||||
// Flagged before the error check: a partly-applied edit leaves the change
|
||||
// committed on the inbounds that succeeded, and those still need the restart.
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
// A partly-applied call committed real changes; a rejected one touched
|
||||
// nothing, and broadcasting those would refetch every panel for nothing.
|
||||
if needRestart || err == nil {
|
||||
notifyClientsChanged()
|
||||
}
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||
return
|
||||
}
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), pendingNodeObj(a.clientService.HasPendingNode(&a.inboundService, email)), nil)
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
notifyClientsChanged()
|
||||
}
|
||||
|
||||
func (a *ClientController) delete(c *gin.Context) {
|
||||
email := c.Param("email")
|
||||
keepTraffic := c.Query("keepTraffic") == "1"
|
||||
needRestart, err := a.clientService.DeleteByEmail(&a.inboundService, email, keepTraffic)
|
||||
// Flagged before the error check: a partly-applied delete already removed
|
||||
// the client from the inbounds that succeeded, and those need the restart.
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
// A partly-applied call committed real removals; a rejected one touched
|
||||
// nothing, and broadcasting those would refetch every panel for nothing.
|
||||
if needRestart || err == nil {
|
||||
notifyClientsChanged()
|
||||
}
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientDeleteSuccess"), nil)
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
notifyClientsChanged()
|
||||
}
|
||||
|
||||
type attachDetachBody struct {
|
||||
@@ -640,15 +652,21 @@ func (a *ClientController) detach(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
needRestart, err := a.clientService.DetachByEmailMany(&a.inboundService, email, body.InboundIds)
|
||||
// Flagged before the error check: a partly-applied detach already removed
|
||||
// the client from the inbounds that succeeded, and those need the restart.
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
// A partly-applied call committed real removals; a rejected one touched
|
||||
// nothing, and broadcasting those would refetch every panel for nothing.
|
||||
if needRestart || err == nil {
|
||||
notifyClientsChanged()
|
||||
}
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||
return
|
||||
}
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientDeleteSuccess"), pendingNodeObj(a.inboundService.AnyNodePending(body.InboundIds)), nil)
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
notifyClientsChanged()
|
||||
}
|
||||
|
||||
type bulkResetRequest struct {
|
||||
|
||||
Reference in New Issue
Block a user