Release 3.8.2.1: reliability, UI probe/logs, CI, signing gates.

Clear sysproxy on core death; probe+reconnect; subscription prune;
Best ignores soft UDP; Windows tray; Android protocol gate; CI workflows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
M4
2026-07-30 02:44:48 +03:00
co-authored by Cursor
parent 6b6c13c933
commit 621c847cb3
39 changed files with 913 additions and 143 deletions
+15
View File
@@ -801,3 +801,18 @@
.tools { grid-template-columns: 1fr; }
}
.log-tail {
max-height: 160px;
overflow: auto;
font-size: .72rem;
line-height: 1.35;
white-space: pre-wrap;
word-break: break-word;
background: var(--panel-bg, rgba(0,0,0,.04));
border: 1px solid var(--line);
border-radius: 10px;
padding: 10px 12px;
margin: 8px 0;
}
.row-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
+40 -4
View File
@@ -3,7 +3,7 @@
const methods = [
"getState","getEditState","connect","disconnect","connectProfile","saveProfile","createProfile",
"selectProfile","deleteProfile","installCore","openURL","pingServers","pingBest",
"checkUpdate","applyUpdate","saveHy2","importSubscription","quit"
"checkUpdate","applyUpdate","saveHy2","importSubscription","getLogs","probeTunnel","savePrefs","quit"
];
if (typeof window.getState === "function") return;
window.__navisHttp = true;
@@ -36,6 +36,11 @@
const proxy = $("proxy");
const nameInput = $("name");
const sysproxy = $("sysproxy");
const bootConnect = $("bootConnect");
const autoReconnect = $("autoReconnect");
const logTail = $("logTail");
const logRefreshBtn = $("logRefreshBtn");
const probeBtn = $("probeBtn");
const profile = $("profile");
const dot = $("dot");
const statusText = $("statusText");
@@ -213,7 +218,7 @@
parts.push(p.name + "|" + (p.protocol || "") + "|" + (p.host || ""));
});
(state.pings || []).forEach((p) => {
parts.push(p.name + "|" + (p.ok ? 1 : 0) + "|" + (p.ms || 0) + "|" + (p.error || ""));
parts.push(p.name + "|" + (p.ok ? 1 : 0) + "|" + (p.soft ? 1 : 0) + "|" + (p.ms || 0) + "|" + (p.error || ""));
});
return parts.join("\n");
}
@@ -282,7 +287,8 @@
const pr = pingMap[p.name];
if (pr && pr.ok) {
ms.className = "ms " + msClass(pr.ms, true);
ms.textContent = pr.ms + " ms";
ms.textContent = (pr.soft ? "~" : "") + pr.ms + " ms";
if (pr.soft) ms.title = "soft-up (UDP без ответа) — не выбирается как «Лучший»";
} else if (pr && pr.error) {
ms.className = "ms bad";
ms.textContent = "—";
@@ -363,6 +369,10 @@
proxy.disabled = lock;
nameInput.disabled = lock;
sysproxy.disabled = lock;
if (bootConnect) bootConnect.disabled = busy;
if (autoReconnect) autoReconnect.disabled = busy;
if (probeBtn) probeBtn.disabled = busy || !connected;
if (logRefreshBtn) logRefreshBtn.disabled = busy;
profile.disabled = lock;
addBtn.disabled = lock;
delBtn.disabled = lock || ((state.profiles || profiles).length <= 1);
@@ -392,6 +402,8 @@
nameInput.value = active.name || state.active_profile || "";
proxy.value = typeof state.proxy === "string" ? state.proxy : (active.proxy || "");
if (typeof state.system_proxy === "boolean") sysproxy.checked = state.system_proxy;
if (bootConnect && typeof state.connect_on_launch === "boolean") bootConnect.checked = state.connect_on_launch;
if (autoReconnect && typeof state.auto_reconnect === "boolean") autoReconnect.checked = state.auto_reconnect;
fillHy2(state.hy2);
formHydrated = true;
if (syncForm) dirty = false;
@@ -420,11 +432,17 @@
heroHint.textContent = n > 1 ? "Клик — выбрать, двойной клик — подключить" : "Выберите сервер и нажмите Подключить";
}
if (!busy && Date.now() > metaHoldUntil) {
setMeta(detail, state.core_ready === false ? "err" : "");
if (!connected && state.last_error) setMeta(state.last_error, "err");
else setMeta(detail, state.core_ready === false ? "err" : "");
metaHoldUntil = 0;
}
}
async function persistPrefs() {
if (typeof savePrefs !== "function") return;
await savePrefs(!!(bootConnect && bootConnect.checked), !!(autoReconnect && autoReconnect.checked), !!sysproxy.checked);
}
async function refresh(opts) {
const needSecrets = !!(opts && opts.syncForm) || (!formHydrated && !dirty);
let state;
@@ -653,6 +671,24 @@
setMeta("Версия " + (latest || "?") + " пропущена. Следующую предложим.", "ok");
});
if (sysproxy) sysproxy.addEventListener("change", () => { persistPrefs().catch(() => {}); dirty = true; });
if (bootConnect) bootConnect.addEventListener("change", () => { persistPrefs().catch(() => {}); });
if (autoReconnect) autoReconnect.addEventListener("change", () => { persistPrefs().catch(() => {}); });
if (logRefreshBtn) logRefreshBtn.addEventListener("click", () => withBusy(async () => {
try {
const t = await getLogs();
if (logTail) logTail.textContent = t || "—";
setMeta("Лог обновлён", "ok");
} catch (e) { setMeta(fmtErr(e), "err"); }
}));
if (probeBtn) probeBtn.addEventListener("click", () => withBusy(async () => {
try {
setMeta("Проверка туннеля…");
await probeTunnel();
setMeta("Туннель OK", "ok");
} catch (e) { setMeta(fmtErr(e), "err"); }
}));
(async () => {
try {
await refresh({ syncForm: true });
+24 -1
View File
@@ -46,7 +46,7 @@
<div class="brand-wrap">
<div class="brand-row">
<h1 class="brand">Navis</h1>
<span class="badge-ver" id="badgeVer">3.8.1</span>
<span class="badge-ver" id="badgeVer">3.8.2</span>
</div>
<p class="tagline">Быстрый клиент · Naive · Hy2 · AWG · Xray</p>
</div>
@@ -122,6 +122,29 @@
<span class="slider"></span>
</label>
</div>
<div class="row">
<span>Подключать при запуске</span>
<label class="switch">
<input id="bootConnect" type="checkbox" />
<span class="slider"></span>
</label>
</div>
<div class="row">
<span>Автопереподключение</span>
<label class="switch">
<input id="autoReconnect" type="checkbox" />
<span class="slider"></span>
</label>
</div>
<details class="panel" id="logBox">
<summary>Логи ядра</summary>
<pre class="log-tail" id="logTail"></pre>
<div class="row-actions">
<button class="action secondary" id="logRefreshBtn" type="button">Обновить лог</button>
<button class="action secondary" id="probeBtn" type="button">Проверить туннель</button>
</div>
</details>
<details class="panel hy2" id="hy2Box">
<summary>Hysteria 2 · BBR / obfuscation</summary>