mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-15 09:06:07 +00:00
ea24ef0a69
* feat(xray): default outbound picker in basic routing Let panel users choose which outbound handles unmatched traffic by moving it to the first position in the template outbounds list. * fix(xray): keep direct/blocked outbounds when changing default * style(routing): revert incidental whitespace churn Drop double blank lines and the reformatted function signature so the default-outbound diff stays focused on behavior.
113 lines
3.8 KiB
TypeScript
113 lines
3.8 KiB
TypeScript
import type { XraySettingsValue } from '@/hooks/useXraySetting';
|
|
import { blockedSettings, directSettings } from './constants';
|
|
|
|
export function ruleGetter(t: XraySettingsValue | null, outboundTag: string, property: string): string[] {
|
|
if (!t?.routing?.rules) return [];
|
|
const out: string[] = [];
|
|
for (const rule of t.routing.rules) {
|
|
if (
|
|
rule &&
|
|
Object.prototype.hasOwnProperty.call(rule, property) &&
|
|
Object.prototype.hasOwnProperty.call(rule, 'outboundTag') &&
|
|
rule.outboundTag === outboundTag
|
|
) {
|
|
const v = (rule as Record<string, unknown>)[property];
|
|
if (Array.isArray(v)) out.push(...(v as string[]));
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function ruleSetter(t: XraySettingsValue, outboundTag: string, property: string, data: string[]): void {
|
|
if (!t.routing) return;
|
|
if (!Array.isArray(t.routing.rules)) t.routing.rules = [];
|
|
const current = ruleGetter(t, outboundTag, property);
|
|
if (current.length === 0) {
|
|
t.routing.rules.push({ type: 'field', outboundTag, [property]: data });
|
|
return;
|
|
}
|
|
const next: typeof t.routing.rules = [];
|
|
let inserted = false;
|
|
for (const rule of t.routing.rules) {
|
|
const matches =
|
|
rule &&
|
|
Object.prototype.hasOwnProperty.call(rule, property) &&
|
|
Object.prototype.hasOwnProperty.call(rule, 'outboundTag') &&
|
|
rule.outboundTag === outboundTag;
|
|
if (matches) {
|
|
if (!inserted && data.length > 0) {
|
|
(rule as Record<string, unknown>)[property] = data;
|
|
next.push(rule);
|
|
inserted = true;
|
|
}
|
|
} else {
|
|
next.push(rule);
|
|
}
|
|
}
|
|
t.routing.rules = next;
|
|
}
|
|
|
|
export function syncOutbound(t: XraySettingsValue, tag: string, settings: Record<string, unknown>) {
|
|
if (!t.outbounds || !t.routing) return;
|
|
const rules = t.routing.rules || [];
|
|
const haveRules = rules.some((r) => r?.outboundTag === tag);
|
|
const idx = t.outbounds.findIndex((o) => o?.tag === tag);
|
|
if (!haveRules && idx > 0) t.outbounds.splice(idx, 1);
|
|
if (haveRules && idx < 0) t.outbounds.push(settings as never);
|
|
}
|
|
|
|
export function getDefaultOutboundTag(t: XraySettingsValue | null): string {
|
|
const tag = t?.outbounds?.[0]?.tag;
|
|
return typeof tag === 'string' && tag.length > 0 ? tag : 'direct';
|
|
}
|
|
|
|
export function setDefaultOutboundTag(t: XraySettingsValue, tag: string): void {
|
|
if (!tag) return;
|
|
if (!Array.isArray(t.outbounds)) t.outbounds = [];
|
|
const idx = t.outbounds.findIndex((o) => o?.tag === tag);
|
|
if (idx < 0) {
|
|
if (tag === 'direct') t.outbounds.push(directSettings as never);
|
|
else if (tag === 'blocked') t.outbounds.push(blockedSettings as never);
|
|
else return;
|
|
const newIdx = t.outbounds.length - 1;
|
|
const [moved] = t.outbounds.splice(newIdx, 1);
|
|
t.outbounds.unshift(moved);
|
|
} else if (idx > 0) {
|
|
const [moved] = t.outbounds.splice(idx, 1);
|
|
t.outbounds.unshift(moved);
|
|
}
|
|
}
|
|
|
|
export function propagateOutboundTagRename(
|
|
t: XraySettingsValue,
|
|
oldTag: string,
|
|
newTag: string,
|
|
): void {
|
|
if (!oldTag || !newTag || oldTag === newTag) return;
|
|
|
|
const rules = t.routing?.rules;
|
|
if (Array.isArray(rules)) {
|
|
for (const rule of rules) {
|
|
if (rule?.outboundTag === oldTag) rule.outboundTag = newTag;
|
|
}
|
|
}
|
|
|
|
const balancers = t.routing?.balancers;
|
|
if (Array.isArray(balancers)) {
|
|
for (const balancer of balancers) {
|
|
if (balancer?.fallbackTag === oldTag) balancer.fallbackTag = newTag;
|
|
if (Array.isArray(balancer?.selector)) {
|
|
balancer.selector = balancer.selector.map((sel) => (sel === oldTag ? newTag : sel));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(t.outbounds)) {
|
|
for (const outbound of t.outbounds) {
|
|
const sockopt = (outbound as { streamSettings?: { sockopt?: { dialerProxy?: string } } })
|
|
?.streamSettings?.sockopt;
|
|
if (sockopt?.dialerProxy === oldTag) sockopt.dialerProxy = newTag;
|
|
}
|
|
}
|
|
}
|