Files
amnezia_web-PRO/public/index.html
2026-06-21 18:22:36 +03:00

408 lines
28 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.28">
</head>
<body class="page">
<aside class="panel-promo-strip" role="complementary" aria-label="Поддержка и донат">
<div class="panel-promo-strip-inner">
<p class="panel-promo-subtitle muted">Поддержите проект через Boosty, Ozon СБП и Telegram — ниже те же ссылки в подвале.</p>
<div class="panel-promo-links">
<a class="panel-promo-a" href="https://github.com/andrey271192/amnezia_web-PRO" target="_blank" rel="noopener noreferrer">⭐ GitHub · PRO</a>
<span class="panel-promo-sep" aria-hidden="true">·</span>
<a class="panel-promo-a" href="https://boosty.to/andrey27/donate" target="_blank" rel="noopener noreferrer">💖 Boosty</a>
<span class="panel-promo-sep" aria-hidden="true">·</span>
<a class="panel-promo-a" href="https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798" target="_blank" rel="noopener noreferrer">💳 Ozon&nbsp;СБП</a>
<span class="panel-promo-sep" aria-hidden="true">·</span>
<a class="panel-promo-a" href="https://t.me/lot_andrey" target="_blank" rel="noopener noreferrer">✉️ Telegram&nbsp;@lot_andrey</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>
<nav class="gate-donate muted" aria-label="Поддержка и донат">
Поддержка веб‑панелей (вне этого сервера):<br/>
<a href="https://github.com/andrey271192/amnezia_web-PRO" target="_blank" rel="noopener noreferrer">GitHub · PRO</a>
<span class="gate-donate-dot" aria-hidden="true">·</span>
<a href="https://boosty.to/andrey27/donate" target="_blank" rel="noopener noreferrer">Boosty</a>
<span class="gate-donate-dot" aria-hidden="true">·</span>
<a href="https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798" target="_blank" rel="noopener noreferrer">Ozon СБП</a>
<span class="gate-donate-dot" aria-hidden="true">·</span>
<a href="https://t.me/lot_andrey" target="_blank" rel="noopener noreferrer">Telegram</a>
</nav>
</div>
</div>
<div id="app-root" class="shell hidden">
<header class="top">
<div>
<p class="eyebrow">Панель сервера</p>
<h1>Пользователи AmneziaWG PRO</h1>
<p class="sub">Дату отключения задаёте сами (локальное время браузера). Кнопка «Выключить» убирает клиента из туннеля сразу. «Задать дату» без галочки — только запись в таблице; с галочкой — клиент остаётся в туннеле до наступления времени. Несколько инстансов задаются переменной <code class="inline">AWG_PROFILES</code>; установщик при апдейте подставляет её из прежнего контейнера или из <code class="inline">/root/amnezia-admin.awg-profiles.json</code>. Конфиг .conf — только если на сервере есть <code class="inline">last_config</code>; иначе используйте приложение Amnezia. Отдельно: раздел <strong>Telegram MTProtoпрокси</strong> ставит официальный Dockerобраз <code class="inline">telegrammessenger/proxy</code> через панель.</p>
</div>
<div class="token-box">
<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">
Отдельный Dockerконтейнер (<strong>telegrammessenger/proxy</strong>), публикация порта на хосте VPS.
Ссылка <code class="inline">tg://proxy?…</code> подставляет хост из URL панели, если не заданы переменные; для постоянного IP/DNS укажите <code class="inline">MTPRO_PUBLIC_HOST</code> или <code class="inline">CLIENT_CONFIG_ENDPOINT</code>.
Секрет после установки сохраните вне панели — здесь он показывается маскированно.
За nginx/Caddy проксируйте префикс <code class="inline">/api/</code> целиком (см. README).
</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" id="instances-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">Разверните AmneziaWG прямо из панели — без приложения Amnezia, с выбором порта</span>
</span>
</summary>
<div class="panel-fold-body">
<div id="instances-grid" class="instances-grid"></div>
<form id="instance-form" class="instance-form">
<div class="instance-form-row">
<label for="instance-variant">Протокол</label>
<select id="instance-variant" class="form-select">
<option value="awg2">AmneziaWG 2.0 (рекомендуется)</option>
<option value="awg">AmneziaWG</option>
<option value="legacy">AmneziaWG Legacy</option>
</select>
</div>
<div class="instance-form-row">
<label for="instance-port">Порт (UDP)</label>
<input id="instance-port" type="number" min="1" max="65535" placeholder="напр. 51820" autocomplete="off">
</div>
<button type="submit" class="btn primary"> Развернуть инстанс</button>
</form>
<p class="muted instance-hint">Создаёт серверный контейнер AmneziaWG из публичного образа, генерирует ключи и NAT. Затем в «Пользователях» выберите инстанс и создавайте клиентов.</p>
</div>
</details>
<details class="panel-fold" id="direct-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">Прямое подключение к этому серверу — Endpoint = публичный IP этого VPS</span>
</span>
</summary>
<div class="panel-fold-body">
<div class="muted cascade-guide">
<p class="cascade-intro">
<strong>Обычное подключение.</strong> Создаёт нового клиента AmneziaWG и сразу скачивает файл
<code class="inline">.conf</code>, где <strong>Endpoint указывает на этот сервер напрямую</strong>.
Импортируйте файл в приложение Amnezia на телефоне или ПК. IP в туннеле подберётся автоматически.
</p>
</div>
<form id="direct-form" class="cascade-form">
<label for="direct-name">Имя клиента</label>
<input id="direct-name" type="text" placeholder="Например iPhone Андрей" autocomplete="off">
<label for="direct-tunnel-ip">IP в VPN-туннеле (необязательно)</label>
<input id="direct-tunnel-ip" type="text" placeholder="Пусто — подберём свободный в подсети" autocomplete="off">
<button type="submit" class="btn primary">Создать и скачать .conf</button>
</form>
</div>
</details>
<details class="panel-fold cascade-panel" id="import-panel">
<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">Перенос из готового .conf или backup Amnezia, где внутри есть конфиг</span>
</span>
</summary>
<div class="panel-fold-body">
<div class="muted cascade-guide">
<p class="cascade-intro">
Вставьте содержимое клиентского <code class="inline">.conf</code> или JSON backup. Панель добавит peer в текущий инстанс,
восстановит запись в <code class="inline">clientsTable</code> и сохранит исходный конфиг для дальнейшего экспорта.
</p>
</div>
<form id="import-form" class="cascade-form import-form">
<label for="import-name">Имя клиента / префикс</label>
<input id="import-name" type="text" placeholder="Например iPhone из backup" autocomplete="off">
<label for="import-config">.conf или JSON backup</label>
<textarea id="import-config" rows="10" spellcheck="false" placeholder="[Interface]&#10;PrivateKey = ...&#10;Address = 10.8.1.10/32&#10;&#10;[Peer]&#10;PublicKey = ..."></textarea>
<button type="submit" class="btn primary">Импортировать в текущий инстанс</button>
</form>
</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">Клиент стучится в «первый» узел → трафик сюда; ключи выдаём на этом VPS</span>
</span>
</summary>
<div class="panel-fold-body">
<div class="muted cascade-guide">
<p class="cascade-intro">
<strong>Что здесь происходит.</strong> Приложение Amnezia всегда смотрит на поле <strong>Endpoint</strong> в конфиге.
При каскаде клиент «видит» только <strong>промежуточный узел</strong> (часто недорогой VPS поближе к пользователям), а <strong>реальное соединение AmneziaWG живёт на этом сервере</strong>, где крутится ваш основной контейнер.
Ключ и peer создаём здесь и выдаём файл <code class="inline">.conf</code> — Endpoint в файле будет на промежуточную машину, а не на прямой IP этого VPS в интернете.
</p>
<p class="cascade-intro cascade-intro-tight"><strong>Сначала нужна пересылка порта до этого VPS.</strong> На промежуточном сервере настройте проброс DNAT UDP (и TCP при необходимости).
Удобный вариант с веб-панелью iptables —
репозиторий <a href="https://github.com/andrey271192/kaskad_web_vpn" target="_blank" rel="noopener noreferrer"><strong>kaskad_web_vpn</strong></a>:
ставится одной командой ниже; в её панели создаёте правило «входящий порт → UDP на IP:порт этого AmneziaWG». Подробнее — README в том же репозитории.</p>
<pre class="cascade-snippet" tabindex="0" role="note" aria-label="Установка kaskad_web_vpn на промежуточном VPS">curl -fsSL https://raw.githubusercontent.com/andrey271192/kaskad_web_vpn/main/install.sh | sudo bash</pre>
<p class="cascade-intro cascade-intro-tight">После установки откройте <code class="inline">http://ПЕРЕХОДНОЙ_IP:8088/setup</code> (или свой <code class="inline">HOST_PORT</code>), задайте пароль админки каскада и добавьте правило проброса на <strong><code class="inline">ListenPort</code> этого инстанса</strong> на данном VPS (видно в шапке панели или в <code class="inline">awg show</code>).</p>
<ol class="cascade-steps">
<li>На промежуточном VPS: <strong>kaskad_web_vpn</strong> (команда выше) или свой DNAT / проброс порта на роутере.</li>
<li>Проверьте, что с промежуточной машины доходит UDP до WG-порта на <strong>этом</strong> сервере (если ниже поле порта Endpoint пустое — это тот же порт, что <code class="inline">ListenPort</code> здесь).</li>
<li>В форме ниже в <strong>Endpoint</strong> укажите публичный <strong>IP или DNS промежуточного узла</strong> — тот адрес, с которым реально установит туннель клиентское приложение из интернета.</li>
<li>Нажмите <strong>Создать на сервере и скачать .conf</strong> и импортируйте файл в Amnezia на телефоне или ПК.</li>
</ol>
<p class="cascade-intro cascade-intro-tight"><strong>Порт Endpoint</strong> — когда снаружи слушает не тот номер порта, что WG здесь (пусто значит «как у сервера»). <strong>IP в VPN-туннеле</strong> можно не заполнять — подберём свободный адрес в той же подсети, что у остальных клиентов.</p>
</div>
<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-PRO</strong>
<span class="support-sep" aria-hidden="true">·</span>
<a href="https://github.com/andrey271192/amnezia_web-PRO" target="_blank" rel="noopener noreferrer">⭐ GitHub · amnezia_web-PRO</a>
<span class="support-sep" aria-hidden="true">·</span>
<a href="https://boosty.to/andrey27/donate" target="_blank" rel="noopener noreferrer">💖 Boosty</a>
<span class="support-sep" aria-hidden="true">·</span>
<a href="https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798" target="_blank" rel="noopener noreferrer">💳 Ozon&nbsp;СБП</a>
<span class="support-sep" aria-hidden="true">·</span>
<a href="https://t.me/lot_andrey" target="_blank" rel="noopener noreferrer">✉️ Telegram&nbsp;@lot_andrey</a>
</div>
<p class="support-blurb">
Поддержать проект — поставь звезду на GitHub И донат. Связаться с автором — 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.28" type="module"></script>
</body>
</html>