Живые обновления прогресса и раздел «Раздача» (live-status)

Воркер ведёт in-memory снимок телеметрии раздач (прогресс, скорость, ETA,
рейтинг, сиды/пиры, отдано) под отдельным RWMutex, обновляя его на каждом
тике поллинга сразу после построения byHash — без лишних вызовов qBittorrent
и без хранения в БД (волатильно). qbt.Torrent дополнен полями телеметрии.

Веб-UI читает снимок через узкий контракт LiveStatus: карточки активных
загрузок показывают живой прогресс-бар (htmx-поллинг фрагмента every 3s,
точечно — без сброса фильтров), на странице загрузки появилась секция
«Раздача» для сидирующих задач. Начальный кадр рендерится сразу со
значениями; при отсутствии данных UI деградирует штатно.

Капабилити live-status (OpenSpec), web-ui дополнен. Change заархивирован.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-06-30 20:38:25 +03:00
co-authored by Claude Opus 4.8
parent b646381cf9
commit ef75a0d302
22 changed files with 1094 additions and 14 deletions
+115
View File
@@ -0,0 +1,115 @@
# live-status Specification
## Purpose
TBD - created by archiving change live-status. Update Purpose after archive.
## Requirements
### Requirement: Снимок живой телеметрии
Воркер — единственный сэмплер qBittorrent — SHALL на каждом тике поллинга
обновлять in-memory снимок телеметрии всех известных раздач. Снимок MUST NOT
персиститься в БД: он волатилен и переживает только до рестарта процесса.
#### Scenario: Обновление снимка на тике
- **WHEN** воркер завершает успешный тик поллинга qBittorrent
- **THEN** снимок телеметрии содержит актуальные данные по каждой раздаче,
присутствующей в ответе qBittorrent
#### Scenario: Снимок волатилен
- **WHEN** процесс только что перезапущен и первый тик поллинга ещё не прошёл
- **THEN** снимок пуст, а UI отображает задачи без живых значений, не падая
### Requirement: Состав телеметрии
Телеметрия одной раздачи SHALL включать прогресс (доля 0..1), скорость
загрузки и ETA, а для сидирующих раздач дополнительно — рейтинг, число сидов
и пиров, объём отданного и скорость отдачи. Значения SHALL извлекаться из
ответа qBittorrent `/torrents/info` без дополнительного сетевого вызова.
#### Scenario: Телеметрия качающейся задачи
- **WHEN** торрент задачи находится в состоянии загрузки
- **THEN** в снимке для неё доступны прогресс, скорость загрузки и ETA
#### Scenario: Телеметрия сидирующей задачи
- **WHEN** торрент задачи завершён и раздаётся
- **THEN** в снимке для неё доступны рейтинг, число сидов/пиров, объём
отданного и скорость отдачи
### Requirement: Чтение телеметрии транспортом
Сервис SHALL предоставлять чтение снимка телеметрии по задаче через
изолированный контракт, не зависящий от способа доставки в браузер (поллинг
сейчас, SSE в будущем). Если для задачи нет записи в снимке (соответствующий
торрент отсутствовал в qBittorrent на последнем тике), чтение SHALL сообщать
об отсутствии данных, а UI MUST деградировать без живых значений, не падая.
#### Scenario: Данные есть
- **WHEN** транспорт читает телеметрию задачи, чей торрент был в последнем тике
- **THEN** он получает живые значения этой задачи
#### Scenario: Данных нет
- **WHEN** транспорт читает телеметрию задачи, торрента которой нет в qBittorrent
- **THEN** он получает признак отсутствия данных и рендерит страницу без живых
значений
### Requirement: Свежесть не выше тика поллинга
Живые значения, видимые в браузере, SHALL быть не свежее последнего тика
поллинга воркера; браузер MUST NOT опрашивать qBittorrent напрямую. Любой
запрос UI за телеметрией SHALL обслуживаться из in-memory снимка, не порождая
обращения к qBittorrent — поэтому частота обновления UI может быть выбрана
свободно (в т.ч. чаще тика для плавности), не нагружая qBittorrent.
#### Scenario: Браузер не обгоняет воркер
- **WHEN** браузер запрашивает фрагмент телеметрии чаще, чем длится тик
поллинга
- **THEN** он получает значения последнего тика, и обращения к qBittorrent при
этом не происходит
### Requirement: Живой прогресс активных загрузок
Веб-UI SHALL обновлять прогресс активных (downloading) загрузок на главной без
перезагрузки страницы — поллингом фрагмента через htmx. Обновление MUST NOT
сбрасывать клиентские фильтр, поиск и прокрутку. Когда задача покидает
состояние downloading, поллинг её прогресса SHALL прекращаться.
#### Scenario: Прогресс растёт без перезагрузки
- **WHEN** загрузка качается и пользователь смотрит на главную
- **THEN** её прогресс-бар, скорость и ETA обновляются на месте без
перезагрузки страницы
#### Scenario: Клиентское состояние сохраняется
- **WHEN** применён фильтр или поиск и происходит фоновое обновление прогресса
- **THEN** выбранный фильтр, текст поиска и позиция прокрутки не сбрасываются
#### Scenario: Завершение останавливает поллинг
- **WHEN** загрузка переходит из downloading в другое состояние
- **THEN** фоновый поллинг прогресса для этой карточки прекращается
### Requirement: Секция раздачи на странице загрузки
Страница `/download/{id}` SHALL показывать секцию «Раздача» с живой статистикой
(рейтинг, число сидов и пиров, объём отданного, скорость отдачи) для задач,
чей торрент сидирует. Если живых данных по задаче нет, секция SHALL
отсутствовать либо явно показывать «нет данных», не ломая остальную страницу.
#### Scenario: Сидирующая задача показывает раздачу
- **WHEN** открыта страница задачи, торрент которой раздаётся
- **THEN** в секции «Раздача» видны рейтинг, сиды/пиры, отдано и скорость отдачи
#### Scenario: Нет живых данных — секция деградирует
- **WHEN** открыта страница задачи, торрента которой нет в qBittorrent
- **THEN** секция «Раздача» отсутствует или показывает «нет данных», а
распознавание, файлы и история отображаются нормально
+11 -5
View File
@@ -9,7 +9,6 @@ self-hosted шрифтов, единая дизайн-система (`jellybit.
спойлер контекста). Превью раскладки берётся из единой логики `internal/layout`,
а не дублируется в шаблонах. Тонкий транспорт над ядром (приём — `ingest`,
команды — `worker`, чтение — `store`).
## Requirements
### Requirement: Встроенная отдача статики
@@ -91,10 +90,11 @@ self-hosted шрифтов, единая дизайн-система (`jellybit.
Веб-UI SHALL предоставлять страницы: список загрузок с единым окном
добавления и фильтром/поиском (`/`), экран ревью одной загрузки (`/review/{id}`)
и страницу просмотра одной загрузки (`/download/{id}`) с распознаванием,
файлами→раскладкой и историей. Состояние `deleted` SHALL
быть скрыто в списке по умолчанию (с переключателем «показать всё»).
(Раздел живой статистики раздачи на странице загрузки — вне scope этого change,
вводится вместе с живыми обновлениями.)
файлами→раскладкой, историей и — для сидирующих задач — секцией живой
статистики раздачи. Карточки активных (downloading) загрузок в списке SHALL
содержать индикатор прогресса. Состояние `deleted` SHALL быть скрыто в списке
по умолчанию (с переключателем «показать всё»). Механика живого обновления
прогресса и наполнение секции раздачи определяются capability `live-status`.
#### Scenario: Просмотр одной загрузки
@@ -102,6 +102,12 @@ self-hosted шрифтов, единая дизайн-система (`jellybit.
- **THEN** отрисовывается страница с её распознаванием, файлами, раскладкой и
историей
#### Scenario: Прогресс активной загрузки в списке
- **WHEN** в списке есть загрузка в состоянии `downloading`
- **THEN** её карточка содержит индикатор прогресса (прогресс-бар со скоростью
и ETA)
#### Scenario: Удалённые скрыты по умолчанию
- **WHEN** в списке есть загрузки в состоянии `deleted` и фильтр «показать