feat(admin): блок «Новости» + doc-watcher + авто-уведомления о сертификатах УЦ
Новый раздел /admin/news — лента событий системы (окна техработ НРД, обновления документации, переустановка сертификатов УЦ). Каждая новость со временем, типом (maintenance/feature/doc-update/system/ manual), опциональным окном действительности (ValidFrom..ValidTo) и ссылкой на источник. Лента не очищается — служит журналом для аудита. На дашборде /admin/ — компактный блок «📢 Новости»: показывает максимум 3 актуальных события (активных сейчас или с окном, начинающимся в ближайшие 7 дней; в остатке — самые свежие). Окна техработ при наступлении становятся жёлтыми (border-left, ValidFrom..ValidTo). В ленте можно добавлять новости вручную (форма на /admin/news), скрывать (soft-delete через Dismissed). Дедуп по ID. Doc-watcher: горутина в bj-server, раз в сутки качает страницы НРД (дефолтные источники — moex-most, программы НРД, криптосервис), парсит HTML на ссылки .pdf, скачивает новые версии в DOC/ (со старыми переименовывая в .YYYY-MM-DD.pdf.bak для аудита), и публикует новость «Обновлена документация: <file>». Sha256-дедуп — пере-импорта неизменённого PDF не будет. Cacerts.go: FetchCACertificates теперь принимает *RuntimeConfig и при успешной переустановке сертификата эмитирует NewsItem «Обновлён сертификат УЦ: <CN>». Если сертификат истекает в ближайшие 14 дней — отдельная новость-предупреждение. Это закрывает запрос «получает в авто режиме и предупреждает об этом» из обсуждения. SeedDefaultNews публикует при старте bj-server две известные новости: - TEST3 недоступен 18.05.2026 — 22.05.2026 (НРД письмо НРД-И-2026-8452) - Робот-автотест MOEX МОСТ доступен на TEST3 с 12.05.2026 Скачаны три свежие инструкции с nsd.ru/services/novye-servisy/moex-most-dlya-m2m/: - DOC/instruktsiya-po-testirovaniyu-s-robotom.pdf (новая, 12.05.2026) - DOC/instruktsiya-dlya-osuschestvleniya-obmena-soobscheniyami-...-fizicheskim-litsom-samomu-sebe.pdf (новая, 12.05.2026) - DOC/servis-most-m2m.pdf (актуальная общая инструкция) Mastered tasks: #46, #47, #48.
This commit is contained in:
@@ -32,9 +32,11 @@ var defaultNSDCAURLs = []string{
|
||||
}
|
||||
|
||||
// FetchCACertificates скачивает все URL из настроек, парсит .cer, и при
|
||||
// успехе вызывает certmgr -inst -store mroot. Возвращает обновлённую
|
||||
// CACertsSettings (для записи в runtime-конфиг) и сводку как строку.
|
||||
func FetchCACertificates(ctx context.Context, s CACertsSettings) (CACertsSettings, string) {
|
||||
// успехе вызывает certmgr -inst -store mroot. Если передан rc — на каждое
|
||||
// фактическое изменение сертификата (новый или изменился SHA-256)
|
||||
// публикуется новость в ленту через rc.AddNews. На сертификаты,
|
||||
// истекающие в ближайшие 14 дней — отдельная новость-предупреждение.
|
||||
func FetchCACertificates(ctx context.Context, s CACertsSettings, rc *RuntimeConfig) (CACertsSettings, string) {
|
||||
if len(s.URLs) == 0 {
|
||||
return s, "Список URL пуст. Добавьте ссылки на .cer-файлы УЦ НРД в /admin/setup → «Сертификаты УЦ»."
|
||||
}
|
||||
@@ -90,12 +92,59 @@ func FetchCACertificates(ctx context.Context, s CACertsSettings) (CACertsSetting
|
||||
}
|
||||
|
||||
// Импорт через certmgr.
|
||||
isNew := true
|
||||
for _, old := range s.FetchedCerts {
|
||||
if old.URL == u && old.Error == "" {
|
||||
isNew = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := importCertToStore(ctx, der, store); err != nil {
|
||||
fc.Error = "certmgr: " + err.Error()
|
||||
fmt.Fprintf(&logBuf, "%s — certmgr упал: %s\n", u, err)
|
||||
if rc != nil {
|
||||
_ = rc.AddNews(NewsItem{
|
||||
ID: "ca-error-" + fc.SHA256[:12],
|
||||
At: now,
|
||||
Kind: "system",
|
||||
Title: "Не удалось импортировать сертификат УЦ",
|
||||
Body: "URL: " + u + "\nCN: " + fc.SubjectCN + "\nОшибка: " + err.Error(),
|
||||
URL: u,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(&logBuf, "%s — импортирован в %s (CN=%s, sha256=%s...)\n",
|
||||
u, store, fc.SubjectCN, fc.SHA256[:12])
|
||||
if rc != nil {
|
||||
kindTitle := "Обновлён сертификат УЦ"
|
||||
if isNew {
|
||||
kindTitle = "Установлен новый сертификат УЦ"
|
||||
}
|
||||
_ = rc.AddNews(NewsItem{
|
||||
ID: "ca-update-" + fc.SHA256[:12],
|
||||
At: now,
|
||||
Kind: "feature",
|
||||
Title: kindTitle + ": " + fc.SubjectCN,
|
||||
Body: fmt.Sprintf("Хранилище: %s\nИздатель: %s\nДействителен до: %s\nSHA-256: %s…\nURL источника: %s",
|
||||
store, fc.IssuerCN, fc.NotAfter.Format("02.01.2006"), fc.SHA256[:16], u),
|
||||
URL: u,
|
||||
ValidTo: fc.NotAfter,
|
||||
})
|
||||
// Предупреждение если истекает в ближайшие 14 дней.
|
||||
if !fc.NotAfter.IsZero() && time.Until(fc.NotAfter) < 14*24*time.Hour {
|
||||
_ = rc.AddNews(NewsItem{
|
||||
ID: "ca-expiring-" + fc.SHA256[:12],
|
||||
At: now,
|
||||
Kind: "system",
|
||||
Title: "⚠ Сертификат УЦ скоро истечёт: " + fc.SubjectCN,
|
||||
Body: fmt.Sprintf("Срок действия — %s (через %d дней). Получите новую версию у УЦ и обновите URL в /admin/setup → «Сертификаты УЦ».",
|
||||
fc.NotAfter.Format("02.01.2006"),
|
||||
int(time.Until(fc.NotAfter)/(24*time.Hour))),
|
||||
URL: u,
|
||||
ValidTo: fc.NotAfter,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
newFetched = append(newFetched, fc)
|
||||
}
|
||||
@@ -192,7 +241,7 @@ func StartCACertsAutoUpdater(rc *RuntimeConfig) func() {
|
||||
for {
|
||||
s := rc.Snapshot().CACerts
|
||||
if s.AutoUpdate && len(s.URLs) > 0 {
|
||||
updated, _ := FetchCACertificates(ctx, s)
|
||||
updated, _ := FetchCACertificates(ctx, s, rc)
|
||||
if err := rc.UpdateCACerts(updated); err != nil {
|
||||
log.Printf("ca-certs auto-update: save failed: %v", err)
|
||||
} else {
|
||||
@@ -245,7 +294,7 @@ func (h *setupHandlers) fetchCACertsNow(w http.ResponseWriter, r *http.Request)
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute)
|
||||
defer cancel()
|
||||
cur := h.rc.Snapshot().CACerts
|
||||
updated, summary := FetchCACertificates(ctx, cur)
|
||||
updated, summary := FetchCACertificates(ctx, cur, h.rc)
|
||||
if err := h.rc.UpdateCACerts(updated); err != nil {
|
||||
setupFlash(w, r, "Сертификаты УЦ: ошибка сохранения: "+err.Error())
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user