mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 21:24:10 +00:00
fix(clients): preserve ClientRecord on inbound delete + filter Attached inbounds to multi-client protocols
Replace the global orphan sweep in setRemoteTrafficLocked with a per-inbound diff cleanup: only delete a ClientRecord whose email disappeared from a snap-tracked inbound (i.e. a node-side delete). Inbounds that vanished entirely from the snap (e.g. admin deleted the inbound on master) aren't iterated, so a client whose last attachment came from that inbound is now left alone instead of being deleted alongside the inbound. ClientFormModal and ClientBulkAddModal now filter the Attached inbounds dropdown to protocols that actually support multiple clients: shadowsocks, vless, vmess, trojan, hysteria, hysteria2, and portfallback (which routes through VLESS settings). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
ef98a932d7
commit
ad56b1bbd7
3 changed files with 71 additions and 21 deletions
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue