diff --git a/README.md b/README.md index b9d34d0..a99679a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # amnezia_web -Открытая **базовая** веб-панель для **просмотра** клиентов **AmneziaWG** на своём VPS: таблица клиентов, статус «в туннеле», AllowedIPs, несколько инстансов через **`AWG_PROFILES`**, часы сервера и браузера. -**Нет** в интерфейсе и по API: включение/выключение peer, правка дат отключения, переименование, удаление, экспорт `.conf`, «Новый клиент под каскад», Cloudflare WARP, синхронизация времени хоста по SSH — это **[версия PRO](https://boosty.to/andrey27/purchase/3906453?ssource=DIRECT&share=subscription_link)** (приватный репозиторий **amnezia_web-PRO**, доступ подписчикам). +Открытая **базовая** веб-панель для **просмотра** клиентов **AmneziaWG** на своём VPS: таблица клиентов, статус «в туннеле», AllowedIPs, несколько инстансов через **`AWG_PROFILES`**, часы сервера и браузера. В редакции **FREE/community** можно **удалить** клиента с сервера (peer и строка в таблице). +**Нет** в интерфейсе и по API в FREE: включение/выключение peer, правка дат отключения, переименование, экспорт `.conf`, «Новый клиент под каскад», Cloudflare WARP, синхронизация времени хоста по SSH — это **[версия PRO](https://boosty.to/andrey27/purchase/3906453?ssource=DIRECT&share=subscription_link)** (приватный репозиторий **amnezia_web-PRO**, доступ подписчикам). Редакция **`community`** задаётся автоматически файлом **`.amnezia-panel-edition`** в корне репозитория (`community`) или переменной окружения **`AMNEZIA_EDITION=community`** в контейнере. Кнопка и текст про подписку настраиваются **`COMMUNITY_UPGRADE_URL`** и **`COMMUNITY_UPGRADE_PITCH`**. diff --git a/docs/panel-guide.md b/docs/panel-guide.md index aa898ef..46a0980 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` если в записи есть `userData.last_config`. | +| **Пользователи** | В FREE: просмотр и **удаление** peer с сервера. В PRO дополнительно: вкл/выкл, переименование, даты отключения; экспорт `.conf` если в записи есть `userData.last_config`. | ## Переключатель «Инстанс» не отображается diff --git a/package.json b/package.json index 29c8037..e275966 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "amnezia-admin", "version": "1.1.8", "private": true, - "description": "amnezia_web — базовая панель AmneziaWG (только просмотр клиентов; полная версия — PRO).", + "description": "amnezia_web — базовая панель AmneziaWG (FREE: просмотр и удаление клиентов; полная версия — PRO).", "license": "MIT", "repository": { "type": "git", diff --git a/public/app.js b/public/app.js index 55bb981..37d4769 100644 --- a/public/app.js +++ b/public/app.js @@ -63,10 +63,11 @@ let uiHidden = { users: false, warp: false, cascade: false }; const editionBanner = document.querySelector("#edition-banner"); const DEFAULT_HEADER_SUB = document.querySelector(".top .sub")?.textContent?.trim() || ""; -/** Состояние редакции панели (community = только просмотр клиентов). */ +/** Состояние редакции панели (community = просмотр + удаление; остальные мутации — PRO). */ let editionState = { tier: "pro", readOnlyClients: false, + allowDeleteClients: false, upgradeUrl: null, upgradePitch: null, showDebugWg: true, @@ -79,6 +80,7 @@ function applyEditionPayload(data) { editionState = { tier: ed.tier === "community" ? "community" : "pro", readOnlyClients: Boolean(ed.readOnlyClients), + allowDeleteClients: Boolean(ed.allowDeleteClients), upgradeUrl: typeof ed.upgradeUrl === "string" ? ed.upgradeUrl : null, upgradePitch: typeof ed.upgradePitch === "string" ? ed.upgradePitch : null, showDebugWg: ed.showDebugWg !== false, @@ -95,7 +97,7 @@ function applyEditionPayload(data) { if (subEl) { if (editionState.tier === "community") { subEl.textContent = - "Базовая панель amnezia_web: только просмотр клиентов AmneziaWG и статусов. Управление туннелем, экспорт .conf, каскад, Cloudflare WARP и синхронизация времени хоста — в версии PRO."; + "Базовая панель amnezia_web: просмотр клиентов и статусов, а также удаление клиента с сервера. Включение/выключение туннеля, даты, переименование, экспорт .conf, каскад, Cloudflare WARP и синхронизация времени хоста — в версии PRO."; } else { subEl.textContent = DEFAULT_HEADER_SUB; } @@ -109,7 +111,7 @@ function applyEditionPayload(data) { const textCol = document.createElement("div"); textCol.className = "edition-banner-text"; const strong = document.createElement("strong"); - strong.textContent = "Базовая версия · только просмотр"; + strong.textContent = "Базовая версия · просмотр и удаление клиентов"; const pitch = document.createElement("p"); pitch.className = "edition-banner-pitch muted"; pitch.textContent = editionState.upgradePitch || ""; @@ -823,10 +825,20 @@ function renderRows(clients) { actTd.className = "actions"; if (editionState.readOnlyClients) { - const lock = document.createElement("span"); - lock.className = "muted"; - lock.textContent = "Только PRO"; - actTd.appendChild(lock); + if (editionState.allowDeleteClients) { + const note = document.createElement("div"); + note.className = "muted"; + note.style.fontSize = "0.85rem"; + note.style.marginBottom = "0.35rem"; + note.textContent = "Вкл/выкл, даты, переименование, экспорт — PRO"; + actTd.appendChild(note); + actTd.appendChild(btn("Удалить", "btn small warn", () => confirmDelete(c.name, c.clientId))); + } else { + const lock = document.createElement("span"); + lock.className = "muted"; + lock.textContent = "Только PRO"; + actTd.appendChild(lock); + } } else { if (c.activeInConf) { actTd.appendChild(btn("Выключить", "btn small ghost", () => openDisableDialog(c))); diff --git a/public/index.html b/public/index.html index 077f69c..4d5fe3f 100644 --- a/public/index.html +++ b/public/index.html @@ -198,7 +198,7 @@ ✉️ Telegram @lot_andrey

