Fix xray config bug
This commit is contained in:
@@ -10,6 +10,9 @@ let managedTlsForwardersState = [];
|
|||||||
let editingXrayClientId = null;
|
let editingXrayClientId = null;
|
||||||
let wzInbounds = [];
|
let wzInbounds = [];
|
||||||
let wzLoadedFullConfig = null;
|
let wzLoadedFullConfig = null;
|
||||||
|
let wzLoadedConfigText = "";
|
||||||
|
let wzLoadedServerID = null;
|
||||||
|
let wzDirty = false;
|
||||||
let dashboardCache = { sshUsers: [], xrayInbounds: [], me: null };
|
let dashboardCache = { sshUsers: [], xrayInbounds: [], me: null };
|
||||||
let currentTab = "dashboard";
|
let currentTab = "dashboard";
|
||||||
let inboundsRefreshInFlight = false;
|
let inboundsRefreshInFlight = false;
|
||||||
@@ -2612,22 +2615,37 @@ function setXrayCfgMode(mode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("wzLogLevel")?.addEventListener("change", updateFullConfigFromWizard);
|
document.getElementById("wzLogLevel")?.addEventListener("change", () => { wzDirty = true; });
|
||||||
|
|
||||||
|
function cloneJsonSafe(obj) {
|
||||||
|
return obj && typeof obj === "object" ? JSON.parse(JSON.stringify(obj)) : obj;
|
||||||
|
}
|
||||||
|
|
||||||
function loadWizardFromConfig() {
|
function loadWizardFromConfig() {
|
||||||
|
const serverID = selectedXrayServer();
|
||||||
const target = selectedXrayServerLabel();
|
const target = selectedXrayServerLabel();
|
||||||
const st = document.getElementById("wzStatus");
|
const st = document.getElementById("wzStatus");
|
||||||
|
wzLoadedServerID = null;
|
||||||
|
wzDirty = false;
|
||||||
if (st) st.textContent = `Loading config from ${target}...`;
|
if (st) st.textContent = `Loading config from ${target}...`;
|
||||||
api(withServerParam("/api/xray/config", selectedXrayServer())).then(async res => {
|
api(withServerParam("/api/xray/config", serverID)).then(async res => {
|
||||||
if (!res.ok) throw new Error(await res.text());
|
if (!res.ok) throw new Error(await res.text());
|
||||||
const raw = await res.text();
|
const raw = await res.text();
|
||||||
const cfg = JSON.parse(raw);
|
const cfg = JSON.parse(raw);
|
||||||
wzLoadedFullConfig = cfg;
|
wzLoadedServerID = serverID || "local";
|
||||||
|
wzLoadedConfigText = raw;
|
||||||
|
wzLoadedFullConfig = cloneJsonSafe(cfg);
|
||||||
document.getElementById("wzLogLevel").value = cfg.log?.loglevel || "warning";
|
document.getElementById("wzLogLevel").value = cfg.log?.loglevel || "warning";
|
||||||
wzInbounds = (cfg.inbounds || []).filter(ib => ib.tag !== "api");
|
wzInbounds = cloneJsonSafe((cfg.inbounds || []).filter(ib => ib && ib.tag !== "api")) || [];
|
||||||
renderWzInbounds();
|
renderWzInbounds();
|
||||||
|
wzDirty = false;
|
||||||
if (st) st.textContent = `Config loaded from ${target}.`;
|
if (st) st.textContent = `Config loaded from ${target}.`;
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
wzLoadedServerID = null;
|
||||||
|
wzLoadedConfigText = "";
|
||||||
|
wzLoadedFullConfig = null;
|
||||||
|
wzInbounds = [];
|
||||||
|
renderWzInbounds();
|
||||||
if (e.message === "auth") doAuthError();
|
if (e.message === "auth") doAuthError();
|
||||||
else if (st) st.textContent = "Error: " + e.message;
|
else if (st) st.textContent = "Error: " + e.message;
|
||||||
});
|
});
|
||||||
@@ -2663,7 +2681,7 @@ function renderWzInbounds() {
|
|||||||
const delBtn = document.createElement("button");
|
const delBtn = document.createElement("button");
|
||||||
delBtn.className = "btn btn-danger btn-sm";
|
delBtn.className = "btn btn-danger btn-sm";
|
||||||
delBtn.textContent = "Remove";
|
delBtn.textContent = "Remove";
|
||||||
delBtn.onclick = () => { wzInbounds.splice(i,1); renderWzInbounds(); updateFullConfigFromWizard(); };
|
delBtn.onclick = () => { wzInbounds.splice(i,1); wzDirty = true; renderWzInbounds(); };
|
||||||
row.appendChild(delBtn);
|
row.appendChild(delBtn);
|
||||||
list.appendChild(row);
|
list.appendChild(row);
|
||||||
});
|
});
|
||||||
@@ -2812,61 +2830,79 @@ function wzSaveInbound() {
|
|||||||
ib.streamSettings = { network: "tcp" };
|
ib.streamSettings = { network: "tcp" };
|
||||||
}
|
}
|
||||||
wzInbounds.push(ib);
|
wzInbounds.push(ib);
|
||||||
|
wzDirty = true;
|
||||||
renderWzInbounds();
|
renderWzInbounds();
|
||||||
document.getElementById("wzAddInboundForm").classList.add("hidden");
|
document.getElementById("wzAddInboundForm").classList.add("hidden");
|
||||||
updateFullConfigFromWizard();
|
|
||||||
document.getElementById("wzPort").value = "";
|
document.getElementById("wzPort").value = "";
|
||||||
document.getElementById("wzTag").value = "";
|
document.getElementById("wzTag").value = "";
|
||||||
document.getElementById("wzListenIP").value = "";
|
document.getElementById("wzListenIP").value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ensureXrayApiInbound() {
|
function buildConfigFromVisualEditor() {
|
||||||
return {
|
const selectedID = selectedXrayServer() || "local";
|
||||||
tag: "api",
|
if (!wzLoadedConfigText || String(wzLoadedServerID || "") !== String(selectedID)) {
|
||||||
listen: "127.0.0.1",
|
throw new Error("config for this server is not loaded yet");
|
||||||
port: 10085,
|
}
|
||||||
protocol: "dokodemo-door",
|
|
||||||
settings: { address: "127.0.0.1" }
|
let cfg;
|
||||||
};
|
try {
|
||||||
|
cfg = JSON.parse(wzLoadedConfigText);
|
||||||
|
} catch (_) {
|
||||||
|
cfg = cloneJsonSafe(wzLoadedFullConfig || {});
|
||||||
|
}
|
||||||
|
if (!cfg || typeof cfg !== "object" || Array.isArray(cfg)) {
|
||||||
|
throw new Error("loaded config is not an object");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve the selected server's full JSON exactly as the base.
|
||||||
|
// The visual tab is intentionally conservative: it only updates fields that
|
||||||
|
// are visible here, so pressing Save cannot wipe routing/outbounds/policy/etc.
|
||||||
|
cfg.log = cfg.log && typeof cfg.log === "object" ? cfg.log : {};
|
||||||
|
cfg.log.loglevel = document.getElementById("wzLogLevel")?.value || cfg.log.loglevel || "warning";
|
||||||
|
|
||||||
|
const existingInbounds = Array.isArray(cfg.inbounds) ? cfg.inbounds : [];
|
||||||
|
const hiddenApiInbounds = existingInbounds.filter(ib => ib && ib.tag === "api");
|
||||||
|
const visualInbounds = cloneJsonSafe((wzInbounds || []).filter(ib => ib && ib.tag !== "api")) || [];
|
||||||
|
cfg.inbounds = [...hiddenApiInbounds, ...visualInbounds];
|
||||||
|
|
||||||
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFullConfigFromWizard() {
|
function updateFullConfigFromWizard() {
|
||||||
const cfg = wzLoadedFullConfig && typeof wzLoadedFullConfig === "object" ? JSON.parse(JSON.stringify(wzLoadedFullConfig)) : {};
|
const cfg = buildConfigFromVisualEditor();
|
||||||
cfg.log = cfg.log || {};
|
wzLoadedFullConfig = cloneJsonSafe(cfg);
|
||||||
cfg.log.loglevel = document.getElementById("wzLogLevel")?.value || cfg.log.loglevel || "warning";
|
|
||||||
const existingInbounds = Array.isArray(cfg.inbounds) ? cfg.inbounds : [];
|
|
||||||
const apiInbound = existingInbounds.find(ib => ib && ib.tag === "api") || ensureXrayApiInbound();
|
|
||||||
cfg.inbounds = [apiInbound, ...(wzInbounds || []).filter(ib => ib && ib.tag !== "api")];
|
|
||||||
if (!cfg.api) cfg.api = { tag: "api", services: ["HandlerService", "LoggerService", "StatsService"] };
|
|
||||||
if (!cfg.stats) cfg.stats = {};
|
|
||||||
if (!cfg.policy) cfg.policy = { levels: { "0": { statsUserUplink: true, statsUserDownlink: true } }, system: { statsInboundUplink: true, statsInboundDownlink: true } };
|
|
||||||
if (!cfg.outbounds) cfg.outbounds = [
|
|
||||||
{ tag:"direct", protocol:"freedom", settings:{} },
|
|
||||||
{ tag:"blocked", protocol:"blackhole", settings:{} },
|
|
||||||
{ tag:"api", protocol:"freedom", settings:{} }
|
|
||||||
];
|
|
||||||
if (!cfg.routing) cfg.routing = { rules: [{ type: "field", inboundTag: ["api"], outboundTag: "api" }] };
|
|
||||||
wzLoadedFullConfig = cfg;
|
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyWizardConfig() {
|
async function applyWizardConfig() {
|
||||||
const st = document.getElementById("wzStatus");
|
const st = document.getElementById("wzStatus");
|
||||||
const target = selectedXrayServerLabel();
|
const target = selectedXrayServerLabel();
|
||||||
|
const selectedID = selectedXrayServer() || "local";
|
||||||
|
|
||||||
|
if (String(wzLoadedServerID || "") !== String(selectedID) || !wzLoadedConfigText) {
|
||||||
|
if (st) st.textContent = `Reloading config from ${target} before saving...`;
|
||||||
|
loadWizardFromConfig();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let cfg;
|
let cfg;
|
||||||
try {
|
try {
|
||||||
cfg = updateFullConfigFromWizard();
|
cfg = buildConfigFromVisualEditor();
|
||||||
if (!cfg || typeof cfg !== "object") throw new Error("config is not loaded");
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if (st) st.textContent = `Invalid visual config: ${e.message}`;
|
if (st) st.textContent = `Invalid visual config: ${e.message}`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st) st.textContent = `Saving config to ${target}...`;
|
if (st) st.textContent = `Saving config to ${target}...`;
|
||||||
try {
|
try {
|
||||||
const res = await api(withServerParam("/api/xray/config", selectedXrayServer()), { method:"POST", body: JSON.stringify(cfg, null, 2) });
|
const body = JSON.stringify(cfg, null, 2);
|
||||||
|
const res = await api(withServerParam("/api/xray/config", selectedID), { method:"POST", body });
|
||||||
if (!res.ok) throw new Error(await res.text());
|
if (!res.ok) throw new Error(await res.text());
|
||||||
wzLoadedFullConfig = cfg;
|
wzLoadedConfigText = body;
|
||||||
|
wzLoadedFullConfig = cloneJsonSafe(cfg);
|
||||||
|
wzLoadedServerID = selectedID;
|
||||||
|
wzDirty = false;
|
||||||
if (st) st.textContent = `Saved on ${target}. Restarting Xray...`;
|
if (st) st.textContent = `Saved on ${target}. Restarting Xray...`;
|
||||||
await xrayCtrl("restart");
|
await xrayCtrl("restart");
|
||||||
if (st) st.textContent = `Config saved on ${target} and Xray restarted.`;
|
if (st) st.textContent = `Config saved on ${target} and Xray restarted.`;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
setTimeout(function(){document.documentElement.classList.remove("i18n-pending");},2500);
|
setTimeout(function(){document.documentElement.classList.remove("i18n-pending");},2500);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="assets/app.css?v=20260511xrayfullconfig2"/>
|
<link rel="stylesheet" href="assets/app.css?v=20260511visualsafesave1"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app">
|
<div class="app">
|
||||||
@@ -1115,6 +1115,6 @@
|
|||||||
</div><!-- /shell -->
|
</div><!-- /shell -->
|
||||||
</div><!-- /app -->
|
</div><!-- /app -->
|
||||||
|
|
||||||
<script defer src="assets/app.js?v=20260511xrayfullconfig2"></script>
|
<script defer src="assets/app.js?v=20260511visualsafesave1"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user