Files
amnezia_web/public/index.html
Андрей Бобырев 3c2866818a 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 <cursoragent@cursor.com>
2026-05-15 04:14:35 +03:00

318 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AmneziaWG — просмотр клиентов</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<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 rel="stylesheet" href="/styles.css?v=1.2.23">
<script>
(function () {
try {
var saved = localStorage.getItem("amnezia.theme");
var t = saved === "light" || saved === "dark" ? saved : "auto";
var resolved = t === "auto"
? (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
: t;
document.documentElement.setAttribute("data-theme-pref", t);
document.documentElement.setAttribute("data-theme", resolved);
} catch (e) {}
})();
</script>
</head>
<body class="page">
<aside id="panel-promo-strip" class="panel-promo-strip hidden" role="complementary" aria-label="Поддержка проекта">
<div class="panel-promo-strip-inner">
<p id="panel-promo-subtitle" class="panel-promo-subtitle muted"></p>
<div class="panel-promo-links">
<a id="panel-strip-donate" class="panel-promo-a hidden" target="_blank" rel="noopener noreferrer"></a>
<span id="panel-strip-sep-donate-ozon" class="panel-promo-sep hidden" aria-hidden="true">·</span>
<a id="panel-strip-ozon" class="panel-promo-a hidden" target="_blank" rel="noopener noreferrer"></a>
<span id="panel-strip-sep-ozon-tg" class="panel-promo-sep hidden" aria-hidden="true">·</span>
<a id="panel-strip-telegram" class="panel-promo-a hidden" target="_blank" rel="noopener noreferrer"></a>
</div>
</div>
</aside>
<div id="login-gate" class="gate hidden" aria-hidden="true">
<div class="gate-card">
<p class="eyebrow">Панель сервера</p>
<h1>Вход</h1>
<p class="sub">Введите пароль администратора панели.</p>
<form id="login-form">
<label for="login-password">Пароль</label>
<input id="login-password" type="password" autocomplete="current-password" required>
<button type="submit" class="btn primary full">Войти</button>
<p id="login-error" class="status err" role="alert"></p>
</form>
</div>
</div>
<div id="app-root" class="shell hidden">
<header class="top">
<div>
<p class="eyebrow">Панель сервера</p>
<h1>Пользователи AmneziaWG FREE</h1>
<p class="sub">Базовая открытая панель: просмотр клиентов AmneziaWG и (после входа) установка Telegram MTProtoпрокси в Docker без PRO. Полное управление peer, экспорт .conf, каскад, Cloudflare WARP и синхронизация времени хоста — в версии PRO.</p>
</div>
<div class="token-box">
<div class="theme-switch" role="group" aria-label="Тема оформления">
<label for="theme-select" class="theme-switch-label muted">Тема</label>
<select id="theme-select" class="theme-select" aria-label="Тема оформления">
<option value="auto">Системная</option>
<option value="light">Светлая</option>
<option value="dark">Тёмная</option>
</select>
</div>
<div class="session-actions">
<button type="button" id="logout" class="btn ghost full">Выйти</button>
</div>
<details class="pw-change">
<summary>Сменить пароль</summary>
<form id="pw-form">
<label for="pw-current">Текущий пароль</label>
<input id="pw-current" type="password" autocomplete="current-password" required>
<label for="pw-new">Новый пароль</label>
<input id="pw-new" type="password" autocomplete="new-password" required minlength="8">
<label for="pw-new2">Повтор нового пароля</label>
<input id="pw-new2" type="password" autocomplete="new-password" required minlength="8">
<button type="submit" class="btn primary full">Сохранить новый пароль</button>
<p id="pw-msg" class="status" role="status"></p>
</form>
</details>
</div>
</header>
<div id="edition-banner" class="edition-banner hidden" role="region" aria-label="Редакция панели"></div>
<section class="toolbar">
<div class="pill proto-pill"><span class="dot ok"></span><span id="proto-label">Протокол: AmneziaWG</span></div>
<div id="proto-switch" class="proto-switch hidden">
<label for="proto-select" class="proto-switch-label muted">Инстанс</label>
<select id="proto-select" class="proto-select" aria-label="Инстанс AmneziaWG"></select>
</div>
<div class="clock-strip" role="status" aria-live="polite">
<div class="clock-row clock-row--server">
<span class="muted clock-kind">Сервер <span class="clock-kind-sub">(часы VPS / контейнера)</span></span>
<time id="clock-server" class="clock-time"></time>
</div>
<div class="clock-row clock-row--your-place">
<span class="muted clock-kind">Ваше место <span class="clock-kind-sub">(браузер)</span></span>
<time id="clock-local" class="clock-time"></time>
</div>
<p id="clock-zone-diff" class="clock-zone-diff muted hidden" role="note"></p>
<p class="clock-hint muted">
Цифры разнятся, если пояса разные — это нормально для одного момента UTC. «Сверить» подтягивает время с VPS.
</p>
<button type="button" id="clock-sync" class="btn small ghost clock-sync-btn">
Сверить время
</button>
<details class="clock-host-sync">
<summary class="clock-sync-summary">Синхронизация: выставить часы <strong>хоста VPS</strong> под <strong>ваше устройство</strong></summary>
<p id="sync-host-hint" class="muted clock-sync-hint"></p>
<label for="sync-root-pw">Пароль root на хосте (не сохраняется)</label>
<input id="sync-root-pw" type="password" autocomplete="off" class="clock-sync-input">
<button type="button" id="sync-host-time" class="btn small primary clock-sync-submit">
Взять время с этого устройства и записать на хост VPS
</button>
</details>
</div>
<button type="button" id="refresh" class="btn primary">Обновить</button>
</section>
<p id="profile-hint" class="profile-hint muted hidden" role="note"></p>
<p id="status" class="status" role="status"></p>
<details class="panel-fold warp-panel" id="warp-panel" hidden>
<summary class="fold-summary">
<span class="fold-arrow" aria-hidden="true"></span>
<span class="fold-titles">
<span class="fold-h">Cloudflare WARP</span>
<span class="muted fold-meta" id="warp-status-line"></span>
</span>
</summary>
<div class="panel-fold-body">
<p class="muted warp-intro">
<strong>Необязательно.</strong> Если достаточно обычного AmneziaWG без выхода части клиентов через Cloudflare — раздел можно не трогать (статус «Не установлен» — норма).
Если WARP нужен: выход в интернет через Cloudflare для <em>выбранных</em> клиентов (правила маршрутизации и NAT в контейнере).
Один раз на хосте VPS под root: в каталоге с репозиторием выполните <code class="inline">bash scripts/warp-amnezia.sh install</code>.
Затем отметьте клиентов ниже и нажмите «Применить» (контейнер AWG перезапустится). Установить или удалить WARP с хоста можно кнопками ниже (пароль root по SSH — как при синхронизации времени). Убрать вручную: <code class="inline">bash scripts/warp-amnezia.sh uninstall</code> — см. README.
</p>
<div id="warp-actions" class="warp-actions"></div>
<div id="warp-client-list" class="warp-client-list"></div>
<details class="raw warp-raw">
<summary>Вывод wg show warp</summary>
<pre id="warp-wg-show"></pre>
</details>
</div>
</details>
<details class="panel-fold mtproto-panel" id="mtproto-panel" hidden>
<summary class="fold-summary">
<span class="fold-arrow" aria-hidden="true"></span>
<span class="fold-titles">
<span class="fold-h">Telegram MTProtoпрокси</span>
<span class="muted fold-meta" id="mtproto-status-line"></span>
</span>
</summary>
<div class="panel-fold-body">
<p class="muted warp-intro mtproto-intro">
<strong>В базовой панели (FREE).</strong> Отдельный Dockerконтейнер (образ Telegram), публикация порта на хосте VPS.
Ссылка вида <code class="inline">tg://proxy?…</code> может появляться без env, если панель открыта по публичному IP/DNS; для фиксированного хоста задайте <code class="inline">MTPRO_PUBLIC_HOST</code> или <code class="inline">CLIENT_CONFIG_ENDPOINT</code>
(переменные контейнера панели, см. install.sh README).
Сохраните <strong>секрет</strong>, который показывается один раз после установки — в интерфейсе он маскируется.
</p>
<p id="mtproto-callout" class="mtproto-callout hidden" role="note"></p>
<div id="mtproto-link-card" class="mtproto-link-card hidden" role="region" aria-label="Ссылка для Telegram">
<div class="mtproto-link-row">
<strong class="mtproto-link-label">Ссылка в Telegram</strong>
<a id="mtproto-link-anchor" class="mtproto-link-anchor" target="_blank" rel="noopener noreferrer"></a>
</div>
<div class="mtproto-link-row mtproto-link-actions">
<button type="button" id="mtproto-link-copy" class="btn small ghost">Скопировать</button>
<span id="mtproto-link-copy-state" class="muted warp-muted"></span>
</div>
</div>
<div class="mtproto-fields">
<label for="mtproto-host-port">Порт хоста (необязательно — по умолчанию см. MTPRO_PUBLISH_PORT, обычно 8443)</label>
<input id="mtproto-host-port" type="number" min="512" max="65535" autocomplete="off" placeholder="8443 или другой свободный порт">
<label for="mtproto-secret-opt">Кастомный секрет hex (32 символа, необязательно)</label>
<input id="mtproto-secret-opt" type="text" maxlength="32" autocomplete="off" placeholder="Пусто — сгенерируется">
</div>
<div id="mtproto-actions" class="warp-actions"></div>
<p id="mtproto-banner" class="status ok hidden" role="status"></p>
<details class="raw warp-raw">
<summary>Хвост логов контейнера mtprotoproxy</summary>
<pre id="mtproto-logs-tail"></pre>
</details>
</div>
</details>
<details class="panel-fold cascade-panel" id="cascade-panel" open>
<summary class="fold-summary">
<span class="fold-arrow" aria-hidden="true"></span>
<span class="fold-titles">
<span class="fold-h">Новый клиент под каскад</span>
<span class="muted fold-meta">Создать peer и скачать .conf</span>
</span>
</summary>
<div class="panel-fold-body">
<p class="muted cascade-intro">
Укажите <strong>Endpoint</strong> — публичный IP или DNS узла, <em>куда клиент будет подключаться первым шагом</em>
(промежуточный сервер, домашний роутер с пробросом порта и т.д.). Порт по умолчанию совпадает с <code class="inline">ListenPort</code> этого инстанса.
Клиент будет <strong>создан на текущем сервере AmneziaWG</strong> (новые ключи); IP в туннеле можно задать явно или оставить пустым — подберём свободный в той же подсети, что у остальных.
</p>
<form id="cascade-form" class="cascade-form">
<label for="cascade-endpoint">Endpoint (хост для клиента)</label>
<input id="cascade-endpoint" type="text" placeholder="Например 203.0.113.50 или ddns.example.com" required autocomplete="off">
<label for="cascade-port">Порт Endpoint</label>
<input id="cascade-port" type="number" min="1" max="65535" placeholder="Пусто — как у сервера" autocomplete="off">
<label for="cascade-tunnel-ip">IP в VPN-туннеле (необязательно)</label>
<input id="cascade-tunnel-ip" type="text" placeholder="Например 10.8.1.20 — та же подсеть /24 что у других" autocomplete="off">
<label for="cascade-name">Имя в таблице</label>
<input id="cascade-name" type="text" placeholder="Например Телефон через узел X" autocomplete="off">
<button type="submit" class="btn primary">Создать на сервере и скачать .conf</button>
</form>
</div>
</details>
<details class="panel-fold" id="users-panel" open>
<summary class="fold-summary">
<span class="fold-arrow" aria-hidden="true"></span>
<span class="fold-titles">
<span class="fold-h">Пользователи</span>
<span class="muted fold-meta" id="peer-count"></span>
</span>
</summary>
<div class="panel-fold-body">
<div class="table-wrap">
<table class="clients-table">
<thead>
<tr>
<th>Имя</th>
<th>IP</th>
<th>Статус</th>
<th>Последнее отключение</th>
<th class="th-actions">Действия</th>
</tr>
</thead>
<tbody id="rows"></tbody>
</table>
</div>
</div>
</details>
<details class="raw panel-fold-raw" id="wg-raw-details">
<summary class="fold-summary fold-summary--raw">
<span class="fold-arrow" aria-hidden="true"></span>
<span class="fold-titles">
<span class="fold-h">Вывод awg show (отладка)</span>
</span>
</summary>
<pre id="wg-show"></pre>
</details>
</div>
<footer class="support-footer">
<div class="support-line">
<strong class="support-title">amnezia_web</strong>
<span class="support-sep" aria-hidden="true">·</span>
<a href="https://github.com/andrey271192/amnezia_web" target="_blank" rel="noopener noreferrer">GitHub</a>
<span id="footer-promo-gap1" class="support-sep hidden" aria-hidden="true">·</span>
<a id="footer-promo-donate" class="support-promo-link hidden" target="_blank" rel="noopener noreferrer"></a>
<span id="footer-promo-sep-donate-ozon" class="support-sep hidden" aria-hidden="true">·</span>
<a id="footer-promo-ozon" class="support-promo-link hidden" target="_blank" rel="noopener noreferrer"></a>
<span id="footer-promo-sep-ozon-tg" class="support-sep hidden" aria-hidden="true">·</span>
<a id="footer-promo-telegram" class="support-promo-link hidden" target="_blank" rel="noopener noreferrer"></a>
</div>
<p class="support-blurb">
Базовая панель: AWGклиенты, статусы и MTProtoпрокси (Docker). Ссылки поддержки (Boosty, Ozon СБП, Telegram) подставляются из настроек сервера — только в этом интерфейсе, не на пользовательских лендингах.
</p>
</footer>
<dialog id="disconnect-dt-dialog" class="dt-dialog">
<div class="dt-dialog-inner">
<h3 id="dt-dialog-title">Дата отключения</h3>
<p class="muted" id="dt-dialog-client"></p>
<label for="dt-dialog-input">Дата и время</label>
<input type="datetime-local" id="dt-dialog-input" step="60">
<div id="dt-dialog-extra" class="dt-dialog-extra hidden">
<p class="dt-dialog-hint" id="dt-dialog-hint"></p>
<label class="dt-checkbox-label">
<input type="checkbox" id="dt-dialog-schedule-tunnel">
Убрать из туннеля в указанное время (до этого клиент остаётся в туннеле)
</label>
</div>
<div class="dt-dialog-actions">
<button type="button" class="btn ghost" id="dt-dialog-cancel">Отмена</button>
<button type="button" class="btn primary" id="dt-dialog-ok">Подтвердить</button>
</div>
</div>
</dialog>
<dialog id="warp-ssh-dialog" class="dt-dialog">
<div class="dt-dialog-inner">
<h3 id="warp-ssh-title">WARP на хосте VPS</h3>
<p class="muted" id="warp-ssh-lead"></p>
<label for="warp-ssh-pw">Пароль root на хосте VPS (не сохраняется)</label>
<input id="warp-ssh-pw" type="password" autocomplete="off" class="clock-sync-input" />
<div class="dt-dialog-actions">
<button type="button" class="btn ghost" id="warp-ssh-cancel">Отмена</button>
<button type="button" class="btn primary" id="warp-ssh-ok">Выполнить</button>
</div>
<p id="warp-ssh-err" class="status err" role="alert"></p>
</div>
</dialog>
<script src="/app.js?v=1.2.23" type="module"></script>
</body>
</html>