ИШ: этапы обновления шлюза в окне обновления bj, проверка нового ИШ, свежая версия в строке НРД
- окно обновления bj после перезапуска ведёт обновление ИШ по этапам (загрузка, остановка, резервная копия, установка, запуск, проверка) и закрывается только после проверки; пока шлюз обновляется, то же окно на любой странице - bj-igate-admin: этапы stop/backup/verify; после запуска сверяются версия, служба, движок, каналы и ключ с прежними (иначе автооткат), обмен с НРД - версия ИШ в строке НРД читается всегда заново (кэш 6 ч — только сайт НРД); после установки версии пересобираются сразу Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
@@ -73,19 +73,22 @@ run_as_igate_keyring() { # команда в сессии igate с разбло
|
|||||||
dbus-run-session -- bash -c 'eval "$(printf "%s\n" "$(cat "$HOME/.keyring-pass")" | gnome-keyring-daemon --replace --unlock --components=secrets,pkcs11 2>/dev/null)"; export GNOME_KEYRING_CONTROL; '"$1"
|
dbus-run-session -- bash -c 'eval "$(printf "%s\n" "$(cat "$HOME/.keyring-pass")" | gnome-keyring-daemon --replace --unlock --components=secrets,pkcs11 2>/dev/null)"; export GNOME_KEYRING_CONTROL; '"$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channels_json() { # каналы ИШ: код, включён, адрес НРД, ключ, крипто, депонент, папка
|
||||||
|
if runuser -u postgres -- psql -lqt 2>/dev/null | cut -d'|' -f1 | grep -qw "$DB"; then
|
||||||
|
psqlq -c "select coalesce(json_agg(json_build_object('id',c.id,'code',c.code,'enabled',c.enable,
|
||||||
|
'endpoint',(select value from parameters p where p.chanel_id=c.id and p.name='wslEndpoint'),
|
||||||
|
'profile',(select value from parameters p where p.chanel_id=c.id and p.name='cryptography.profile'),
|
||||||
|
'crypto',(select value from parameters p where p.chanel_id=c.id and p.name='cryptography.type'),
|
||||||
|
'deponent',(select value from parameters p where p.chanel_id=c.id and p.name='repositoryCode'),
|
||||||
|
'dir',(select value from parameters p where p.chanel_id=c.id and p.name='dir')) order by c.id),'[]') from channels c" 2>/dev/null || echo "[]"
|
||||||
|
else echo "[]"; fi
|
||||||
|
}
|
||||||
|
|
||||||
case "$cmd" in
|
case "$cmd" in
|
||||||
status)
|
status)
|
||||||
inst=false; [ -x "$IGATE_DIR/igate-svc/igate-svc" ] && inst=true
|
inst=false; [ -x "$IGATE_DIR/igate-svc/igate-svc" ] && inst=true
|
||||||
svc=$(systemctl is-active igate 2>/dev/null); st=$(engine_state)
|
svc=$(systemctl is-active igate 2>/dev/null); st=$(engine_state)
|
||||||
chans="[]"
|
chans=$(channels_json)
|
||||||
if runuser -u postgres -- psql -lqt 2>/dev/null | cut -d'|' -f1 | grep -qw "$DB"; then
|
|
||||||
chans=$(psqlq -c "select coalesce(json_agg(json_build_object('id',c.id,'code',c.code,'enabled',c.enable,
|
|
||||||
'endpoint',(select value from parameters p where p.chanel_id=c.id and p.name='wslEndpoint'),
|
|
||||||
'profile',(select value from parameters p where p.chanel_id=c.id and p.name='cryptography.profile'),
|
|
||||||
'crypto',(select value from parameters p where p.chanel_id=c.id and p.name='cryptography.type'),
|
|
||||||
'deponent',(select value from parameters p where p.chanel_id=c.id and p.name='repositoryCode'),
|
|
||||||
'dir',(select value from parameters p where p.chanel_id=c.id and p.name='dir')) order by c.id),'[]') from channels c" 2>/dev/null || echo "[]")
|
|
||||||
fi
|
|
||||||
rb=""; BK=$(cat /var/backups/ish-last-backup.txt 2>/dev/null)
|
rb=""; BK=$(cat /var/backups/ish-last-backup.txt 2>/dev/null)
|
||||||
[ -n "$BK" ] && [ -s "$BK/version" ] && [ -s "$BK/opt-nsd-igate.tar.gz" ] && [ -s "$BK/igate-db.dump" ] && [ -s "$BK/var-lib-igate.tar.gz" ] && rb=$(cat "$BK/version")
|
[ -n "$BK" ] && [ -s "$BK/version" ] && [ -s "$BK/opt-nsd-igate.tar.gz" ] && [ -s "$BK/igate-db.dump" ] && [ -s "$BK/var-lib-igate.tar.gz" ] && rb=$(cat "$BK/version")
|
||||||
echo "{\"ok\":true,\"installed\":$inst,\"version\":$(q "$(igate_version)"),\"service\":$(q "$svc"),\"engine\":$(q "$st"),\"rollback\":$(q "$rb"),\"channels\":${chans:-[]}}" ;;
|
echo "{\"ok\":true,\"installed\":$inst,\"version\":$(q "$(igate_version)"),\"service\":$(q "$svc"),\"engine\":$(q "$st"),\"rollback\":$(q "$rb"),\"channels\":${chans:-[]}}" ;;
|
||||||
@@ -168,11 +171,14 @@ PY
|
|||||||
runuser -u postgres -- psql -qtAc "select pg_reload_conf()" >/dev/null
|
runuser -u postgres -- psql -qtAc "select pg_reload_conf()" >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
st install "установка пакета ИШ"
|
oldv=""; [ -d "$IGATE_DIR" ] && oldv=$(igate_version)
|
||||||
|
st stop "остановка ${oldv:+старого }ИШ${oldv:+ $oldv}"
|
||||||
systemctl stop igate >/dev/null 2>&1 || true
|
systemctl stop igate >/dev/null 2>&1 || true
|
||||||
pkill -9 -u "$SVC_USER" -f igate-svc 2>/dev/null; pkill -9 -u "$SVC_USER" -f Xvfb 2>/dev/null; rm -f /tmp/CoreFxPipe_NSD.IGate.*
|
pkill -9 -u "$SVC_USER" -f igate-svc 2>/dev/null; pkill -9 -u "$SVC_USER" -f Xvfb 2>/dev/null; rm -f /tmp/CoreFxPipe_NSD.IGate.*
|
||||||
if [ -d "$IGATE_DIR" ]; then
|
if [ -d "$IGATE_DIR" ]; then
|
||||||
|
st backup "резервная копия ИШ $oldv (программа, данные, база)"
|
||||||
BK=/var/backups/ish-$(date +%Y%m%d-%H%M%S); mkdir -p "$BK"
|
BK=/var/backups/ish-$(date +%Y%m%d-%H%M%S); mkdir -p "$BK"
|
||||||
|
channels_json > "$BK/channels.json" # для проверки после обновления: каналы и ключи те же
|
||||||
tar czf "$BK/opt-nsd-igate.tar.gz" -C "$(dirname "$IGATE_DIR")" "$(basename "$IGATE_DIR")" 2>/dev/null
|
tar czf "$BK/opt-nsd-igate.tar.gz" -C "$(dirname "$IGATE_DIR")" "$(basename "$IGATE_DIR")" 2>/dev/null
|
||||||
runuser -u postgres -- pg_dump -Fc "$DB" > "$BK/igate-db.dump" 2>/dev/null
|
runuser -u postgres -- pg_dump -Fc "$DB" > "$BK/igate-db.dump" 2>/dev/null
|
||||||
# для отката: данные ИШ (без флешки с ключом и кэша), юнит, обёртка запуска, версия
|
# для отката: данные ИШ (без флешки с ключом и кэша), юнит, обёртка запуска, версия
|
||||||
@@ -188,6 +194,7 @@ PY
|
|||||||
newv=$(basename "$deb" | sed -E "s/^igate_([0-9.]+)-([0-9]+)_.*/\\1.\\2/") # igate_110.0-997_amd64.deb → 110.0.997
|
newv=$(basename "$deb" | sed -E "s/^igate_([0-9.]+)-([0-9]+)_.*/\\1.\\2/") # igate_110.0-997_amd64.deb → 110.0.997
|
||||||
die() { st rollback "$1 — откатываю к прежней версии"; echo "== ошибка обновления: $1 — откат"; BJ_RB_REASON="$1" BJ_RB_TO="$newv" /usr/local/sbin/bj-igate-admin rollback-now; exit 1; }
|
die() { st rollback "$1 — откатываю к прежней версии"; echo "== ошибка обновления: $1 — откат"; BJ_RB_REASON="$1" BJ_RB_TO="$newv" /usr/local/sbin/bj-igate-admin rollback-now; exit 1; }
|
||||||
fi
|
fi
|
||||||
|
st install "установка ${FULLBK:+нового }ИШ${newv:+ $newv}"
|
||||||
echo "igate igate/service boolean false" | debconf-set-selections
|
echo "igate igate/service boolean false" | debconf-set-selections
|
||||||
echo "igate igate/shortcut boolean false" | debconf-set-selections
|
echo "igate igate/shortcut boolean false" | debconf-set-selections
|
||||||
DEBIAN_FRONTEND=noninteractive dpkg -i --force-confnew "$deb" || { DEBIAN_FRONTEND=noninteractive apt-get -f install -y -qq || die "пакет ИШ не установился (dpkg)"; }
|
DEBIAN_FRONTEND=noninteractive dpkg -i --force-confnew "$deb" || { DEBIAN_FRONTEND=noninteractive apt-get -f install -y -qq || die "пакет ИШ не установился (dpkg)"; }
|
||||||
@@ -264,7 +271,44 @@ SQL
|
|||||||
systemctl restart igate || die "служба igate не запустилась"
|
systemctl restart igate || die "служба igate не запустилась"
|
||||||
wait_engine 120 || die "ИШ запущен, но движок не перешёл в Running за 2 минуты. Журнал ИШ: $DATA_DIR/logs-svc/log.txt"
|
wait_engine 120 || die "ИШ запущен, но движок не перешёл в Running за 2 минуты. Журнал ИШ: $DATA_DIR/logs-svc/log.txt"
|
||||||
case "$(j file)" in /var/lib/bj/igate-upload/*) rm -f "$(j file)" ;; esac # загруженный архив больше не нужен (с образа — остаётся)
|
case "$(j file)" in /var/lib/bj/igate-upload/*) rm -f "$(j file)" ;; esac # загруженный архив больше не нужен (с образа — остаётся)
|
||||||
st done "ИШ $(igate_version) установлен и работает; дальше — канал контура (TEST3 или боевой)" ;;
|
if [ -n "${FULLBK:-}" ]; then
|
||||||
|
# обновление: «готово» — только когда новый ИШ проверен; не прошло — die (автооткат к прежней версии)
|
||||||
|
st verify "проверка нового ИШ: версия, служба, каналы, ключ, обмен с НРД"
|
||||||
|
v=$(igate_version)
|
||||||
|
[ "$v" = "$newv" ] || die "после установки версия ИШ $v, ожидалась $newv"
|
||||||
|
echo "✓ версия ИШ $v"
|
||||||
|
sleep 10
|
||||||
|
[ "$(systemctl is-active igate)" = active ] && [ "$(engine_state)" = Running ] || die "ИШ $v остановился после запуска"
|
||||||
|
echo "✓ служба igate работает, движок Running"
|
||||||
|
now=$(channels_json)
|
||||||
|
chk=$(python3 - "$FULLBK/channels.json" "$now" <<'PY'
|
||||||
|
import json,sys
|
||||||
|
keys=('code','enabled','endpoint','profile','crypto','deponent','dir')
|
||||||
|
try: old=json.load(open(sys.argv[1]))
|
||||||
|
except Exception: old=None
|
||||||
|
new=json.loads(sys.argv[2] or '[]')
|
||||||
|
pick=lambda l:[{k:c.get(k) for k in keys} for c in l]
|
||||||
|
if old is not None and pick(old)!=pick(new):
|
||||||
|
print('ERR каналы ИШ после обновления не совпадают с прежними: было %s, стало %s'%(pick(old),pick(new))); sys.exit()
|
||||||
|
on=[c for c in new if c.get('enabled')]
|
||||||
|
print('OK '+'; '.join('канал %s включён, ключ %s'%(c['code'],c.get('profile') or '—') for c in on) if on else 'OK каналов не настроено')
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
case "$chk" in OK*) echo "✓ ${chk#OK } — как до обновления" ;; *) die "${chk#ERR }" ;; esac
|
||||||
|
xok=""
|
||||||
|
if printf '%s' "$now" | grep -q '"enabled" : true'; then
|
||||||
|
pid=$(pgrep -u "$SVC_USER" -f "$IGATE_DIR/igate-svc/igate-svc" | tail -1)
|
||||||
|
for _ in $(seq 1 30); do
|
||||||
|
grep -a "PID_$pid " "$DATA_DIR/logs-svc/log.txt" 2>/dev/null | grep -q -- '-> OK (HTTP 200)' && { xok=1; break; }
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
# НРД не ответил — не повод откатывать (старая версия связаться тоже не сможет): итог с предупреждением
|
||||||
|
[ -n "$xok" ] && echo "✓ обмен с НРД: запрос к НРД прошёл (HTTP 200)" || echo "⚠ за 90 с ИШ не получил ответа НРД — проверьте связь"
|
||||||
|
fi
|
||||||
|
st done "ИШ обновлён с $oldv до $v и проверен: служба и движок работают, каналы и ключ как до обновления${xok:+, обмен с НРД идёт}${xok:-; ответа НРД пока нет — проверьте связь}"
|
||||||
|
else
|
||||||
|
st done "ИШ $(igate_version) установлен и работает; дальше — канал контура (TEST3 или боевой)"
|
||||||
|
fi ;;
|
||||||
|
|
||||||
rollback)
|
rollback)
|
||||||
pgrep -f "bj-igate-admin (install|rollback)-now" >/dev/null && err "установка или откат ИШ уже идёт"
|
pgrep -f "bj-igate-admin (install|rollback)-now" >/dev/null && err "установка или откат ИШ уже идёт"
|
||||||
|
|||||||
+25
-14
@@ -234,20 +234,7 @@ func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) {
|
|||||||
})
|
})
|
||||||
// ход установки: этап, сообщение, журнал и сколько секунд идёт текущий этап (по времени записи файла)
|
// ход установки: этап, сообщение, журнал и сколько секунд идёт текущий этап (по времени записи файла)
|
||||||
mux.HandleFunc("/admin/setup/igate/install-status", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/admin/setup/igate/install-status", func(w http.ResponseWriter, r *http.Request) {
|
||||||
raw, err := os.ReadFile(igateInstallStatusPath)
|
jsonOut(w, igateInstallStatusJSON(rc))
|
||||||
out := map[string]any{}
|
|
||||||
if err != nil || json.Unmarshal(raw, &out) != nil {
|
|
||||||
jsonOut(w, map[string]any{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if fi, err := os.Stat(igateInstallStatusPath); err == nil {
|
|
||||||
out["elapsed"] = int(time.Since(fi.ModTime()).Seconds())
|
|
||||||
}
|
|
||||||
st := igateInstallStatus()
|
|
||||||
if st.Stage == "error" && out["stage"] != "error" { // молчит 20 минут — прервалась
|
|
||||||
out["stage"], out["msg"] = "error", st.Msg
|
|
||||||
}
|
|
||||||
jsonOut(w, out)
|
|
||||||
})
|
})
|
||||||
// канал контура в ИШ (адрес, отпечаток УЦ, папка обмена); ключ — мастером установки ключа
|
// канал контура в ИШ (адрес, отпечаток УЦ, папка обмена); ключ — мастером установки ключа
|
||||||
mux.HandleFunc("/admin/setup/igate/contour", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/admin/setup/igate/contour", func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -410,6 +397,30 @@ type igateInstallState struct {
|
|||||||
// igateInstallStatusPath — файл хода установки; var — для тестов.
|
// igateInstallStatusPath — файл хода установки; var — для тестов.
|
||||||
var igateInstallStatusPath = "/var/lib/bj/igate-install-status.json"
|
var igateInstallStatusPath = "/var/lib/bj/igate-install-status.json"
|
||||||
|
|
||||||
|
// igateInstallStatusJSON — ход установки/обновления ИШ для страниц: этап, сообщение, журнал, сколько
|
||||||
|
// секунд идёт этап (elapsed), идёт ли сейчас (busy). Как только установка закончилась, версии
|
||||||
|
// пересобираются тут же — строка «Интеграционный шлюз НРД (ИШ): …» не показывает прежнюю.
|
||||||
|
func igateInstallStatusJSON(rc *RuntimeConfig) map[string]any {
|
||||||
|
out := map[string]any{}
|
||||||
|
raw, err := os.ReadFile(igateInstallStatusPath)
|
||||||
|
if err != nil || json.Unmarshal(raw, &out) != nil {
|
||||||
|
return map[string]any{}
|
||||||
|
}
|
||||||
|
fi, err := os.Stat(igateInstallStatusPath)
|
||||||
|
if err == nil {
|
||||||
|
out["elapsed"] = int(time.Since(fi.ModTime()).Seconds())
|
||||||
|
}
|
||||||
|
st := igateInstallStatus()
|
||||||
|
if st.Stage == "error" && out["stage"] != "error" { // молчит 20 минут — прервалась
|
||||||
|
out["stage"], out["msg"] = "error", st.Msg
|
||||||
|
}
|
||||||
|
out["busy"] = st.Busy
|
||||||
|
if !st.Busy && err == nil && fi.ModTime().After(systemVersions(rc).At) {
|
||||||
|
refreshVersions(rc)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func igateInstallStatus() igateInstallState {
|
func igateInstallStatus() igateInstallState {
|
||||||
p := igateInstallStatusPath
|
p := igateInstallStatusPath
|
||||||
var st igateInstallState
|
var st igateInstallState
|
||||||
|
|||||||
@@ -82,20 +82,24 @@ var (
|
|||||||
// сайт НРД на каждый показ статуса). Сетевая часть best-effort: при недоступности
|
// сайт НРД на каждый показ статуса). Сетевая часть best-effort: при недоступности
|
||||||
// сайта возвращается только установленная версия.
|
// сайта возвращается только установленная версия.
|
||||||
func ishVersionInfo(ctx context.Context) IshVersionInfo {
|
func ishVersionInfo(ctx context.Context) IshVersionInfo {
|
||||||
|
// установленную версию читаем всегда заново (ИШ мог обновиться минуту назад), сайт — из кэша
|
||||||
ishVerMu.Lock()
|
ishVerMu.Lock()
|
||||||
if ishVerCache != nil && time.Since(ishVerCache.CheckedAt) < ishVerTTL {
|
site := ishVerCache
|
||||||
c := *ishVerCache
|
|
||||||
ishVerMu.Unlock()
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
ishVerMu.Unlock()
|
ishVerMu.Unlock()
|
||||||
|
if site == nil || time.Since(site.CheckedAt) >= ishVerTTL {
|
||||||
info := IshVersionInfo{
|
linux, latest := ishSiteVersions(ctx)
|
||||||
Installed: ishInstalledVersion(),
|
site = &IshVersionInfo{SiteLinux: linux, SiteLatest: latest, CheckedAt: time.Now().UTC()}
|
||||||
CheckedAt: time.Now().UTC(),
|
ishVerMu.Lock()
|
||||||
|
ishVerCache = site
|
||||||
|
ishVerMu.Unlock()
|
||||||
}
|
}
|
||||||
linux, latest := ishSiteVersions(ctx)
|
info := IshVersionInfo{
|
||||||
info.SiteLinux, info.SiteLatest = linux, latest
|
Installed: ishInstalledVersion(),
|
||||||
|
SiteLinux: site.SiteLinux,
|
||||||
|
SiteLatest: site.SiteLatest,
|
||||||
|
CheckedAt: site.CheckedAt,
|
||||||
|
}
|
||||||
|
linux, latest := info.SiteLinux, info.SiteLatest
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case info.Installed == "":
|
case info.Installed == "":
|
||||||
@@ -113,10 +117,6 @@ func ishVersionInfo(ctx context.Context) IshVersionInfo {
|
|||||||
info.UpToDate = false
|
info.UpToDate = false
|
||||||
info.Note = "доступно обновление до " + linux + " (Astra-Linux)"
|
info.Note = "доступно обновление до " + linux + " (Astra-Linux)"
|
||||||
}
|
}
|
||||||
|
|
||||||
ishVerMu.Lock()
|
|
||||||
ishVerCache = &info
|
|
||||||
ishVerMu.Unlock()
|
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,22 +15,27 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type updateProgressState struct {
|
type updateProgressState struct {
|
||||||
Seq int64 `json:"seq"`
|
Seq int64 `json:"seq"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Finished bool `json:"finished"`
|
Finished bool `json:"finished"`
|
||||||
Step int `json:"step"`
|
Step int `json:"step"`
|
||||||
Steps int `json:"steps"`
|
Steps int `json:"steps"`
|
||||||
Stage string `json:"stage"`
|
Stage string `json:"stage"`
|
||||||
Done int64 `json:"done"`
|
Done int64 `json:"done"`
|
||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
Target string `json:"target,omitempty"` // версия ядра после обновления (если ядро обновляется)
|
Target string `json:"target,omitempty"` // версия ядра после обновления (если ядро обновляется)
|
||||||
Result string `json:"result,omitempty"`
|
Result string `json:"result,omitempty"`
|
||||||
Note string `json:"note,omitempty"` // пояснение, которое нужно показать и после перезапуска ядра
|
Note string `json:"note,omitempty"` // пояснение, которое нужно показать и после перезапуска ядра
|
||||||
Err string `json:"error,omitempty"`
|
Err string `json:"error,omitempty"`
|
||||||
Started time.Time `json:"started"`
|
Started time.Time `json:"started"`
|
||||||
Version string `json:"version"` // версия работающего bj-server
|
Version string `json:"version"` // версия работающего bj-server
|
||||||
|
IgateTo string `json:"igate_to,omitempty"` // bj сам обновит ИШ до этой версии (ещё не обновил)
|
||||||
|
Igate map[string]any `json:"igate,omitempty"` // ход установки ИШ (как /admin/setup/igate/install-status)
|
||||||
|
Up int `json:"up"` // секунд с запуска bj-server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var procStarted = time.Now()
|
||||||
|
|
||||||
var updProg = struct {
|
var updProg = struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
s updateProgressState
|
s updateProgressState
|
||||||
@@ -110,6 +115,7 @@ func (h *setupHandlers) updateProgress(w http.ResponseWriter, r *http.Request) {
|
|||||||
s := updProg.s
|
s := updProg.s
|
||||||
updProg.Unlock()
|
updProg.Unlock()
|
||||||
s.Version = BuildVersion
|
s.Version = BuildVersion
|
||||||
|
s.IgateTo, s.Igate, s.Up = igatePendingUpgrade(), igateInstallStatusJSON(h.rc), int(time.Since(procStarted).Seconds())
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
_ = json.NewEncoder(w).Encode(s)
|
_ = json.NewEncoder(w).Encode(s)
|
||||||
|
|||||||
@@ -1030,6 +1030,9 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
|
|||||||
<div id="upd-bar" style="height:100%;width:0;background:var(--accent,#2b50d8);transition:width .4s"></div>
|
<div id="upd-bar" style="height:100%;width:0;background:var(--accent,#2b50d8);transition:width .4s"></div>
|
||||||
</div>
|
</div>
|
||||||
<p id="upd-detail" style="margin:8px 0 0;font-size:13px;color:var(--muted,#667);min-height:18px"></p>
|
<p id="upd-detail" style="margin:8px 0 0;font-size:13px;color:var(--muted,#667);min-height:18px"></p>
|
||||||
|
<ol id="upd-ig" style="display:none;list-style:none;padding:0;margin:12px 0 0;font-size:14px;line-height:1.9"></ol>
|
||||||
|
<details id="upd-ig-logw" style="display:none;margin-top:8px"><summary class="muted" style="font-size:12.5px;cursor:pointer">Журнал установки ИШ</summary>
|
||||||
|
<pre id="upd-ig-log" style="max-height:200px;overflow:auto;font-size:11.5px;white-space:pre-wrap;background:var(--card-2,#eef0f4);padding:8px;border-radius:8px;margin:6px 0 0"></pre></details>
|
||||||
<div id="upd-done" style="display:none;margin-top:14px;text-align:right"><button type="button" class="btn" onclick="location.reload()">Готово</button></div>
|
<div id="upd-done" style="display:none;margin-top:14px;text-align:right"><button type="button" class="btn" onclick="location.reload()">Готово</button></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1055,6 +1058,62 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
|
|||||||
tick();
|
tick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Обновление шлюза НРД (ИШ): с обновлением bj пришёл дистрибутив ИШ — bj ставит его сам после перезапуска.
|
||||||
|
Окно не закрывается, пока новый ИШ не установлен и не проверен; на любой странице, пока идёт, —
|
||||||
|
то же окно (страницы не показывают полуобновлённое состояние). Ошибка — текст, журнал и автооткат. */
|
||||||
|
var IGS=[['prep','Загрузка и проверка дистрибутива ИШ'],['stop','Остановка старого ИШ'],['backup','Резервная копия старого ИШ'],
|
||||||
|
['install','Установка нового ИШ'],['start','Запуск нового ИШ'],['verify','Проверка: версия, каналы, ключ, обмен с НРД']];
|
||||||
|
function igKey(s){ var g=s.stage||''; if(/^(queued|deps|dist|user|db|package)$/.test(g)) return 'prep';
|
||||||
|
if(g==='config'||g==='schema') return 'install'; return g; }
|
||||||
|
var ig={on:false,t0:0,seen:false,rb:false,idx:-1};
|
||||||
|
function igDraw(s, waiting){
|
||||||
|
var k=igKey(s||{}), i=-1; IGS.forEach(function(x,j){ if(x[0]===k) i=j; });
|
||||||
|
if(!ig.rb && i>ig.idx) ig.idx=i; if(waiting) ig.idx=-1;
|
||||||
|
var list=document.getElementById('upd-ig'); list.style.display='block';
|
||||||
|
list.innerHTML=IGS.map(function(x,j){
|
||||||
|
var st=j<ig.idx||(s&&s.stage==='done'&&!waiting)?'<span style="color:var(--ok,#2e8b57)">✓</span>':j===ig.idx&&!ig.rb||(waiting&&j===0)?'<span style="color:var(--accent,#2b50d8)">⟳</span>':'<span style="color:var(--muted,#99a)">○</span>';
|
||||||
|
var t=j===ig.idx&&!ig.rb&&s&&s.stage!=='done'?' <span class="muted" style="font-size:12.5px">— '+(s.msg||'')+', '+(s.elapsed||0)+' с</span>':'';
|
||||||
|
return '<li>'+st+' '+x[1]+t+'</li>'; }).join('')+(s&&s.stage==='done'&&!waiting?'<li><span style="color:var(--ok,#2e8b57)">✓</span> <b>Шлюз установлен и работает</b></li>':'');
|
||||||
|
var lw=document.getElementById('upd-ig-logw'), lg=document.getElementById('upd-ig-log');
|
||||||
|
if(s&&s.log){ lw.style.display='block'; lg.textContent=s.log; lg.scrollTop=lg.scrollHeight; }
|
||||||
|
}
|
||||||
|
window.bjIgPhase=function(note){
|
||||||
|
if(ig.on) return; ig.on=true; ig.t0=Date.now(); window.bjIgOv=true; finished=true;
|
||||||
|
show(); document.querySelector('#upd-modal h3').textContent='Обновление шлюза НРД (ИШ)';
|
||||||
|
doneEl.style.display='none'; bar.style.background='var(--accent,#2b50d8)'; bar.style.width='5%';
|
||||||
|
if(note) detail.textContent=note.charAt(0).toUpperCase()+note.slice(1)+'.';
|
||||||
|
(function tick(){
|
||||||
|
fetch('/admin/setup/update/progress',{cache:'no-store',credentials:'same-origin'}).then(function(r){ if(!r.ok) throw 0; return r.json(); }).then(function(p){
|
||||||
|
var s=p.igate||{}, run=s.busy===true, since=(Date.now()-ig.t0)/1000;
|
||||||
|
var fresh=run||ig.seen||(s.elapsed!==undefined&&s.elapsed<since+5); // итог именно этой установки, не прошлой
|
||||||
|
if(run) ig.seen=true;
|
||||||
|
if(s.stage==='rollback'||(ig.rb&&run)){ ig.rb=true; igDraw(s,false);
|
||||||
|
stageEl.innerHTML='<span style="color:var(--warn,#b7791f)">⟲ Обновление не прошло — возвращаю прежнюю версию ИШ</span>'; detail.textContent=s.msg||''; bar.style.width='90%'; }
|
||||||
|
else if(run){ igDraw(s,false); stageEl.textContent='Идёт обновление шлюза НРД. Не закрывайте страницу: обмен с НРД на 1–3 минуты приостановлен.';
|
||||||
|
bar.style.width=Math.max(8,Math.round((ig.idx+1)/(IGS.length+1)*100))+'%'; }
|
||||||
|
else if(fresh&&s.stage==='done'&&!p.igate_to){ igDraw(s,false); finishIg(s.msg,true); return; }
|
||||||
|
else if(fresh&&s.stage==='error'){ igDraw(s,false); finishIg(s.msg,false); return; }
|
||||||
|
else { igDraw(null,true);
|
||||||
|
if(since>15*60){ finishIg('bj не начал обновление шлюза за 15 минут. Шлюз работает на прежней версии; обновить можно на странице НРД → «Перенастроить» → «Шлюз».',false); return; }
|
||||||
|
stageEl.textContent='Получен новый ИШ'+(p.igate_to?' '+p.igate_to:'')+' — bj начнёт обновление шлюза через несколько секунд.'; }
|
||||||
|
setTimeout(tick,2500);
|
||||||
|
}).catch(function(){ setTimeout(tick,3000); });
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
function finishIg(msg, ok){
|
||||||
|
stageEl.innerHTML=ok?'<span style="color:var(--ok,#2e8b57)">✓ Готово</span>':'<span style="color:var(--err,#c0392b)">✗ Обновление шлюза не прошло</span>';
|
||||||
|
detail.textContent=msg||''; bar.style.width='100%'; bar.style.background=ok?'var(--ok,#2e8b57)':'var(--err,#c0392b)';
|
||||||
|
doneEl.style.display='block'; var b=doneEl.querySelector('button');
|
||||||
|
if(ok){ var left=8, t=function(){ if(left<=0){ location.reload(); return; } b.textContent='Готово (страница обновится через '+left+' с)'; left--; setTimeout(t,1000); }; t(); }
|
||||||
|
else { document.getElementById('upd-ig-logw').open=true; b.textContent='Закрыть'; }
|
||||||
|
}
|
||||||
|
function igPending(p){ return !!(p&&(p.igate_to||(p.igate&&p.igate.busy))); }
|
||||||
|
// страница открыта, когда шлюз обновляется (или вот-вот начнёт после перезапуска bj) — то же окно
|
||||||
|
fetch('/admin/setup/update/progress',{cache:'no-store',credentials:'same-origin'}).then(function(r){ return r.ok?r.json():null; }).then(function(p){
|
||||||
|
if(!p||p.active) return;
|
||||||
|
var s=p.igate||{};
|
||||||
|
if(s.busy||(p.igate_to&&s.stage!=='error'&&p.up<12*60)) window.bjIgPhase('');
|
||||||
|
}).catch(function(){});
|
||||||
function run(form){
|
function run(form){
|
||||||
var lastChange=Date.now(), lastKey='';
|
var lastChange=Date.now(), lastKey='';
|
||||||
// сторож: если отчёт не меняется полторы минуты, перезагружаем страницу — не оставляем «зависшее» окно
|
// сторож: если отчёт не меняется полторы минуты, перезагружаем страницу — не оставляем «зависшее» окно
|
||||||
@@ -1087,7 +1146,9 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
|
|||||||
// сменилась или отчёт начался заново (новый процесс) — обновление завершено
|
// сменилась или отчёт начался заново (новый процесс) — обновление завершено
|
||||||
if(!lost && startVer!==null && (p.version!==startVer || (seq0>0 && p.seq===0 && !p.active))){ lost=true; }
|
if(!lost && startVer!==null && (p.version!==startVer || (seq0>0 && p.seq===0 && !p.active))){ lost=true; }
|
||||||
if(lost){ // bj вернулся после перезапуска
|
if(lost){ // bj вернулся после перезапуска
|
||||||
if(!target || p.version===target || p.version!==startVer){ finish('Готово: bj перезапущен, работает версия '+p.version+'.', true); detail.textContent=note ? note.charAt(0).toUpperCase()+note.slice(1)+'.' : ''; return; }
|
if(!target || p.version===target || p.version!==startVer){
|
||||||
|
if(igPending(p)){ window.bjIgPhase('bj обновлён до '+p.version+'; теперь обновляется шлюз НРД'); return; }
|
||||||
|
finish('Готово: bj перезапущен, работает версия '+p.version+'.', true); detail.textContent=note ? note.charAt(0).toUpperCase()+note.slice(1)+'.' : ''; return; }
|
||||||
}
|
}
|
||||||
var fresh = p.seq!==seq0; // только отчёт этой попытки
|
var fresh = p.seq!==seq0; // только отчёт этой попытки
|
||||||
if(p.active){ seenActive=true;
|
if(p.active){ seenActive=true;
|
||||||
@@ -1099,6 +1160,7 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
|
|||||||
} else if(p.finished && fresh){
|
} else if(p.finished && fresh){
|
||||||
if(p.error){ finish('Обновление не выполнено', false); detail.textContent=p.error; return; }
|
if(p.error){ finish('Обновление не выполнено', false); detail.textContent=p.error; return; }
|
||||||
if(target && p.version!==target){ stageEl.textContent='Шаг 4 из 4: перезапуск bj'; bar.style.width='90%'; detail.textContent='Ждём, пока bj поднимется с новой версией…'; }
|
if(target && p.version!==target){ stageEl.textContent='Шаг 4 из 4: перезапуск bj'; bar.style.width='90%'; detail.textContent='Ждём, пока bj поднимется с новой версией…'; }
|
||||||
|
else if(igPending(p)){ window.bjIgPhase(p.result||''); return; }
|
||||||
else { finish('Готово', true); detail.textContent=p.result||''; return; }
|
else { finish('Готово', true); detail.textContent=p.result||''; return; }
|
||||||
} else if(postDone && !fresh && Date.now()-t0>8000){
|
} else if(postDone && !fresh && Date.now()-t0>8000){
|
||||||
finish('Готово', true); detail.textContent='Результат — в строке «Последняя проверка».'; return;
|
finish('Готово', true); detail.textContent='Результат — в строке «Последняя проверка».'; return;
|
||||||
|
|||||||
@@ -354,7 +354,7 @@
|
|||||||
document.getElementById('ig-ov-h').innerHTML=h; document.getElementById('ig-ov-m').textContent=m||'';
|
document.getElementById('ig-ov-h').innerHTML=h; document.getElementById('ig-ov-m').textContent=m||'';
|
||||||
var l=document.getElementById('ig-ov-log'); if(log){ l.style.display='block'; l.textContent=log; l.scrollTop=l.scrollHeight; } else l.style.display='none';
|
var l=document.getElementById('ig-ov-log'); if(log){ l.style.display='block'; l.textContent=log; l.scrollTop=l.scrollHeight; } else l.style.display='none';
|
||||||
document.getElementById('ig-ov-btns').style.display=fin?'block':'none'; }
|
document.getElementById('ig-ov-btns').style.display=fin?'block':'none'; }
|
||||||
function ovStart(){ if(ov.on) return; ov.on=true; ov.t0=Date.now(); ovTick(); ov.iv=setInterval(ovTick,3000); }
|
function ovStart(){ if(window.bjIgPhase){ window.bjIgPhase(''); return; } if(ov.on) return; ov.on=true; ov.t0=Date.now(); ovTick(); ov.iv=setInterval(ovTick,3000); }
|
||||||
function ovTick(){
|
function ovTick(){
|
||||||
fetch('/admin/setup/igate/install-status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(s){
|
fetch('/admin/setup/igate/install-status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(s){
|
||||||
var run=s.stage&&s.stage!=='done'&&s.stage!=='error';
|
var run=s.stage&&s.stage!=='done'&&s.stage!=='error';
|
||||||
|
|||||||
Reference in New Issue
Block a user