diff --git a/README.md b/README.md index fb363d6..f8db85e 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ ## Скриншоты -Интерфейс ориентирован на просмотр клиентов, удаление peer и установку MTProto‑прокси; расширенные действия с клиентами в UI недоступны. +Интерфейс: управление клиентами (вкл/выкл, переименование, даты, экспорт `.conf`), удаление peer и установка MTProto‑прокси. Каскад, Cloudflare WARP и синхронизация времени хоста — только при включённых pro‑инструментах на сервере.

Панель: пользователи AmneziaWG, выбор инстанса, время, статусы в туннеле diff --git a/docs/panel-guide.md b/docs/panel-guide.md index cf3c82c..de7ac19 100644 --- a/docs/panel-guide.md +++ b/docs/panel-guide.md @@ -10,7 +10,7 @@ | **Время** | Отображение часов контейнера и браузера; синхронизация времени хоста VPS по SSH (если доступно). | | **Cloudflare WARP** | *Необязательно.* Вывод части клиентов в интернет через интерфейс `warp` в контейнере AWG. Если WARP не ставили — статус **«Не установлен»** нормален; панель и VPN без этого работают. Установка и удаление — скрипт `scripts/warp-amnezia.sh` на хосте (`install` / **`uninstall`**), подробности в основном [README](../README.md). | | **Новый клиент под каскад** | Создание нового peer на сервере с **вашим Endpoint** (промежуточный узел); выдача готового `.conf`. | -| **Пользователи** | Просмотр клиентов и **удаление** peer с сервера. Вкл/выкл, переименование, даты отключения и экспорт `.conf` в базовой версии недоступны. | +| **Пользователи** | Просмотр клиентов, **вкл/выкл**, переименование, даты отключения, экспорт `.conf`, **удаление** peer с сервера. | ## Переключатель «Инстанс» не отображается diff --git a/package.json b/package.json index 7e1656a..97a3a32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amnezia-admin", - "version": "1.2.30", + "version": "1.2.31", "private": true, "description": "amnezia_web — открытая базовая веб-панель AmneziaWG на VPS (Docker): просмотр и удаление клиентов, MTProto-прокси.", "license": "MIT", diff --git a/public/app.js b/public/app.js index dceb3dc..232205c 100644 --- a/public/app.js +++ b/public/app.js @@ -86,11 +86,12 @@ let warpSshPendingCmd = null; /** Какие панели скрыты настройкой сервера (`UI_HIDE_SECTIONS`). */ let uiHidden = { users: false, warp: false, cascade: false, mtproto: false }; -/** Базовые возможности панели (просмотр клиентов + удаление). */ +/** Базовые возможности панели (управление клиентами + удаление). */ let editionState = { - readOnlyClients: true, + readOnlyClients: false, allowDeleteClients: true, showDebugWg: false, + proHostTools: false, }; function applyEditionPayload(data) { @@ -100,15 +101,16 @@ function applyEditionPayload(data) { readOnlyClients: ed.readOnlyClients !== false, allowDeleteClients: ed.allowDeleteClients !== false, showDebugWg: Boolean(ed.showDebugWg), + proHostTools: ed.proHostTools === true, }; const titleEl = document.querySelector(".top h1"); if (titleEl) titleEl.textContent = "Пользователи AmneziaWG"; const subEl = document.querySelector(".top .sub"); if (subEl) { subEl.textContent = - "Базовая панель: просмотр клиентов и статусов, удаление клиента с сервера, установка Telegram MTProto‑прокси (Docker)."; + "Просмотр клиентов и статусов, вкл/выкл, переименование, даты отключения, экспорт .conf, удаление, установка Telegram MTProto‑прокси (Docker)."; } - document.querySelector(".clock-host-sync")?.classList.toggle("hidden", editionState.readOnlyClients); + document.querySelector(".clock-host-sync")?.classList.toggle("hidden", !editionState.proHostTools); if (wgRawDetails) wgRawDetails.hidden = uiHidden.users || !editionState.showDebugWg; } @@ -313,7 +315,7 @@ function applyUiHiddenFromPayload(data) { if (wgRawDetails) wgRawDetails.hidden = uiHidden.users || !editionState.showDebugWg; if (cascadePanel) cascadePanel.hidden = uiHidden.cascade; if (warpPanel) warpPanel.hidden = uiHidden.warp; - if (warpCfPanel) warpCfPanel.hidden = editionState.readOnlyClients; + if (warpCfPanel) warpCfPanel.hidden = !editionState.proHostTools; if (mtprotoPanel) mtprotoPanel.hidden = uiHidden.mtproto; void refreshMtprotoPanel(); } @@ -777,7 +779,7 @@ async function loadTimeSyncCaps() { const btn = document.querySelector("#sync-host-time"); try { const c = await api("/api/time-sync-capabilities"); - if (editionState.readOnlyClients) { + if (!editionState.proHostTools) { if (hint) { hint.textContent = "Синхронизация времени хоста по SSH недоступна в базовой версии панели."; @@ -1216,7 +1218,7 @@ function redrawWarpCfPresets() { async function refreshWarpCfPanel() { if (!warpCfPanel || !warpCfStatusLine || !warpCfActionsEl || !warpCfLogEl) return; - if (editionState.readOnlyClients) { + if (!editionState.proHostTools) { warpCfPanel.hidden = true; return; } diff --git a/public/index.html b/public/index.html index 424b25d..82e9191 100644 --- a/public/index.html +++ b/public/index.html @@ -7,7 +7,7 @@ - + + diff --git a/server.js b/server.js index 7577ef9..5ab75b4 100644 --- a/server.js +++ b/server.js @@ -102,9 +102,10 @@ function panelPromoFooterPayload() { function editionPayload() { return { - readOnlyClients: true, + readOnlyClients: false, allowDeleteClients: true, showDebugWg: false, + proHostTools: false, }; } @@ -430,7 +431,7 @@ function requireAuthOrExportToken(req, res, next) { function rejectUnavailableFeature(res) { res.status(403).json({ error: - "Эта функция недоступна в базовой версии панели: полное управление клиентами, экспорт .conf, каскад, Cloudflare WARP и синхронизация времени хоста.", + "Эта функция недоступна в базовой версии панели: каскад, Cloudflare WARP и синхронизация времени хоста.", }); } @@ -2503,7 +2504,7 @@ app.post("/api/warp/routing", requireAuth, requireProTier, async (req, res) => { } }); -app.post("/api/clients/disable", requireAuth, requireProTier, async (req, res) => { +app.post("/api/clients/disable", requireAuth, async (req, res) => { const rt = runtimeForRequest(req); const clientId = req.body?.clientId; if (!clientId) return res.status(400).json({ error: "clientId required" }); @@ -2526,7 +2527,7 @@ app.post("/api/clients/disable", requireAuth, requireProTier, async (req, res) = } }); -app.post("/api/clients/enable", requireAuth, requireProTier, async (req, res) => { +app.post("/api/clients/enable", requireAuth, async (req, res) => { const rt = runtimeForRequest(req); const clientId = req.body?.clientId; if (!clientId) return res.status(400).json({ error: "clientId required" }); @@ -2576,7 +2577,7 @@ AllowedIPs = ${ips}`; } }); -app.post("/api/clients/disconnect-date", requireAuth, requireProTier, async (req, res) => { +app.post("/api/clients/disconnect-date", requireAuth, async (req, res) => { const rt = runtimeForRequest(req); const clientId = req.body?.clientId; if (!clientId) return res.status(400).json({ error: "clientId required" }); @@ -2616,7 +2617,7 @@ app.post("/api/clients/disconnect-date", requireAuth, requireProTier, async (req } }); -app.post("/api/clients/rename", requireAuth, requireProTier, async (req, res) => { +app.post("/api/clients/rename", requireAuth, async (req, res) => { const rt = runtimeForRequest(req); const clientId = req.body?.clientId; const rawName = req.body?.name ?? req.body?.clientName;