База bj на своём PostgreSQL; «Не заполнено: …» для пустых полей; запись канала ИШ — в фоне, с ходом на странице

- шаг «База bj»: перенос на сервер PostgreSQL организации (проверка подключения, версии, прав, пустой базы;
  pg_dump → psql; сверка числа строк по таблицам; переключение и перезапуск); прежняя база — запасная копия;
  резервные копии: дамп внешней базы по её подключению, восстановление её не трогает
- все формы: пустые видимые обязательные поля называются по имени и подсвечиваются; DEPO — и на сервере
- «Обновить/Создать канал»: в фоне bj, ход по этапам с таймером (и после перезагрузки страницы), переходы
  заблокированы до конца, в конце проверка, что шлюз загрузил канал; состояние ИШ во время записи — без
  очереди к root-обработчику (страница больше не висит на «Проверяю…»)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-10-07 00:00:35 +03:00
parent 3a9bc5d7fa
commit 4adc1398f6
16 changed files with 832 additions and 50 deletions
+19 -17
View File
@@ -139,6 +139,15 @@ var igateVersionRe = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) {
// состояние ИШ и каналов + что выбрано в bj
mux.HandleFunc("/admin/setup/igate/status", func(w http.ResponseWriter, r *http.Request) {
// пока записывается канал, root-обработчик занят им (до двух минут) и запрос состояния ждал бы в очереди —
// страница после перезагрузки висела на «Проверяю…»; отдаём последнее известное состояние
if v := igateContourJob.view(); v.Running {
if last := igateLastStatus.get(); last != nil {
last["busy"] = "идёт запись канала " + strings.ToUpper(v.Contour)
jsonOut(w, last)
return
}
}
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
defer cancel()
res, err := runKeymediaOp(ctx, "igate-status", nil)
@@ -146,6 +155,7 @@ func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) {
jsonOut(w, map[string]any{"ok": false, "error": err.Error()})
return
}
defer igateLastStatus.set(res)
s := rc.Snapshot()
res["active_contour"] = ""
if s.NSD.Profile != "" {
@@ -240,24 +250,16 @@ func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) {
jsonOut(w, map[string]any{"ok": false, "error": "сначала укажите депозитарный код организации (мастер, раздел «Организация»)"})
return
}
extra, _ := json.Marshal(map[string]string{"contour": contour, "crypto": crypto, "deponent": dep})
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Minute)
defer cancel()
res, err := runKeymediaOp(ctx, "igate-contour", map[string]string{"extra": string(extra)})
if err == nil && res["ok"] == true && rc.Snapshot().NSD.Profile == "" {
// контур в bj ещё не выбран (новая установка) — первый настроенный канал и есть рабочий
n := rc.Snapshot().NSD
n.Profile, n.IGWBaseURL = contour+"-"+crypto, pickStr(n.IGWBaseURL, igateDefaultURL)
if rc.UpdateNSD(n) == nil {
res["output"] = asString(res["output"]) + ". bj работает с этим контуром и перезапускается — обновите страницу через 15 секунд"
go func() {
time.Sleep(2 * time.Second)
_, _ = restartUnit(context.Background(), "bj-server")
}()
}
if !igateContourJob.start(contour) {
jsonOut(w, map[string]any{"ok": true, "running": true}) // уже идёт — страница покажет ход
return
}
refreshCertStore(rc)
jsonOut(w, opResult(res, err))
go igateContourRun(rc, contour, crypto, dep)
jsonOut(w, map[string]any{"ok": true, "started": true})
})
// ход записи канала: страница показывает его и после перезагрузки
mux.HandleFunc("/admin/setup/igate/contour-status", func(w http.ResponseWriter, r *http.Request) {
jsonOut(w, igateContourJob.view())
})
// переключить bj на контур: профиль отправки, база DEPO контура; bj перезапускается
mux.HandleFunc("/admin/setup/igate/switch", func(w http.ResponseWriter, r *http.Request) {