diff --git a/deploy/release/component-versions.json b/deploy/release/component-versions.json index 8ff227e..f519e2f 100644 --- a/deploy/release/component-versions.json +++ b/deploy/release/component-versions.json @@ -20,8 +20,8 @@ "version": "2026.10.21.6" }, "bj-server": { - "fingerprint": "6a2896e2078e2388caf8822568efa8b7d798a51891433ce1e6413cb7a11eeca1", - "version": "2026.10.21.14" + "fingerprint": "98167dc3f6c196784867f7eb88e091c693b160277f09735971e44e2c18f21a61", + "version": "2026.10.21.15" }, "crypto-service.jar": { "fingerprint": "2c4fd536bc9b1c20c69a70e8dcd374a61847b263aeb2e474c45debb01fbd8233", diff --git a/internal/lkgateway/setup.go b/internal/lkgateway/setup.go index 0253cb9..7f61246 100644 --- a/internal/lkgateway/setup.go +++ b/internal/lkgateway/setup.go @@ -56,6 +56,7 @@ func registerSetup(mux *http.ServeMux, a *admin, rc *RuntimeConfig, svc *Service h.renderSetup(w, r, "") }) mux.HandleFunc("/admin/setup/postgres", h.savePostgres) + registerWizDefer(mux) // «Настроить позже» в мастере mux.HandleFunc("/admin/setup/postgres/quick-start", h.quickStartPostgres) h.registerPGMove(mux) h.registerFancy(mux) // вкладка «Обмен Fancy (MSSQL)» @@ -200,8 +201,9 @@ type WizardData struct { StepRequired bool StepTitle string ChapterTitle string - ChaptersLine string // «Четыре главы: сервер, организация, НРД, запуск.» - Modules []WizModule // глава «Запуск»: модули по лицензии + ChaptersLine string // «Четыре главы: сервер, организация, НРД, запуск.» + Modules []WizModule // глава «Запуск»: модули по лицензии + Deferred map[int]bool // шаги, отложенные кнопкой «Настроить позже» (и ещё не настроенные) Done struct { License bool Depo bool @@ -279,18 +281,21 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) { d.Modules = wizModuleViews(h.rc, s) d.Done.Server, _ = timeStatus(r.Context()) d.Done.Server = d.Done.Server && !s.WorkHours.SavedAt.IsZero() // и рабочее время организации задано + def := loadWizDeferred() d.Done.Modules = true - for _, m := range d.Modules { - if !m.Ready { + for i := range d.Modules { + d.Modules[i].Deferred = !d.Modules[i].Ready && def.Modules[d.Modules[i].Key] + if !d.Modules[i].Ready { d.Done.Modules = false } } // Главы (wizchapters.go): в пути только шаги, нужные этой установке — по лицензии и без сделанного // установщиком. Без ?step — первый несделанный обязательный шаг, а если всё сделано — проверка. d.Done.Users = doneUsers() - _, wizUpdAvail := updateComponentRows(h.rc) // шаг 14 «Обновление»: зелёный, пока нет версии новее - done := map[int]bool{1: d.Done.License, 2: d.Done.Postgres, 3: d.Done.Depo, 4: d.Done.Org, 5: d.Done.Quik, 6: d.Done.Crypto, - 7: d.Done.NSD, 8: d.Done.Certs || h.keyWiz.installed(), 9: d.Done.Alerts, 10: d.Done.TestRun, 11: d.Done.Modules, 12: d.Done.Server, 13: d.Done.Users, 14: !wizUpdAvail} + // Выполнено ли — одно правило с плашкой на главной (wizDone), с учётом «Настроить позже». + done := wizDone(r.Context(), h.rc, d.ValidataVer, d.Done.Certs || h.keyWiz.installed()) + // «отложено» показываем, только пока шаг не настроен по-настоящему + 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) step := wizDefaultStep(path, done) if v := r.URL.Query().Get("step"); v != "" { diff --git a/internal/lkgateway/setupsteps.go b/internal/lkgateway/setupsteps.go index fe859ee..a65c3dc 100644 --- a/internal/lkgateway/setupsteps.go +++ b/internal/lkgateway/setupsteps.go @@ -5,7 +5,6 @@ package lkgateway import ( "context" - "strings" "sync" "time" @@ -41,28 +40,18 @@ func setupProbes(rc *RuntimeConfig) (string, int) { return setupProbeCache.validata, setupProbeCache.certs } -// setupSteps — шаги мастера, которые относятся к этой установке (по лицензии), и выполнены ли они. +// setupSteps — шаги мастера этой установки и выполнены ли они: ровно тот путь и те же +// правила, что в мастере (wizPath + wizDone), чтобы плашка на главной не расходилась с ним. func setupSteps(rc *RuntimeConfig) []SetupStep { - s := rc.Snapshot() - mnp := moduleAllowed(rc, "quik-mnp") validata, certs := setupProbes(rc) - steps := []SetupStep{ - {1, "Лицензия", licenseStatus(rc).Valid}, - {2, "База bj", s.Postgres.DSN != ""}, + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + done := wizDone(ctx, rc, validata, certs > 0) + path, _ := wizPath(rc, done, 0) + var steps []SetupStep + for _, n := range path { + steps = append(steps, SetupStep{n, wizStepTitles[n], done[n]}) } - if mnp { - steps = append(steps, SetupStep{3, "Учёт DEPO", strings.TrimSpace(s.Fancy.Server) != "" && s.Fancy.DepoTablesCheckOK && s.Fancy.DepoJobCheckOK}) - } - steps = append(steps, SetupStep{4, "Наша организация", doneOrg(s)}) - if mnp { - steps = append(steps, SetupStep{5, "QUIK МНП", doneQuik(s)}) - } - steps = append(steps, - SetupStep{6, "Валидата", validata != "" || (s.Crypto.Provider != "" && s.Crypto.Provider != "stub" && s.Crypto.ModulePath != "")}, - SetupStep{7, "Шлюз НРД", doneNSD(s)}, - SetupStep{8, "Ключ и сертификаты", certs > 0}, - SetupStep{9, "Оповещения и копии", doneAlerts(s)}, - ) return steps } diff --git a/internal/lkgateway/web/templates/admin_wizard.html b/internal/lkgateway/web/templates/admin_wizard.html index 490edc2..ddd41e5 100644 --- a/internal/lkgateway/web/templates/admin_wizard.html +++ b/internal/lkgateway/web/templates/admin_wizard.html @@ -636,8 +636,10 @@

