Шапка: служебные разделы в меню пользователя, статус НРД и время друг над другом

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
zuevav
2026-09-18 11:20:51 +03:00
parent b4b4788035
commit 5bafd60df6
2 changed files with 59 additions and 10 deletions
+27
View File
@@ -0,0 +1,27 @@
package lkgateway
import (
"html/template"
"net/http/httptest"
"os"
"testing"
)
// Снимок шапки для визуальной проверки: страница пишется в файл, если задан BJ_SHOT_OUT.
func TestTopbarShot(t *testing.T) {
out := os.Getenv("BJ_SHOT_OUT")
if out == "" {
t.Skip("BJ_SHOT_OUT не задан")
}
a, err := newAdmin()
if err != nil {
t.Fatal(err)
}
rec := httptest.NewRecorder()
p := nowPage("Настройка", "setup")
p.NPR = true
render(rec, a.modulePage, modulePageData{page: p, Content: template.HTML(`<div style="padding:40px" class="muted">содержимое страницы</div>`)})
if err := os.WriteFile(out, rec.Body.Bytes(), 0o644); err != nil {
t.Fatal(err)
}
}
+32 -10
View File
@@ -85,7 +85,7 @@ body {
border-radius:10px; box-shadow:0 8px 30px rgba(0,0,0,.14); min-width:210px; z-index:60; padding:6px; }
.nav .ddmenu a { display:block; padding:8px 12px; border-radius:7px; }
.topbar-right { display:flex; align-items:center; gap:12px; flex:none; padding-left:10px; }
@media (max-width:1200px){ .sys-status span:not(.pulse){ display:none } .sys-status{ padding:5px 8px } .topbar .clock{ display:none } }
@media (max-width:1100px){ .sys-status span:not(.pulse){ display:none } .sys-status{ padding:5px 8px } .topbar .clock{ display:none } .user-dd .user-role{ display:none } }
/* Строка системного статуса — состояние читается всегда */
.sys-status { display:inline-flex; align-items:center; gap:7px; font-size:12px; font-weight:600; color:var(--text-2); padding:5px 11px 5px 9px; border:1px solid var(--border); border-radius:20px; background:var(--card-2); white-space:nowrap; }
.sys-status .pulse { width:8px; height:8px; border-radius:50%; background:var(--ok); box-shadow:0 0 0 0 var(--ok-weak); animation:pulse 2.4s var(--ease) infinite; }
@@ -99,7 +99,20 @@ body {
}
.theme-toggle svg { width:16px; height:16px; }
.theme-toggle:hover { background:var(--border); color:var(--text); }
.topbar .clock { font-size:12px; color:var(--muted); font-family:var(--mono); font-variant-numeric:tabular-nums; letter-spacing:-0.02em; }
.topbar .clock { font-size:11px; color:var(--muted); font-family:var(--mono); font-variant-numeric:tabular-nums; letter-spacing:-0.02em; }
.sys-block { display:inline-flex; flex-direction:column; align-items:center; gap:2px; line-height:1.1; }
.sys-block .sys-status { padding:3px 9px 3px 7px; font-size:11.5px; }
.user-dd .ddbtn.user-chip { display:inline-flex; flex-direction:column; align-items:flex-start; padding:4px 10px; border-radius:8px; margin:0; max-width:220px; }
.user-dd .user-name { font-size:12.5px; }
.nav .ddmenu.ddmenu-right, .user-dd .ddmenu { left:auto; right:0; min-width:190px; }
.user-dd { position:relative; }
.user-dd .ddmenu { display:none; position:absolute; top:100%; right:0; margin-top:8px; background:var(--card); border:1px solid var(--border); border-radius:10px; padding:6px; box-shadow:0 10px 30px rgba(0,0,0,.12); z-index:50; }
.user-dd:hover .ddmenu, .user-dd:focus-within .ddmenu { display:block; }
.user-dd::after { content:''; position:absolute; top:100%; left:0; right:0; height:10px; }
.user-dd .ddmenu a { display:block; padding:8px 12px; border-radius:7px; font-size:13px; color:var(--text-2); text-decoration:none; white-space:nowrap; }
.user-dd .ddmenu a:hover, .user-dd .ddmenu a.active { background:var(--accent-weak); color:var(--accent-strong); }
.user-dd:hover .ddbtn, .user-dd.active .ddbtn { background:var(--accent-weak); }
.ddsep { display:block; height:1px; background:var(--border); margin:6px 4px; }
/* ---- Контент ---- */
main { padding:24px; max-width:1200px; margin:0 auto; }
@@ -304,16 +317,25 @@ label { font-size:13px; font-weight:500; }
<a href="/admin/handbook">Справочник НРД</a>{{end}}
</span>
</span>{{end}}
{{if can "settings"}}<span class="group-label">Администратор</span>
<a href="/admin/setup" class="{{if eq .Active "setup"}}active{{end}}">Настройка</a>
<a href="/admin/status" class="{{if eq .Active "status"}}active{{end}}">Статус</a>{{end}}
<a href="/admin/help" class="{{if eq .Active "help"}}active{{end}}">Справка</a>
</nav>
<div class="topbar-right">
{{with curUser}}<span class="user-chip" title="{{.RoleTitle}}{{if eq .Source "ad"}} · вход через AD{{end}}"><span class="user-name">{{if .Name}}{{.Name}}{{else}}{{.Login}}{{end}}</span><span class="user-role">{{.RoleTitle}}</span></span><a class="user-logout" href="/logout" title="Выйти">Выйти</a>{{else}}{{if authOpen}}<a class="user-logout" href="/admin/setup#users" title="Пользователи не созданы — вход открыт">без входа</a>{{end}}{{end}}
<span class="sys-status {{if .IsMockMode}}warn{{end}}" title="Состояние обмена с НРД">
<span class="pulse"></span>{{if .IsMockMode}}{{if devOn}}Эмуляция{{else}}Нет связи с НРД{{end}}{{else}}НРД на связи{{end}}</span>
<span class="clock">{{.Now}}</span>
<span class="sys-block">
<span class="sys-status {{if .IsMockMode}}warn{{end}}" title="Состояние обмена с НРД">
<span class="pulse"></span>{{if .IsMockMode}}{{if devOn}}Эмуляция{{else}}Нет связи с НРД{{end}}{{else}}НРД на связи{{end}}</span>
<span class="clock">{{.Now}}</span>
</span>
<span class="dd user-dd{{if or (eq .Active "setup") (eq .Active "status") (eq .Active "help")}} active{{end}}">
<span class="ddbtn user-chip" title="{{with curUser}}{{.RoleTitle}}{{if eq .Source "ad"}} · вход через AD{{end}}{{else}}Пользователи не созданы — вход открыт{{end}}">
<span class="user-name">{{with curUser}}{{if .Name}}{{.Name}}{{else}}{{.Login}}{{end}}{{else}}без входа{{end}} ▾</span>
<span class="user-role">{{with curUser}}{{.RoleTitle}}{{else}}вход открыт{{end}}</span>
</span>
<span class="ddmenu ddmenu-right">
{{if can "settings"}}<a href="/admin/setup" class="{{if eq .Active "setup"}}active{{end}}">Настройка</a>
<a href="/admin/status" class="{{if eq .Active "status"}}active{{end}}">Статус системы</a>{{end}}
<a href="/admin/help" class="{{if eq .Active "help"}}active{{end}}">Справка</a>
{{with curUser}}<span class="ddsep"></span><a href="/logout">Выйти</a>{{else}}{{if authOpen}}<span class="ddsep"></span><a href="/admin/setup#users">Создать пользователей</a>{{end}}{{end}}
</span>
</span>
<button class="theme-toggle" id="theme-toggle" title="Светлая/тёмная тема" aria-label="Переключить тему"></button>
</div>
</header>