mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-25 07:15:01 +03:00
feat(hosts): add drag-and-drop and global subscription ordering (#6895)
Hosts already had one saved order on the Hosts page, but subscriptions honoured it only inside each inbound, so a host of a later inbound could never come before one of an earlier inbound (#6894). A new opt-in setting makes raw/base64, Clash/Mihomo and JSON subscriptions follow the Hosts page order across inbounds; the default keeps today's inbound-first output unchanged. The Hosts table gains drag-and-drop reordering next to the Up/Down buttons, on a pointer-reorder hook the Routing Rules table now shares.
This commit is contained in:
@@ -136,6 +136,7 @@ func (s *SubJsonService) GetJson(subId string, host string, alwaysReturnArray bo
|
||||
|
||||
seenEmails := make(map[string]struct{})
|
||||
entries := make([]subConfigEntry, 0, len(inbounds))
|
||||
var ordered []orderedSubEntry[func() subConfigEntry]
|
||||
// Prepare Inbounds
|
||||
for _, inbound := range inbounds {
|
||||
clients := subReq.matchingClients(inbound, subId)
|
||||
@@ -149,8 +150,10 @@ func (s *SubJsonService) GetJson(subId string, host string, alwaysReturnArray bo
|
||||
continue
|
||||
}
|
||||
subReq.projectThroughFallbackMaster(inbound)
|
||||
if hostEps := subReq.hostEndpoints(inbound, "json"); len(hostEps) > 0 {
|
||||
injectExternalProxy(inbound, hostEps)
|
||||
if !subReq.hostsFirst {
|
||||
if hostEps := subReq.hostEndpoints(inbound, "json"); len(hostEps) > 0 {
|
||||
injectExternalProxy(inbound, hostEps)
|
||||
}
|
||||
}
|
||||
|
||||
var inboundConfigs []json_util.RawMessage
|
||||
@@ -159,7 +162,17 @@ func (s *SubJsonService) GetJson(subId string, host string, alwaysReturnArray bo
|
||||
hasEnabledClient = true
|
||||
}
|
||||
seenEmails[client.Email] = struct{}{}
|
||||
inboundConfigs = append(inboundConfigs, s.getConfig(subReq, inbound, client, host)...)
|
||||
if subReq.hostsFirst {
|
||||
for _, variant := range subReq.subscriptionHostVariants(inbound, "json") {
|
||||
render := func() subConfigEntry {
|
||||
configs := s.getConfig(subReq, variant.inbound(subReq, inbound), client, host)
|
||||
return subConfigEntry{sortIndex: inbound.SubSortIndex, id: inbound.Id, configs: configs, hostOrder: variant.order}
|
||||
}
|
||||
ordered = append(ordered, orderedSubEntry[func() subConfigEntry]{variant.order, render})
|
||||
}
|
||||
} else {
|
||||
inboundConfigs = append(inboundConfigs, s.getConfig(subReq, inbound, client, host)...)
|
||||
}
|
||||
}
|
||||
if len(inboundConfigs) > 0 {
|
||||
entries = append(entries, subConfigEntry{
|
||||
@@ -169,6 +182,14 @@ func (s *SubJsonService) GetJson(subId string, host string, alwaysReturnArray bo
|
||||
})
|
||||
}
|
||||
}
|
||||
if subReq.hostsFirst {
|
||||
// Render in final endpoint order so first-link usage remarks stay first.
|
||||
for _, render := range orderedSubValues(ordered) {
|
||||
if entry := render(); len(entry.configs) > 0 {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
entries = s.appendBalancerEntries(entries)
|
||||
|
||||
// Inbounds arrive sorted by (sub_sort_index, id); balancers interleave by
|
||||
@@ -183,6 +204,23 @@ func (s *SubJsonService) GetJson(subId string, host string, alwaysReturnArray bo
|
||||
return entries[i].id < entries[j].id
|
||||
})
|
||||
var configArray []json_util.RawMessage
|
||||
if subReq.hostsFirst {
|
||||
// Preserve balancer slots from the legacy sort; reorder endpoint slots only.
|
||||
var endpoints []subConfigEntry
|
||||
for _, entry := range entries {
|
||||
if entry.kind == 0 {
|
||||
endpoints = append(endpoints, entry)
|
||||
}
|
||||
}
|
||||
sort.SliceStable(endpoints, func(i, j int) bool { return hostOrderLess(endpoints[i].hostOrder, endpoints[j].hostOrder) })
|
||||
next := 0
|
||||
for i := range entries {
|
||||
if entries[i].kind == 0 {
|
||||
entries[i] = endpoints[next]
|
||||
next++
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, entry := range entries {
|
||||
configArray = append(configArray, entry.configs...)
|
||||
}
|
||||
@@ -255,6 +293,7 @@ func (s *SubJsonService) GetJson(subId string, host string, alwaysReturnArray bo
|
||||
// subConfigEntry is one ordered block of the JSON subscription: an inbound's
|
||||
// configs (kind 0) or a balancer config (kind 1).
|
||||
type subConfigEntry struct {
|
||||
hostOrder *hostEntryOrder
|
||||
sortIndex int
|
||||
kind int
|
||||
id int
|
||||
|
||||
Reference in New Issue
Block a user