From 2899bba55f3cfa53b869694a8bc9eb4732e98f0b Mon Sep 17 00:00:00 2001 From: andrey271192 Date: Tue, 16 Jun 2026 19:57:49 +0300 Subject: [PATCH] style: compact icon action buttons, no horizontal scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace text action buttons with 32px icon buttons (tooltips kept via title/aria-label): ⏻ выключить, ▶ включить, ⬇ .conf, 🔗 прямая ссылка, ⧉ копировать URL, 🗑 удалить. Actions cell shrinks to content (width:1%, nowrap) so the table fits without left-right scrolling. --- public/app.js | 23 +++++++++++++++++------ public/styles.css | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/public/app.js b/public/app.js index a76ed40..54f05ef 100644 --- a/public/app.js +++ b/public/app.js @@ -988,31 +988,31 @@ function renderRows(clients) { actTd.appendChild(lock); } else { if (c.activeInConf) { - actTd.appendChild(btn("Выключить", "btn small ghost", () => openDisableDialog(c))); + actTd.appendChild(iconBtn("⏻", "Выключить", "btn small ghost icon", () => openDisableDialog(c))); } else { actTd.appendChild( - btn("Включить", "btn small primary", () => mutate("/api/clients/enable", c.clientId)) + iconBtn("▶", "Включить", "btn small primary icon", () => mutate("/api/clients/enable", c.clientId)) ); } if (c.exportAvailable) { const direct = document.createElement("a"); direct.className = "btn small ghost"; direct.href = clientExportGetUrl(c.clientId); - direct.textContent = "Прямая ссылка"; + direct.textContent = "🔗"; direct.classList.add("icon"); direct.rel = "noopener"; direct.title = "Открыть в новой вкладке — скачается .conf, если вы авторизованы в этой панели (cookies)."; - actTd.appendChild(btn("Скачать .conf", "btn small ghost", () => void downloadClientConfig(c))); + actTd.appendChild(iconBtn("⬇", "Скачать .conf", "btn small ghost icon", () => void downloadClientConfig(c))); actTd.appendChild(direct); actTd.appendChild( - btn("Копировать URL", "btn small ghost", async () => { + iconBtn("⧉", "Копировать URL", "btn small ghost icon", async () => { const ok = await copyTextToClipboard(clientExportGetUrl(c.clientId)); setStatus(ok ? "Ссылка скопирована (вставьте в браузер, будучи залогиненным)." : "Не удалось скопировать.", !ok); }), ); } - actTd.appendChild(btn("Удалить", "btn small warn", () => confirmDelete(c.name, c.clientId))); + actTd.appendChild(iconBtn("🗑", "Удалить", "btn small warn icon", () => confirmDelete(c.name, c.clientId))); } tr.append(nameTd, ipTd, stTd, offTd, actTd); @@ -1029,6 +1029,17 @@ function btn(label, cls, onClick) { return b; } +function iconBtn(glyph, title, cls, onClick) { + const b = document.createElement("button"); + b.type = "button"; + b.className = cls; + b.textContent = glyph; + b.title = title; + b.setAttribute("aria-label", title); + b.addEventListener("click", onClick); + return b; +} + /** Для политики WARP на сервере нужны IPv4 вида 10.8.x.x/32 */ function parseIpv4Cidrs(allowedIps) { if (!allowedIps) return []; diff --git a/public/styles.css b/public/styles.css index 29e1d8d..605e243 100644 --- a/public/styles.css +++ b/public/styles.css @@ -656,7 +656,8 @@ table.clients-table { .clients-table td.actions { vertical-align: top; - min-width: 360px; + width: 1%; + white-space: nowrap; } table { @@ -1162,3 +1163,18 @@ tr:last-child td { white-space: pre-wrap; font-size: 0.82rem; } + + +.actions { gap: 0.3rem; flex-wrap: nowrap; } +.actions .btn.icon { + width: 32px; + min-width: 32px; + height: 32px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 1rem; + line-height: 1; +} +.actions a.btn.icon { text-decoration: none; }