mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-13 09:36:05 +00:00
Replaces the placeholder on the Basics tab with a structured form for the most-touched fields of the xray template — outbound + routing strategy, log levels, traffic stat counters, and the "basic routing" shortcuts (block torrent / IPs / domains, direct IPs / domains, IPv4 forced, WARP / NordVPN routing). - useXraySetting.js: hoists a parsed `templateSettings` reactive alongside the JSON string, with two cooperating watches that keep them in sync. Editing structured fields stringifies into xraySetting for the dirty-poll + Advanced JSON tab; editing the JSON re-parses into templateSettings only when valid, so structured tabs stay readable mid-edit. - BasicsTab.vue: collapse panels mirror the legacy partial — General, Statistics, Logs, Basic routing. Every input is a computed v-model reading/writing into templateSettings; the routing-rule shortcuts funnel through ruleGetter/ruleSetter which match the legacy templateRuleGetter/templateRuleSetter behavior (replace-first, drop-duplicates, pop-the-rule-when-empty). Direct/IPv4 setters also call syncOutbound() to provision/prune the matching outbound. - XrayPage.vue: imports BasicsTab + derives `warpExist`/`nordExist` from the parsed templateSettings. WARP/NordVPN provisioning modals are still placeholders that toast — those land in 6-v with the routing/outbound editors. Default tab flips back to Basics so users land on the structured editor. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
157 lines
4.8 KiB
JavaScript
157 lines
4.8 KiB
JavaScript
// Drives the xray page's fetch / dirty / save lifecycle. The Go side
|
|
// returns the live xraySetting (the full JSON config), the inboundTags
|
|
// list, and a few sidecar values (clientReverseTags, outboundTestUrl)
|
|
// the structured tabs need. We keep the JSON as a string here — pretty-
|
|
// printed for the textarea; tabs that want a parsed view can JSON.parse
|
|
// it themselves.
|
|
|
|
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
|
import { HttpUtil, PromiseUtil } from '@/utils';
|
|
|
|
const DIRTY_POLL_MS = 1000;
|
|
|
|
// Hoists the parsed `templateSettings` alongside the JSON string so
|
|
// structured tabs (Basics/Routing/Outbounds/etc.) can mutate fields
|
|
// directly while the Advanced (JSON) tab edits the same data as text.
|
|
// We keep both in sync with two cooperating watches:
|
|
// • mutating templateSettings re-stringifies into xraySetting;
|
|
// • editing the JSON text re-parses into templateSettings (only on
|
|
// valid JSON — invalid edits leave templateSettings untouched
|
|
// so the structured tabs don't blow up while the user types).
|
|
let syncing = false;
|
|
|
|
export function useXraySetting() {
|
|
const fetched = ref(false);
|
|
const spinning = ref(false);
|
|
const saveDisabled = ref(true);
|
|
|
|
const xraySetting = ref('');
|
|
const oldXraySetting = ref('');
|
|
|
|
// Parsed mirror — null until first successful fetch / parse.
|
|
const templateSettings = ref(null);
|
|
|
|
const outboundTestUrl = ref('https://www.google.com/generate_204');
|
|
const oldOutboundTestUrl = ref('');
|
|
|
|
const inboundTags = ref([]);
|
|
const clientReverseTags = ref([]);
|
|
const restartResult = ref('');
|
|
|
|
async function fetchAll() {
|
|
const msg = await HttpUtil.post('/panel/xray/');
|
|
if (!msg?.success) return;
|
|
const obj = JSON.parse(msg.obj);
|
|
const pretty = JSON.stringify(obj.xraySetting, null, 2);
|
|
syncing = true;
|
|
xraySetting.value = pretty;
|
|
oldXraySetting.value = pretty;
|
|
templateSettings.value = obj.xraySetting;
|
|
syncing = false;
|
|
inboundTags.value = obj.inboundTags || [];
|
|
clientReverseTags.value = obj.clientReverseTags || [];
|
|
outboundTestUrl.value = obj.outboundTestUrl || 'https://www.google.com/generate_204';
|
|
oldOutboundTestUrl.value = outboundTestUrl.value;
|
|
fetched.value = true;
|
|
saveDisabled.value = true;
|
|
}
|
|
|
|
// Structured tabs mutate templateSettings deeply. Re-stringify on
|
|
// change so the Advanced JSON view + the dirty-poll see the edits.
|
|
watch(
|
|
templateSettings,
|
|
(next) => {
|
|
if (syncing || !next) return;
|
|
syncing = true;
|
|
try {
|
|
xraySetting.value = JSON.stringify(next, null, 2);
|
|
} finally {
|
|
syncing = false;
|
|
}
|
|
},
|
|
{ deep: true },
|
|
);
|
|
|
|
// Advanced JSON edits — only refresh templateSettings when the text
|
|
// parses, so structured tabs stay readable mid-edit.
|
|
watch(xraySetting, (next) => {
|
|
if (syncing) return;
|
|
try {
|
|
const parsed = JSON.parse(next);
|
|
syncing = true;
|
|
try {
|
|
templateSettings.value = parsed;
|
|
} finally {
|
|
syncing = false;
|
|
}
|
|
} catch (_e) { /* ignore — wait for user to finish */ }
|
|
});
|
|
|
|
async function saveAll() {
|
|
spinning.value = true;
|
|
try {
|
|
const msg = await HttpUtil.post('/panel/xray/update', {
|
|
xraySetting: xraySetting.value,
|
|
outboundTestUrl: outboundTestUrl.value || 'https://www.google.com/generate_204',
|
|
});
|
|
if (msg?.success) await fetchAll();
|
|
} finally {
|
|
spinning.value = false;
|
|
}
|
|
}
|
|
|
|
async function restartXray() {
|
|
spinning.value = true;
|
|
try {
|
|
const msg = await HttpUtil.post('/panel/api/server/restartXrayService');
|
|
if (msg?.success) {
|
|
// Match legacy: short pause, then poll for the result blob so
|
|
// the popover surfaces any startup error from the new process.
|
|
await PromiseUtil.sleep(500);
|
|
const r = await HttpUtil.get('/panel/xray/getXrayResult');
|
|
if (r?.success) restartResult.value = r.obj || '';
|
|
}
|
|
} finally {
|
|
spinning.value = false;
|
|
}
|
|
}
|
|
|
|
// Same 1s busy-loop pattern the settings page uses — keep it cheap
|
|
// and consistent. Real work (the JSON diff) is just a string compare.
|
|
let timer = null;
|
|
function startDirtyPoll() {
|
|
if (timer != null) return;
|
|
timer = setInterval(() => {
|
|
saveDisabled.value =
|
|
oldXraySetting.value === xraySetting.value
|
|
&& oldOutboundTestUrl.value === outboundTestUrl.value;
|
|
}, DIRTY_POLL_MS);
|
|
}
|
|
function stopDirtyPoll() {
|
|
if (timer != null) {
|
|
clearInterval(timer);
|
|
timer = null;
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
fetchAll();
|
|
startDirtyPoll();
|
|
});
|
|
onUnmounted(stopDirtyPoll);
|
|
|
|
return {
|
|
fetched,
|
|
spinning,
|
|
saveDisabled,
|
|
xraySetting,
|
|
templateSettings,
|
|
outboundTestUrl,
|
|
inboundTags,
|
|
clientReverseTags,
|
|
restartResult,
|
|
fetchAll,
|
|
saveAll,
|
|
restartXray,
|
|
};
|
|
}
|