From 3c2866818ab331c711fa0e7552a548c177f70827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=91=D0=BE=D0=B1?= =?UTF-8?q?=D1=8B=D1=80=D0=B5=D0=B2?= Date: Fri, 15 May 2026 04:14:35 +0300 Subject: [PATCH] perf(mtproto): fast tg link; logs via separate endpoint /api/mtproto/status skips docker logs. /api/mtproto/logs loads tail async. Install response sets link card immediately. v1.2.23. Co-authored-by: Cursor --- README.md | 2 ++ package.json | 2 +- public/app.js | 58 +++++++++++++++++++++++++++++------------------ public/index.html | 4 ++-- server.js | 17 ++++++++++---- 5 files changed, 53 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index de79215..9ce7859 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ cd /opt/amnezia-admin && chmod +x scripts/install.sh && sudo SKIP_DOWNLOAD=1 bas | `UI_HIDE_MTPROTO` | _(не задано)_ | `1` или **`UI_HIDE_SECTIONS=...,mtproto`** — скрыть раздел установки MTProto в веб‑интерфейсе (по умолчанию виден даже FREE). | | `FREE_PANEL_CONTAINER_FOR_PRO_INSTALL` и др. | см. `server.js` | Имена контейнеров для `docker rm` перед install (по умолчанию `amnezia-admin`, `amnezia-web-landing`, `amnezia-admin-pro`). | +Примечание для MTProto на панели: **`GET /api/mtproto/status`** отдаёт состояние и **`tg://` без синхронного `docker logs`**; хвост логов подгружается вторым запросом **`GET /api/mtproto/logs`**, чтобы блок со ссылкой открывался быстрее. + Чтобы включить форму установки из панели после «обычной» установки без **`INSTALL_FREE…`**, задайте в контейнере **`AMNEZIA_EDITION=community`** и **`ALLOW_COMMUNITY_GITHUB_ACTIVATION=1`** (или проще переустановите с **`INSTALL_FREE_COMMUNITY_ACTIVATION=1`**). URL приватного `install.sh` при необходимости переопределите через **`COMMUNITY_PRIVATE_INSTALL_SCRIPT_URL`**. **Важно:** при одноразовом запуске с переменными в одной строке с `curl … | sudo bash` они **пропадают** — нужен **`export`** перед `curl` и **`sudo -E bash`**, см. блок **«Установка для клиента»** выше. diff --git a/package.json b/package.json index 92aebbc..b089598 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amnezia-admin", - "version": "1.2.22", + "version": "1.2.23", "private": true, "description": "amnezia_web — базовая панель AmneziaWG (FREE: просмотр и удаление клиентов; полная версия — PRO).", "license": "MIT", diff --git a/public/app.js b/public/app.js index 1687f78..80bb970 100644 --- a/public/app.js +++ b/public/app.js @@ -426,6 +426,28 @@ function applyEditionPayload(data) { if (wgRawDetails) wgRawDetails.hidden = uiHidden.users || !editionState.showDebugWg; } +/** @param {string} [tgLink] */ +function setMtprotoLinkCard(tgLink) { + const t = typeof tgLink === "string" ? tgLink.trim() : ""; + if (!mtprotoLinkCardEl) return; + if (!t) { + mtprotoLinkCardEl.classList.add("hidden"); + delete mtprotoLinkCardEl.dataset.tgLink; + if (mtprotoLinkAnchorEl) { + mtprotoLinkAnchorEl.removeAttribute("href"); + mtprotoLinkAnchorEl.textContent = ""; + } + return; + } + mtprotoLinkCardEl.classList.remove("hidden"); + mtprotoLinkCardEl.dataset.tgLink = t; + if (mtprotoLinkAnchorEl) { + mtprotoLinkAnchorEl.href = t; + mtprotoLinkAnchorEl.textContent = t; + } + if (mtprotoLinkCopyState) mtprotoLinkCopyState.textContent = ""; +} + async function refreshMtprotoPanel() { if (!mtprotoPanel || !mtprotoStatusLine || !mtprotoActionsEl) return; if (uiHidden.mtproto) { @@ -434,6 +456,7 @@ async function refreshMtprotoPanel() { mtprotoCalloutEl.textContent = ""; mtprotoCalloutEl.classList.add("hidden"); } + setMtprotoLinkCard(""); return; } mtprotoPanel.hidden = false; @@ -443,11 +466,17 @@ async function refreshMtprotoPanel() { mtprotoBanner.textContent = ""; } try { + if (mtprotoLogsTailEl) mtprotoLogsTailEl.textContent = "Загрузка логов…"; const snap = await api("/api/mtproto/status"); - if (mtprotoLogsTailEl) { - mtprotoLogsTailEl.textContent = - typeof snap.logsTail === "string" ? snap.logsTail : ""; - } + void (async () => { + try { + const lg = await api("/api/mtproto/logs"); + const tail = typeof lg?.logsTail === "string" ? lg.logsTail : ""; + if (mtprotoLogsTailEl) mtprotoLogsTailEl.textContent = tail; + } catch { + if (mtprotoLogsTailEl) mtprotoLogsTailEl.textContent = "(лог не загрузился — обновите раздел позже)"; + } + })(); if (mtprotoCalloutEl) { if (snap.exists) { @@ -485,20 +514,7 @@ async function refreshMtprotoPanel() { mtprotoActionsEl.appendChild(imgHint); } - if (mtprotoLinkCardEl) { - if (snap.tgLink) { - mtprotoLinkCardEl.classList.remove("hidden"); - mtprotoLinkCardEl.dataset.tgLink = snap.tgLink; - if (mtprotoLinkAnchorEl) { - mtprotoLinkAnchorEl.href = snap.tgLink; - mtprotoLinkAnchorEl.textContent = snap.tgLink; - } - if (mtprotoLinkCopyState) mtprotoLinkCopyState.textContent = ""; - } else { - mtprotoLinkCardEl.classList.add("hidden"); - delete mtprotoLinkCardEl.dataset.tgLink; - } - } + setMtprotoLinkCard(snap.tgLink); const row = document.createElement("div"); row.className = "warp-actions"; @@ -516,6 +532,7 @@ async function refreshMtprotoPanel() { } if (/^[a-f0-9]{32}$/.test(sec)) body.secret = sec; const j = await api("/api/mtproto/install", { method: "POST", body: JSON.stringify(body) }); + if (typeof j.tgLink === "string" && j.tgLink.trim()) setMtprotoLinkCard(j.tgLink.trim()); const lines = []; if (typeof j.secretHex === "string") lines.push(`Секрет (сохраните): ${j.secretHex}`); if (typeof j.tgLink === "string" && j.tgLink) lines.push(`Ссылка: ${j.tgLink}`); @@ -580,10 +597,7 @@ async function refreshMtprotoPanel() { mtprotoCalloutEl.textContent = ""; mtprotoCalloutEl.classList.add("hidden"); } - if (mtprotoLinkCardEl) { - mtprotoLinkCardEl.classList.add("hidden"); - delete mtprotoLinkCardEl.dataset.tgLink; - } + setMtprotoLinkCard(""); if (mtprotoLogsTailEl) mtprotoLogsTailEl.textContent = ""; const err = document.createElement("p"); err.className = "muted warp-muted err"; diff --git a/public/index.html b/public/index.html index 611c41a..126952c 100644 --- a/public/index.html +++ b/public/index.html @@ -7,7 +7,7 @@ - + + diff --git a/server.js b/server.js index b06a0a8..88d5483 100644 --- a/server.js +++ b/server.js @@ -2246,12 +2246,19 @@ app.get("/api/mtproto/status", requireAuth, (req, res) => { return res.status(403).json({ error: MSG_UI_MTProto_OFF }); } const snap = mtprotoSnapshot(hostFromRequest(req)); - let logsTail = ""; - if (snap.exists && snap.running) { - const l = dockerSpawnSync(["logs", "--tail", "100", MTPRO_CONTAINER], 12_000); - if (l.code === 0) logsTail = l.stdout.trim().slice(-4500); + res.json({ ...snap }); +}); + +app.get("/api/mtproto/logs", requireAuth, (req, res) => { + if (effectiveUiHidden().mtproto) { + return res.status(403).json({ error: MSG_UI_MTProto_OFF }); } - res.json({ ...snap, logsTail }); + const snap = mtprotoSnapshot(hostFromRequest(req)); + if (!snap.exists || !snap.running) return res.json({ logsTail: "" }); + const l = dockerSpawnSync(["logs", "--tail", "100", MTPRO_CONTAINER], 12_000); + let logsTail = ""; + if (l.code === 0) logsTail = l.stdout.trim().slice(-4500); + res.json({ logsTail }); }); app.post("/api/mtproto/install", requireAuth, (req, res) => {