diff --git a/internal/lkgateway/keywizard.go b/internal/lkgateway/keywizard.go index ad8672c..543c3ba 100644 --- a/internal/lkgateway/keywizard.go +++ b/internal/lkgateway/keywizard.go @@ -351,6 +351,7 @@ func (h *setupHandlers) keyWizardUseMedia(w http.ResponseWriter, r *http.Request h.keyWiz.set(3, "ok", "ИШ перезапущен и работает: "+ishMsg) h.keyWiz.set(4, "ok", "Ключ с флешки подключён — проверьте подключения на странице «Статус»") h.keyWiz.Done = true + keyWizInstalled.Store(true) h.keyWiz.mu.Unlock() _ = os.RemoveAll(staging) refreshCertStore(h.rc) @@ -593,6 +594,7 @@ func (h *setupHandlers) keyWizardInstall(w http.ResponseWriter, r *http.Request) h.keyWiz.set(4, "ok", "Ключ установлен — проверьте подключения на странице «Статус»") } h.keyWiz.Done = true + keyWizInstalled.Store(true) h.keyWiz.mu.Unlock() // Ключ уже на флешке и в ИШ — распаковка staging больше не нужна. diff --git a/internal/lkgateway/onboarding_test.go b/internal/lkgateway/onboarding_test.go index 912298e..bf37ca6 100644 --- a/internal/lkgateway/onboarding_test.go +++ b/internal/lkgateway/onboarding_test.go @@ -210,16 +210,20 @@ func TestWizardChaptersWithoutLicense(t *testing.T) { rc, _ := NewRuntimeConfig(t.TempDir() + "/setup.json") done := map[int]bool{2: true, 6: true} path, chapters := wizPath(rc, done, 4) - want := []int{14, 1, 12, 4, 7, 8, 9, 13, 10} // обновление — первый пункт; без лицензии нет учёта DEPO, QUIK и модулей; база и Валидата — сделаны; учётные записи — перед проверкой + want := []int{1, 14, 12, 4, 7, 8, 9, 13, 10} // обновление — сразу после лицензии; без лицензии нет учёта DEPO, QUIK и модулей; база и Валидата — сделаны; учётные записи — перед проверкой if fmt.Sprint(path) != fmt.Sprint(want) { t.Fatalf("путь %v, ожидался %v", path, want) } - if len(chapters) != 5 || !chapters[2].Current || chapters[2].Steps[0].N != 4 { + if len(chapters) != 4 || !chapters[1].Current || chapters[1].Steps[0].N != 4 { t.Fatalf("главы: %+v", chapters) } if p, n := wizNeighbours(path, 4); p != 12 || n != 7 { t.Fatalf("соседи шага 4: %d %d", p, n) } + // шаг «База bj» сделан установщиком и в пути его нет (открыт по ссылке) — соседи по порядку глав, не начало + if p, n := wizNeighbours(path, 2); p != 14 || n != 12 { + t.Fatalf("соседи шага 2 вне пути: %d %d", p, n) + } if d := wizDefaultStep(path, map[int]bool{1: true, 4: false}); d != 4 { t.Fatalf("по умолчанию: %d", d) } @@ -233,10 +237,10 @@ func TestWizardChaptersWithoutLicense(t *testing.T) { if len(wizModuleViews(rc, rc.Snapshot())) != 0 { t.Fatal("без лицензии модулей в мастере быть не должно") } - if chapters[2].Title != "Организация" { - t.Fatalf("без учёта DEPO и QUIK глава «Организация», а не %q", chapters[2].Title) + if chapters[1].Title != "Организация" { + t.Fatalf("без учёта DEPO и QUIK глава «Организация», а не %q", chapters[1].Title) } - if l := wizChaptersLine(chapters); l != "Пять глав: обновление, сервер, организация, НРД, запуск." { + if l := wizChaptersLine(chapters); l != "Четыре главы: сервер, организация, НРД, запуск." { t.Fatalf("подзаголовок: %q", l) } } diff --git a/internal/lkgateway/setup.go b/internal/lkgateway/setup.go index 7f61246..31a32c9 100644 --- a/internal/lkgateway/setup.go +++ b/internal/lkgateway/setup.go @@ -198,6 +198,8 @@ type WizardData struct { Chapters []WizChapter Prev, Next int StepDone bool + Unfinished int // обязательный несделанный шаг (последний шаг ведёт к нему, а не «к работе») + UnfinishedT string // его название StepRequired bool StepTitle string ChapterTitle string @@ -293,7 +295,7 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) { // установщиком. Без ?step — первый несделанный обязательный шаг, а если всё сделано — проверка. d.Done.Users = doneUsers() // Выполнено ли — одно правило с плашкой на главной (wizDone), с учётом «Настроить позже». - done := wizDone(r.Context(), h.rc, d.ValidataVer, d.Done.Certs || h.keyWiz.installed()) + done := wizDone(r.Context(), h.rc, d.ValidataVer, d.Done.Certs || h.keyWiz.installed() || keyWizInstalled.Load()) // «отложено» показываем, только пока шаг не настроен по-настоящему d.Deferred = map[int]bool{9: def.Steps[9] && !d.Done.Alerts, 11: def.Steps[11] && !d.Done.Modules, 13: def.Steps[13] && !d.Done.Users} path, _ := wizPath(h.rc, done, 0) @@ -307,6 +309,9 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) { d.Path, d.Chapters = wizPath(h.rc, done, step) d.Prev, d.Next = wizNeighbours(d.Path, step) d.StepDone, d.StepRequired, d.StepTitle = done[step], wizRequired[step], wizStepTitles[step] + if u := wizDefaultStep(path, done); wizRequired[u] && !done[u] && u != step { + d.Unfinished, d.UnfinishedT = u, wizStepTitles[u] + } for _, c := range d.Chapters { if c.Current { d.ChapterTitle = c.Title diff --git a/internal/lkgateway/setupsteps.go b/internal/lkgateway/setupsteps.go index a65c3dc..1f7bb90 100644 --- a/internal/lkgateway/setupsteps.go +++ b/internal/lkgateway/setupsteps.go @@ -6,6 +6,7 @@ package lkgateway import ( "context" "sync" + "sync/atomic" "time" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/cryptocli" @@ -40,13 +41,18 @@ func setupProbes(rc *RuntimeConfig) (string, int) { return setupProbeCache.validata, setupProbeCache.certs } +// keyWizInstalled — мастер ключа дошёл до конца в этом запуске bj (ключ подключён, подпись подтверждена). +// Сверка справочника шлюза асинхронная и отстаёт; по этой отметке шаг «Ключ подписи» выполнен сразу — +// и в мастере, и в плашке на главной. +var keyWizInstalled atomic.Bool + // setupSteps — шаги мастера этой установки и выполнены ли они: ровно тот путь и те же // правила, что в мастере (wizPath + wizDone), чтобы плашка на главной не расходилась с ним. func setupSteps(rc *RuntimeConfig) []SetupStep { validata, certs := setupProbes(rc) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - done := wizDone(ctx, rc, validata, certs > 0) + done := wizDone(ctx, rc, validata, certs > 0 || keyWizInstalled.Load()) path, _ := wizPath(rc, done, 0) var steps []SetupStep for _, n := range path { diff --git a/internal/lkgateway/web/templates/admin_wizard.html b/internal/lkgateway/web/templates/admin_wizard.html index 647ec98..ca37a7b 100644 --- a/internal/lkgateway/web/templates/admin_wizard.html +++ b/internal/lkgateway/web/templates/admin_wizard.html @@ -85,7 +85,7 @@
    {{range .Chapters}}{{range .Steps}}
  1. {{if not .Done}}{{.Num}}{{end}}{{.Title}}
  2. {{end}}{{end}}
