mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-09 21:00:58 +00:00
inbounds: allow custom monthly traffic reset days (#6071)
This commit is contained in:
@@ -1868,6 +1868,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"trafficResetDay": {
|
||||
"description": "Day of month for monthly traffic resets",
|
||||
"example": 1,
|
||||
"maximum": 31,
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"up": {
|
||||
"description": "Upload traffic in bytes",
|
||||
"format": "int64",
|
||||
@@ -1894,6 +1901,7 @@
|
||||
"tag",
|
||||
"total",
|
||||
"trafficReset",
|
||||
"trafficResetDay",
|
||||
"up"
|
||||
],
|
||||
"type": "object"
|
||||
@@ -3159,6 +3167,7 @@
|
||||
"tag": "in-443-tcp",
|
||||
"total": 0,
|
||||
"trafficReset": "never",
|
||||
"trafficResetDay": 1,
|
||||
"up": 0
|
||||
}
|
||||
]
|
||||
|
||||
@@ -450,6 +450,7 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"tag": "in-443-tcp",
|
||||
"total": 0,
|
||||
"trafficReset": "never",
|
||||
"trafficResetDay": 1,
|
||||
"up": 0
|
||||
},
|
||||
"InboundClientIps": {
|
||||
|
||||
@@ -1842,6 +1842,13 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"trafficResetDay": {
|
||||
"description": "Day of month for monthly traffic resets",
|
||||
"example": 1,
|
||||
"maximum": 31,
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"up": {
|
||||
"description": "Upload traffic in bytes",
|
||||
"format": "int64",
|
||||
@@ -1868,6 +1875,7 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"tag",
|
||||
"total",
|
||||
"trafficReset",
|
||||
"trafficResetDay",
|
||||
"up"
|
||||
],
|
||||
"type": "object"
|
||||
|
||||
@@ -428,6 +428,7 @@ export interface Inbound {
|
||||
tag: string;
|
||||
total: number;
|
||||
trafficReset: string;
|
||||
trafficResetDay: number;
|
||||
up: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -453,6 +453,7 @@ export const InboundSchema = z.object({
|
||||
tag: z.string(),
|
||||
total: z.number().int(),
|
||||
trafficReset: z.enum(['never', 'hourly', 'daily', 'weekly', 'monthly']),
|
||||
trafficResetDay: z.number().int().min(1).max(31),
|
||||
up: z.number().int(),
|
||||
});
|
||||
export type Inbound = z.infer<typeof InboundSchema>;
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface RawInboundRow {
|
||||
enable?: boolean;
|
||||
expiryTime?: number;
|
||||
trafficReset?: string;
|
||||
trafficResetDay?: number;
|
||||
lastTrafficResetTime?: number;
|
||||
nodeId?: number | null;
|
||||
shareAddrStrategy?: string;
|
||||
@@ -60,6 +61,7 @@ export interface WireInboundPayload {
|
||||
enable: boolean;
|
||||
expiryTime: number;
|
||||
trafficReset: TrafficReset;
|
||||
trafficResetDay: number;
|
||||
lastTrafficResetTime: number;
|
||||
listen: string;
|
||||
port: number;
|
||||
@@ -202,6 +204,7 @@ export function rawInboundToFormValues(row: RawInboundRow): InboundFormValues {
|
||||
down: row.down ?? 0,
|
||||
total: row.total ?? 0,
|
||||
trafficReset: coerceTrafficReset(row.trafficReset),
|
||||
trafficResetDay: Math.min(31, Math.max(1, row.trafficResetDay ?? 1)),
|
||||
lastTrafficResetTime: row.lastTrafficResetTime ?? 0,
|
||||
nodeId: row.nodeId ?? null,
|
||||
shareAddrStrategy: coerceShareAddrStrategy(row.shareAddrStrategy),
|
||||
@@ -344,6 +347,7 @@ export function formValuesToWirePayload(values: InboundFormValues): WireInboundP
|
||||
enable: values.enable,
|
||||
expiryTime: values.expiryTime,
|
||||
trafficReset: values.trafficReset,
|
||||
trafficResetDay: values.trafficResetDay,
|
||||
lastTrafficResetTime: values.lastTrafficResetTime,
|
||||
listen: values.listen,
|
||||
port: values.port,
|
||||
|
||||
@@ -30,6 +30,7 @@ export type DBInboundInit = Partial<{
|
||||
enable: boolean;
|
||||
expiryTime: number;
|
||||
trafficReset: string;
|
||||
trafficResetDay: number;
|
||||
lastTrafficResetTime: number;
|
||||
listen: string;
|
||||
port: number;
|
||||
@@ -76,6 +77,7 @@ export class DBInbound {
|
||||
enable: boolean;
|
||||
expiryTime: number;
|
||||
trafficReset: string;
|
||||
trafficResetDay: number;
|
||||
lastTrafficResetTime: number;
|
||||
|
||||
listen: string;
|
||||
@@ -105,6 +107,7 @@ export class DBInbound {
|
||||
this.enable = true;
|
||||
this.expiryTime = 0;
|
||||
this.trafficReset = "never";
|
||||
this.trafficResetDay = 1;
|
||||
this.lastTrafficResetTime = 0;
|
||||
|
||||
this.listen = "";
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
isSS2022,
|
||||
} from '@/lib/xray/protocol-capabilities';
|
||||
import {
|
||||
InboundDbFieldsSchema,
|
||||
InboundFormBaseSchema,
|
||||
InboundFormSchema,
|
||||
type InboundFormValues,
|
||||
@@ -255,6 +256,7 @@ export default function InboundFormModal({
|
||||
const wTunnelNetwork = useWatch({ control, name: 'settings.allowedNetwork' });
|
||||
const wTotal = (useWatch({ control, name: 'total' }) as number | undefined) ?? 0;
|
||||
const wExpiry = (useWatch({ control, name: 'expiryTime' }) as number | undefined) ?? 0;
|
||||
const trafficReset = useWatch({ control, name: 'trafficReset' }) ?? 'never';
|
||||
const autoTagRef = useRef(true);
|
||||
const lastWrittenTagRef = useRef('');
|
||||
const currentTagInput = (): InboundTagInput => ({
|
||||
@@ -619,6 +621,16 @@ export default function InboundFormModal({
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{trafficReset === 'monthly' && (
|
||||
<FormField
|
||||
name="trafficResetDay"
|
||||
label={t('pages.inbounds.periodicTrafficResetDay')}
|
||||
rules={{ validate: rhfZodValidate(InboundDbFieldsSchema.shape.trafficResetDay) }}
|
||||
>
|
||||
<InputNumber min={1} max={31} />
|
||||
</FormField>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<Tooltip title={t('pages.inbounds.leaveBlankToNeverExpire')}>
|
||||
|
||||
@@ -22,6 +22,7 @@ export const InboundDbFieldsSchema = z.object({
|
||||
down: z.number().int().min(0).default(0),
|
||||
total: z.number().int().min(0).default(0),
|
||||
trafficReset: TrafficResetSchema.default('never'),
|
||||
trafficResetDay: z.number().int().min(1).max(31).default(1),
|
||||
lastTrafficResetTime: z.number().int().default(0),
|
||||
nodeId: z.number().int().nullable().optional(),
|
||||
shareAddrStrategy: ShareAddrStrategySchema.default('node'),
|
||||
|
||||
@@ -32,6 +32,7 @@ const vlessRow: RawInboundRow = {
|
||||
total: 1_000_000_000,
|
||||
expiryTime: 0,
|
||||
trafficReset: 'monthly',
|
||||
trafficResetDay: 15,
|
||||
lastTrafficResetTime: 0,
|
||||
tag: 'inbound-1',
|
||||
nodeId: null,
|
||||
@@ -267,6 +268,7 @@ describe('formValuesToWirePayload', () => {
|
||||
enable: payload.enable,
|
||||
expiryTime: payload.expiryTime,
|
||||
trafficReset: payload.trafficReset,
|
||||
trafficResetDay: payload.trafficResetDay,
|
||||
lastTrafficResetTime: payload.lastTrafficResetTime,
|
||||
nodeId: payload.nodeId ?? null,
|
||||
});
|
||||
@@ -276,8 +278,13 @@ describe('formValuesToWirePayload', () => {
|
||||
expect(replay.listen).toBe(original.listen);
|
||||
expect(replay.up).toBe(original.up);
|
||||
expect(replay.down).toBe(original.down);
|
||||
expect(replay.trafficResetDay).toBe(original.trafficResetDay);
|
||||
expect(replay.streamSettings).toEqual(original.streamSettings);
|
||||
});
|
||||
|
||||
it('defaults a missing monthly reset day to the first', () => {
|
||||
expect(rawInboundToFormValues({ ...vlessRow, trafficResetDay: undefined }).trafficResetDay).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('subSortIndex', () => {
|
||||
|
||||
Reference in New Issue
Block a user