From ed598281e82760bac2accea75477e10c7bda5a0f Mon Sep 17 00:00:00 2001 From: zuevav Date: Wed, 7 Oct 2026 10:24:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=90=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D1=81=D0=B5=D1=81=D1=81=D0=B8=D0=B8=20=E2=80=94=20?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0=D0=BC=D0=B8:=20=C2=AB?= =?UTF-8?q?=D0=97=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B8=D1=82=D1=8C=C2=BB=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=B2=D1=8B=D0=BB=D0=B5=D0=B7=D0=B0=D0=B5=D1=82?= =?UTF-8?q?=20=D0=B7=D0=B0=20=D0=BA=D0=B0=D1=80=D1=82=D0=BE=D1=87=D0=BA?= =?UTF-8?q?=D1=83;=20=D0=B1=D1=80=D0=B0=D1=83=D0=B7=D0=B5=D1=80=20=D0=B8?= =?UTF-8?q?=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D0=B0=20=D0=BE=D0=B4?= =?UTF-8?q?=D0=BD=D0=B8=D0=BC=20=D1=81=D0=BB=D0=BE=D0=B2=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi --- internal/lkgateway/auth.go | 41 +++++++++++++++++++ .../lkgateway/web/templates/admin_setup.html | 8 ++-- internal/lkgateway/web/templates/layout.html | 9 ++++ 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/internal/lkgateway/auth.go b/internal/lkgateway/auth.go index 4d64b23..3d24c17 100644 --- a/internal/lkgateway/auth.go +++ b/internal/lkgateway/auth.go @@ -486,6 +486,47 @@ type SessionView struct { Created, Seen time.Time } +// Device — браузер и система одним словом («Chrome · Windows») вместо строки User-Agent. +func (v SessionView) Device() string { + ua := v.UA + var b, o string + switch { + case strings.Contains(ua, "YaBrowser"): + b = "Яндекс Браузер" + case strings.Contains(ua, "Edg/"): + b = "Edge" + case strings.Contains(ua, "Firefox/"): + b = "Firefox" + case strings.Contains(ua, "Chrome/"): + b = "Chrome" + case strings.Contains(ua, "Safari/"): + b = "Safari" + case strings.Contains(ua, "curl/"): + b = "curl" + } + switch { + case strings.Contains(ua, "Windows"): + o = "Windows" + case strings.Contains(ua, "iPhone"), strings.Contains(ua, "iPad"): + o = "iOS" + case strings.Contains(ua, "Mac OS"): + o = "macOS" + case strings.Contains(ua, "Android"): + o = "Android" + case strings.Contains(ua, "Linux"): + o = "Linux" + } + switch { + case b != "" && o != "": + return b + " · " + o + case b != "" || o != "": + return b + o + case ua == "": + return "браузер неизвестен" + } + return "другой браузер" +} + // sessionID — короткий отпечаток токена: им список ссылается на сессию, сам токен в страницу не попадает. func sessionID(token string) string { sum := sha256.Sum256([]byte("bj-session-id:" + token)) diff --git a/internal/lkgateway/web/templates/admin_setup.html b/internal/lkgateway/web/templates/admin_setup.html index 8f5ba50..8402425 100644 --- a/internal/lkgateway/web/templates/admin_setup.html +++ b/internal/lkgateway/web/templates/admin_setup.html @@ -145,10 +145,10 @@

Активные сессии ({{len $U.Sessions}})

- {{if $U.Sessions}} - {{range $U.Sessions}} - {{end}} -
ПользовательВходАктивностьАдресБраузер
{{.Login}}{{msk .Created}}{{msk .Seen}}{{.IP}}{{.UA}}
{{else}}

Сессий нет.

{{end}} + {{if $U.Sessions}}
+ {{range $U.Sessions}}
{{.Login}}вход {{msk .Created}} · активность {{msk .Seen}} · {{.IP}} · {{.Device}} +
{{end}} +
{{else}}

Сессий нет.

{{end}}
diff --git a/internal/lkgateway/web/templates/layout.html b/internal/lkgateway/web/templates/layout.html index df4003e..aa8a2b4 100644 --- a/internal/lkgateway/web/templates/layout.html +++ b/internal/lkgateway/web/templates/layout.html @@ -365,6 +365,15 @@ label { font-size:13px; font-weight:500; } .usr-del{background:none !important;border:0 !important;color:var(--err);font-size:13.5px;font-weight:500;padding:6px 2px;cursor:pointer} .usr-del:hover{text-decoration:underline} @media (max-width:600px){.usr-form{grid-template-columns:minmax(0,1fr)} .usr-main small{white-space:normal}} +/* активные сессии: строка — кто, когда, откуда; «Завершить» справа и не вылезает за карточку */ +.ses-list{display:grid;gap:6px;margin-top:8px} +.ses{display:flex;align-items:center;gap:12px;padding:8px 12px;border:1px solid var(--border);border-radius:10px;background:var(--card)} +.ses-main{flex:1;min-width:0} +.ses-main b{display:block;font-size:14px} +.ses-main small{display:block;color:var(--muted);font-size:12.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.ses-main code{font-size:11.5px} +.ses .btn-sm{flex:none;padding:5px 12px;font-size:12.5px} +@media (max-width:600px){.ses-main small{white-space:normal}} /* «Расширенные настройки» — под раскрытием, заголовком подблока */ .adv-box>summary{display:flex;flex-direction:column;gap:2px;cursor:pointer;list-style:none} .adv-box>summary::-webkit-details-marker{display:none}