Мастер: «Настроить позже» и единый расчёт шагов для мастера и главной
- Необязательные шаги «Модули», «Оповещения и копии», «Учётные записи» и каждый модуль отдельно можно отложить кнопкой «Настроить позже»: мастер считает их завершёнными, глава «Запуск» закрывается. Отметку можно снять («Вернуть в мастер»); как только шаг настроен по-настоящему, отметка не показывается. Хранится в /var/lib/bj/.bj/wizard-deferred.json (wizdefer.go). - Шаг «Оповещения и копии» показывает, что уже настроено: почта, Zabbix, резервные копии — видно частичную настройку, сделанную в «Настройке». - Плашка «Требуется настройка — N из M» на главной считалась по старому списку из 9 шагов и расходилась с мастером. Теперь и мастер, и главная используют одну функцию wizDone и путь wizPath — те же шаги, лицензия и отметки «отложено». Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user