mirror of
https://github.com/andrey271192/amnezia_web-PRO.git
synced 2026-11-04 00:43:15 +00:00
v1.3.0: AWG 3.1/3.0/2.0/1.x/WireGuard from the panel, real cascade, server domain, light/dark theme
- client .conf: only obfuscation keys present on the server (AWG 1.x got S3/S4/I1 -> no traffic) - install.sh no longer puts panel-managed containers into AWG_PROFILES (wrong instance on switch) - UDP port busy check via docker instead of ss inside the panel container - default profile never hijacks a managed container Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -629,6 +629,7 @@ loginForm.addEventListener("submit", async (ev) => {
|
||||
await loadTimeSyncCaps();
|
||||
await loadClients();
|
||||
await loadInstances();
|
||||
await loadSettings();
|
||||
} catch (e) {
|
||||
loginError.textContent = String(e.message || e);
|
||||
}
|
||||
@@ -647,6 +648,7 @@ logoutBtn.addEventListener("click", async () => {
|
||||
refreshBtn.addEventListener("click", () => {
|
||||
loadClients();
|
||||
loadInstances();
|
||||
loadSettings();
|
||||
});
|
||||
|
||||
const cascadeForm = document.querySelector("#cascade-form");
|
||||
@@ -1730,6 +1732,7 @@ async function boot() {
|
||||
await loadTimeSyncCaps();
|
||||
await loadClients();
|
||||
await loadInstances();
|
||||
await loadSettings();
|
||||
} else {
|
||||
showLogin();
|
||||
loginPassword.focus();
|
||||
@@ -1738,3 +1741,50 @@ async function boot() {
|
||||
|
||||
initMtprotoLinkCopy();
|
||||
boot();
|
||||
|
||||
// ── Тема, адрес сервера, вышестоящий сервер каскада ──
|
||||
document.querySelector("#theme-toggle")?.addEventListener("click", () => {
|
||||
const next = document.documentElement.dataset.theme === "light" ? "dark" : "light";
|
||||
document.documentElement.dataset.theme = next;
|
||||
try { localStorage.setItem("theme", next); } catch { /* private mode */ }
|
||||
});
|
||||
|
||||
async function loadSettings() {
|
||||
const input = document.querySelector("#settings-endpoint");
|
||||
const hint = document.querySelector("#settings-hint");
|
||||
if (!input) return;
|
||||
try {
|
||||
const st = await api("/api/settings");
|
||||
input.value = st.endpointHost || "";
|
||||
if (hint) hint.textContent = `Сейчас в конфигах клиентов: ${st.effectiveEndpoint || "адрес из браузера"}. Новый адрес действует на все выгрузки .conf, в том числе старых клиентов.`;
|
||||
} catch { /* старый сервер без /api/settings */ }
|
||||
}
|
||||
|
||||
document.querySelector("#settings-form")?.addEventListener("submit", async (ev) => {
|
||||
ev.preventDefault();
|
||||
const endpointHost = document.querySelector("#settings-endpoint")?.value.trim() || "";
|
||||
try {
|
||||
const r = await api("/api/settings", { method: "POST", body: JSON.stringify({ endpointHost }) });
|
||||
setStatus(r.resolved ? `Адрес сохранён: ${endpointHost} → ${r.resolved.join(", ")}` : "Адрес сохранён.", false);
|
||||
await loadSettings();
|
||||
} catch (e) {
|
||||
setStatus(String(e.message || e), true);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector("#upstream-form")?.addEventListener("submit", async (ev) => {
|
||||
ev.preventDefault();
|
||||
const configText = document.querySelector("#upstream-conf")?.value || "";
|
||||
const b = ev.target.querySelector("button[type=submit]");
|
||||
if (b) b.disabled = true;
|
||||
try {
|
||||
setStatus("Подключаю вышестоящий сервер…", false);
|
||||
const r = await api("/api/cascade/upstream", { method: "POST", body: JSON.stringify({ configText }) });
|
||||
setStatus(r.handshake ? "Каскад подключён: рукопожатие есть. Отметьте клиентов ниже и нажмите «Применить»." : "Туннель поднят, но рукопожатия пока нет — проверьте Endpoint и UDP-порт вышестоящего сервера.", !r.handshake);
|
||||
await loadClients();
|
||||
} catch (e) {
|
||||
setStatus(String(e.message || e), true);
|
||||
} finally {
|
||||
if (b) b.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
<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">
|
||||
<script>try{var t=localStorage.getItem("theme")||(matchMedia("(prefers-color-scheme: light)").matches?"light":"dark");document.documentElement.dataset.theme=t}catch(e){}</script>
|
||||
<link rel="stylesheet" href="/styles.css?v=1.3.0">
|
||||
</head>
|
||||
<body class="page">
|
||||
<aside class="panel-promo-strip" role="complementary" aria-label="Поддержка и донат">
|
||||
@@ -58,6 +59,7 @@
|
||||
</div>
|
||||
<div class="token-box">
|
||||
<div class="session-actions">
|
||||
<button type="button" id="theme-toggle" class="btn ghost full" title="Светлая / тёмная тема">◐ Тема</button>
|
||||
<button type="button" id="logout" class="btn ghost full">Выйти</button>
|
||||
</div>
|
||||
<details class="pw-change">
|
||||
@@ -121,7 +123,7 @@
|
||||
<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="fold-h">Cloudflare WARP / каскад</span>
|
||||
<span class="muted fold-meta" id="warp-status-line"></span>
|
||||
</span>
|
||||
</summary>
|
||||
@@ -132,6 +134,11 @@
|
||||
Один раз на хосте 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>
|
||||
<form id="upstream-form" class="cascade-form">
|
||||
<label for="upstream-conf"><strong>Каскад через другой сервер</strong> — вставьте клиентский .conf (AmneziaWG или WireGuard) другого сервера. Он станет выходом вместо WARP: отмеченные ниже клиенты пойдут в интернет через тот сервер.</label>
|
||||
<textarea id="upstream-conf" rows="6" placeholder="[Interface] PrivateKey = … [Peer] Endpoint = …" autocomplete="off"></textarea>
|
||||
<button type="submit" class="btn primary">Подключить вышестоящий сервер</button>
|
||||
</form>
|
||||
<div id="warp-actions" class="warp-actions"></div>
|
||||
<div id="warp-client-list" class="warp-client-list"></div>
|
||||
<details class="raw warp-raw">
|
||||
@@ -198,8 +205,10 @@
|
||||
<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>
|
||||
<option value="awg31">AmneziaWG 3.1 — новейший, свежее приложение Amnezia</option>
|
||||
<option value="awg3">AmneziaWG 3.0</option>
|
||||
<option value="awg">AmneziaWG 1.x — роутеры Keenetic/OpenWrt, старые приложения</option>
|
||||
<option value="legacy">WireGuard (Legacy) — любой WG-клиент, без обфускации</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="instance-form-row">
|
||||
@@ -208,7 +217,15 @@
|
||||
</div>
|
||||
<button type="submit" class="btn primary">+ Развернуть инстанс</button>
|
||||
</form>
|
||||
<p class="muted instance-hint">Создаёт серверный контейнер AmneziaWG из публичного образа, генерирует ключи и NAT. Затем в «Пользователях» выберите инстанс и создавайте клиентов.</p>
|
||||
<p class="muted instance-hint">Создаёт серверный контейнер AmneziaWG из публичного образа, генерирует ключи и NAT. Затем в «Пользователях» выберите инстанс и создавайте клиентов. Можно держать несколько версий рядом на разных портах: 3.1/2.0 — для телефонов и ПК, 1.x или WireGuard — для роутеров. Не забудьте открыть UDP-порт в фаерволе хостинга.</p>
|
||||
<form id="settings-form" class="instance-form">
|
||||
<div class="instance-form-row">
|
||||
<label for="settings-endpoint">Домен или IP сервера в конфигах клиентов</label>
|
||||
<input id="settings-endpoint" type="text" placeholder="vpn.example.com — пусто: IP из установки" autocomplete="off">
|
||||
</div>
|
||||
<button type="submit" class="btn primary">Сохранить адрес</button>
|
||||
</form>
|
||||
<p id="settings-hint" class="muted instance-hint"></p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -406,6 +423,6 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script src="/app.js?v=1.2.28" type="module"></script>
|
||||
<script src="/app.js?v=1.3.0" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
--danger: #fb7185;
|
||||
--ok: #4ade80;
|
||||
--shadow: 0 24px 80px rgba(0, 0, 0, 0.45);
|
||||
--field: #0a0f16;
|
||||
--text-soft: #cbd5f5;
|
||||
--overlay: rgba(10, 15, 22, 0.92);
|
||||
color-scheme: dark;
|
||||
font-family: "DM Sans", system-ui, sans-serif;
|
||||
}
|
||||
|
||||
@@ -15,6 +19,28 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Светлая тема: те же переменные, переключатель в шапке (localStorage "theme"). */
|
||||
html[data-theme="light"] {
|
||||
--bg: #f4f6fb;
|
||||
--card: #ffffff;
|
||||
--line: rgba(15, 23, 42, 0.12);
|
||||
--text: #0f172a;
|
||||
--muted: #526077;
|
||||
--accent: #0369a1;
|
||||
--danger: #be123c;
|
||||
--ok: #15803d;
|
||||
--shadow: 0 12px 40px rgba(15, 23, 42, 0.12);
|
||||
--field: #ffffff;
|
||||
--text-soft: #1e293b;
|
||||
--overlay: rgba(255, 255, 255, 0.94);
|
||||
color-scheme: light;
|
||||
}
|
||||
html[data-theme="light"] .panel-promo-strip {
|
||||
background: linear-gradient(90deg, #e0f2fe, #eef2ff);
|
||||
border-bottom-color: rgba(3, 105, 161, 0.25);
|
||||
}
|
||||
html[data-theme="light"] .panel-promo-a { color: #92400e; }
|
||||
|
||||
body.page {
|
||||
margin: 0;
|
||||
background: radial-gradient(circle at 20% 20%, rgba(125, 211, 252, 0.08), transparent 35%),
|
||||
@@ -123,7 +149,7 @@ h1 {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
padding: 1rem;
|
||||
background: #0c121b;
|
||||
background: var(--field);
|
||||
}
|
||||
|
||||
.token-box label {
|
||||
@@ -191,7 +217,7 @@ h1 {
|
||||
padding: 0.65rem 0.75rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--line);
|
||||
background: #0a0f16;
|
||||
background: var(--field);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
margin-bottom: 0.75rem;
|
||||
@@ -255,7 +281,7 @@ h1 {
|
||||
padding: 0.55rem 0.65rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--line);
|
||||
background: #0a0f16;
|
||||
background: var(--field);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
}
|
||||
@@ -265,7 +291,7 @@ h1 {
|
||||
padding: 0.65rem 0.75rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--line);
|
||||
background: #0a0f16;
|
||||
background: var(--field);
|
||||
color: var(--text);
|
||||
font-family: "JetBrains Mono", ui-monospace, monospace;
|
||||
font-size: 0.85rem;
|
||||
@@ -294,7 +320,7 @@ h1 {
|
||||
padding: 0.45rem 0.55rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--line);
|
||||
background: #0a0f16;
|
||||
background: var(--field);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-size: 0.85rem;
|
||||
@@ -407,7 +433,7 @@ h1 {
|
||||
padding: 0.45rem 0.55rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--line);
|
||||
background: #0a0f16;
|
||||
background: var(--field);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-size: 0.85rem;
|
||||
@@ -715,7 +741,7 @@ tr:last-child td {
|
||||
.ip {
|
||||
font-family: "JetBrains Mono", ui-monospace, monospace;
|
||||
font-size: 0.85rem;
|
||||
color: #dbeafe;
|
||||
color: var(--text-soft);
|
||||
}
|
||||
|
||||
.badge {
|
||||
@@ -736,7 +762,7 @@ tr:last-child td {
|
||||
|
||||
.badge.off {
|
||||
border-color: rgba(148, 163, 184, 0.35);
|
||||
color: #e2e8f0;
|
||||
color: var(--text-soft);
|
||||
background: rgba(148, 163, 184, 0.06);
|
||||
}
|
||||
|
||||
@@ -833,11 +859,11 @@ tr:last-child td {
|
||||
.cascade-snippet {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
background: #0a0f16;
|
||||
background: var(--field);
|
||||
border: 1px solid var(--line);
|
||||
padding: 0.75rem 0.95rem;
|
||||
border-radius: 12px;
|
||||
color: #cbd5f5;
|
||||
color: var(--text-soft);
|
||||
font-size: 0.78rem;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
margin: 0.5rem 0 0.85rem;
|
||||
@@ -884,11 +910,11 @@ tr:last-child td {
|
||||
.raw pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
background: #0a0f16;
|
||||
background: var(--field);
|
||||
border: 1px solid var(--line);
|
||||
padding: 0.75rem;
|
||||
border-radius: 12px;
|
||||
color: #cbd5f5;
|
||||
color: var(--text-soft);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
@@ -927,7 +953,7 @@ tr:last-child td {
|
||||
padding: 0.55rem 0.65rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--line);
|
||||
background: #0a0f16;
|
||||
background: var(--field);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
}
|
||||
@@ -975,7 +1001,7 @@ tr:last-child td {
|
||||
margin-top: auto;
|
||||
padding: 1.25rem clamp(1rem, 3vw, 1.5rem) 1.5rem;
|
||||
border-top: 1px solid var(--line);
|
||||
background: rgba(10, 15, 22, 0.92);
|
||||
background: var(--overlay);
|
||||
}
|
||||
|
||||
.support-line {
|
||||
@@ -1227,3 +1253,12 @@ tr:last-child td {
|
||||
.instance-empty { padding: 12px; }
|
||||
|
||||
.inst-src { font-size: 0.6rem; color: var(--muted); border: 1px solid var(--line); border-radius: 999px; padding: 2px 7px; vertical-align: middle; }
|
||||
|
||||
/* Светлая тема: пастельные надписи тёмной темы на белом не читаются */
|
||||
html[data-theme="light"] .clock-zone-diff--accent,
|
||||
html[data-theme="light"] .cascade-guide a,
|
||||
html[data-theme="light"] .cascade-guide a:hover { color: #0369a1; }
|
||||
html[data-theme="light"] .btn.warn { color: #9f1239; }
|
||||
html[data-theme="light"] .status.err { color: #b91c1c; }
|
||||
html[data-theme="light"] .badge.on { color: #166534; }
|
||||
html[data-theme="light"] body.page { background: var(--bg); }
|
||||
|
||||
Reference in New Issue
Block a user