From 6185db586a8d61205f5aa9aba91684cf6d7e7070 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 23 May 2026 23:05:43 +0200 Subject: [PATCH] fix(clients): drop tombstone gate that blocked re-import after delete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ClientService.Delete tombstones a just-deleted email for 90s to keep a late node snapshot from resurrecting it. The same check was also gating the create branch of SyncInbound — which silently dropped clients on any legitimate re-add (delete inbound + re-import within 90s left the clients table empty even though settings.clients carried the rows). The snapshot-side caller in setRemoteTraffic already filters tombstoned emails before handing the list to SyncInbound, so removing the duplicate check inside SyncInbound preserves the protection where it's needed and unblocks user-initiated re-imports. While here, mirror the addInbound shape in importInbound (NodeID=0→nil normalisation, early return on error, broadcastInboundsUpdate) and fan out a notifyClientsChanged from add/del/update/import so an open Clients page picks up settings.clients reconciliation without a manual refresh. --- web/controller/inbound.go | 19 +++++++++++++++---- web/service/client.go | 3 --- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/web/controller/inbound.go b/web/controller/inbound.go index a74d6e6e..a50648e3 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -166,6 +166,7 @@ func (a *InboundController) addInbound(c *gin.Context) { a.xrayService.SetToNeedRestart() } a.broadcastInboundsUpdate(user.Id) + notifyClientsChanged() } // delInbound deletes an inbound configuration by its ID. @@ -186,6 +187,7 @@ func (a *InboundController) delInbound(c *gin.Context) { } user := session.GetLoginUser(c) a.broadcastInboundsUpdate(user.Id) + notifyClientsChanged() } // updateInbound updates an existing inbound configuration. @@ -221,6 +223,7 @@ func (a *InboundController) updateInbound(c *gin.Context) { } user := session.GetLoginUser(c) a.broadcastInboundsUpdate(user.Id) + notifyClientsChanged() } // setInboundEnable flips only the enable flag of an inbound. This is a @@ -299,6 +302,9 @@ func (a *InboundController) importInbound(c *gin.Context) { user := session.GetLoginUser(c) inbound.Id = 0 inbound.UserId = user.Id + if inbound.NodeID != nil && *inbound.NodeID == 0 { + inbound.NodeID = nil + } if inbound.Tag == "" { if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) @@ -312,12 +318,17 @@ func (a *InboundController) importInbound(c *gin.Context) { inbound.ClientStats[index].Enable = true } - needRestart := false - inbound, needRestart, err = a.inboundService.AddInbound(inbound) - jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, err) - if err == nil && needRestart { + inbound, needRestart, err := a.inboundService.AddInbound(inbound) + if err != nil { + jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) + return + } + jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, nil) + if needRestart { a.xrayService.SetToNeedRestart() } + a.broadcastInboundsUpdate(user.Id) + notifyClientsChanged() } // resolveHost mirrors what sub.SubService.ResolveRequest does for the host diff --git a/web/service/client.go b/web/service/client.go index 99130017..1705aa64 100644 --- a/web/service/client.go +++ b/web/service/client.go @@ -208,9 +208,6 @@ func (s *ClientService) SyncInbound(tx *gorm.DB, inboundId int, clients []model. return err } if errors.Is(err, gorm.ErrRecordNotFound) { - if isClientEmailTombstoned(email) { - continue - } if err := tx.Create(incoming).Error; err != nil { return err }