mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 23:27:14 +00:00
fix(inbounds): allow negative subSortIndex for subscription order (#6465)
* fix(inbounds): allow negative subSortIndex for subscription order Preserve explicitly set negative indices so primary inbounds can sort ahead of the default without renumbering peers; keep 0/omitted → 1. * fix(inbounds): gofumpt model.go and trim subSortIndex comments --------- Co-authored-by: mrchatam <287639636+mrchatam@users.noreply.github.com>
This commit is contained in:
@@ -2604,9 +2604,8 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"sniffing": {},
|
||||
"streamSettings": {},
|
||||
"subSortIndex": {
|
||||
"description": "1-based sort order of this inbound's links in subscription output only (lower first; ties by id)",
|
||||
"description": "Sort order of this inbound's links in subscription output only (lower first; negatives allowed; 0/omitted → 1; ties by id)",
|
||||
"example": 1,
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"tag": {
|
||||
|
||||
@@ -638,7 +638,7 @@ export const InboundSchema = z.object({
|
||||
shareAddrStrategy: z.enum(['node', 'listen', 'custom']),
|
||||
sniffing: z.unknown(),
|
||||
streamSettings: z.unknown(),
|
||||
subSortIndex: z.number().int().min(1),
|
||||
subSortIndex: z.number().int(),
|
||||
tag: z.string(),
|
||||
total: z.number().int(),
|
||||
trafficReset: z.enum(['never', 'hourly', 'daily', 'weekly', 'monthly']),
|
||||
|
||||
@@ -210,7 +210,7 @@ export function rawInboundToFormValues(row: RawInboundRow): InboundFormValues {
|
||||
nodeId: row.nodeId ?? null,
|
||||
shareAddrStrategy: coerceShareAddrStrategy(row.shareAddrStrategy),
|
||||
shareAddr: row.shareAddr ?? '',
|
||||
subSortIndex: Math.max(1, row.subSortIndex ?? 1),
|
||||
subSortIndex: row.subSortIndex == null || row.subSortIndex === 0 ? 1 : row.subSortIndex,
|
||||
disableFlow: row.disableFlow ?? false,
|
||||
protocol,
|
||||
settings,
|
||||
|
||||
@@ -697,7 +697,7 @@ export default function InboundFormModal({
|
||||
t('pages.inbounds.form.subSortIndexHelp'),
|
||||
)}
|
||||
>
|
||||
<InputNumber min={1} />
|
||||
<InputNumber />
|
||||
</FormField>
|
||||
|
||||
{protocol === Protocols.VLESS && (
|
||||
|
||||
@@ -119,7 +119,7 @@ export default function InboundList({
|
||||
);
|
||||
|
||||
const hasAnySubSortIndex = useMemo(
|
||||
() => dbInbounds.some((i) => (i.subSortIndex ?? 1) > 1),
|
||||
() => dbInbounds.some((i) => (i.subSortIndex ?? 1) !== 1),
|
||||
[dbInbounds],
|
||||
);
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ export const InboundDbFieldsSchema = z.object({
|
||||
nodeId: z.number().int().nullable().optional(),
|
||||
shareAddrStrategy: ShareAddrStrategySchema.default('node'),
|
||||
shareAddr: z.string().default(''),
|
||||
subSortIndex: z.number().int().min(1).default(1),
|
||||
subSortIndex: z.number().int().default(1),
|
||||
disableFlow: z.boolean().default(false),
|
||||
});
|
||||
export type InboundDbFields = z.infer<typeof InboundDbFieldsSchema>;
|
||||
|
||||
@@ -329,10 +329,10 @@ describe('subSortIndex', () => {
|
||||
expect(values.subSortIndex).toBe(1);
|
||||
});
|
||||
|
||||
it('rawInboundToFormValues preserves valid values and clamps below-minimum ones to 1', () => {
|
||||
it('rawInboundToFormValues preserves positives and negatives; maps 0/absent to 1', () => {
|
||||
expect(rawInboundToFormValues({ ...vlessRow, subSortIndex: 5 }).subSortIndex).toBe(5);
|
||||
expect(rawInboundToFormValues({ ...vlessRow, subSortIndex: 0 }).subSortIndex).toBe(1);
|
||||
expect(rawInboundToFormValues({ ...vlessRow, subSortIndex: -10 }).subSortIndex).toBe(1);
|
||||
expect(rawInboundToFormValues({ ...vlessRow, subSortIndex: -10 }).subSortIndex).toBe(-10);
|
||||
});
|
||||
|
||||
it('formValuesToWirePayload includes subSortIndex in the payload', () => {
|
||||
@@ -348,18 +348,15 @@ describe('subSortIndex', () => {
|
||||
expect(replay.subSortIndex).toBe(42);
|
||||
});
|
||||
|
||||
it('InboundDbFieldsSchema enforces an integer minimum of 1 and defaults to 1', () => {
|
||||
it('InboundDbFieldsSchema accepts integers including negatives and defaults to 1', () => {
|
||||
// Reject for the RIGHT reason: the issue must be about subSortIndex, not some
|
||||
// unrelated field — otherwise a schema that rejects everything would pass.
|
||||
const nonInt = InboundDbFieldsSchema.partial().safeParse({ subSortIndex: 1.5 });
|
||||
expect(nonInt.success).toBe(false);
|
||||
if (!nonInt.success) expect(nonInt.error.issues[0]?.path).toContain('subSortIndex');
|
||||
|
||||
const belowMin = InboundDbFieldsSchema.partial().safeParse({ subSortIndex: 0 });
|
||||
expect(belowMin.success).toBe(false);
|
||||
if (!belowMin.success) expect(belowMin.error.issues[0]?.path).toContain('subSortIndex');
|
||||
|
||||
// A valid integer >= 1 must pass (guards against a mutant rejecting all values).
|
||||
expect(InboundDbFieldsSchema.partial().safeParse({ subSortIndex: 0 }).success).toBe(true);
|
||||
expect(InboundDbFieldsSchema.partial().safeParse({ subSortIndex: -1 }).success).toBe(true);
|
||||
expect(InboundDbFieldsSchema.partial().safeParse({ subSortIndex: 5 }).success).toBe(true);
|
||||
expect(InboundDbFieldsSchema.parse({}).subSortIndex).toBe(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user