|
@@ -14,7 +14,6 @@ import {
|
|
|
type OutboundTrafficRow,
|
|
type OutboundTrafficRow,
|
|
|
} from '@/schemas/xray';
|
|
} from '@/schemas/xray';
|
|
|
|
|
|
|
|
-const DIRTY_POLL_MS = 1000;
|
|
|
|
|
const DEFAULT_TEST_URL = 'https://www.google.com/generate_204';
|
|
const DEFAULT_TEST_URL = 'https://www.google.com/generate_204';
|
|
|
// One HTTP-mode batch request tests this many outbounds through a single
|
|
// One HTTP-mode batch request tests this many outbounds through a single
|
|
|
// shared temp xray instance; chunking keeps responses bounded (~30s worst
|
|
// shared temp xray instance; chunking keeps responses bounded (~30s worst
|
|
@@ -22,6 +21,10 @@ const DEFAULT_TEST_URL = 'https://www.google.com/generate_204';
|
|
|
// results progressively.
|
|
// results progressively.
|
|
|
const HTTP_BATCH_CHUNK = 16;
|
|
const HTTP_BATCH_CHUNK = 16;
|
|
|
|
|
|
|
|
|
|
+function normalizeOutboundTestUrl(url: string) {
|
|
|
|
|
+ return url || DEFAULT_TEST_URL;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
export function isUdpOutbound(outbound: unknown): boolean {
|
|
export function isUdpOutbound(outbound: unknown): boolean {
|
|
|
const o = outbound as { protocol?: string; streamSettings?: { network?: string } } | null | undefined;
|
|
const o = outbound as { protocol?: string; streamSettings?: { network?: string } } | null | undefined;
|
|
|
const p = o?.protocol;
|
|
const p = o?.protocol;
|
|
@@ -125,10 +128,11 @@ export function useXraySetting(): UseXraySettingResult {
|
|
|
staleTime: Infinity,
|
|
staleTime: Infinity,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- const [saveDisabled, setSaveDisabled] = useState(true);
|
|
|
|
|
const [xraySetting, setXraySettingState] = useState('');
|
|
const [xraySetting, setXraySettingState] = useState('');
|
|
|
const [templateSettings, setTemplateSettingsState] = useState<XraySettingsValue | null>(null);
|
|
const [templateSettings, setTemplateSettingsState] = useState<XraySettingsValue | null>(null);
|
|
|
const [outboundTestUrl, setOutboundTestUrlState] = useState(DEFAULT_TEST_URL);
|
|
const [outboundTestUrl, setOutboundTestUrlState] = useState(DEFAULT_TEST_URL);
|
|
|
|
|
+ const [savedXraySetting, setSavedXraySetting] = useState('');
|
|
|
|
|
+ const [savedOutboundTestUrl, setSavedOutboundTestUrl] = useState(DEFAULT_TEST_URL);
|
|
|
const [inboundTags, setInboundTags] = useState<string[]>([]);
|
|
const [inboundTags, setInboundTags] = useState<string[]>([]);
|
|
|
const [clientReverseTags, setClientReverseTags] = useState<string[]>([]);
|
|
const [clientReverseTags, setClientReverseTags] = useState<string[]>([]);
|
|
|
const [subscriptionOutbounds, setSubscriptionOutbounds] = useState<unknown[]>([]);
|
|
const [subscriptionOutbounds, setSubscriptionOutbounds] = useState<unknown[]>([]);
|
|
@@ -139,38 +143,40 @@ export function useXraySetting(): UseXraySettingResult {
|
|
|
const [subscriptionTestStates, setSubscriptionTestStates] = useState<Record<string, OutboundTestState>>({});
|
|
const [subscriptionTestStates, setSubscriptionTestStates] = useState<Record<string, OutboundTestState>>({});
|
|
|
const [testingAll, setTestingAll] = useState(false);
|
|
const [testingAll, setTestingAll] = useState(false);
|
|
|
|
|
|
|
|
- const oldXraySettingRef = useRef('');
|
|
|
|
|
- const oldOutboundTestUrlRef = useRef('');
|
|
|
|
|
const syncingRef = useRef(false);
|
|
const syncingRef = useRef(false);
|
|
|
const xraySettingRef = useRef('');
|
|
const xraySettingRef = useRef('');
|
|
|
const outboundTestUrlRef = useRef(outboundTestUrl);
|
|
const outboundTestUrlRef = useRef(outboundTestUrl);
|
|
|
|
|
+ const savedXraySettingRef = useRef(savedXraySetting);
|
|
|
|
|
+ const savedOutboundTestUrlRef = useRef(savedOutboundTestUrl);
|
|
|
const templateSettingsRef = useRef<XraySettingsValue | null>(null);
|
|
const templateSettingsRef = useRef<XraySettingsValue | null>(null);
|
|
|
const subscriptionOutboundsRef = useRef<unknown[]>([]);
|
|
const subscriptionOutboundsRef = useRef<unknown[]>([]);
|
|
|
|
|
|
|
|
xraySettingRef.current = xraySetting;
|
|
xraySettingRef.current = xraySetting;
|
|
|
outboundTestUrlRef.current = outboundTestUrl;
|
|
outboundTestUrlRef.current = outboundTestUrl;
|
|
|
|
|
+ savedXraySettingRef.current = savedXraySetting;
|
|
|
|
|
+ savedOutboundTestUrlRef.current = savedOutboundTestUrl;
|
|
|
templateSettingsRef.current = templateSettings;
|
|
templateSettingsRef.current = templateSettings;
|
|
|
subscriptionOutboundsRef.current = subscriptionOutbounds;
|
|
subscriptionOutboundsRef.current = subscriptionOutbounds;
|
|
|
|
|
|
|
|
- // Seed local editor state from the config query. Runs on first fetch and
|
|
|
|
|
- // every time the query refetches (e.g. after a successful save).
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
if (!configQuery.data) return;
|
|
if (!configQuery.data) return;
|
|
|
const obj = configQuery.data;
|
|
const obj = configQuery.data;
|
|
|
const pretty = JSON.stringify(obj.xraySetting, null, 2);
|
|
const pretty = JSON.stringify(obj.xraySetting, null, 2);
|
|
|
- syncingRef.current = true;
|
|
|
|
|
- setXraySettingState(pretty);
|
|
|
|
|
- setTemplateSettingsState(obj.xraySetting);
|
|
|
|
|
- oldXraySettingRef.current = pretty;
|
|
|
|
|
- syncingRef.current = false;
|
|
|
|
|
|
|
+ const nextUrl = normalizeOutboundTestUrl(obj.outboundTestUrl || '');
|
|
|
setInboundTags(obj.inboundTags || []);
|
|
setInboundTags(obj.inboundTags || []);
|
|
|
setClientReverseTags(obj.clientReverseTags || []);
|
|
setClientReverseTags(obj.clientReverseTags || []);
|
|
|
setSubscriptionOutbounds(obj.subscriptionOutbounds || []);
|
|
setSubscriptionOutbounds(obj.subscriptionOutbounds || []);
|
|
|
setSubscriptionOutboundTags(obj.subscriptionOutboundTags || []);
|
|
setSubscriptionOutboundTags(obj.subscriptionOutboundTags || []);
|
|
|
- const nextUrl = obj.outboundTestUrl || DEFAULT_TEST_URL;
|
|
|
|
|
|
|
+ const isDirty = savedXraySettingRef.current !== xraySettingRef.current
|
|
|
|
|
+ || savedOutboundTestUrlRef.current !== normalizeOutboundTestUrl(outboundTestUrlRef.current);
|
|
|
|
|
+ if (isDirty) return;
|
|
|
|
|
+ syncingRef.current = true;
|
|
|
|
|
+ setXraySettingState(pretty);
|
|
|
|
|
+ setTemplateSettingsState(obj.xraySetting);
|
|
|
|
|
+ setSavedXraySetting(pretty);
|
|
|
|
|
+ syncingRef.current = false;
|
|
|
setOutboundTestUrlState(nextUrl);
|
|
setOutboundTestUrlState(nextUrl);
|
|
|
- oldOutboundTestUrlRef.current = nextUrl;
|
|
|
|
|
- setSaveDisabled(true);
|
|
|
|
|
|
|
+ setSavedOutboundTestUrl(nextUrl);
|
|
|
}, [configQuery.data]);
|
|
}, [configQuery.data]);
|
|
|
|
|
|
|
|
const fetched = configQuery.data !== undefined || configQuery.isError;
|
|
const fetched = configQuery.data !== undefined || configQuery.isError;
|
|
@@ -220,7 +226,7 @@ export function useXraySetting(): UseXraySettingResult {
|
|
|
const saveMut = useMutation({
|
|
const saveMut = useMutation({
|
|
|
mutationFn: async () => {
|
|
mutationFn: async () => {
|
|
|
const sentXraySetting = xraySettingRef.current;
|
|
const sentXraySetting = xraySettingRef.current;
|
|
|
- const sentTestUrl = outboundTestUrlRef.current || DEFAULT_TEST_URL;
|
|
|
|
|
|
|
+ const sentTestUrl = normalizeOutboundTestUrl(outboundTestUrlRef.current);
|
|
|
const msg = await HttpUtil.post('/panel/api/xray/update', {
|
|
const msg = await HttpUtil.post('/panel/api/xray/update', {
|
|
|
xraySetting: sentXraySetting,
|
|
xraySetting: sentXraySetting,
|
|
|
outboundTestUrl: sentTestUrl,
|
|
outboundTestUrl: sentTestUrl,
|
|
@@ -229,9 +235,8 @@ export function useXraySetting(): UseXraySettingResult {
|
|
|
},
|
|
},
|
|
|
onSuccess: ({ msg, sentXraySetting, sentTestUrl }) => {
|
|
onSuccess: ({ msg, sentXraySetting, sentTestUrl }) => {
|
|
|
if (!msg?.success) return;
|
|
if (!msg?.success) return;
|
|
|
- oldXraySettingRef.current = sentXraySetting;
|
|
|
|
|
- oldOutboundTestUrlRef.current = sentTestUrl;
|
|
|
|
|
- setSaveDisabled(true);
|
|
|
|
|
|
|
+ setSavedXraySetting(sentXraySetting);
|
|
|
|
|
+ setSavedOutboundTestUrl(sentTestUrl);
|
|
|
queryClient.invalidateQueries({ queryKey: keys.xray.config() });
|
|
queryClient.invalidateQueries({ queryKey: keys.xray.config() });
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
@@ -425,14 +430,8 @@ export function useXraySetting(): UseXraySettingResult {
|
|
|
}
|
|
}
|
|
|
}, [testingAll, testOutbound, testSubscriptionOutbound, postOutboundTestBatch]);
|
|
}, [testingAll, testOutbound, testSubscriptionOutbound, postOutboundTestBatch]);
|
|
|
|
|
|
|
|
- useEffect(() => {
|
|
|
|
|
- const timer = window.setInterval(() => {
|
|
|
|
|
- const dirtyXray = oldXraySettingRef.current !== xraySettingRef.current;
|
|
|
|
|
- const dirtyUrl = oldOutboundTestUrlRef.current !== outboundTestUrlRef.current;
|
|
|
|
|
- setSaveDisabled(!(dirtyXray || dirtyUrl));
|
|
|
|
|
- }, DIRTY_POLL_MS);
|
|
|
|
|
- return () => window.clearInterval(timer);
|
|
|
|
|
- }, []);
|
|
|
|
|
|
|
+ const saveDisabled = savedXraySetting === xraySetting
|
|
|
|
|
+ && savedOutboundTestUrl === normalizeOutboundTestUrl(outboundTestUrl);
|
|
|
|
|
|
|
|
const outboundsTraffic = useMemo(() => trafficQuery.data ?? [], [trafficQuery.data]);
|
|
const outboundsTraffic = useMemo(() => trafficQuery.data ?? [], [trafficQuery.data]);
|
|
|
|
|
|