mirror of
https://github.com/andrey271192/amnezia_web.git
synced 2026-09-20 14:41:59 +00:00
fix: enable client management in basic panel
After PRO removal, readOnlyClients stayed true and client API routes still used requireProTier, blocking toggle/rename/dates/export in UI. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
## Скриншоты
|
## Скриншоты
|
||||||
|
|
||||||
Интерфейс ориентирован на просмотр клиентов, удаление peer и установку MTProto‑прокси; расширенные действия с клиентами в UI недоступны.
|
Интерфейс: управление клиентами (вкл/выкл, переименование, даты, экспорт `.conf`), удаление peer и установка MTProto‑прокси. Каскад, Cloudflare WARP и синхронизация времени хоста — только при включённых pro‑инструментах на сервере.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/screenshots/panel-users-table.png" alt="Панель: пользователи AmneziaWG, выбор инстанса, время, статусы в туннеле" width="780"/>
|
<img src="docs/screenshots/panel-users-table.png" alt="Панель: пользователи AmneziaWG, выбор инстанса, время, статусы в туннеле" width="780"/>
|
||||||
|
|||||||
@@ -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` в базовой версии недоступны. |
|
| **Пользователи** | Просмотр клиентов, **вкл/выкл**, переименование, даты отключения, экспорт `.conf`, **удаление** peer с сервера. |
|
||||||
|
|
||||||
## Переключатель «Инстанс» не отображается
|
## Переключатель «Инстанс» не отображается
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "amnezia-admin",
|
"name": "amnezia-admin",
|
||||||
"version": "1.2.30",
|
"version": "1.2.31",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "amnezia_web — открытая базовая веб-панель AmneziaWG на VPS (Docker): просмотр и удаление клиентов, MTProto-прокси.",
|
"description": "amnezia_web — открытая базовая веб-панель AmneziaWG на VPS (Docker): просмотр и удаление клиентов, MTProto-прокси.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -86,11 +86,12 @@ let warpSshPendingCmd = null;
|
|||||||
/** Какие панели скрыты настройкой сервера (`UI_HIDE_SECTIONS`). */
|
/** Какие панели скрыты настройкой сервера (`UI_HIDE_SECTIONS`). */
|
||||||
let uiHidden = { users: false, warp: false, cascade: false, mtproto: false };
|
let uiHidden = { users: false, warp: false, cascade: false, mtproto: false };
|
||||||
|
|
||||||
/** Базовые возможности панели (просмотр клиентов + удаление). */
|
/** Базовые возможности панели (управление клиентами + удаление). */
|
||||||
let editionState = {
|
let editionState = {
|
||||||
readOnlyClients: true,
|
readOnlyClients: false,
|
||||||
allowDeleteClients: true,
|
allowDeleteClients: true,
|
||||||
showDebugWg: false,
|
showDebugWg: false,
|
||||||
|
proHostTools: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function applyEditionPayload(data) {
|
function applyEditionPayload(data) {
|
||||||
@@ -100,15 +101,16 @@ function applyEditionPayload(data) {
|
|||||||
readOnlyClients: ed.readOnlyClients !== false,
|
readOnlyClients: ed.readOnlyClients !== false,
|
||||||
allowDeleteClients: ed.allowDeleteClients !== false,
|
allowDeleteClients: ed.allowDeleteClients !== false,
|
||||||
showDebugWg: Boolean(ed.showDebugWg),
|
showDebugWg: Boolean(ed.showDebugWg),
|
||||||
|
proHostTools: ed.proHostTools === true,
|
||||||
};
|
};
|
||||||
const titleEl = document.querySelector(".top h1");
|
const titleEl = document.querySelector(".top h1");
|
||||||
if (titleEl) titleEl.textContent = "Пользователи AmneziaWG";
|
if (titleEl) titleEl.textContent = "Пользователи AmneziaWG";
|
||||||
const subEl = document.querySelector(".top .sub");
|
const subEl = document.querySelector(".top .sub");
|
||||||
if (subEl) {
|
if (subEl) {
|
||||||
subEl.textContent =
|
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;
|
if (wgRawDetails) wgRawDetails.hidden = uiHidden.users || !editionState.showDebugWg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +315,7 @@ function applyUiHiddenFromPayload(data) {
|
|||||||
if (wgRawDetails) wgRawDetails.hidden = uiHidden.users || !editionState.showDebugWg;
|
if (wgRawDetails) wgRawDetails.hidden = uiHidden.users || !editionState.showDebugWg;
|
||||||
if (cascadePanel) cascadePanel.hidden = uiHidden.cascade;
|
if (cascadePanel) cascadePanel.hidden = uiHidden.cascade;
|
||||||
if (warpPanel) warpPanel.hidden = uiHidden.warp;
|
if (warpPanel) warpPanel.hidden = uiHidden.warp;
|
||||||
if (warpCfPanel) warpCfPanel.hidden = editionState.readOnlyClients;
|
if (warpCfPanel) warpCfPanel.hidden = !editionState.proHostTools;
|
||||||
if (mtprotoPanel) mtprotoPanel.hidden = uiHidden.mtproto;
|
if (mtprotoPanel) mtprotoPanel.hidden = uiHidden.mtproto;
|
||||||
void refreshMtprotoPanel();
|
void refreshMtprotoPanel();
|
||||||
}
|
}
|
||||||
@@ -777,7 +779,7 @@ async function loadTimeSyncCaps() {
|
|||||||
const btn = document.querySelector("#sync-host-time");
|
const btn = document.querySelector("#sync-host-time");
|
||||||
try {
|
try {
|
||||||
const c = await api("/api/time-sync-capabilities");
|
const c = await api("/api/time-sync-capabilities");
|
||||||
if (editionState.readOnlyClients) {
|
if (!editionState.proHostTools) {
|
||||||
if (hint) {
|
if (hint) {
|
||||||
hint.textContent =
|
hint.textContent =
|
||||||
"Синхронизация времени хоста по SSH недоступна в базовой версии панели.";
|
"Синхронизация времени хоста по SSH недоступна в базовой версии панели.";
|
||||||
@@ -1216,7 +1218,7 @@ function redrawWarpCfPresets() {
|
|||||||
|
|
||||||
async function refreshWarpCfPanel() {
|
async function refreshWarpCfPanel() {
|
||||||
if (!warpCfPanel || !warpCfStatusLine || !warpCfActionsEl || !warpCfLogEl) return;
|
if (!warpCfPanel || !warpCfStatusLine || !warpCfActionsEl || !warpCfLogEl) return;
|
||||||
if (editionState.readOnlyClients) {
|
if (!editionState.proHostTools) {
|
||||||
warpCfPanel.hidden = true;
|
warpCfPanel.hidden = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="/styles.css?v=1.2.30">
|
<link rel="stylesheet" href="/styles.css?v=1.2.31">
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
try {
|
try {
|
||||||
@@ -357,6 +357,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<script src="/app.js?v=1.2.30" type="module"></script>
|
<script src="/app.js?v=1.2.31" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
13
server.js
13
server.js
@@ -102,9 +102,10 @@ function panelPromoFooterPayload() {
|
|||||||
|
|
||||||
function editionPayload() {
|
function editionPayload() {
|
||||||
return {
|
return {
|
||||||
readOnlyClients: true,
|
readOnlyClients: false,
|
||||||
allowDeleteClients: true,
|
allowDeleteClients: true,
|
||||||
showDebugWg: false,
|
showDebugWg: false,
|
||||||
|
proHostTools: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +431,7 @@ function requireAuthOrExportToken(req, res, next) {
|
|||||||
function rejectUnavailableFeature(res) {
|
function rejectUnavailableFeature(res) {
|
||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
error:
|
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 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" });
|
||||||
@@ -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 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" });
|
||||||
@@ -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 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" });
|
||||||
@@ -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 rt = runtimeForRequest(req);
|
||||||
const clientId = req.body?.clientId;
|
const clientId = req.body?.clientId;
|
||||||
const rawName = req.body?.name ?? req.body?.clientName;
|
const rawName = req.body?.name ?? req.body?.clientName;
|
||||||
|
|||||||
Reference in New Issue
Block a user