-
Всё сохраняется сразу — можно прерваться и продолжить позже.
Настроить позже →
+
Всё сохраняется сразу — можно прерваться и продолжить позже.
Выйти из мастера →
@@ -97,7 +97,7 @@ {{if eq .Step 1}} {{/* восстановление из резервной копии — только на первой странице (/first-run), в мастере его нет */}}
-

Лицензия

+

Лицензия

{{if .Done.License}}

Ключ продукта открывает модули bj и обновления.

Организация
{{.Lic.Tenant}}
@@ -109,11 +109,8 @@ {{if and .Update .Update.HasUpdate}}

Есть новая версия bj — {{.Update.Available}}

-

С исправлениями ядра, установщика и служб. Поставьте её до настройки: bj перезапустится примерно на 10 секунд.
{{.Update.Message}}

-
- - -
+

Поставьте её до настройки — следующий шаг «Обновление».

+ К обновлению →
{{else if .Done.License}}
Лицензия активна{{if .Update}}, установлена последняя версия bj{{end}}.
{{end}} {{if .Done.License}}
Сменить ключ продукта (обычно не нужно) @@ -136,7 +133,7 @@ {{/* ============= ШАГ 3: Учёт DEPO (Fancy) — по экранам ============= */}} {{if eq .Step 3}}
-

