feat(inbound): excludeFromSub hides links without disabling (#6463)

* feat(inbound): excludeFromSub hides links without disabling

Add a per-inbound flag that omits subscription output while keeping the
inbound enabled for Xray, auth, and traffic accounting. Fixes #6435.

* fix(inbound): excludeFromSub review follow-ups

gofumpt model.go, sync docs OpenAPI, keep excludeFromSub master-authored
on node mirror, and exercise the legacy add-column migration path in tests.

* fix(sub): keep excluded inbounds' clients in the usage header

The excludeFromSub filter sat in getInboundsBySubId's SQL, so an excluded
inbound's clients never reached seenEmails in the raw, Clash or JSON
renderer. A client that lives only on a hidden inbound (one client per
inbound sharing a subId) dropped out of the Subscription-Userinfo usage,
quota and expiry and out of the info-node state, while the inbound kept
serving it and counting its traffic.

The query returns every enabled inbound again; each renderer skips an
excluded inbound's links but still counts its clients, the same rule the
Clash renderer already applies to external links it cannot express.

---------

Co-authored-by: mrchatam <mrchatam@users.noreply.github.com>
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
mrchatam
2026-09-27 13:56:37 +03:30
committed by GitHub
parent 12d51d7195
commit 6f40a75909
36 changed files with 347 additions and 0 deletions
@@ -54,6 +54,7 @@ export interface RawInboundRow {
shareAddrStrategy?: string;
shareAddr?: string;
subSortIndex?: number;
excludeFromSub?: boolean;
disableFlow?: boolean;
clientStats?: unknown;
}
@@ -83,6 +84,7 @@ export interface WireInboundPayload {
shareAddrStrategy: ShareAddrStrategy;
shareAddr: string;
subSortIndex: number;
excludeFromSub: boolean;
disableFlow: boolean;
}
@@ -219,6 +221,7 @@ export function rawInboundToFormValues(row: RawInboundRow): InboundFormValues {
shareAddrStrategy: coerceShareAddrStrategy(row.shareAddrStrategy),
shareAddr: row.shareAddr ?? '',
subSortIndex: row.subSortIndex == null || row.subSortIndex === 0 ? 1 : row.subSortIndex,
excludeFromSub: row.excludeFromSub ?? false,
disableFlow: row.disableFlow ?? false,
protocol,
settings,
@@ -387,6 +390,7 @@ export function formValuesToWirePayload(values: InboundFormValues): WireInboundP
shareAddrStrategy: values.shareAddrStrategy,
shareAddr: values.shareAddr,
subSortIndex: values.subSortIndex,
excludeFromSub: values.excludeFromSub,
disableFlow: values.disableFlow,
};
if (values.nodeId != null) payload.nodeId = values.nodeId;