feat(panel): adaptive monitoring poll intervals (v1.x closure phase 1)
test / test (push) Has been cancelled

Replace fixed 5 s hx-trigger polling with data-poll markers and panel.js
scheduling: 5 s while active, 30 s when idle, none when tab is hidden.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mixeme
2026-08-08 11:13:02 +03:00
parent 998b05821b
commit c839f68c57
12 changed files with 117 additions and 31 deletions
+5 -2
View File
@@ -153,8 +153,11 @@ unless noted.
| `/backup` | Full backup download, domain import |
| `/account` | Admin username/password |
HTMX polling refreshes monitoring fragments; polling does not extend session
idle timeout (only non-`HX-Request` GET and mutating requests count as activity).
HTMX polling refreshes monitoring fragments (5 s while the operator is active on
the page, 30 s when the tab is visible but idle, none when hidden — scheduled in
`panel.js` via `data-poll`, not `hx-trigger="every …"`); polling does not extend
session idle timeout (only non-`HX-Request` GET and mutating requests count as
activity).
### Sessions
+2 -1
View File
@@ -54,7 +54,8 @@
- **Фаза 2 плана ревизии выполнена (2026-08-06)** (GUI polish, P2): опрос мониторинговых страниц не уходит на сервер, пока вкладка скрыта — фильтр повешен на `htmx:beforeRequest` в `panel.js`, а не на встроенный в htmx фильтр триггера (тот вычисляется через `new Function`, что CSP панели `default-src 'self'` без `unsafe-eval` молча ломает); тёмная тема переписана с каскада `!important` на переопределение CSS-переменных в одном блоке `prefers-color-scheme: dark`; дублирующее правило `main { max-width }` сведено к одному базовому плюс задокументированные постраничные оверрайды. Только CSS/JS, поведения сервера не касается; вживую не проверялось (нет Docker локально) — кандидат на следующий прогон на стенде.
- **Фаза 3 плана ревизии выполнена (2026-08-06)** (operational improvements, P2P3): (1) log-tailer сохраняет позицию чтения — таблица `logtail_state` (миграция `0003`, `internal/store/logtail.go`) хранит offset + отпечаток первых 512 байт лога, `internal/logtail/offset.go` решает откуда стартовать: отпечаток совпал → продолжаем с offset (дочитывается хвост, написанный пока панель лежала); не совпал (лог сменился/пересоздан) → читаем файл с начала (повторный разбор безвреден, `UpdateStatus` идемпотентен); записи нет вовсе (первый запуск) → с конца, как раньше. Запись offset — не чаще раза в 5 с, плюс форс при ротации и на выключении; сохраняется позиция *потреблённых* байт (минус недочитанная частичная строка). (2) L2-лимит перестал промахиваться при параллельных сессиях: между проверкой на MAIL FROM и вставкой строки на end-of-message сообщение не видно в БД, поэтому N одновременных сессий пропускали друг друга — теперь к счёту из БД добавляются «в полёте» (`internal/milter/inflight.go`, общий на процесс реестр резерваций); резервация освобождается после записи в send-log, на ABORT и по TTL 10 минут (у go-milter нет колбэка на закрытие соединения, а вечная резервация — это fail-closed-дрейф, которого у лимитера быть не должно). Транзакция «count+insert», как предлагал review, невозможна буквально: эти два шага разнесены по разным стадиям SMTP-транзакции. Тесты: restart/rotation-resume для tailer'а, четыре сценария резерваций для лимита. `gofmt`/`go vet` чистые; `go test ./...` — падения только известные Windows-специфичные (`internal/domain`, `TestFollowTailsAndRotates`). Не проверено на стенде (нет Docker локально) — кандидат на следующий прогон на стенде.
- **Добор по плану ревизии выполнен (2026-08-06):** (1) проект переехал на единственную площадку — GitHub (Codeberg уходит): вместе с URL, лицензионными шапками SVG/HTML и docs переехал путь Go-модуля на `github.com/mixeme/selfpost` (`go.mod`, `test/e2e/go.mod`, все импорты, `MODULE` в Makefile, `-ldflags` в Dockerfile и development.md) — оставлять импорты на исчезающем хосте нельзя, `go get`/`go install` сломались бы; (2) ссылки на архивную спецификацию убраны из кода целиком — не только «spec 7.x», как просило ревью, но и «spec 4/5/6/8/9», страдавшие тем же, каждая заменена на живой документ с секцией там, где документ большой; (3) [architecture.md](architecture.md) § Code layers — диаграмма слоёв (A2); (4) `TestParseDelivery` расширен экзотикой mail.log — и **вскрыл реальный баг**: шаблон брал `status=` жадно, то есть последнее вхождение в строке, а Postfix дописывает ответ удалённого сервера дословно, поэтому отказ с `status=sent` в тексте ответа попадал в журнал как доставленный (исправлено на ленивый разбор); (5) `CONTRIBUTING.md` перенесён в 2.x, бамп тега образа и git-тег оставлены в [roadmap.md](roadmap.md) § v1.x. `gofmt`/`go vet` чистые в обоих модулях, `go test ./...` — падения только известные Windows-специфичные (`internal/domain`, `TestFollowTailsAndRotates`). На стенде не проверялось (нет Docker локально).
- **Дальше:** релизный гейт (Фаза 0) закрыт по существу — e2e C.4 и ревизия § D пройдены; остаются только шаги, которые делаются в момент резки версии (бамп тега образа в compose, git tag) по явной команде пользователя. Все polish-фазы плана ревизии (1, 1.5, 2, 3) закрыты.
- **v1.x-closure Фаза 1 выполнена (2026-08-08)** (адаптивный опрос мониторинга): четыре HTMX-фрагмента (`status_body`, `mail_queue_body`, `system_log_body`, `deliveries_rows`) несут `data-poll` и `hx-trigger="load"` только для первого запроса; `panel.js` планирует следующий опрос после `htmx:afterSwap` / `htmx:responseError` — 5 s при активности оператора на странице, 30 s при видимой, но простаивающей вкладке, 0 при скрытой (`beforeRequest` + сброс таймеров на `visibilitychange`). Без `hx-trigger="every … [expr]"` (CSP / `unsafe-eval`). Docs: `architecture.md`, `roadmap.md`, CHANGELOG. На стенде не проверялось.
- **Дальше:** v1.x-closure [v1.x-closure-plan.md](v1.x-closure-plan.md) — **Фаза 1 закрыта** (адаптивный опрос мониторинга: 5 s / 30 s / 0 при скрытой вкладке, `data-poll` в `panel.js`). Следующий шаг — **Фаза 2** (mail.log в `/data` + postqueue reconcile). Релизный гейт по коду закрыт; бамп тега образа и git tag — по явной команде (Фазы 4–5).
- **Принятые риски** — [security.md](security.md). **Опционально v1.x / 2.x** — [roadmap.md](roadmap.md) (хвост документации, send-log gaps, Фаза O1+, роль администратора домена).
- **Прод:** инстанс с реальным Let's Encrypt сертификатом и живым deliverability (DKIM/SPF pass). Контейнер там всё ещё на образе v1.0 — Фаза 14 в него не выкатывалась. При апгрейде: админа один раз разлогинит (сменилось имя cookie), а от reverse-proxy требуется передача исходного `Host` (Apache-фрагмент из `deploy/` это делает).
+3 -2
View File
@@ -71,14 +71,15 @@ git-тег `vX.Y.Z`; `implementation-plan.md` в `docs/archive/`, ссылок
Стоит одного абзаца со списком всех файлов `docs/` и одной строкой на каждый.
Мелочь, но именно она делает набор документов набором, а не россыпью.
**Опрос мониторинга у открытой, но незанятой вкладки.** Скрытая вкладка уже не
**Опрос мониторинга у открытой, но незанятой вкладки.** ~~Скрытая вкладка уже не
опрашивает сервер (фильтр на `htmx:beforeRequest` в
[panel.js](../internal/web/static/panel.js)). Остаток: вкладка на переднем
плане, с которой не работают, всё равно ходит раз в 5 с. Кандидат — адаптивный
интервал (5 с при активности, 30 с при простое) по `htmx:afterRequest` без
изменения `hx-trigger`. Ценность низкая: нагрузка — один SQL-запрос и рендер
фрагмента, так что это скорее гигиена, чем экономия. Допустимый исход —
осознанно не делать.
осознанно не делать.~~ **Закрыто (v1.x-closure Фаза 1):** адаптивный интервал
5 с / 30 с / 0 (скрытая вкладка) в `panel.js` через `data-poll`.
**Send-log vs `mail.log` (частично закрыто).** Persist позиции чтения сделан
(таблица `logtail_state`, миграция `0003`): после
+7 -7
View File
@@ -35,7 +35,7 @@
## Общий чек-лист прогресса
- [ ] **Фаза 1** — адаптивный опрос мониторинга
- [x] **Фаза 1** — адаптивный опрос мониторинга
- [ ] **Фаза 2** — mail.log в `/data` + postqueue reconcile
- [ ] **Фаза 3** — docs: development.md, README, удаление планов и `docs/archive/`
- [ ] **Фаза 4** — релизный коммит `1.0.0` (по явной команде)
@@ -60,12 +60,12 @@
### Чек-лист
- [ ] `status_body.html`, `mail_queue_body.html`, `system_log_body.html`, `deliveries_rows.html``hx-trigger="load"` + маркер polling
- [ ] `panel.js``lastActivity`, schedule после `htmx:afterRequest` (5s / 30s)
- [ ] `htmx:afterSwap` — реинициализация polling-элементов
- [ ] `beforeRequest` для hidden tab — оставлен
- [ ] `architecture.md` — одна строка про polling
- [ ] CHANGELOG `[Unreleased]`
- [x] `status_body.html`, `mail_queue_body.html`, `system_log_body.html`, `deliveries_rows.html``hx-trigger="load"` + маркер polling
- [x] `panel.js``lastActivity`, schedule после `htmx:afterRequest` (5s / 30s)
- [x] `htmx:afterSwap` — реинициализация polling-элементов
- [x] `beforeRequest` для hidden tab — оставлен
- [x] `architecture.md` — одна строка про polling
- [x] CHANGELOG `[Unreleased]`
- [ ] Стенд (опционально): idle → ~30s; активность → ~5s; hidden → 0 запросов
---