diff --git a/internal/lkgateway/updater.go b/internal/lkgateway/updater.go index d98bbfc..c9e8f63 100644 --- a/internal/lkgateway/updater.go +++ b/internal/lkgateway/updater.go @@ -97,6 +97,7 @@ func (u *Updater) CheckForUpdate(ctx context.Context) (UpdateStatus, error) { st.Available, st.Notes = m.Version, m.Notes st.Components = release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion) release.MarkSplitModules(st.Components, u.allowed) + off := markUnlicensed(st.Components, u.allowed) var upd []string avail := map[string]string{} for _, c := range st.Components { @@ -115,10 +116,39 @@ func (u *Updater) CheckForUpdate(ctx context.Context) (UpdateStatus, error) { } else { st.Message = "все установленные компоненты актуальны (релиз " + m.Version + ")" } + st.Message += unlicensedNote(off) u.saveCheckResult(st, avail) return st, nil } +// markUnlicensed — модули, установленные на сервере, но снятые с лицензии: не обновляются (сервер +// обновлений их файлы не отдаёт), в списке — с пояснением. Возвращает названия таких модулей. +func markUnlicensed(states []release.ComponentState, allowed func(string) bool) []string { + var off []string + for i := range states { + c := &states[i] + if c.Core || c.Feature == "" || c.Feature == "updates" || allowed(c.Feature) { + continue + } + c.Update, c.New = false, false + if c.Present { + c.Blocked = "не входит в лицензию — модуль отключён и не обновляется; чтобы подключить, свяжитесь с ZETIT" + off = append(off, "«"+c.Title+"»") + } + } + return off +} + +func unlicensedNote(off []string) string { + if len(off) == 0 { + return "" + } + if len(off) == 1 { + return "; модуль " + off[0] + " не входит в лицензию — отключён и не обновлялся; чтобы подключить, свяжитесь с ZETIT" + } + return "; модули " + strings.Join(off, ", ") + " не входят в лицензию — отключены и не обновлялись; чтобы подключить, свяжитесь с ZETIT" +} + // updateChannel — канал обновлений установки (пусто = stable). func updateChannel(s UpdateSettings) string { if c := strings.TrimSpace(s.Channel); c != "" { @@ -174,6 +204,7 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro } plan := release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion) release.MarkSplitModules(plan, u.allowed) + off := markUnlicensed(plan, u.allowed) want := map[string]bool{} for _, n := range names { want[n] = true @@ -184,11 +215,16 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro case len(want) > 0 && !want[c.Name]: case c.Update: selected = append(selected, c) + case want[c.Name] && c.Blocked != "": + return "", fmt.Errorf("%s: %s", c.Title, c.Blocked) case want[c.Name]: return "", fmt.Errorf("%s: обновление не требуется (установлена %s, в релизе %s)", c.Title, pickVersion(c.Installed), pickVersion(c.Available)) } } if len(selected) == 0 { + if len(off) > 0 { + return "обновлять нечего: компоненты лицензии актуальны (релиз " + m.Version + ")" + unlicensedNote(off), nil + } return "", fmt.Errorf("нечего обновлять: все установленные компоненты актуальны (релиз %s)", m.Version) } // Прежний root-установщик не умеет впервые ставить выделенные из ядра модули: пока он сам не @@ -240,6 +276,7 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro progressTarget(c.Available) } } + progressNote(strings.TrimPrefix(unlicensedNote(off), "; ")) progressStage(2, "Скачивание файлов") stopWatch := progressWatchDir(staging, total) for _, c := range selected { @@ -282,6 +319,7 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro if len(deferred) > 0 { msg += "; модули " + strings.Join(deferred, ", ") + " будут установлены после перезапуска bj-server новым установщиком" } + msg += unlicensedNote(off) return msg, nil } @@ -355,6 +393,7 @@ func updateComponentRows(rc *RuntimeConfig) ([]release.ComponentState, bool) { } } release.MarkSplitModules(out, func(f string) bool { return moduleAllowed(rc, f) }) + markUnlicensed(out, func(f string) bool { return moduleAllowed(rc, f) }) // Последняя проверка не удалась — предлагать установку по старым данным нельзя: // кнопка ушла бы в тот же отказ. Показываем причину и просим проверить заново. if updateResultFailed(set.LastResult) { @@ -475,6 +514,7 @@ func (u *Updater) applyBundle(ctx context.Context, zipPath string) (string, erro } plan := release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion) release.MarkSplitModules(plan, u.allowed) + markUnlicensed(plan, u.allowed) var req struct { Names []string `json:"names"` New []string `json:"new,omitempty"` diff --git a/internal/lkgateway/updprogress.go b/internal/lkgateway/updprogress.go index b0d6a5c..3c093e3 100644 --- a/internal/lkgateway/updprogress.go +++ b/internal/lkgateway/updprogress.go @@ -25,6 +25,7 @@ type updateProgressState struct { Total int64 `json:"total"` Target string `json:"target,omitempty"` // версия ядра после обновления (если ядро обновляется) Result string `json:"result,omitempty"` + Note string `json:"note,omitempty"` // пояснение, которое нужно показать и после перезапуска ядра Err string `json:"error,omitempty"` Started time.Time `json:"started"` Version string `json:"version"` // версия работающего bj-server @@ -59,6 +60,12 @@ func progressTarget(v string) { updProg.s.Target = v } +func progressNote(n string) { + updProg.Lock() + defer updProg.Unlock() + updProg.s.Note = n +} + func progressEnd(result string, err error) { updProg.Lock() defer updProg.Unlock() diff --git a/internal/lkgateway/web/templates/layout.html b/internal/lkgateway/web/templates/layout.html index a8e1e7f..ba2a541 100644 --- a/internal/lkgateway/web/templates/layout.html +++ b/internal/lkgateway/web/templates/layout.html @@ -599,7 +599,7 @@ label { font-size:13px; font-weight:500; } doneEl.style.display='block'; } function run(form){ - var startVer=null, target='', seenActive=false, postDone=false, lost=false, t0=Date.now(), seq0=null; + var note='', startVer=null, target='', seenActive=false, postDone=false, lost=false, t0=Date.now(), seq0=null; show(); stageEl.textContent='Отправка запроса…'; bar.style.width='3%'; detail.textContent=''; var xhr=new XMLHttpRequest(); xhr.open('POST', form.action); var file=form.querySelector('input[type=file]'); @@ -613,8 +613,9 @@ label { font-size:13px; font-weight:500; } 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){ if(startVer===null){ startVer=p.version; seq0=p.seq; } if(p.target) target=p.target; + if(p.note) note=p.note; if(lost){ // bj вернулся после перезапуска - if(!target || p.version===target || p.version!==startVer){ finish('Готово: bj перезапущен, работает версия '+p.version+'.', true); detail.textContent=''; return; } + if(!target || p.version===target || p.version!==startVer){ finish('Готово: bj перезапущен, работает версия '+p.version+'.', true); detail.textContent=note ? note.charAt(0).toUpperCase()+note.slice(1)+'.' : ''; return; } } var fresh = p.seq!==seq0 || seenActive || postDone; if(p.active){ seenActive=true;