Release 4.0.1+1: remove account binding / centralized provisioning (no panel credentials in binary; configs via user's own subscription URL); add recommended-services block on About page; Windows 4.0.1.1.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+83
-81
@@ -473,7 +473,7 @@
|
||||
.cur-server .ms.mid { color: var(--ms-mid); }
|
||||
.cur-server .ms.bad { color: var(--danger); }
|
||||
|
||||
/* Account / subscription cards */
|
||||
/* Subscription card */
|
||||
.subinfo {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
@@ -936,6 +936,43 @@
|
||||
}
|
||||
.shop-link:hover { text-decoration: underline; }
|
||||
|
||||
/* «Рекомендуемые сервисы» — маленький ненавязчивый блок в углу «О программе» */
|
||||
.svc {
|
||||
margin-top: 12px;
|
||||
padding: 10px 12px 11px;
|
||||
border-radius: 14px;
|
||||
border: 1px dashed var(--line);
|
||||
background: transparent;
|
||||
}
|
||||
.svc h4 {
|
||||
margin: 0 0 7px;
|
||||
font-family: Outfit, sans-serif;
|
||||
font-size: .66rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.svc-grid { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.svc-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
font: inherit;
|
||||
font-size: .74rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
box-shadow: none;
|
||||
transition: color .15s, border-color .15s;
|
||||
}
|
||||
.svc-link:hover { color: var(--accent-deep); border-color: rgba(13,138,102,.45); }
|
||||
.svc-link .ic { font-size: .85rem; line-height: 1; }
|
||||
|
||||
.about-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
@@ -1077,25 +1114,6 @@
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- Аккаунт: показывается, когда пользователь «запомнен» (configs/account.json) -->
|
||||
<section class="subinfo" id="accountBox" hidden>
|
||||
<div class="subinfo-title">Аккаунт</div>
|
||||
<div class="subinfo-row">
|
||||
<span class="k">Пользователь</span>
|
||||
<span class="v" id="accName"></span>
|
||||
</div>
|
||||
<div class="subinfo-row" id="accExpireRow" hidden>
|
||||
<span class="k">Действует до</span>
|
||||
<span class="v" id="accExpire"></span>
|
||||
</div>
|
||||
<div class="subinfo-row" id="accTrafficRow" hidden>
|
||||
<span class="k">Тариф</span>
|
||||
<span class="v" id="accTraffic"></span>
|
||||
</div>
|
||||
<button class="action primary" id="renewBtn" type="button">Продлить 50 ГБ</button>
|
||||
<p class="remna-hint" style="margin-top:2px">Продление сбрасывает трафик и добавляет 30 дней тому же пользователю. Также продлевается автоматически при истечении.</p>
|
||||
</section>
|
||||
|
||||
<section class="subinfo" id="subInfoCard" hidden>
|
||||
<div class="subinfo-title">Подписка</div>
|
||||
<div class="subinfo-row" id="subExpireRow" hidden>
|
||||
@@ -1292,6 +1310,12 @@
|
||||
<button class="action primary" id="shopBtn" type="button">Открыть магазин</button>
|
||||
<button class="shop-link" id="shopLink" type="button">https://evilfox.win/</button>
|
||||
</section>
|
||||
<!-- Рекомендуемые сервисы: ненавязчивый блок в нижнем углу «О программе».
|
||||
Ссылки задаются в скрипте — константа RECOMMENDED_SERVICES. -->
|
||||
<section class="svc" aria-label="Рекомендуемые сервисы">
|
||||
<h4>Рекомендуемые сервисы</h4>
|
||||
<div class="svc-grid" id="svcGrid"></div>
|
||||
</section>
|
||||
<button class="shop-link" id="quitBtn" type="button" hidden style="display:none;margin:12px auto 0;width:auto">Выйти</button>
|
||||
</section>
|
||||
</div>
|
||||
@@ -1327,7 +1351,7 @@
|
||||
"getState","connect","disconnect","connectProfile","saveProfile","createProfile",
|
||||
"selectProfile","deleteProfile","installCore","openURL","pingServers","pingBest",
|
||||
"checkUpdate","applyUpdate","saveHy2","importSubscription","saveRemnawave","importRemnawave",
|
||||
"provisionUser","renewAccount","setMode","getLogs","quit"
|
||||
"setMode","getLogs","quit"
|
||||
];
|
||||
if (typeof window.getState === "function") return;
|
||||
window.__navisHttp = true;
|
||||
@@ -1378,8 +1402,6 @@
|
||||
const storeUpdateNote = $("storeUpdateNote");
|
||||
const themeToggle = $("themeToggle");
|
||||
const themeSwitch = $("themeSwitch");
|
||||
const renewBtn = $("renewBtn");
|
||||
const accountBox = $("accountBox");
|
||||
const modeProxyBtn = $("modeProxyBtn");
|
||||
const modeVpnBtn = $("modeVpnBtn");
|
||||
const modeSection = $("modeSection");
|
||||
@@ -1464,6 +1486,15 @@
|
||||
const protoChip = $("protoChip");
|
||||
const heroHint = $("heroHint");
|
||||
const SHOP_URL = "https://evilfox.win/";
|
||||
// Список рекомендуемых сервисов — замените ссылки/названия здесь.
|
||||
// Хост каждой ссылки должен быть разрешён в allowlist openURL
|
||||
// (internal/apphost/app.go → allowedLinkHosts), иначе ссылка не откроется.
|
||||
const RECOMMENDED_SERVICES = [
|
||||
{ icon: "🛒", name: "EvilFox Shop", url: "https://evilfox.win/" },
|
||||
{ icon: "✈️", name: "Telegram", url: "https://t.me/evilfox" },
|
||||
{ icon: "🖥️", name: "Хостинг de4ima", url: "https://de4ima.uk" },
|
||||
{ icon: "🦊", name: "Git", url: "https://git.de4ima.uk/Evilfox/navi" }
|
||||
];
|
||||
const AUTO_BEST_KEY = "navis.autoBest";
|
||||
|
||||
try {
|
||||
@@ -1736,44 +1767,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function renderAccount(acc, subInfo) {
|
||||
if (!accountBox) return;
|
||||
if (!acc) {
|
||||
accountBox.hidden = true;
|
||||
return;
|
||||
}
|
||||
accountBox.hidden = false;
|
||||
$("accName").textContent = acc.username || "—";
|
||||
|
||||
const expRow = $("accExpireRow");
|
||||
if (acc.expire_at) {
|
||||
expRow.hidden = false;
|
||||
const daysLeft = Math.floor((acc.expire_at * 1000 - Date.now()) / 86400000);
|
||||
const el = $("accExpire");
|
||||
if (daysLeft < 0) {
|
||||
el.textContent = fmtDate(acc.expire_at) + " · истёк";
|
||||
el.classList.add("warn");
|
||||
} else {
|
||||
el.textContent = fmtDate(acc.expire_at) + " · осталось " + daysLeft + " дн.";
|
||||
el.classList.toggle("warn", daysLeft <= 3);
|
||||
}
|
||||
} else {
|
||||
expRow.hidden = true;
|
||||
}
|
||||
|
||||
const trafRow = $("accTrafficRow");
|
||||
const limit = acc.traffic_limit_bytes || 0;
|
||||
const used = subInfo ? ((subInfo.upload || 0) + (subInfo.download || 0)) : 0;
|
||||
if (limit > 0 || used > 0) {
|
||||
trafRow.hidden = false;
|
||||
$("accTraffic").textContent = limit > 0
|
||||
? (fmtTraffic(used) + " из " + fmtTraffic(limit))
|
||||
: (fmtTraffic(used) + " · Безлимит");
|
||||
} else {
|
||||
trafRow.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function renderMode(state) {
|
||||
currentMode = state.mode === "vpn" ? "vpn" : "proxy";
|
||||
if (modeSection) {
|
||||
@@ -1894,7 +1887,6 @@
|
||||
updateBtn.disabled = busy;
|
||||
subBtn.disabled = busy;
|
||||
subUrl.disabled = busy;
|
||||
if (renewBtn) renewBtn.disabled = busy || !state.provision_ready;
|
||||
if (modeProxyBtn) modeProxyBtn.disabled = busy || connected;
|
||||
if (modeVpnBtn) modeVpnBtn.disabled = busy || connected;
|
||||
btn.disabled = busy;
|
||||
@@ -1903,7 +1895,6 @@
|
||||
fillProfiles(state.profiles || [], state.active_profile || state.profile);
|
||||
renderUpdate(state.update, state.version);
|
||||
renderSubInfo(state.sub_info);
|
||||
renderAccount(state.account, state.sub_info);
|
||||
renderMode(state);
|
||||
if (typeof state.subscription_url === "string" && !dirty) {
|
||||
subUrl.value = state.subscription_url;
|
||||
@@ -1975,7 +1966,7 @@
|
||||
|
||||
function paintButtonsLocked(locked) {
|
||||
[btn, coreBtn, saveBtn, addBtn, delBtn, profile, pingBtn, bestBtn, updCheckBtn, aboutUpdBtn,
|
||||
updateBtn, skipUpdateBtn, subBtn, subUrl, renewBtn, modeProxyBtn, modeVpnBtn, logsRefreshBtn].forEach((b) => {
|
||||
updateBtn, skipUpdateBtn, subBtn, subUrl, modeProxyBtn, modeVpnBtn, logsRefreshBtn].forEach((b) => {
|
||||
if (b) b.disabled = locked;
|
||||
});
|
||||
}
|
||||
@@ -2079,6 +2070,34 @@
|
||||
shopBtn.addEventListener("click", openShop);
|
||||
shopLink.addEventListener("click", openShop);
|
||||
|
||||
// «Рекомендуемые сервисы» — рендер из RECOMMENDED_SERVICES (см. константу выше).
|
||||
(function renderRecommendedServices() {
|
||||
const grid = $("svcGrid");
|
||||
if (!grid) return;
|
||||
RECOMMENDED_SERVICES.forEach((s) => {
|
||||
const b = document.createElement("button");
|
||||
b.type = "button";
|
||||
b.className = "svc-link";
|
||||
b.title = s.url;
|
||||
const ic = document.createElement("span");
|
||||
ic.className = "ic";
|
||||
ic.textContent = s.icon || "•";
|
||||
const nm = document.createElement("span");
|
||||
nm.textContent = s.name;
|
||||
b.append(ic, nm);
|
||||
b.addEventListener("click", async () => {
|
||||
try {
|
||||
setMeta("Открываю " + s.name + "…");
|
||||
await openURL(s.url);
|
||||
setMeta("Открыто в браузере", "ok");
|
||||
} catch (err) {
|
||||
setMeta("Не удалось открыть ссылку: " + String(err), "err");
|
||||
}
|
||||
});
|
||||
grid.appendChild(b);
|
||||
});
|
||||
})();
|
||||
|
||||
async function runImportSubscription() {
|
||||
const url = subUrl.value.trim();
|
||||
if (!url) {
|
||||
@@ -2114,23 +2133,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
if (renewBtn) {
|
||||
renewBtn.addEventListener("click", () => withBusy(async () => {
|
||||
try {
|
||||
setMeta("Продление тарифа (50 ГБ / +30 дней)…");
|
||||
const r = await renewAccount();
|
||||
formHydrated = false;
|
||||
dirty = false;
|
||||
let msg = "Продлено: " + 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 (_) {}
|
||||
}
|
||||
setMeta(msg, "ok");
|
||||
} catch (e) { setMeta(String(e), "err"); }
|
||||
}));
|
||||
}
|
||||
|
||||
async function switchMode(mode) {
|
||||
if (mode === currentMode) return;
|
||||
await withBusy(async () => {
|
||||
|
||||
Reference in New Issue
Block a user