fix(admin): кнопка «Проверить документацию» возвращает на /admin/news + браузерный UA для nsd.ru
Три бага в 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 сохранён в коде на случай ручного ввода
инцидентов в будущем.
This commit is contained in:
@@ -176,9 +176,9 @@ func downloadAndParseCert(ctx context.Context, rawURL string) ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "bj-server/1.0 (cacerts auto-fetch)")
|
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36")
|
||||||
client := &http.Client{Timeout: 30 * time.Second}
|
// noProxyClient определён в news.go — игнорирует HTTPS_PROXY (zetit).
|
||||||
resp, err := client.Do(req)
|
resp, err := noProxyClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("сеть: %w", err)
|
return nil, fmt.Errorf("сеть: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,18 @@ func EnsureDocSources(rc *RuntimeConfig) {
|
|||||||
// pdfHrefRe — ищет в HTML href'ы, заканчивающиеся на .pdf (case-insensitive).
|
// pdfHrefRe — ищет в HTML href'ы, заканчивающиеся на .pdf (case-insensitive).
|
||||||
var pdfHrefRe = regexp.MustCompile(`(?i)href="([^"]+\.pdf)"`)
|
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, ищет
|
// CheckDocSources обходит все DocSource из настроек, парсит HTML, ищет
|
||||||
// новые PDF и скачивает их в DOC/. На каждое нововведение эмитирует
|
// новые PDF и скачивает их в DOC/. На каждое нововведение эмитирует
|
||||||
// NewsItem типа "doc-update". Возвращает суммарную строку для лога.
|
// NewsItem типа "doc-update". Возвращает суммарную строку для лога.
|
||||||
@@ -121,8 +133,10 @@ func fetchPDFLinks(ctx context.Context, pageURL string) ([]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "bj-server/1.0 (doc-watcher)")
|
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36")
|
||||||
resp, err := http.DefaultClient.Do(req)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -174,8 +188,10 @@ func checkPDF(ctx context.Context, pdfURL string, known map[string]string) (stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "bj-server/1.0 (doc-watcher)")
|
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36")
|
||||||
resp, err := http.DefaultClient.Do(req)
|
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 {
|
if err != nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
@@ -225,8 +241,10 @@ func downloadPDFToDOC(ctx context.Context, pdfURL string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "bj-server/1.0 (doc-watcher)")
|
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36")
|
||||||
resp, err := http.DefaultClient.Do(req)
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-10
@@ -778,20 +778,25 @@ func tryHTTPHealth(u string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setupFlash шлёт 303 с flash-сообщением в query. Если запрос пришёл
|
// setupFlash шлёт 303 с flash-сообщением в query. Если запрос пришёл
|
||||||
// со страницы мастера (/admin/wizard), возвращаем туда же с сохранением
|
// с какой-то «принимающей flash» страницы (/admin/wizard, /admin/news,
|
||||||
// номера шага — пользователь не должен «выпадать» из визарда после POST.
|
// /admin/setup) — возвращаем туда же. Иначе дефолт — /admin/setup.
|
||||||
|
// Это нужно чтобы пользователь не «выпадал» из текущего контекста после
|
||||||
|
// POST-действия (нажал кнопку «Проверить обновления» в Новостях — должен
|
||||||
|
// остаться в Новостях со флешем).
|
||||||
func setupFlash(w http.ResponseWriter, r *http.Request, msg string) {
|
func setupFlash(w http.ResponseWriter, r *http.Request, msg string) {
|
||||||
target := "/admin/setup"
|
|
||||||
if ref := r.Header.Get("Referer"); ref != "" {
|
if ref := r.Header.Get("Referer"); ref != "" {
|
||||||
if u, err := url.Parse(ref); err == nil && strings.HasPrefix(u.Path, "/admin/wizard") {
|
if u, err := url.Parse(ref); err == nil {
|
||||||
q := u.Query()
|
for _, prefix := range []string{"/admin/wizard", "/admin/news", "/admin/setup"} {
|
||||||
q.Set("flash", msg)
|
if strings.HasPrefix(u.Path, prefix) {
|
||||||
target = u.Path + "?" + q.Encode()
|
q := u.Query()
|
||||||
http.Redirect(w, r, target, http.StatusSeeOther)
|
q.Set("flash", msg)
|
||||||
return
|
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 — здесь не нужно
|
// _q извлекает Request из ResponseWriter trick — здесь не нужно
|
||||||
|
|||||||
@@ -45,41 +45,6 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2>Добавить вручную</h2>
|
|
||||||
<form method="post" action="/admin/news/add" style="display:grid;gap:8px;grid-template-columns:1fr 1fr;align-items:end">
|
|
||||||
<div style="grid-column:1 / 3">
|
|
||||||
<label class="muted" style="font-size:12px">Заголовок</label>
|
|
||||||
<input type="text" name="title" required placeholder="Например: TEST3 будет недоступен 01.06—03.06" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
|
|
||||||
</div>
|
|
||||||
<div style="grid-column:1 / 3">
|
|
||||||
<label class="muted" style="font-size:12px">Тело (опционально)</label>
|
|
||||||
<textarea name="body" rows="2" placeholder="Подробности, ссылка на письмо, контакт" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;font-family:inherit"></textarea>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="muted" style="font-size:12px">Тип</label>
|
|
||||||
<select name="kind" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%">
|
|
||||||
<option value="manual">manual — ручная заметка</option>
|
|
||||||
<option value="maintenance">maintenance — окно техработ</option>
|
|
||||||
<option value="feature">feature — новая возможность</option>
|
|
||||||
<option value="system">system — внимание</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div></div>
|
|
||||||
<div>
|
|
||||||
<label class="muted" style="font-size:12px">Действует с (опц.)</label>
|
|
||||||
<input type="date" name="valid_from" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="muted" style="font-size:12px">Действует по (опц.)</label>
|
|
||||||
<input type="date" name="valid_to" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%">
|
|
||||||
</div>
|
|
||||||
<div style="grid-column:1 / 3">
|
|
||||||
<button type="submit" class="btn">Добавить</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 style="margin:24px 0 12px 0">Лента ({{len .Settings.News.Items}})</h2>
|
<h2 style="margin:24px 0 12px 0">Лента ({{len .Settings.News.Items}})</h2>
|
||||||
|
|
||||||
{{if not .Settings.News.Items}}
|
{{if not .Settings.News.Items}}
|
||||||
|
|||||||
Reference in New Issue
Block a user