Учётная система DEPO (Fancy)

+

Учётная система DEPO (Fancy)

{{if not .DepoLicensed}}

Модуль обмена с DEPO и QUIK не входит в текущую лицензию — шаг пропускается.

{{else}} @@ -334,7 +331,7 @@ Обработка событий (процедуры){{if .Settings.Fancy.DepoProcCheckOK}}✓ {{.Settings.Fancy.DepoProcCheck}}{{else if .Settings.Fancy.DepoProcCheck}}⚠ {{.Settings.Fancy.DepoProcCheck}}{{else}}не проверялась — «Перепроверить всё»{{end}} {{if .Settings.Fancy.Server}}{{template "accPrefix" .}}{{end}} - {{if .Done.Depo}}

Учёт DEPO подключён и проверен. Дальше — «Организация»: bj предложит реквизиты вашей организации из DEPO.

{{else}}

Не всё проверено — можно продолжить настройку (шаг 4 и дальше) и вернуться сюда позже. Пока обработка не работает, записи bj ждут в таблицах обмена со статусом TODO и будут обработаны, когда она заработает.

{{end}} + {{if .Done.Depo}}

Учёт DEPO подключён и проверен. Дальше — «Организация»: bj предложит реквизиты вашей организации из DEPO.

{{else}}

Не всё проверено — можно продолжить настройку (шаг «Организация» и дальше) и вернуться сюда позже. Пока обработка не работает, записи bj ждут в таблицах обмена со статусом TODO и будут обработаны, когда она заработает.

{{end}}
{{end}} {{end}} @@ -344,7 +341,7 @@ {{/* ============= ШАГ 2: База bj (PostgreSQL) ============= */}} {{if eq .Step 2}}
-

База данных bj (PostgreSQL)

+

База данных bj (PostgreSQL)

База самого bj: журнал заявок и поручений, история обмена с НРД и DEPO, пользователи.

{{if and .Done.Postgres (pgLocal .Settings.Postgres.DSN)}}
База готова{{if .PGSummary}}: {{.PGSummary}}{{end}}. Её создал установщик — ничего делать не нужно.
Хранить базу bj на своём сервере PostgreSQL (необязательно) @@ -426,7 +423,7 @@ {{/* ============= ШАГ 4: Наша организация ============= */}} {{if eq .Step 4}}
-

Наша организация

+

Наша организация

От имени вашей организации bj отправляет и принимает переводы. {{if .DepoLicensed}}Реквизиты bj берёт из DEPO — проверьте и сохраните.{{else}}Найдите себя в справочнике участников НРД.{{end}}

{{if .Suggest}}{{if .Suggest.OrgName}}{{if not .Done.Org}}
Определено из DEPO: {{.Suggest.OrgName}}, ИНН {{.Suggest.OrgINN}}, код {{.Suggest.OrgCode}}{{if .Suggest.AccountID}}, счёт НРД {{.Suggest.AccountID}}{{end}}. Поля ниже заполнены — проверьте и нажмите «Сохранить».
{{end}}{{end}}{{end}} {{with .Fancy}}{{template "orgSection" .}}{{end}} @@ -438,7 +435,7 @@ {{/* ============= ШАГ 5: QUIK МНП ============= */}} {{if eq .Step 5}}
-

