diff --git a/package.json b/package.json index 6f0dba2..6b443d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amnezia-admin", - "version": "1.1.5", + "version": "1.1.6", "private": true, "description": "Web-панель AmneziaWG: несколько инстансов, WARP, каскад/Endpoint, экспорт .conf (Docker)", "license": "MIT", diff --git a/public/app.js b/public/app.js index e9aba5f..ccb9975 100644 --- a/public/app.js +++ b/public/app.js @@ -551,11 +551,17 @@ function renderRows(clients) { ); nameWrap.append(strong, renameWrap); if (!c.exportAvailable) { + const hintFold = document.createElement("details"); + hintFold.className = "hint-mini"; + const sum = document.createElement("summary"); + sum.textContent = "Нет готового .conf на сервере (last_config)"; + hintFold.appendChild(sum); const exHint = document.createElement("p"); exHint.className = "muted export-missing-hint"; exHint.textContent = "Конфиг с сервера недоступен (нет last_config). Создайте клиента с нужным Endpoint в блоке «Новый клиент под каскад» ниже или возьмите ключ из приложения Amnezia."; - nameWrap.appendChild(exHint); + hintFold.appendChild(exHint); + nameWrap.appendChild(hintFold); } nameTd.appendChild(nameWrap); diff --git a/public/index.html b/public/index.html index 801fb17..50a2e42 100644 --- a/public/index.html +++ b/public/index.html @@ -90,71 +90,95 @@

- + -
-
-

Новый клиент под каскад

+
+ + + + Новый клиент под каскад + Создать peer и скачать .conf + + +
+

+ Укажите Endpoint — публичный IP или DNS узла, куда клиент будет подключаться первым шагом + (промежуточный сервер, домашний роутер с пробросом порта и т.д.). Порт по умолчанию совпадает с ListenPort этого инстанса. + Клиент будет создан на текущем сервере AmneziaWG (новые ключи); IP в туннеле можно задать явно или оставить пустым — подберём свободный в той же подсети, что у остальных. +

+
+ + + + + + + + + + + + + +
-

- Укажите Endpoint — публичный IP или DNS узла, куда клиент будет подключаться первым шагом - (промежуточный сервер, домашний роутер с пробросом порта и т.д.). Порт по умолчанию совпадает с ListenPort этого инстанса. - Клиент будет создан на текущем сервере AmneziaWG (новые ключи); IP в туннеле можно задать явно или оставить пустым — подберём свободный в той же подсети, что у остальных. -

-
- - +
- - - - - - - - - - - -
- -
- +
+ + + + Пользователи + + + +
+
+ + + + + + + + + + + +
ИмяIPСтатусПоследнее отключениеДействия
+
-
- - - - - - - - - - - -
ИмяIPСтатусПоследнее отключение
-
-
+ -
+
+ + + + Вывод awg show (отладка) + +

     
diff --git a/public/styles.css b/public/styles.css index f344a29..0fa3880 100644 --- a/public/styles.css +++ b/public/styles.css @@ -28,7 +28,7 @@ body.page { .shell { flex: 1 0 auto; - max-width: 960px; + max-width: min(1120px, 100%); margin: 0 auto; padding: clamp(1.5rem, 4vw, 2.5rem) clamp(1rem, 3vw, 1.5rem) 3rem; } @@ -437,13 +437,151 @@ h1 { font-size: 1rem; } +/* Сворачиваемые блоки (стрелка в summary, как disclosure) */ +.panel-fold { + margin-top: 1rem; + border: 1px solid var(--line); + border-radius: 16px; + background: var(--card); + overflow: hidden; +} + +.panel-fold > summary.fold-summary { + list-style: none; + display: flex; + align-items: flex-start; + gap: 0.65rem; + padding: 0.95rem 1.1rem; + cursor: pointer; + user-select: none; + border-bottom: 1px solid transparent; +} + +.panel-fold[open] > summary.fold-summary { + border-bottom-color: var(--line); +} + +.panel-fold > summary.fold-summary::-webkit-details-marker { + display: none; +} + +.fold-arrow { + flex-shrink: 0; + width: 0; + height: 0; + margin-top: 0.32rem; + border-style: solid; + border-width: 5px 0 5px 8px; + border-color: transparent transparent transparent var(--accent); + transition: transform 0.15s ease; +} + +.panel-fold[open] > summary.fold-summary .fold-arrow { + transform: rotate(90deg); + margin-top: 0.42rem; +} + +.fold-titles { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.35rem 1rem; + flex: 1; + min-width: 0; +} + +.fold-h { + font-weight: 700; + font-size: 1rem; + margin: 0; +} + +.fold-meta { + font-size: 0.82rem; +} + +.panel-fold-body { + padding: 0 1.1rem 1.1rem; +} + +.panel-fold-body > .warp-intro:first-child, +.panel-fold-body > .cascade-intro:first-child { + margin-top: 0.85rem; +} + +.panel-fold-raw { + margin-top: 1rem; +} + +.panel-fold-raw > summary.fold-summary--raw { + list-style: none; + display: flex; + align-items: flex-start; + gap: 0.65rem; + padding: 0.65rem 0; + cursor: pointer; + user-select: none; +} + +.panel-fold-raw[open] > summary.fold-summary--raw .fold-arrow { + transform: rotate(90deg); + margin-top: 0.42rem; +} + +.panel-fold-raw > summary.fold-summary--raw::-webkit-details-marker { + display: none; +} + +.panel-fold-raw > pre { + margin-top: 0.5rem; +} + +.th-actions { + width: 1%; + white-space: nowrap; + text-align: right; +} + +.table-wrap { + overflow-x: auto; +} + .muted { color: var(--muted); font-size: 0.88rem; } -.table-wrap { - overflow-x: auto; +table.clients-table { + table-layout: fixed; +} + +.clients-table th:nth-child(1), +.clients-table td:nth-child(1) { + width: 26%; + vertical-align: top; +} + +.clients-table th:nth-child(2), +.clients-table td:nth-child(2) { + width: 17%; +} + +.clients-table th:nth-child(3), +.clients-table td:nth-child(3) { + width: 14%; +} + +.clients-table th:nth-child(4), +.clients-table td:nth-child(4) { + width: 22%; +} + +.clients-table .name-cell { + min-width: 0; +} + +.clients-table td.actions { + vertical-align: top; } table { @@ -525,7 +663,30 @@ tr:last-child td { margin: 0.35rem 0 0; font-size: 0.72rem; line-height: 1.35; - max-width: 22rem; + max-width: none; +} + +.hint-mini { + margin-top: 0.35rem; + border: 1px solid var(--line); + border-radius: 8px; + padding: 0.35rem 0.5rem; + background: rgba(0, 0, 0, 0.18); +} + +.hint-mini summary { + cursor: pointer; + font-size: 0.72rem; + color: var(--muted); + list-style: none; +} + +.hint-mini summary::-webkit-details-marker { + display: none; +} + +.hint-mini .export-missing-hint { + margin: 0.4rem 0 0; } .profile-hint {