mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-30 01:34:59 +03:00
feat(clients): bind host rules to individual clients in subscriptions (#6620)
Host rules applied per inbound only, so every client of an inbound got the same subscription endpoints (#6618). A client can now be bound to one or more host rules, and its raw, JSON and Clash subscriptions render only those hosts, in either subscription order; an unbound client is unchanged. A binding whose hosts are deleted, disabled or excluded from the format fails closed: the client gets no links there but stays in traffic accounting. Host edits keep row ids, so bindings survive them; nil leaves a stored binding alone and an empty list clears it.
This commit is contained in:
@@ -132,6 +132,7 @@ func (s *SubJsonService) GetJson(subId string, host string, alwaysReturnArray bo
|
||||
|
||||
var header string
|
||||
var hasInactiveExternal bool
|
||||
var hasBoundClosed bool
|
||||
var hasEnabledClient bool
|
||||
|
||||
seenEmails := make(map[string]struct{})
|
||||
@@ -150,9 +151,14 @@ func (s *SubJsonService) GetJson(subId string, host string, alwaysReturnArray bo
|
||||
continue
|
||||
}
|
||||
subReq.projectThroughFallbackMaster(inbound)
|
||||
var baseEps []map[string]any
|
||||
baseStream := inbound.StreamSettings
|
||||
// getConfig rewrites inbound.Listen/Port per endpoint while rendering a client, so bound
|
||||
// endpoints resolve against this pre-loop snapshot.
|
||||
baseInbound := *inbound
|
||||
if !subReq.hostsFirst {
|
||||
if hostEps := subReq.hostEndpoints(inbound, "json"); len(hostEps) > 0 {
|
||||
injectExternalProxy(inbound, hostEps)
|
||||
if baseEps = subReq.hostEndpoints(inbound, "json"); len(baseEps) > 0 {
|
||||
injectExternalProxy(inbound, baseEps)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,16 +169,29 @@ func (s *SubJsonService) GetJson(subId string, host string, alwaysReturnArray bo
|
||||
}
|
||||
seenEmails[client.Email] = struct{}{}
|
||||
if subReq.hostsFirst {
|
||||
for _, variant := range subReq.subscriptionHostVariants(inbound, "json") {
|
||||
variants, closed := subReq.clientHostVariants(&baseInbound, client, "json")
|
||||
hasBoundClosed = hasBoundClosed || closed
|
||||
for _, variant := range variants {
|
||||
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)...)
|
||||
continue
|
||||
}
|
||||
effective := inbound
|
||||
if effectiveEps, bound := subReq.clientHostEndpoints(&baseInbound, client, "json", baseEps); bound {
|
||||
if len(effectiveEps) == 0 {
|
||||
hasBoundClosed = true
|
||||
continue
|
||||
}
|
||||
clone := baseInbound
|
||||
clone.StreamSettings = baseStream
|
||||
injectExternalProxy(&clone, effectiveEps)
|
||||
effective = &clone
|
||||
}
|
||||
inboundConfigs = append(inboundConfigs, s.getConfig(subReq, effective, client, host)...)
|
||||
}
|
||||
if len(inboundConfigs) > 0 {
|
||||
entries = append(entries, subConfigEntry{
|
||||
@@ -254,7 +273,7 @@ func (s *SubJsonService) GetJson(subId string, host string, alwaysReturnArray bo
|
||||
}
|
||||
}
|
||||
|
||||
if len(configArray) == 0 && !hasInactiveExternal {
|
||||
if len(configArray) == 0 && !hasInactiveExternal && !hasBoundClosed {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user