Обновление с индикатором хода (этапы, скачанные МБ, перезапуск ядра); в мастере ключ продукта не просится повторно
- /admin/setup/update/progress: этап, байты, целевая версия ядра; ApplyUpdate/ApplyBundle отчитываются; - окно в layout: формы установки обновлений отправляются скриптом, окно ждёт перезапуска ядра и сверяет версию; - шаг 1 мастера: «Ключ продукта введён», смена ключа — под ссылкой. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package lkgateway
|
||||
|
||||
// Ход обновления для окна с индикатором на странице: этапы (проверка, скачивание, установка,
|
||||
// перезапуск) и байты скачанного. Страница опрашивает GET /admin/setup/update/progress; когда
|
||||
// обновляется ядро, bj-server перезапускается — страница видит обрыв связи, ждёт и сверяет
|
||||
// версию работающего ядра с целевой.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type updateProgressState struct {
|
||||
Seq int64 `json:"seq"`
|
||||
Active bool `json:"active"`
|
||||
Finished bool `json:"finished"`
|
||||
Step int `json:"step"`
|
||||
Steps int `json:"steps"`
|
||||
Stage string `json:"stage"`
|
||||
Done int64 `json:"done"`
|
||||
Total int64 `json:"total"`
|
||||
Target string `json:"target,omitempty"` // версия ядра после обновления (если ядро обновляется)
|
||||
Result string `json:"result,omitempty"`
|
||||
Err string `json:"error,omitempty"`
|
||||
Started time.Time `json:"started"`
|
||||
Version string `json:"version"` // версия работающего bj-server
|
||||
}
|
||||
|
||||
var updProg = struct {
|
||||
sync.Mutex
|
||||
s updateProgressState
|
||||
}{}
|
||||
|
||||
func progressBegin(steps int) {
|
||||
updProg.Lock()
|
||||
defer updProg.Unlock()
|
||||
updProg.s = updateProgressState{Seq: updProg.s.Seq + 1, Active: true, Steps: steps, Started: time.Now()}
|
||||
}
|
||||
|
||||
func progressStage(step int, stage string) {
|
||||
updProg.Lock()
|
||||
defer updProg.Unlock()
|
||||
updProg.s.Step, updProg.s.Stage = step, stage
|
||||
}
|
||||
|
||||
func progressBytes(done, total int64) {
|
||||
updProg.Lock()
|
||||
defer updProg.Unlock()
|
||||
updProg.s.Done, updProg.s.Total = done, total
|
||||
}
|
||||
|
||||
func progressTarget(v string) {
|
||||
updProg.Lock()
|
||||
defer updProg.Unlock()
|
||||
updProg.s.Target = v
|
||||
}
|
||||
|
||||
func progressEnd(result string, err error) {
|
||||
updProg.Lock()
|
||||
defer updProg.Unlock()
|
||||
updProg.s.Active, updProg.s.Finished, updProg.s.Result = false, true, result
|
||||
if err != nil {
|
||||
updProg.s.Err = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// progressWatchDir — пока идёт скачивание, раз в полсекунды считает байты в каталоге подготовки.
|
||||
// Возвращает функцию остановки.
|
||||
func progressWatchDir(dir string, total int64) func() {
|
||||
stop := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
t := time.NewTicker(500 * time.Millisecond)
|
||||
defer t.Stop()
|
||||
for {
|
||||
var n int64
|
||||
_ = filepath.Walk(dir, func(_ string, fi os.FileInfo, err error) error {
|
||||
if err == nil && fi.Mode().IsRegular() {
|
||||
n += fi.Size()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
progressBytes(n, total)
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return func() { close(stop); wg.Wait(); progressBytes(total, total) }
|
||||
}
|
||||
|
||||
// updateProgressHandler — GET /admin/setup/update/progress.
|
||||
func (h *setupHandlers) updateProgress(w http.ResponseWriter, r *http.Request) {
|
||||
updProg.Lock()
|
||||
s := updProg.s
|
||||
updProg.Unlock()
|
||||
s.Version = BuildVersion
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
_ = json.NewEncoder(w).Encode(s)
|
||||
}
|
||||
Reference in New Issue
Block a user