diff --git a/frontend/src/pages/clients/ClientBulkAddModal.vue b/frontend/src/pages/clients/ClientBulkAddModal.vue index ad7d4fc8..799c1827 100644 --- a/frontend/src/pages/clients/ClientBulkAddModal.vue +++ b/frontend/src/pages/clients/ClientBulkAddModal.vue @@ -72,11 +72,17 @@ const delayedExpireDays = computed({ set: (days) => { form.expiryTime = -86400000 * (days || 0); }, }); +const MULTI_CLIENT_PROTOCOLS = new Set([ + 'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria', 'hysteria2', 'portfallback', +]); + const inboundOptions = computed(() => - (props.inbounds || []).map((ib) => ({ - label: `${ib.remark || `#${ib.id}`} · ${ib.protocol}:${ib.port}`, - value: ib.id, - })), + (props.inbounds || []) + .filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol)) + .map((ib) => ({ + label: `${ib.remark || `#${ib.id}`} · ${ib.protocol}:${ib.port}`, + value: ib.id, + })), ); watch(() => props.open, (next) => { diff --git a/frontend/src/pages/clients/ClientFormModal.vue b/frontend/src/pages/clients/ClientFormModal.vue index b0553150..6c2244ea 100644 --- a/frontend/src/pages/clients/ClientFormModal.vue +++ b/frontend/src/pages/clients/ClientFormModal.vue @@ -85,12 +85,18 @@ function gbToBytes(gb) { return Math.round(gb * 1024 * 1024 * 1024); } +const MULTI_CLIENT_PROTOCOLS = new Set([ + 'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria', 'hysteria2', 'portfallback', +]); + const inboundOptions = computed(() => - (props.inbounds || []).map((ib) => ({ - label: `${ib.remark || `#${ib.id}`} · ${ib.protocol}:${ib.port}`, - value: ib.id, - title: `${ib.remark || ''} (${ib.protocol}:${ib.port})`, - })), + (props.inbounds || []) + .filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol)) + .map((ib) => ({ + label: `${ib.remark || `#${ib.id}`} · ${ib.protocol}:${ib.port}`, + value: ib.id, + title: `${ib.remark || ''} (${ib.protocol}:${ib.port})`, + })), ); const flowCapableIds = computed(() => { diff --git a/web/service/inbound.go b/web/service/inbound.go index 35b65084..1db5f930 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -1277,6 +1277,11 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi } } + type oldSet struct { + inboundID int + emails map[string]struct{} + } + var perInboundOld []oldSet for _, snapIb := range snap.Inbounds { if snapIb == nil { continue @@ -1285,6 +1290,19 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi if !ok { continue } + var oldEmailsRows []string + if err := tx.Table("clients"). + Joins("JOIN client_inbounds ON client_inbounds.client_id = clients.id"). + Where("client_inbounds.inbound_id = ?", c.Id). + Pluck("email", &oldEmailsRows).Error; err == nil { + oldEmails := make(map[string]struct{}, len(oldEmailsRows)) + for _, e := range oldEmailsRows { + if e != "" { + oldEmails[e] = struct{}{} + } + } + perInboundOld = append(perInboundOld, oldSet{inboundID: c.Id, emails: oldEmails}) + } clients, gcErr := s.GetClients(snapIb) if gcErr != nil { @@ -1310,20 +1328,40 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi } } - var orphanEmails []string - if err := tx.Table("clients"). - Joins("LEFT JOIN client_inbounds ON client_inbounds.client_id = clients.id"). - Where("client_inbounds.client_id IS NULL"). - Pluck("clients.email", &orphanEmails).Error; err != nil { - logger.Warning("setRemoteTraffic: orphan sweep query failed:", err) - } else if len(orphanEmails) > 0 { - if err := tx.Where("email IN ?", orphanEmails).Delete(&model.ClientRecord{}).Error; err != nil { - logger.Warning("setRemoteTraffic: orphan sweep delete ClientRecord failed:", err) + for _, old := range perInboundOld { + var stillAttached []string + if err := tx.Table("clients"). + Joins("JOIN client_inbounds ON client_inbounds.client_id = clients.id"). + Where("client_inbounds.inbound_id = ?", old.inboundID). + Pluck("email", &stillAttached).Error; err != nil { + continue } - if err := tx.Where("email IN ?", orphanEmails).Delete(&xray.ClientTraffic{}).Error; err != nil { - logger.Warning("setRemoteTraffic: orphan sweep delete ClientTraffic failed:", err) + stillSet := make(map[string]struct{}, len(stillAttached)) + for _, e := range stillAttached { + stillSet[e] = struct{}{} + } + for email := range old.emails { + if _, kept := stillSet[email]; kept { + continue + } + var attachmentCount int64 + if err := tx.Table("client_inbounds"). + Joins("JOIN clients ON clients.id = client_inbounds.client_id"). + Where("clients.email = ?", email). + Count(&attachmentCount).Error; err != nil { + continue + } + if attachmentCount > 0 { + continue + } + if err := tx.Where("email = ?", email).Delete(&model.ClientRecord{}).Error; err != nil { + logger.Warning("setRemoteTraffic: delete ClientRecord", email, "failed:", err) + } + if err := tx.Where("email = ?", email).Delete(&xray.ClientTraffic{}).Error; err != nil { + logger.Warning("setRemoteTraffic: delete ClientTraffic", email, "failed:", err) + } + structuralChange = true } - structuralChange = true } if err := tx.Commit().Error; err != nil {