- Открытая базовая сборка — только просмотр клиентов. Полная панель PRO (приватный репозиторий) и доступ по подписке — см. Boosty и плашку в интерфейсе после входа. + Открытая базовая сборка — просмотр клиентов и удаление записи с сервера. Полная панель PRO (приватный репозиторий) и доступ по подписке — см. Boosty и плашку в интерфейсе после входа.

diff --git a/server.js b/server.js index 01430ba..246d62d 100644 --- a/server.js +++ b/server.js @@ -52,12 +52,14 @@ const COMMUNITY_UPGRADE_URL = "https://boosty.to/andrey27/purchase/3906453?ssource=DIRECT&share=subscription_link"; const COMMUNITY_UPGRADE_PITCH = process.env.COMMUNITY_UPGRADE_PITCH?.trim() || - "В PRO: вкл/выкл клиентов, даты и расписание отключений, переименование, удаление, экспорт .conf, каскад, Cloudflare WARP, синхронизация времени хоста. Полная сборка — приватный репозиторий amnezia_web-PRO; доступ по подписке Boosty."; + "В PRO: вкл/выкл клиентов, даты и расписание отключений, переименование, экспорт .conf, каскад, Cloudflare WARP, синхронизация времени хоста. В FREE можно удалять клиента с сервера (peer и строка в таблице). Полная сборка — приватный репозиторий amnezia_web-PRO; доступ по подписке Boosty."; function editionPayload() { return { tier: IS_COMMUNITY ? "community" : "pro", readOnlyClients: IS_COMMUNITY, + /** В FREE разрешено удаление записи клиента (остальные мутации — PRO). */ + allowDeleteClients: IS_COMMUNITY, upgradeUrl: IS_COMMUNITY ? COMMUNITY_UPGRADE_URL : null, upgradePitch: IS_COMMUNITY ? COMMUNITY_UPGRADE_PITCH : null, showDebugWg: !IS_COMMUNITY, @@ -1388,7 +1390,7 @@ if (UI_HIDDEN.users || UI_HIDDEN.warp || UI_HIDDEN.cascade) { ); } if (IS_COMMUNITY) { - console.warn(`Редакция community (только просмотр клиентов). PRO: ${COMMUNITY_UPGRADE_URL}`); + console.warn(`Редакция community (просмотр клиентов и удаление). Остальное — PRO: ${COMMUNITY_UPGRADE_URL}`); } if (ALLOW_COMMUNITY_GITHUB_ACTIVATION && IS_COMMUNITY) { console.warn( @@ -2227,7 +2229,7 @@ app.post("/api/clients/rename", requireAuth, requireProTier, async (req, res) => } }); -app.post("/api/clients/delete", requireAuth, requireProTier, async (req, res) => { +app.post("/api/clients/delete", requireAuth, async (req, res) => { const rt = runtimeForRequest(req); const clientId = req.body?.clientId; if (!clientId) return res.status(400).json({ error: "clientId required" });