From 19a2b6dda4570091775281a62c143cda4e6b1321 Mon Sep 17 00:00:00 2001 From: fontvielle Date: Thu, 14 May 2026 16:36:31 +0300 Subject: [PATCH] =?UTF-8?q?fix(admin):=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA?= =?UTF-8?q?=D0=B0=20=C2=AB=D0=9F=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8E=C2=BB=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0?= =?UTF-8?q?=D1=89=D0=B0=D0=B5=D1=82=20=D0=BD=D0=B0=20/admin/news=20+=20?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=83=D0=B7=D0=B5=D1=80=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?UA=20=D0=B4=D0=BB=D1=8F=20nsd.ru?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Три бага в Doc-watcher / Новостях, всплывшие при первом ручном прогоне: 1. setupFlash после POST в /admin/news/check-docs редиректил на /admin/setup, а не на /admin/news, и оператор «выпадал» с ленты. Теперь setupFlash смотрит Referer и возвращает на любой из /admin/wizard, /admin/news, /admin/setup — на ту страницу с которой пришёл POST. 2. http.DefaultClient в news.go и cacerts.go подхватывал HTTPS_PROXY из окружения и шёл через корпоративный zetit, который блокирует nsd.ru (CONNECT 403). Заменил на noProxyClient с явно отключённой проксификацией (Transport.Proxy = nil) — doc-watcher всегда идёт напрямую, независимо от ENV. 3. nsd.ru отдаёт 403 на запросы с UA «bj-server/1.0» (антибот). Заменил на стандартный Chrome User-Agent + браузерные Accept/Accept-Language. После этого moex-most-dlya-m2m.pdf найден и скачан, новость «Обновлена документация» опубликована. Кроме того, по запросу — убрана форма «Добавить вручную» с /admin/news. В UI остался только мониторинг: автоматическая лента событий + ручная кнопка «🔄 Проверить обновления документации сейчас». Handler /admin/news/add сохранён в коде на случай ручного ввода инцидентов в будущем. --- internal/lkgateway/cacerts.go | 6 ++-- internal/lkgateway/news.go | 30 ++++++++++++---- internal/lkgateway/setup.go | 25 +++++++------ .../lkgateway/web/templates/admin_news.html | 35 ------------------- 4 files changed, 42 insertions(+), 54 deletions(-) diff --git a/internal/lkgateway/cacerts.go b/internal/lkgateway/cacerts.go index d2be0e4..dae820a 100644 --- a/internal/lkgateway/cacerts.go +++ b/internal/lkgateway/cacerts.go @@ -176,9 +176,9 @@ func downloadAndParseCert(ctx context.Context, rawURL string) ([]byte, error) { if err != nil { return nil, err } - req.Header.Set("User-Agent", "bj-server/1.0 (cacerts auto-fetch)") - client := &http.Client{Timeout: 30 * time.Second} - resp, err := client.Do(req) + req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36") + // noProxyClient определён в news.go — игнорирует HTTPS_PROXY (zetit). + resp, err := noProxyClient.Do(req) if err != nil { return nil, fmt.Errorf("сеть: %w", err) } diff --git a/internal/lkgateway/news.go b/internal/lkgateway/news.go index 4b39b02..28d2996 100644 --- a/internal/lkgateway/news.go +++ b/internal/lkgateway/news.go @@ -50,6 +50,18 @@ func EnsureDocSources(rc *RuntimeConfig) { // pdfHrefRe — ищет в HTML href'ы, заканчивающиеся на .pdf (case-insensitive). var pdfHrefRe = regexp.MustCompile(`(?i)href="([^"]+\.pdf)"`) +// noProxyClient — HTTP-клиент, который игнорирует переменные окружения +// HTTPS_PROXY / HTTP_PROXY. Корпоративный прокси zetit блокирует +// nsd.ru — поэтому doc-watcher ходит на внешние сайты НРД напрямую. +// Transport.Proxy = nil отключает любую проксификацию (включая +// автодетект через env). +var noProxyClient = &http.Client{ + Timeout: 90 * time.Second, + Transport: &http.Transport{ + Proxy: nil, + }, +} + // CheckDocSources обходит все DocSource из настроек, парсит HTML, ищет // новые PDF и скачивает их в DOC/. На каждое нововведение эмитирует // NewsItem типа "doc-update". Возвращает суммарную строку для лога. @@ -121,8 +133,10 @@ func fetchPDFLinks(ctx context.Context, pageURL string) ([]string, error) { if err != nil { return nil, err } - req.Header.Set("User-Agent", "bj-server/1.0 (doc-watcher)") - resp, err := http.DefaultClient.Do(req) + req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36") + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + req.Header.Set("Accept-Language", "ru-RU,ru;q=0.9,en;q=0.8") + resp, err := noProxyClient.Do(req) if err != nil { return nil, err } @@ -174,8 +188,10 @@ func checkPDF(ctx context.Context, pdfURL string, known map[string]string) (stri if err != nil { return "", false } - req.Header.Set("User-Agent", "bj-server/1.0 (doc-watcher)") - resp, err := http.DefaultClient.Do(req) + req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36") + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + req.Header.Set("Accept-Language", "ru-RU,ru;q=0.9,en;q=0.8") + resp, err := noProxyClient.Do(req) if err != nil { return "", false } @@ -225,8 +241,10 @@ func downloadPDFToDOC(ctx context.Context, pdfURL string) (string, error) { if err != nil { return "", err } - req.Header.Set("User-Agent", "bj-server/1.0 (doc-watcher)") - resp, err := http.DefaultClient.Do(req) + req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36") + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + req.Header.Set("Accept-Language", "ru-RU,ru;q=0.9,en;q=0.8") + resp, err := noProxyClient.Do(req) if err != nil { return "", err } diff --git a/internal/lkgateway/setup.go b/internal/lkgateway/setup.go index 097818b..8138fcd 100644 --- a/internal/lkgateway/setup.go +++ b/internal/lkgateway/setup.go @@ -778,20 +778,25 @@ func tryHTTPHealth(u string) error { } // setupFlash шлёт 303 с flash-сообщением в query. Если запрос пришёл -// со страницы мастера (/admin/wizard), возвращаем туда же с сохранением -// номера шага — пользователь не должен «выпадать» из визарда после POST. +// с какой-то «принимающей flash» страницы (/admin/wizard, /admin/news, +// /admin/setup) — возвращаем туда же. Иначе дефолт — /admin/setup. +// Это нужно чтобы пользователь не «выпадал» из текущего контекста после +// POST-действия (нажал кнопку «Проверить обновления» в Новостях — должен +// остаться в Новостях со флешем). func setupFlash(w http.ResponseWriter, r *http.Request, msg string) { - target := "/admin/setup" if ref := r.Header.Get("Referer"); ref != "" { - if u, err := url.Parse(ref); err == nil && strings.HasPrefix(u.Path, "/admin/wizard") { - q := u.Query() - q.Set("flash", msg) - target = u.Path + "?" + q.Encode() - http.Redirect(w, r, target, http.StatusSeeOther) - return + if u, err := url.Parse(ref); err == nil { + for _, prefix := range []string{"/admin/wizard", "/admin/news", "/admin/setup"} { + if strings.HasPrefix(u.Path, prefix) { + q := u.Query() + q.Set("flash", msg) + http.Redirect(w, r, u.Path+"?"+q.Encode(), http.StatusSeeOther) + return + } + } } } - http.Redirect(w, r, target+"?flash="+url.QueryEscape(msg), http.StatusSeeOther) + http.Redirect(w, r, "/admin/setup?flash="+url.QueryEscape(msg), http.StatusSeeOther) } // _q извлекает Request из ResponseWriter trick — здесь не нужно diff --git a/internal/lkgateway/web/templates/admin_news.html b/internal/lkgateway/web/templates/admin_news.html index 0e6198f..a425096 100644 --- a/internal/lkgateway/web/templates/admin_news.html +++ b/internal/lkgateway/web/templates/admin_news.html @@ -45,41 +45,6 @@ {{end}} -
-

Добавить вручную

-
-
- - -
-
- - -
-
- - -
-
-
- - -
-
- - -
-
- -
-
-
-

Лента ({{len .Settings.News.Items}})

{{if not .Settings.News.Items}}