feat(ui): optional UI_HIDE_* panels; doc WARP free vs paid plans

Hide users/warp/cascade blocks via env; block warp/cascade APIs when hidden.
install.sh passes UI_HIDE_* and restores from previous container on upgrade.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Андрей Бобырев
2026-05-14 20:01:43 +03:00
parent 50558aff63
commit 8f1d7a7c4a
7 changed files with 145 additions and 8 deletions

View File

@@ -21,6 +21,10 @@ const warpActionsEl = document.querySelector("#warp-actions");
const warpClientListEl = document.querySelector("#warp-client-list");
const warpWgShowEl = document.querySelector("#warp-wg-show");
const cascadePanel = document.querySelector("#cascade-panel");
const usersPanel = document.querySelector("#users-panel");
const wgRawDetails = document.querySelector("#wg-raw-details");
const protoSwitch = document.querySelector("#proto-switch");
const protoSelect = document.querySelector("#proto-select");
const protoLabel = document.querySelector("#proto-label");
@@ -43,6 +47,23 @@ const dtExtra = document.querySelector("#dt-dialog-extra");
const dtHint = document.querySelector("#dt-dialog-hint");
const dtScheduleTunnel = document.querySelector("#dt-dialog-schedule-tunnel");
/** Какие панели скрыты настройкой сервера (`UI_HIDE_SECTIONS`). */
let uiHidden = { users: false, warp: false, cascade: false };
function applyUiHiddenFromPayload(data) {
const u = data?.uiHidden;
if (u && typeof u === "object") {
uiHidden = {
users: Boolean(u.users),
warp: Boolean(u.warp),
cascade: Boolean(u.cascade),
};
}
if (usersPanel) usersPanel.hidden = uiHidden.users;
if (wgRawDetails) wgRawDetails.hidden = uiHidden.users;
if (cascadePanel) cascadePanel.hidden = uiHidden.cascade;
}
let dtMode = "disable";
/** @type {Record<string, unknown> | null} */
let dtClient = null;
@@ -514,6 +535,7 @@ pwForm.addEventListener("submit", async (ev) => {
function renderRows(clients) {
rowsEl.innerHTML = "";
if (uiHidden.users) return;
clients.forEach((c) => {
const tr = document.createElement("tr");
@@ -617,6 +639,10 @@ function parseIpv4Cidrs(allowedIps) {
/** @param {{ warp?: Record<string, unknown>; clients: Record<string, unknown>[] }} data */
function renderWarpPanel(data) {
if (!warpPanel || !warpStatusLine || !warpActionsEl || !warpClientListEl || !warpWgShowEl) return;
if (uiHidden.warp) {
warpPanel.hidden = true;
return;
}
const w = data.warp;
if (!w || w.supported === false) {
warpPanel.hidden = true;
@@ -934,9 +960,15 @@ async function loadClients() {
try {
setStatus("Загрузка…", false);
const data = await api("/api/clients");
applyUiHiddenFromPayload(data);
const pref = data.profileLabel ? `${data.profileLabel} · ` : "";
peerCountEl.textContent = `${pref}${data.clients.length} в таблице · ${data.peerCount} peer`;
wgShowEl.textContent = data.wgShow || "";
if (uiHidden.users) {
peerCountEl.textContent = "";
wgShowEl.textContent = "";
} else {
peerCountEl.textContent = `${pref}${data.clients.length} в таблице · ${data.peerCount} peer`;
wgShowEl.textContent = data.wgShow || "";
}
renderWarpPanel(data);
renderRows(data.clients);
setStatus("", false);

View File

@@ -135,9 +135,7 @@
</form>
</section>
<section class="panel">
<div class="panel-head">
<h2>Пользователи</h2>
<section class="panel" id="users-panel">
<span class="muted" id="peer-count"></span>
</div>
<div class="table-wrap">
@@ -156,8 +154,7 @@
</div>
</section>
<details class="raw">
<summary>Вывод awg show (отладка)</summary>
<details class="raw" id="wg-raw-details">
<pre id="wg-show"></pre>
</details>
</div>