mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 20:54:14 +00:00
feat: auto-inherit subTotalGB from existing clients sharing the same SubID
This commit is contained in:
parent
07ac160518
commit
2f3243a2cf
2 changed files with 111 additions and 0 deletions
|
|
@ -97,6 +97,25 @@ const subTotalGB = computed({
|
|||
},
|
||||
});
|
||||
|
||||
// Auto-inherit subTotalGB when the user enters/changes a SubID that
|
||||
// already has a quota configured on other clients.
|
||||
watch(
|
||||
() => client.value?.subId,
|
||||
async (newSubId) => {
|
||||
if (!newSubId || !client.value) return;
|
||||
// Don't overwrite if the admin already set a value.
|
||||
if (client.value.subTotalGB > 0) return;
|
||||
try {
|
||||
const res = await HttpUtil.get(`/panel/api/inbounds/getSubTraffic/${newSubId}`);
|
||||
if (res.success && res.obj && res.obj.total > 0) {
|
||||
client.value.subTotalGB = res.obj.total;
|
||||
}
|
||||
} catch (_) {
|
||||
// Silently ignore — the admin can still set it manually.
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const isExpired = computed(() => {
|
||||
if (props.mode !== 'edit' || !client.value) return false;
|
||||
return client.value.expiryTime > 0 && client.value.expiryTime < Date.now();
|
||||
|
|
|
|||
|
|
@ -295,6 +295,74 @@ func (s *InboundService) normalizeStreamSettings(inbound *model.Inbound) {
|
|||
}
|
||||
}
|
||||
|
||||
// resolveSubTotalGB looks up the shared quota already configured on any
|
||||
// existing sibling client that shares the given subId. Returns the first
|
||||
// non-zero subTotalGB found, or 0 if none exists.
|
||||
func (s *InboundService) resolveSubTotalGB(subId string) int64 {
|
||||
if strings.TrimSpace(subId) == "" {
|
||||
return 0
|
||||
}
|
||||
db := database.GetDB()
|
||||
var result struct {
|
||||
SubTotalGB int64 `gorm:"column:sub_total_gb"`
|
||||
}
|
||||
err := db.Raw(`
|
||||
SELECT COALESCE(JSON_EXTRACT(client.value, '$.subTotalGB'), 0) AS sub_total_gb
|
||||
FROM inbounds,
|
||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||
WHERE REPLACE(JSON_EXTRACT(client.value, '$.subId'), '"', '') = ?
|
||||
AND COALESCE(JSON_EXTRACT(client.value, '$.subTotalGB'), 0) > 0
|
||||
LIMIT 1
|
||||
`, subId).Scan(&result).Error
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return result.SubTotalGB
|
||||
}
|
||||
|
||||
// inheritSubTotalGB mutates the settings JSON in-place: for every client
|
||||
// whose subId matches an existing group with a non-zero subTotalGB, the
|
||||
// client's own subTotalGB is auto-filled if it was left at zero.
|
||||
func (s *InboundService) inheritSubTotalGB(settings *map[string]any) {
|
||||
iClients, ok := (*settings)["clients"].([]any)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// Cache lookups per subId to avoid repeated DB hits.
|
||||
cache := make(map[string]int64)
|
||||
changed := false
|
||||
for i, ic := range iClients {
|
||||
cm, ok := ic.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
subId, _ := cm["subId"].(string)
|
||||
if strings.TrimSpace(subId) == "" {
|
||||
continue
|
||||
}
|
||||
stgb, _ := cm["subTotalGB"].(float64)
|
||||
if stgb > 0 {
|
||||
// Already has a quota — cache it for siblings.
|
||||
cache[subId] = int64(stgb)
|
||||
continue
|
||||
}
|
||||
// Look up from cache or DB.
|
||||
existing, cached := cache[subId]
|
||||
if !cached {
|
||||
existing = s.resolveSubTotalGB(subId)
|
||||
cache[subId] = existing
|
||||
}
|
||||
if existing > 0 {
|
||||
cm["subTotalGB"] = existing
|
||||
iClients[i] = cm
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
(*settings)["clients"] = iClients
|
||||
}
|
||||
}
|
||||
|
||||
// AddInbound creates a new inbound configuration.
|
||||
// It validates port uniqueness, client email uniqueness, and required fields,
|
||||
// then saves the inbound to the database and optionally adds it to the running Xray instance.
|
||||
|
|
@ -341,6 +409,25 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
|||
c.UpdatedAt = now
|
||||
updatedClients = append(updatedClients, c)
|
||||
}
|
||||
// Auto-inherit subTotalGB from existing SubID siblings.
|
||||
stgbCache := make(map[string]int64)
|
||||
for i, c := range updatedClients {
|
||||
subId := strings.TrimSpace(c.SubID)
|
||||
if subId == "" || c.SubTotalGB > 0 {
|
||||
if subId != "" && c.SubTotalGB > 0 {
|
||||
stgbCache[subId] = c.SubTotalGB
|
||||
}
|
||||
continue
|
||||
}
|
||||
existing, cached := stgbCache[subId]
|
||||
if !cached {
|
||||
existing = s.resolveSubTotalGB(subId)
|
||||
stgbCache[subId] = existing
|
||||
}
|
||||
if existing > 0 {
|
||||
updatedClients[i].SubTotalGB = existing
|
||||
}
|
||||
}
|
||||
settings["clients"] = updatedClients
|
||||
if bs, err3 := json.MarshalIndent(settings, "", " "); err3 == nil {
|
||||
inbound.Settings = string(bs)
|
||||
|
|
@ -863,6 +950,8 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
|||
interfaceClients[i] = cm
|
||||
}
|
||||
}
|
||||
// Auto-inherit subTotalGB from existing SubID siblings.
|
||||
s.inheritSubTotalGB(&settings)
|
||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
@ -1413,6 +1502,9 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||
}
|
||||
}
|
||||
|
||||
// Auto-inherit subTotalGB from existing SubID siblings.
|
||||
s.inheritSubTotalGB(&oldSettings)
|
||||
|
||||
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
|
|||
Loading…
Reference in a new issue