QUIK

+

QUIK

Адрес и логин модуля неторговых поручений — у вашего администратора QUIK. Если модуля ещё нет, его подключает ARQA Technologies — письмо для них готово ниже.

{{if .DepoLicensed}}{{template "letterCard" (letter "arqa")}}{{end}} {{if not .DepoLicensed}}

Модуль неторговых поручений не входит в текущую лицензию — шаг пропускается.

@@ -455,7 +452,7 @@ {{/* ============= ШАГ 6: Валидата ============= */}} {{if eq .Step 6}}
-

СКЗИ «Валидата Клиент L»

+

СКЗИ «Валидата Клиент L»

СКЗИ «Валидата Клиент L» проверяет подписи НРД на квитанциях и сообщениях и нужно для работы с ключами и сертификатами (раздел «Ключ»).

{{if .ValidataVer}}
Версия
{{.ValidataVer}}
{{if devOn}}
Каталог
/opt/Validata/VDCSP
{{end}}
@@ -479,7 +476,7 @@ {{/* ============= ШАГ 7: Шлюз НРД (до ключа: ключ ставится в профиль ИШ) ============= */}} {{if eq .Step 7}}
-

Шлюз НРД

+

Шлюз НРД

bj сам ставит и настраивает шлюз (ИШ) и обменивается через него переводами с НРД. Доступ к контурам НРД — по заявке (как подать — ниже).

{{template "igateInstall" .}}
Доступ к контурам НРД — заявкой на сайте НРД, письмами НРД не подключает. Страница сервиса MOEX МОСТ для M2M, раздел «Как подключиться»: заявка на тестирование — система МОСТ, тип «M2M Автотестирование», затем «M2M Подключение»; после тестирования — договор с НРД и боевой контур.
@@ -507,7 +504,7 @@ {{/* ============= ШАГ 8: Ключ и сертификаты (после шлюза: ключ ставится в профиль ИШ) ============= */}} {{if eq .Step 8}}
-

Ключ и сертификаты

+

Ключ и сертификаты

Ключ электронной подписи организации для обмена с НРД. Его выдаёт Удостоверяющий центр Московской биржи: вам передадут архив ключа и пароль.

{{if not .IgateOK}}
Ключ ставится в шлюз НРД — сначала установите шлюз.
{{else if .KeyWiz}}
{{template "keyWizBody" .KeyWiz}}
{{end}} @@ -549,7 +546,7 @@ {{/* ============= ШАГ 12: Время и сеть (глава «Сервер») ============= */}} {{if eq .Step 12}}
-

Время и сеть

+

Время и сеть

Часовой пояс, точное время и адрес сервера в сети.

{{$tz := serverTimeZone}}
@@ -626,7 +623,7 @@ {{/* ============= ШАГ 11: Модули по лицензии (глава «Запуск») ============= */}} {{if eq .Step 11}}
-

Модули

+

Модули

Модули вашей лицензии уже установлены. Каждый настраивается в своём разделе — сейчас или позже.

{{range .Modules}}
@@ -647,7 +644,7 @@ {{/* ============= ШАГ 9: Оповещения и резервные копии ============= */}} {{if eq .Step 9}}
-

Оповещения и резервные копии

+

Оповещения и резервные копии

bj напишет на почту, если НРД не отвечает, перевод застрял{{if .DepoLicensed}}, DEPO не обработал поручение{{end}} или истекает ключ, — и будет сам делать копии сервера. Настройте почтовый сервер, затем кому писать: администратору системы — о сбоях и обновлениях, отделам — о переводах и поручениях. Обмен работает и без этого, но о сбое тогда никто не узнает вовремя.

