fix: keep routing edits during refresh

This commit is contained in:
Андрей Бобырев
2026-06-06 15:25:26 +03:00
parent ca12617a08
commit b5360aa2e1
2 changed files with 40 additions and 6 deletions

View File

@@ -532,6 +532,8 @@ const state = {
userTrafficLoading: false, userTrafficLoading: false,
backupSchedule: null, backupSchedule: null,
routing: null, routing: null,
routingDirty: false,
routingPreviewMap: null,
warp: null, warp: null,
qrLink: "", qrLink: "",
pendingUsers: new Set(), pendingUsers: new Set(),
@@ -656,7 +658,10 @@ function applyI18n() {
$("#languageSelect").value = state.lang; $("#languageSelect").value = state.lang;
$("#settingsLanguage").textContent = state.lang === "ru" ? "Русский" : "English"; $("#settingsLanguage").textContent = state.lang === "ru" ? "Русский" : "English";
$("#settingsTheme").textContent = state.theme === "dark" ? t("darkTheme") : t("lightTheme"); $("#settingsTheme").textContent = state.theme === "dark" ? t("darkTheme") : t("lightTheme");
$("#visualTitle").textContent = t("visualTitle"); const visualPort = state.routingDirty
? (state.routingPreviewMap?.public_port || $("#visualRoutingPort")?.value || state.routing?.port || 443)
: (state.overview?.port_map?.public_port || state.routing?.port || 443);
$("#visualTitle").textContent = t("visualTitle").replace("{port}", visualPort);
$("#visualText").textContent = t("visualText"); $("#visualText").textContent = t("visualText");
updateTrafficControls(); updateTrafficControls();
updateUserTrafficControls(); updateUserTrafficControls();
@@ -921,7 +926,11 @@ function renderOverview() {
$("#settingsBind").textContent = `${bind.host || "127.0.0.1"}:${bind.port || 1984}`; $("#settingsBind").textContent = `${bind.host || "127.0.0.1"}:${bind.port || 1984}`;
$("#metricMode").textContent = cfg.mode || "--"; $("#metricMode").textContent = cfg.mode || "--";
renderSiteStatus(); renderSiteStatus();
if (state.routingDirty && state.routingPreviewMap) {
renderPortMap(state.routingPreviewMap);
} else if (!state.routingDirty) {
renderPortMap(data.port_map || data.port_443 || {}); renderPortMap(data.port_map || data.port_443 || {});
}
$("#metricUsers").textContent = data.users_count ?? 0; $("#metricUsers").textContent = data.users_count ?? 0;
$("#metricProxyTraffic").textContent = fmtBytes(stats.proxy_bytes); $("#metricProxyTraffic").textContent = fmtBytes(stats.proxy_bytes);
$("#metricProxyPackets").textContent = `${stats.proxy_pkts || 0} ${t("packets")}`; $("#metricProxyPackets").textContent = `${stats.proxy_pkts || 0} ${t("packets")}`;
@@ -1458,12 +1467,29 @@ function routingControlPairs() {
} }
function setRoutingControlValues(routing = {}) { function setRoutingControlValues(routing = {}) {
if (state.routingDirty) return;
routingControlPairs().forEach((item) => { routingControlPairs().forEach((item) => {
if (document.activeElement === item.port || document.activeElement === item.mask) return;
item.port.value = routing.port || 443; item.port.value = routing.port || 443;
item.mask.value = routing.mask_host || "google.com"; item.mask.value = routing.mask_host || "google.com";
}); });
} }
function syncRoutingControls(source) {
const form = source?.closest?.("form");
const port = form?.querySelector('[name="port"]')?.value ?? "";
const mask = form?.querySelector('[name="mask_host"]')?.value ?? "";
routingControlPairs().forEach((item) => {
if (item.port !== source && document.activeElement !== item.port) item.port.value = port;
if (item.mask !== source && document.activeElement !== item.mask) item.mask.value = mask;
});
}
function markRoutingDirty(eventObj) {
state.routingDirty = true;
syncRoutingControls(eventObj.target);
}
function setRoutingNotes(routing = {}) { function setRoutingNotes(routing = {}) {
const conflicts = routing.conflicts || []; const conflicts = routing.conflicts || [];
const message = routingSummary(routing); const message = routingSummary(routing);
@@ -1476,6 +1502,7 @@ function setRoutingNotes(routing = {}) {
function renderRoutingSettings(payload = null) { function renderRoutingSettings(payload = null) {
const routing = payload || state.routing || state.overview?.routing || {}; const routing = payload || state.routing || state.overview?.routing || {};
if (state.routingDirty && !payload) return;
if (!payload) { if (!payload) {
setRoutingControlValues(routing); setRoutingControlValues(routing);
} }
@@ -1679,14 +1706,15 @@ async function checkRoutingPort(inputEl = $("#routingPort")) {
try { try {
const data = await api(`/api/routing?port=${encodeURIComponent(port)}`); const data = await api(`/api/routing?port=${encodeURIComponent(port)}`);
renderRoutingSettings(data); renderRoutingSettings(data);
renderPortMap({ state.routingPreviewMap = {
public_port: data.port, public_port: data.port,
configured_port: state.routing?.port || state.overview?.routing?.port || data.port, configured_port: state.routing?.port || state.overview?.routing?.port || data.port,
listeners: data.listeners || [], listeners: data.listeners || [],
routes: [], routes: [],
ok: data.ok, ok: data.ok,
error: data.error, error: data.error,
}); };
renderPortMap(state.routingPreviewMap);
} catch (err) { } catch (err) {
$("#routingStatus").textContent = t("routingBusy"); $("#routingStatus").textContent = t("routingBusy");
$("#routingStatus").className = "status-pill health-error"; $("#routingStatus").className = "status-pill health-error";
@@ -1714,6 +1742,8 @@ async function saveRoutingSettings(eventObj) {
method: "POST", method: "POST",
body: JSON.stringify(payload), body: JSON.stringify(payload),
}); });
state.routingDirty = false;
state.routingPreviewMap = null;
state.routing = data; state.routing = data;
state.overview = await api("/api/overview"); state.overview = await api("/api/overview");
renderRoutingSettings(); renderRoutingSettings();
@@ -1997,8 +2027,12 @@ $("#repairStatsBtn").addEventListener("click", repairStats);
$("#collectStatsBtn").addEventListener("click", collectStats); $("#collectStatsBtn").addEventListener("click", collectStats);
$("#authSettingsForm").addEventListener("submit", updateAuthSettings); $("#authSettingsForm").addEventListener("submit", updateAuthSettings);
$("#visualRoutingForm").addEventListener("submit", saveRoutingSettings); $("#visualRoutingForm").addEventListener("submit", saveRoutingSettings);
$("#visualRoutingPort").addEventListener("input", markRoutingDirty);
$("#visualRoutingMaskHost").addEventListener("input", markRoutingDirty);
$("#visualRoutingPort").addEventListener("change", (eventObj) => checkRoutingPort(eventObj.target)); $("#visualRoutingPort").addEventListener("change", (eventObj) => checkRoutingPort(eventObj.target));
$("#routingSettingsForm").addEventListener("submit", saveRoutingSettings); $("#routingSettingsForm").addEventListener("submit", saveRoutingSettings);
$("#routingPort").addEventListener("input", markRoutingDirty);
$("#routingMaskHost").addEventListener("input", markRoutingDirty);
$("#routingPort").addEventListener("change", (eventObj) => checkRoutingPort(eventObj.target)); $("#routingPort").addEventListener("change", (eventObj) => checkRoutingPort(eventObj.target));
$("#warpSettingsForm").addEventListener("submit", saveWarpSettings); $("#warpSettingsForm").addEventListener("submit", saveWarpSettings);
$("#warpScope").addEventListener("change", renderWarpSettings); $("#warpScope").addEventListener("change", renderWarpSettings);

View File

@@ -11,7 +11,7 @@
document.documentElement.dataset.theme = theme; document.documentElement.dataset.theme = theme;
}()); }());
</script> </script>
<link rel="stylesheet" href="/styles.css?v=2.5.0-admin24"> <link rel="stylesheet" href="/styles.css?v=2.5.0-admin25">
</head> </head>
<body> <body>
<div class="app-shell"> <div class="app-shell">
@@ -482,6 +482,6 @@
<button id="qrCopyBtn" type="button" class="soft" data-i18n="copyLink">Copy link</button> <button id="qrCopyBtn" type="button" class="soft" data-i18n="copyLink">Copy link</button>
</div> </div>
</div> </div>
<script src="/app.js?v=2.5.0-admin24" type="module"></script> <script src="/app.js?v=2.5.0-admin25" type="module"></script>
</body> </body>
</html> </html>