mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-05 23:02:14 +03:00
* feat: add enable/disable toggle for xray routing rules * fix(routing): never let the internal api rule be disabled The Enable/Disable toggle could strip the stats api rule: its table switch was locked, but the rule-form modal's Enable dropdown was not, and stripDisabledRules had no api-rule guard (EnsureStatsRouting's delete only runs when the api rule isn't already first). A disabled api rule then dropped out of the generated config and broke traffic accounting. - stripDisabledRules now always keeps the api rule, even if marked disabled, and strips the panel-only enabled key from every rule - extract isApiRule helper (backend + frontend) and reuse it across the table switch, card switch, and form modal - disable the form-modal Enable dropdown for the api rule - add stripDisabledRules tests covering the api-rule survival path --------- Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
79 lines
2.7 KiB
TypeScript
79 lines
2.7 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
export const RuleProtocolSchema = z.enum(['http', 'tls', 'quic', 'bittorrent']);
|
|
export type RuleProtocol = z.infer<typeof RuleProtocolSchema>;
|
|
|
|
const PortValueSchema = z.union([
|
|
z.number().int().min(0).max(65535),
|
|
z.string(),
|
|
]);
|
|
|
|
export const RuleWebhookSchema = z.object({
|
|
url: z.string(),
|
|
deduplication: z.number().int().min(0).optional(),
|
|
headers: z.record(z.string(), z.string()).optional(),
|
|
});
|
|
export type RuleWebhook = z.infer<typeof RuleWebhookSchema>;
|
|
|
|
export const RuleObjectSchema = z.object({
|
|
type: z.literal('field').default('field'),
|
|
enabled: z.boolean().optional(),
|
|
domain: z.array(z.string()).optional(),
|
|
ip: z.array(z.string()).optional(),
|
|
port: PortValueSchema.optional(),
|
|
sourcePort: PortValueSchema.optional(),
|
|
localPort: PortValueSchema.optional(),
|
|
network: z.string().optional(),
|
|
sourceIP: z.array(z.string()).optional(),
|
|
localIP: z.array(z.string()).optional(),
|
|
user: z.array(z.string()).optional(),
|
|
vlessRoute: PortValueSchema.optional(),
|
|
inboundTag: z.array(z.string()).optional(),
|
|
protocol: z.array(z.string()).optional(),
|
|
attrs: z.record(z.string(), z.string()).optional(),
|
|
process: z.array(z.string()).optional(),
|
|
outboundTag: z.string().optional(),
|
|
balancerTag: z.string().optional(),
|
|
ruleTag: z.string().optional(),
|
|
webhook: RuleWebhookSchema.optional(),
|
|
});
|
|
export type RuleObject = z.infer<typeof RuleObjectSchema>;
|
|
|
|
export const BalancerStrategyTypeSchema = z.enum([
|
|
'random',
|
|
'roundRobin',
|
|
'leastPing',
|
|
'leastLoad',
|
|
]);
|
|
export type BalancerStrategyType = z.infer<typeof BalancerStrategyTypeSchema>;
|
|
|
|
export const BalancerCostObjectSchema = z.object({
|
|
regexp: z.boolean().default(false),
|
|
match: z.string(),
|
|
value: z.number(),
|
|
});
|
|
export type BalancerCostObject = z.infer<typeof BalancerCostObjectSchema>;
|
|
|
|
export const BalancerStrategySettingsSchema = z.object({
|
|
expected: z.number().int().min(0).optional(),
|
|
maxRTT: z.string().optional(),
|
|
tolerance: z.number().min(0).max(1).optional(),
|
|
baselines: z.array(z.string()).optional(),
|
|
costs: z.array(BalancerCostObjectSchema).optional(),
|
|
});
|
|
export type BalancerStrategySettings = z.infer<typeof BalancerStrategySettingsSchema>;
|
|
|
|
export const BalancerStrategySchema = z.object({
|
|
type: BalancerStrategyTypeSchema.default('random'),
|
|
settings: BalancerStrategySettingsSchema.optional(),
|
|
});
|
|
export type BalancerStrategy = z.infer<typeof BalancerStrategySchema>;
|
|
|
|
export const BalancerObjectSchema = z.object({
|
|
tag: z.string().trim().min(1),
|
|
selector: z.array(z.string()).min(1),
|
|
fallbackTag: z.string().optional(),
|
|
strategy: BalancerStrategySchema.optional(),
|
|
});
|
|
export type BalancerObject = z.infer<typeof BalancerObjectSchema>;
|