mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-06 22:22:08 +03:00
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:
@@ -294,6 +294,35 @@ describe('formValuesToWirePayload', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('excludeFromSub', () => {
|
||||
it('DBInbound constructor preserves excludeFromSub from the API row', () => {
|
||||
expect(new DBInbound({ excludeFromSub: true }).excludeFromSub).toBe(true);
|
||||
expect(new DBInbound({ excludeFromSub: false }).excludeFromSub).toBe(false);
|
||||
});
|
||||
|
||||
it('DBInbound defaults excludeFromSub to false when the API omits it', () => {
|
||||
expect(new DBInbound({ protocol: 'vless' }).excludeFromSub).toBe(false);
|
||||
expect(new DBInbound().excludeFromSub).toBe(false);
|
||||
});
|
||||
|
||||
it('rawInboundToFormValues reads excludeFromSub and defaults to false', () => {
|
||||
expect(rawInboundToFormValues({ ...vlessRow, excludeFromSub: true }).excludeFromSub).toBe(true);
|
||||
expect(rawInboundToFormValues(vlessRow).excludeFromSub).toBe(false);
|
||||
});
|
||||
|
||||
it('formValuesToWirePayload includes excludeFromSub', () => {
|
||||
const values = rawInboundToFormValues({ ...vlessRow, excludeFromSub: true });
|
||||
expect(formValuesToWirePayload(values).excludeFromSub).toBe(true);
|
||||
});
|
||||
|
||||
it('excludeFromSub survives raw → DBInbound → values → payload (the edit round-trip)', () => {
|
||||
const db = new DBInbound({ ...vlessRow, excludeFromSub: true } as unknown as DBInboundInit);
|
||||
const values = rawInboundToFormValues(db as unknown as RawInboundRow);
|
||||
const payload = formValuesToWirePayload(values);
|
||||
expect(payload.excludeFromSub).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('disableFlow', () => {
|
||||
it('DBInbound constructor preserves disableFlow from the API row', () => {
|
||||
expect(new DBInbound({ disableFlow: true }).disableFlow).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user