Сейчас: почта — {{if and .Settings.Alerts.SMTP.Enabled .Settings.Alerts.SMTP.Host .Settings.Alerts.SMTP.To}}✓ настроена{{else}}не настроена{{end}}; Zabbix — {{if .Settings.Alerts.Zabbix.Enabled}}✓ включён{{else}}выключен{{end}}; резервные копии — {{if .Settings.Backup.Enabled}}✓ включены{{else}}выключены{{end}}. Шаг закрывается, когда есть оповещения (почта или Zabbix) и включены копии.
@@ -666,7 +663,7 @@ {{/* ============= ШАГ 13: Учётные записи (глава «Запуск», перед проверкой) ============= */}} {{if eq .Step 13}}
-

Учётные записи

+

Учётные записи

У каждого, кто работает с bj, — своя учётная запись: свой пароль и свои права, а в журнале видно, кто что сделал. Заведите сотрудников по ролям; права, пароли и вход через Active Directory меняются позже в разделе Настройка → Пользователи.

{{with .Fancy}}{{template "accountsAdd" (withRet . "/admin/wizard?step=13")}}{{template "accountsAD" (withRet . "/admin/wizard?step=13")}} {{end}} @@ -679,7 +676,7 @@ {{if eq .Step 10}}
{{if devOn}} -

Тестовая заявка

+

Тестовая заявка

Прогон полного цикла: создание заявки → валидация → подпись → отправка в НРД (или mock) → ожидание Decision → подтверждение.

diff --git a/internal/lkgateway/web/templates/part_wiznav.html b/internal/lkgateway/web/templates/part_wiznav.html index 68cbf54..2236aa9 100644 --- a/internal/lkgateway/web/templates/part_wiznav.html +++ b/internal/lkgateway/web/templates/part_wiznav.html @@ -7,6 +7,6 @@ {{define "wizNav"}}
{{if .Prev}}← Назад{{else}}{{end}} - {{if .Next}}{{if or .StepDone (not .StepRequired)}}Далее →{{else}}Пропустить пока →{{end}}{{else}}Перейти к работе →{{end}} + {{if .Next}}{{if or .StepDone (not .StepRequired)}}Далее →{{else}}Пропустить пока →{{end}}{{else if .Unfinished}}Вернуться к шагу «{{.UnfinishedT}}» →{{else}}Перейти к работе →{{end}}
{{end}} diff --git a/internal/lkgateway/wizchapters.go b/internal/lkgateway/wizchapters.go index 3fa236d..91c19ae 100644 --- a/internal/lkgateway/wizchapters.go +++ b/internal/lkgateway/wizchapters.go @@ -1,6 +1,6 @@ package lkgateway -// Мастер настройки — 4 главы вместо 10 шагов (05.10.2026). Внутри глав — прежние шаги (адреса ?step=N не +// Мастер настройки — главы вместо длинного списка шагов (05.10.2026). Внутри глав — прежние шаги (адреса ?step=N не // меняются: на них ссылаются проверка готовности, письма, кнопки «Исправить»). Шаг показывается, только // если он нужен этой установке: // - не входит в лицензию (учёт DEPO и QUIK — «quik-mnp», модули) — шага нет вовсе; @@ -14,8 +14,7 @@ var wizChapters = []struct { Title string Steps []int }{ - {"Обновление", []int{14}}, - {"Сервер", []int{1, 2, 6, 12}}, + {"Сервер", []int{1, 14, 2, 6, 12}}, // обновление — сразу после лицензии: без неё обновлений нет {"Учёт и организация", []int{3, 4, 5}}, {"НРД", []int{7, 8}}, {"Запуск", []int{11, 9, 13, 10}}, @@ -141,8 +140,27 @@ func wizNeighbours(path []int, current int) (prev, next int) { return } } - if len(path) > 0 { // текущий шаг не в пути (открыт по прямой ссылке) — «Далее» ведёт в начало пути - next = path[0] + // текущий шаг не в пути (сделан установщиком, открыт по прямой ссылке) — соседи по порядку глав, + // а не начало мастера + pos := map[int]int{} + for _, c := range wizChapters { + for _, n := range c.Steps { + pos[n] = len(pos) + } + } + cur, known := pos[current] + if !known { + if len(path) > 0 { + next = path[0] + } + return + } + for _, n := range path { + if pos[n] < cur { + prev = n + } else if next == 0 { + next = n + } } return }