{{.What}}

{{if .Ready}}✓{{else}}⚠{{end}} {{.Status}}

+ {{if not .Ready}}
{{if .Deferred}}⏸ отложено — мастер считает модуль завершённым{{else}}{{end}}
{{end}} {{end}} + {{template "wizDefer" .}} {{template "wizNav" .}} {{end}} @@ -647,6 +649,7 @@

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

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) и включены копии.
{{with .Fancy}} @@ -655,6 +658,7 @@ {{template "backupCard" .}} {{end}}
+ {{template "wizDefer" .}} {{template "wizNav" .}}
{{end}} @@ -666,6 +670,7 @@

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

{{with .Fancy}}{{template "accountsAdd" (withRet . "/admin/wizard?step=13")}}{{template "accountsAD" (withRet . "/admin/wizard?step=13")}} {{end}} + {{template "wizDefer" .}} {{template "wizNav" .}} {{end}} diff --git a/internal/lkgateway/web/templates/part_wiznav.html b/internal/lkgateway/web/templates/part_wiznav.html index 1a1e453..68cbf54 100644 --- a/internal/lkgateway/web/templates/part_wiznav.html +++ b/internal/lkgateway/web/templates/part_wiznav.html @@ -1,4 +1,9 @@ {{/* Низ карточки шага мастера: «Назад» слева, «Далее» справа (несделанный обязательный шаг — «Пропустить пока»). */}} +{{/* «Настроить позже» для необязательного шага (9, 11, 13): шаг считается завершённым, вернуть можно. */}} +{{define "wizDefer"}}{{if index .Deferred .Step}}
⏸ Шаг отложен — мастер считает его завершённым. Настроить можно в любой момент в «Настройке». +
+{{else if not .StepDone}}
шаг будет считаться завершённым; вернуться можно в любой момент
{{end}}{{end}} + {{define "wizNav"}}
{{if .Prev}}← Назад{{else}}{{end}} diff --git a/internal/lkgateway/wizchapters.go b/internal/lkgateway/wizchapters.go index 7770cbf..3fa236d 100644 --- a/internal/lkgateway/wizchapters.go +++ b/internal/lkgateway/wizchapters.go @@ -161,6 +161,7 @@ func wizDefaultStep(path []int, done map[int]bool) int { type WizModule struct { Key, Title, What, Status, Link string Ready bool + Deferred bool // «Настроить позже»: мастер считает модуль завершённым } // wizModuleViews — модули этой лицензии (без лицензии модуля в мастере нет). diff --git a/internal/lkgateway/wizdefer.go b/internal/lkgateway/wizdefer.go new file mode 100644 index 0000000..4852942 --- /dev/null +++ b/internal/lkgateway/wizdefer.go @@ -0,0 +1,141 @@ +package lkgateway + +// wizdefer.go — «Настроить позже» в мастере: необязательные шаги (модули, оповещения, +// учётные записи) и отдельные модули можно отложить — мастер считает их завершёнными +// и не держит главу «Запуск» незакрытой. Как только настроено по-настоящему, отметка +// не нужна: готовность всё равно считается из настроек. Отметку можно снять. + +import ( + "context" + "encoding/json" + "net/http" + "os" + "strconv" + "strings" + "sync" +) + +const wizDeferredFile = "/var/lib/bj/.bj/wizard-deferred.json" + +// wizDeferrable — шаги, которые можно отложить (необязательные для обмена с НРД). +var wizDeferrable = map[int]bool{9: true, 11: true, 13: true} + +type wizDeferred struct { + Steps map[int]bool `json:"steps,omitempty"` + Modules map[string]bool `json:"modules,omitempty"` +} + +var wizDeferredMu sync.Mutex + +func loadWizDeferred() wizDeferred { + wizDeferredMu.Lock() + defer wizDeferredMu.Unlock() + return readWizDeferred() +} + +func readWizDeferred() wizDeferred { + var d wizDeferred + if b, err := os.ReadFile(wizDeferredFile); err == nil { + _ = json.Unmarshal(b, &d) + } + if d.Steps == nil { + d.Steps = map[int]bool{} + } + if d.Modules == nil { + d.Modules = map[string]bool{} + } + return d +} + +func updateWizDeferred(mut func(*wizDeferred)) error { + wizDeferredMu.Lock() + defer wizDeferredMu.Unlock() + d := readWizDeferred() + mut(&d) + b, err := json.MarshalIndent(&d, "", " ") + if err != nil { + return err + } + tmp := wizDeferredFile + ".tmp" + if err := os.WriteFile(tmp, b, 0o640); err != nil { + return err + } + return os.Rename(tmp, wizDeferredFile) +} + +// registerWizDefer — POST /admin/setup/wizard-defer: step=N [module=key] [undo=1]. +func registerWizDefer(mux *http.ServeMux) { + mux.HandleFunc("/admin/setup/wizard-defer", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method", http.StatusMethodNotAllowed) + return + } + _ = r.ParseForm() + step, _ := strconv.Atoi(r.FormValue("step")) + module := strings.TrimSpace(r.FormValue("module")) + undo := r.FormValue("undo") == "1" + if !wizDeferrable[step] { + http.Error(w, "этот шаг нельзя отложить", http.StatusBadRequest) + return + } + if module != "" && !moduleKnown(module) { + http.Error(w, "неизвестный модуль", http.StatusBadRequest) + return + } + _ = updateWizDeferred(func(d *wizDeferred) { + switch { + case module != "" && undo: + delete(d.Modules, module) + case module != "": + d.Modules[module] = true + case undo: + delete(d.Steps, step) + default: + d.Steps[step] = true + } + }) + http.Redirect(w, r, "/admin/wizard?step="+strconv.Itoa(step), http.StatusSeeOther) + }) +} + +func moduleKnown(key string) bool { + for _, m := range wizModules { + if m == key { + return true + } + } + return false +} + +// wizDone — выполнены ли шаги мастера. Одно правило для мастера и плашки «Требуется +// настройка» на главной: тот же путь (лицензия, автоматика) и те же отметки «отложено». +// validata — версия «Валидаты» ("" — нет), certsDone — ключ организации подключён. +func wizDone(ctx context.Context, rc *RuntimeConfig, validata string, certsDone bool) map[int]bool { + s := rc.Snapshot() + def := loadWizDeferred() + mnp := moduleAllowed(rc, "quik-mnp") + srv, _ := timeStatus(ctx) + _, upd := updateComponentRows(rc) + mods := true + for _, m := range wizModuleViews(rc, s) { + if !m.Ready && !def.Modules[m.Key] { + mods = false + } + } + return map[int]bool{ + 1: licenseStatus(rc).Valid, + 2: s.Postgres.DSN != "", + 3: !mnp || (strings.TrimSpace(s.Fancy.Server) != "" && s.Fancy.DepoTablesCheckOK && s.Fancy.DepoJobCheckOK), + 4: doneOrg(s), + 5: !mnp || doneQuik(s), + 6: validata != "" || (s.Crypto.Provider != "" && s.Crypto.Provider != "stub" && s.Crypto.ModulePath != ""), + 7: doneNSD(s), + 8: certsDone, + 9: doneAlerts(s) || def.Steps[9], + 10: s.LastTest != nil || (!devMode(rc) && readinessDone()), + 11: mods || def.Steps[11], + 12: srv && !s.WorkHours.SavedAt.IsZero(), + 13: doneUsers() || def.Steps[13], + 14: !upd, + } +}