Release 4.0.0+1: rebrand Navis → EvilFox; centralized Remnawave auto-provisioning (panel credentials baked into binary); remove Remnawave API UI; Windows 4.0.0.1.

Keep dist/navis-release/ path and ship Navis.exe as a same-hash alias of EvilFox.exe so 3.x clients can still update from the Windows branch feed.
This commit is contained in:
Navis
2026-08-01 20:06:25 +03:00
parent e76c0a0977
commit 1144fab54d
32 changed files with 280 additions and 243 deletions
+11 -139
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Navis</title>
<title>EvilFox</title>
<script>
(function () {
try {
@@ -993,8 +993,8 @@
<path d="M22.5 8.2l3.2-1.1-1.1 3.2-5.6 5.6 1.6 1.6 5.6-5.6 3.2-1.1-1.1 3.2" fill="#b8f0d8" opacity=".9"/>
</svg>
</div>
<h1 class="brand">Navis</h1>
<span class="badge-ver" id="badgeVer">3.10.0</span>
<h1 class="brand">EvilFox</h1>
<span class="badge-ver" id="badgeVer">4.0.0</span>
</div>
<button class="nav-btn active" type="button" data-page="home">
@@ -1020,7 +1020,7 @@
<div class="spacer"></div>
<div class="side-foot">
<span class="ver" id="verLabel">Navis</span>
<span class="ver" id="verLabel">EvilFox</span>
<button type="button" class="theme-toggle" id="themeToggle" title="Тема" aria-label="Переключить тему">
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M20.5 14.2A8.2 8.2 0 0 1 9.8 3.5 8.5 8.5 0 1 0 20.5 14.2z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
@@ -1150,17 +1150,6 @@
</div>
</details>
<!-- Выдача доступа: скрыта, когда аккаунт уже привязан
(создание второго конфига отключено — платная подписка появится позже) -->
<div class="remna-box" id="provisionBox" hidden>
<div class="remna-title">Выдать доступ</div>
<p class="remna-hint">Создаёт пользователя в панели Remnawave (тариф из configs/remnawave-api.json, по умолчанию 50 ГБ / 30 дней), запоминает его и сразу импортирует конфиги. Повторно создать аккаунт нельзя — только продлевать.</p>
<div>
<label class="field" for="provUser">Пользователь</label>
<input id="provUser" type="text" placeholder="username / @telegram / email" spellcheck="false" autocomplete="off" />
</div>
<button class="action primary" id="provBtn" type="button">Выдать доступ</button>
</div>
</section>
<!-- ============ Настройки ============ -->
@@ -1173,7 +1162,7 @@
<button type="button" id="modeProxyBtn">Режим прокси</button>
<button type="button" id="modeVpnBtn">Режим VPN</button>
</div>
<p class="remna-hint" id="modeHint">Прокси — трафик идёт через локальный прокси (и системный прокси Windows). VPN — весь трафик, включая игры и приложения, через TUN-адаптер; нужны права администратора и wintun.dll рядом с Navis.exe. Меняется в отключённом состоянии.</p>
<p class="remna-hint" id="modeHint">Прокси — трафик идёт через локальный прокси (и системный прокси Windows). VPN — весь трафик, включая игры и приложения, через TUN-адаптер; нужны права администратора и wintun.dll рядом с EvilFox.exe. Меняется в отключённом состоянии.</p>
</div>
<div class="row">
@@ -1260,33 +1249,6 @@
<label class="check"><input id="hy2Lazy" type="checkbox" /> Lazy connect</label>
</details>
<details class="panel" id="rwPanel">
<summary>Remnawave API</summary>
<div class="remna-box" style="margin-top:8px;border:0;padding:0;background:transparent">
<p class="remna-hint">Панель → short UUID пользователя. Токен API — опционально (для /api/subscriptions/… и /users/…).</p>
<div>
<label class="field" for="rwBase">URL панели</label>
<input id="rwBase" type="text" placeholder="https://panel.example.com" spellcheck="false" />
</div>
<div>
<label class="field" for="rwShort">Short UUID</label>
<input id="rwShort" type="text" placeholder="abc12xyz" spellcheck="false" />
</div>
<div>
<label class="field" for="rwToken">API token</label>
<input id="rwToken" type="password" placeholder="Bearer из панели → API Tokens" spellcheck="false" autocomplete="off" />
</div>
<div>
<label class="field" for="rwCaddy">Caddy X-Api-Key</label>
<input id="rwCaddy" type="password" placeholder="если стоит auth у Caddy" spellcheck="false" autocomplete="off" />
</div>
<div class="sub-bar" style="margin-top:8px">
<button class="action secondary" id="rwSaveBtn" type="button" style="flex:1">Сохранить</button>
<button class="action primary" id="rwSyncBtn" type="button" style="flex:1">Синхронизировать</button>
</div>
</div>
</details>
<details class="panel" id="toolsBox">
<summary>Сервис · cores, обновления</summary>
<div class="tools">
@@ -1317,9 +1279,9 @@
<path d="M22.5 8.2l3.2-1.1-1.1 3.2-5.6 5.6 1.6 1.6 5.6-5.6 3.2-1.1-1.1 3.2" fill="#b8f0d8" opacity=".9"/>
</svg>
</div>
<h2>Navis</h2>
<h2>EvilFox</h2>
<p id="aboutVer">версия…</p>
<p style="margin-top:6px">VPN/прокси-клиент: Remnawave · Naive · Hysteria 2 · AWG · Xray</p>
<p style="margin-top:6px">VPN/прокси-клиент: Naive · Hysteria 2 · AWG · Xray</p>
</div>
<div class="tools" style="margin-top:0">
<button class="action secondary" id="aboutUpdBtn" type="button">Проверить обновление</button>
@@ -1498,15 +1460,6 @@
}
const subUrl = $("subUrl");
const subBtn = $("subBtn");
const rwBase = $("rwBase");
const rwShort = $("rwShort");
const rwToken = $("rwToken");
const rwCaddy = $("rwCaddy");
const rwSaveBtn = $("rwSaveBtn");
const rwSyncBtn = $("rwSyncBtn");
const provisionBox = $("provisionBox");
const provUser = $("provUser");
const provBtn = $("provBtn");
const hero = $("hero");
const protoChip = $("protoChip");
const heroHint = $("heroHint");
@@ -1572,7 +1525,7 @@
function markDirty() { dirty = true; }
[
proxy, nameInput, sysproxy, subUrl, rwBase, rwShort, rwToken, rwCaddy,
proxy, nameInput, sysproxy, subUrl,
$("hy2Congestion"), $("hy2Bbr"), $("hy2Up"), $("hy2Down"),
$("hy2Obfs"), $("hy2ObfsPass"), $("hy2Sni"), $("hy2Pin"), $("hy2Hop"),
$("hy2Insecure"), $("hy2Fast"), $("hy2Lazy")
@@ -1681,7 +1634,7 @@
if (!list.length) {
const empty = document.createElement("div");
empty.className = "server-empty";
empty.textContent = "Нет серверов — добавьте профиль или подписку";
empty.textContent = "Нет серверов — подождите автовыдачу или добавьте подписку";
serverList.appendChild(empty);
return;
}
@@ -1876,7 +1829,7 @@
}
function renderUpdate(u, version) {
const label = "Navis v" + (version || "?");
const label = "EvilFox v" + (version || "?");
verLabel.textContent = label;
if (aboutVer) aboutVer.textContent = "Версия " + (version || "?");
if (badgeVer && version) {
@@ -1941,16 +1894,6 @@
updateBtn.disabled = busy;
subBtn.disabled = busy;
subUrl.disabled = busy;
if (rwBase) rwBase.disabled = busy;
if (rwShort) rwShort.disabled = busy;
if (rwToken) rwToken.disabled = busy;
if (rwCaddy) rwCaddy.disabled = busy;
if (rwSaveBtn) rwSaveBtn.disabled = busy;
if (rwSyncBtn) rwSyncBtn.disabled = busy;
// Выдать доступ — только пока аккаунт НЕ привязан (второй не создаём).
if (provisionBox) provisionBox.hidden = !state.provision_ready || !!state.account;
if (provUser) provUser.disabled = busy;
if (provBtn) provBtn.disabled = busy;
if (renewBtn) renewBtn.disabled = busy || !state.provision_ready;
if (modeProxyBtn) modeProxyBtn.disabled = busy || connected;
if (modeVpnBtn) modeVpnBtn.disabled = busy || connected;
@@ -1965,12 +1908,6 @@
if (typeof state.subscription_url === "string" && !dirty) {
subUrl.value = state.subscription_url;
}
if (state.remnawave && !dirty) {
rwBase.value = state.remnawave.base_url || "";
rwShort.value = state.remnawave.short_uuid || "";
if (typeof state.remnawave.token === "string") rwToken.value = state.remnawave.token;
if (typeof state.remnawave.caddy_token === "string") rwCaddy.value = state.remnawave.caddy_token;
}
if (syncForm || (!formHydrated && !dirty)) {
const active = (state.profiles || []).find((p) => p.name === profile.value) || {};
@@ -2038,8 +1975,7 @@
function paintButtonsLocked(locked) {
[btn, coreBtn, saveBtn, addBtn, delBtn, profile, pingBtn, bestBtn, updCheckBtn, aboutUpdBtn,
updateBtn, skipUpdateBtn, subBtn, subUrl, rwBase, rwShort, rwToken, rwCaddy, rwSaveBtn, rwSyncBtn,
provUser, provBtn, renewBtn, modeProxyBtn, modeVpnBtn, logsRefreshBtn].forEach((b) => {
updateBtn, skipUpdateBtn, subBtn, subUrl, renewBtn, modeProxyBtn, modeVpnBtn, logsRefreshBtn].forEach((b) => {
if (b) b.disabled = locked;
});
}
@@ -2166,15 +2102,6 @@
return "Импортировано: " + r;
}
function readRemnawave() {
return {
base_url: (rwBase && rwBase.value || "").trim(),
short_uuid: (rwShort && rwShort.value || "").trim(),
token: (rwToken && rwToken.value || "").trim(),
caddy_token: (rwCaddy && rwCaddy.value || "").trim()
};
}
subBtn.addEventListener("click", () => withBusy(async () => {
try {
await runImportSubscription();
@@ -2187,61 +2114,6 @@
}
});
if (rwSaveBtn) {
rwSaveBtn.addEventListener("click", () => withBusy(async () => {
try {
await saveRemnawave(readRemnawave());
setMeta("Настройки Remnawave сохранены", "ok");
} catch (e) { setMeta(String(e), "err"); }
}));
}
if (rwSyncBtn) {
rwSyncBtn.addEventListener("click", () => withBusy(async () => {
try {
const s = readRemnawave();
if (!s.base_url || !s.short_uuid) {
setMeta("Укажите URL панели и short UUID", "err");
return;
}
setMeta("Remnawave: загрузка конфигов…");
const r = await importRemnawave(s);
formHydrated = false;
dirty = false;
setMeta("Remnawave: " + importSummary(r).toLowerCase() + " · пинг…", "ok");
await runBest(!!autoBest.checked);
} catch (e) { setMeta(String(e), "err"); }
}));
}
if (provBtn) {
provBtn.addEventListener("click", () => withBusy(async () => {
try {
const u = (provUser.value || "").trim();
if (!u) {
setMeta("Укажите имя пользователя (username / @telegram / email)", "err");
return;
}
setMeta("Выдача доступа через панель…");
const r = await provisionUser(u);
formHydrated = false;
dirty = false;
let msg = (r.created ? "Создан пользователь " : "Найден пользователь ") + r.username;
if (r.traffic_limit_bytes > 0) msg += " · " + fmtTraffic(r.traffic_limit_bytes);
if (r.expire_at) {
try { msg += " · до " + new Date(r.expire_at * 1000).toLocaleDateString("ru-RU"); } catch (_) {}
}
msg += " · импортировано серверов: " + (r.imported || 0);
setMeta(msg, "ok");
} catch (e) { setMeta(String(e), "err"); }
}));
provUser.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
provBtn.click();
}
});
}
if (renewBtn) {
renewBtn.addEventListener("click", () => withBusy(async () => {
try {