mirror of
https://github.com/andrey271192/amnezia_web.git
synced 2026-09-20 14:41:59 +00:00
feat(community): allow deleting clients in FREE tier
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# amnezia_web
|
# amnezia_web
|
||||||
|
|
||||||
Открытая **базовая** веб-панель для **просмотра** клиентов **AmneziaWG** на своём VPS: таблица клиентов, статус «в туннеле», AllowedIPs, несколько инстансов через **`AWG_PROFILES`**, часы сервера и браузера.
|
Открытая **базовая** веб-панель для **просмотра** клиентов **AmneziaWG** на своём VPS: таблица клиентов, статус «в туннеле», AllowedIPs, несколько инстансов через **`AWG_PROFILES`**, часы сервера и браузера. В редакции **FREE/community** можно **удалить** клиента с сервера (peer и строка в таблице).
|
||||||
**Нет** в интерфейсе и по API: включение/выключение peer, правка дат отключения, переименование, удаление, экспорт `.conf`, «Новый клиент под каскад», Cloudflare WARP, синхронизация времени хоста по SSH — это **[версия PRO](https://boosty.to/andrey27/purchase/3906453?ssource=DIRECT&share=subscription_link)** (приватный репозиторий **amnezia_web-PRO**, доступ подписчикам).
|
**Нет** в интерфейсе и по 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`**.
|
Редакция **`community`** задаётся автоматически файлом **`.amnezia-panel-edition`** в корне репозитория (`community`) или переменной окружения **`AMNEZIA_EDITION=community`** в контейнере. Кнопка и текст про подписку настраиваются **`COMMUNITY_UPGRADE_URL`** и **`COMMUNITY_UPGRADE_PITCH`**.
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
| **Время** | Отображение часов контейнера и браузера; синхронизация времени хоста VPS по SSH (если доступно). |
|
| **Время** | Отображение часов контейнера и браузера; синхронизация времени хоста VPS по SSH (если доступно). |
|
||||||
| **Cloudflare WARP** | *Необязательно.* Вывод части клиентов в интернет через интерфейс `warp` в контейнере AWG. Если WARP не ставили — статус **«Не установлен»** нормален; панель и VPN без этого работают. Установка и удаление — скрипт `scripts/warp-amnezia.sh` на хосте (`install` / **`uninstall`**), подробности в основном [README](../README.md). |
|
| **Cloudflare WARP** | *Необязательно.* Вывод части клиентов в интернет через интерфейс `warp` в контейнере AWG. Если WARP не ставили — статус **«Не установлен»** нормален; панель и VPN без этого работают. Установка и удаление — скрипт `scripts/warp-amnezia.sh` на хосте (`install` / **`uninstall`**), подробности в основном [README](../README.md). |
|
||||||
| **Новый клиент под каскад** | Создание нового peer на сервере с **вашим Endpoint** (промежуточный узел); выдача готового `.conf`. |
|
| **Новый клиент под каскад** | Создание нового peer на сервере с **вашим Endpoint** (промежуточный узел); выдача готового `.conf`. |
|
||||||
| **Пользователи** | Вкл/выкл peer, удаление, переименование, даты отключения; экспорт `.conf` если в записи есть `userData.last_config`. |
|
| **Пользователи** | В FREE: просмотр и **удаление** peer с сервера. В PRO дополнительно: вкл/выкл, переименование, даты отключения; экспорт `.conf` если в записи есть `userData.last_config`. |
|
||||||
|
|
||||||
## Переключатель «Инстанс» не отображается
|
## Переключатель «Инстанс» не отображается
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "amnezia-admin",
|
"name": "amnezia-admin",
|
||||||
"version": "1.1.8",
|
"version": "1.1.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "amnezia_web — базовая панель AmneziaWG (только просмотр клиентов; полная версия — PRO).",
|
"description": "amnezia_web — базовая панель AmneziaWG (FREE: просмотр и удаление клиентов; полная версия — PRO).",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -63,10 +63,11 @@ let uiHidden = { users: false, warp: false, cascade: false };
|
|||||||
const editionBanner = document.querySelector("#edition-banner");
|
const editionBanner = document.querySelector("#edition-banner");
|
||||||
const DEFAULT_HEADER_SUB = document.querySelector(".top .sub")?.textContent?.trim() || "";
|
const DEFAULT_HEADER_SUB = document.querySelector(".top .sub")?.textContent?.trim() || "";
|
||||||
|
|
||||||
/** Состояние редакции панели (community = только просмотр клиентов). */
|
/** Состояние редакции панели (community = просмотр + удаление; остальные мутации — PRO). */
|
||||||
let editionState = {
|
let editionState = {
|
||||||
tier: "pro",
|
tier: "pro",
|
||||||
readOnlyClients: false,
|
readOnlyClients: false,
|
||||||
|
allowDeleteClients: false,
|
||||||
upgradeUrl: null,
|
upgradeUrl: null,
|
||||||
upgradePitch: null,
|
upgradePitch: null,
|
||||||
showDebugWg: true,
|
showDebugWg: true,
|
||||||
@@ -79,6 +80,7 @@ function applyEditionPayload(data) {
|
|||||||
editionState = {
|
editionState = {
|
||||||
tier: ed.tier === "community" ? "community" : "pro",
|
tier: ed.tier === "community" ? "community" : "pro",
|
||||||
readOnlyClients: Boolean(ed.readOnlyClients),
|
readOnlyClients: Boolean(ed.readOnlyClients),
|
||||||
|
allowDeleteClients: Boolean(ed.allowDeleteClients),
|
||||||
upgradeUrl: typeof ed.upgradeUrl === "string" ? ed.upgradeUrl : null,
|
upgradeUrl: typeof ed.upgradeUrl === "string" ? ed.upgradeUrl : null,
|
||||||
upgradePitch: typeof ed.upgradePitch === "string" ? ed.upgradePitch : null,
|
upgradePitch: typeof ed.upgradePitch === "string" ? ed.upgradePitch : null,
|
||||||
showDebugWg: ed.showDebugWg !== false,
|
showDebugWg: ed.showDebugWg !== false,
|
||||||
@@ -95,7 +97,7 @@ function applyEditionPayload(data) {
|
|||||||
if (subEl) {
|
if (subEl) {
|
||||||
if (editionState.tier === "community") {
|
if (editionState.tier === "community") {
|
||||||
subEl.textContent =
|
subEl.textContent =
|
||||||
"Базовая панель amnezia_web: только просмотр клиентов AmneziaWG и статусов. Управление туннелем, экспорт .conf, каскад, Cloudflare WARP и синхронизация времени хоста — в версии PRO.";
|
"Базовая панель amnezia_web: просмотр клиентов и статусов, а также удаление клиента с сервера. Включение/выключение туннеля, даты, переименование, экспорт .conf, каскад, Cloudflare WARP и синхронизация времени хоста — в версии PRO.";
|
||||||
} else {
|
} else {
|
||||||
subEl.textContent = DEFAULT_HEADER_SUB;
|
subEl.textContent = DEFAULT_HEADER_SUB;
|
||||||
}
|
}
|
||||||
@@ -109,7 +111,7 @@ function applyEditionPayload(data) {
|
|||||||
const textCol = document.createElement("div");
|
const textCol = document.createElement("div");
|
||||||
textCol.className = "edition-banner-text";
|
textCol.className = "edition-banner-text";
|
||||||
const strong = document.createElement("strong");
|
const strong = document.createElement("strong");
|
||||||
strong.textContent = "Базовая версия · только просмотр";
|
strong.textContent = "Базовая версия · просмотр и удаление клиентов";
|
||||||
const pitch = document.createElement("p");
|
const pitch = document.createElement("p");
|
||||||
pitch.className = "edition-banner-pitch muted";
|
pitch.className = "edition-banner-pitch muted";
|
||||||
pitch.textContent = editionState.upgradePitch || "";
|
pitch.textContent = editionState.upgradePitch || "";
|
||||||
@@ -823,10 +825,20 @@ function renderRows(clients) {
|
|||||||
actTd.className = "actions";
|
actTd.className = "actions";
|
||||||
|
|
||||||
if (editionState.readOnlyClients) {
|
if (editionState.readOnlyClients) {
|
||||||
|
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");
|
const lock = document.createElement("span");
|
||||||
lock.className = "muted";
|
lock.className = "muted";
|
||||||
lock.textContent = "Только PRO";
|
lock.textContent = "Только PRO";
|
||||||
actTd.appendChild(lock);
|
actTd.appendChild(lock);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (c.activeInConf) {
|
if (c.activeInConf) {
|
||||||
actTd.appendChild(btn("Выключить", "btn small ghost", () => openDisableDialog(c)));
|
actTd.appendChild(btn("Выключить", "btn small ghost", () => openDisableDialog(c)));
|
||||||
|
|||||||
@@ -198,7 +198,7 @@
|
|||||||
<a href="https://t.me/lot_andrey" target="_blank" rel="noopener noreferrer">✉️ Telegram @lot_andrey</a>
|
<a href="https://t.me/lot_andrey" target="_blank" rel="noopener noreferrer">✉️ Telegram @lot_andrey</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="support-blurb">
|
<p class="support-blurb">
|
||||||
Открытая базовая сборка — только просмотр клиентов. Полная панель PRO (приватный репозиторий) и доступ по подписке — см. Boosty и плашку в интерфейсе после входа.
|
Открытая базовая сборка — просмотр клиентов и удаление записи с сервера. Полная панель PRO (приватный репозиторий) и доступ по подписке — см. Boosty и плашку в интерфейсе после входа.
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
@@ -52,12 +52,14 @@ const COMMUNITY_UPGRADE_URL =
|
|||||||
"https://boosty.to/andrey27/purchase/3906453?ssource=DIRECT&share=subscription_link";
|
"https://boosty.to/andrey27/purchase/3906453?ssource=DIRECT&share=subscription_link";
|
||||||
const COMMUNITY_UPGRADE_PITCH =
|
const COMMUNITY_UPGRADE_PITCH =
|
||||||
process.env.COMMUNITY_UPGRADE_PITCH?.trim() ||
|
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() {
|
function editionPayload() {
|
||||||
return {
|
return {
|
||||||
tier: IS_COMMUNITY ? "community" : "pro",
|
tier: IS_COMMUNITY ? "community" : "pro",
|
||||||
readOnlyClients: IS_COMMUNITY,
|
readOnlyClients: IS_COMMUNITY,
|
||||||
|
/** В FREE разрешено удаление записи клиента (остальные мутации — PRO). */
|
||||||
|
allowDeleteClients: IS_COMMUNITY,
|
||||||
upgradeUrl: IS_COMMUNITY ? COMMUNITY_UPGRADE_URL : null,
|
upgradeUrl: IS_COMMUNITY ? COMMUNITY_UPGRADE_URL : null,
|
||||||
upgradePitch: IS_COMMUNITY ? COMMUNITY_UPGRADE_PITCH : null,
|
upgradePitch: IS_COMMUNITY ? COMMUNITY_UPGRADE_PITCH : null,
|
||||||
showDebugWg: !IS_COMMUNITY,
|
showDebugWg: !IS_COMMUNITY,
|
||||||
@@ -1388,7 +1390,7 @@ if (UI_HIDDEN.users || UI_HIDDEN.warp || UI_HIDDEN.cascade) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (IS_COMMUNITY) {
|
if (IS_COMMUNITY) {
|
||||||
console.warn(`Редакция community (только просмотр клиентов). PRO: ${COMMUNITY_UPGRADE_URL}`);
|
console.warn(`Редакция community (просмотр клиентов и удаление). Остальное — PRO: ${COMMUNITY_UPGRADE_URL}`);
|
||||||
}
|
}
|
||||||
if (ALLOW_COMMUNITY_GITHUB_ACTIVATION && IS_COMMUNITY) {
|
if (ALLOW_COMMUNITY_GITHUB_ACTIVATION && IS_COMMUNITY) {
|
||||||
console.warn(
|
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 rt = runtimeForRequest(req);
|
||||||
const clientId = req.body?.clientId;
|
const clientId = req.body?.clientId;
|
||||||
if (!clientId) return res.status(400).json({ error: "clientId required" });
|
if (!clientId) return res.status(400).json({ error: "clientId required" });
|
||||||
|
|||||||
Reference in New Issue
Block a user