feat(admin): баннер «🟡 РЕЖИМ ЭМУЛЯЦИИ» сверху страниц когда mock

Когда ИШ НРД не настроен (NSD.IGWBaseURL пустой) или провайдер СКЗИ =
stub — рисуем жёлтую плашку сверху каждой страницы админки с явной
надписью «РЕЖИМ ЭМУЛЯЦИИ — реального обмена с НРД нет» и ссылкой
«Настроить →» на /admin/wizard. Это нужно чтобы пользователь видя
быстро-подтверждённую заявку не думал что это настоящий обмен.

Реализация: добавлено поле IsMockMode/MockReason в page struct,
nowPage() читает globalRC.Snapshot() и заполняет их. globalRC — пакетная
переменная (заполняется один раз в RegisterAdmin), чтобы не таскать
*RuntimeConfig через все renderXxx-вызовы. Сам баннер — в layout.html
перед <main>, поэтому виден везде включая карточку заявки.
This commit is contained in:
fontvielle
2026-05-14 16:44:24 +03:00
parent 19a2b6dda4
commit 1ffe62133c
2 changed files with 34 additions and 4 deletions
+24 -4
View File
@@ -140,11 +140,18 @@ func newAdmin() (*admin, error) {
// page — общий "конверт" данных для всех шаблонов.
type page struct {
Title string
Active string
Now string
Title string
Active string
Now string
IsMockMode bool // true если ИШ не настроен — bj-server в режиме эмуляции
MockReason string // короткое описание почему mock
}
// globalRC — ссылка на runtime-конфиг для template-funcs/page helpers.
// Заполняется один раз в RegisterAdmin. Альтернатива — таскать rc через
// все renderXxx-функции, что шумно при широком фан-ауте.
var globalRC *RuntimeConfig
// homeData — данные дашборда.
type homeData struct {
page
@@ -186,6 +193,7 @@ func RegisterAdmin(mux *http.ServeMux, svc *Service, rc *RuntimeConfig, getOpts
if err != nil {
return nil, err
}
globalRC = rc
mux.HandleFunc("/admin/", func(w http.ResponseWriter, r *http.Request) {
p := strings.TrimPrefix(r.URL.Path, "/admin/")
@@ -284,7 +292,19 @@ func render(w http.ResponseWriter, t *template.Template, data any) {
}
func nowPage(title, active string) page {
return page{Title: title, Active: active, Now: time.Now().Format("02.01.2006 15:04:05")}
p := page{Title: title, Active: active, Now: time.Now().Format("02.01.2006 15:04:05")}
if globalRC != nil {
s := globalRC.Snapshot()
switch {
case s.NSD.IGWBaseURL == "":
p.IsMockMode = true
p.MockReason = "ИШ НРД не настроен — заявки идут через внутренний mock (Decision эмитируется через 3 сек)"
case s.Crypto.Provider == "" || s.Crypto.Provider == "stub":
p.IsMockMode = true
p.MockReason = "Провайдер СКЗИ = stub — подпись не делается, реальный обмен с НРД невозможен"
}
}
return p
}
// topNews отбирает максимум N новостей: сначала те, что активны прямо сейчас
@@ -54,6 +54,16 @@ button:hover, .btn:hover { opacity: .9; }
</nav>
<span class="muted" style="margin-left:auto">{{.Now}}</span>
</header>
{{if .IsMockMode}}
<div style="background:rgba(232,177,58,0.15);border-bottom:2px solid var(--warn);padding:10px 24px;display:flex;align-items:center;gap:12px;font-size:13px">
<span style="font-size:18px">🟡</span>
<div>
<strong style="color:var(--warn)">РЕЖИМ ЭМУЛЯЦИИ</strong> — реального обмена с НРД нет.
<span class="muted" style="margin-left:6px">{{.MockReason}}</span>
</div>
<a href="/admin/wizard" style="margin-left:auto;font-size:13px">Настроить →</a>
</div>
{{end}}
<main>
{{template "content" .}}
</main>