Живые обновления прогресса и раздел «Раздача» (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
+2
View File
@@ -216,6 +216,8 @@ a:hover{text-decoration:underline}
/* прогресс */
.progress{height:5px;background:var(--surface-2);border-radius:3px;overflow:hidden;margin-top:10px}
.progress i{display:block;height:100%;background:var(--st-prog);border-radius:3px;transition:width .3s}
.dl-live:empty{display:none}
.dl-stats{margin-top:6px;font-size:var(--fs-xs);color:var(--text-dim);font-variant-numeric:tabular-nums}
/* ---------- 7. Спойлер ---------- */
details.spoiler{margin-top:var(--sp-3)}
+3
View File
@@ -69,6 +69,9 @@
</div>
{{end}}
<!-- Раздача (живая статистика; секция деградирует, если торрент не сидирует) -->
{{template "seeding" .Seeding}}
<!-- История -->
<div class="section">
<div class="section-head"><h2>История</h2></div>
+2
View File
@@ -65,6 +65,8 @@
{{template "status_badge" .State}}
</div>
{{if .IsDownloading}}{{template "progress" .Progress}}{{end}}
{{if .Context}}
<details class="spoiler">
<summary>контекст</summary>
+4
View File
@@ -0,0 +1,4 @@
{{define "progress"}}<div class="dl-live" id="dl-live-{{.ID}}"{{if .Active}} hx-get="/fragments/downloads/{{.ID}}/progress" hx-trigger="every 3s" hx-swap="outerHTML"{{end}}>{{if .Active}}
<div class="progress"><i style="width:{{.Percent}}%"></i></div>
{{if .Has}}<div class="dl-stats">{{.Percent}}% · ↓ {{.DlSpeed}}{{if .ETA}} · осталось {{.ETA}}{{end}}</div>{{end}}
{{end}}</div>{{end}}
+10
View File
@@ -0,0 +1,10 @@
{{define "seeding"}}{{if .Has}}<div class="section" id="seeding-{{.ID}}" hx-get="/fragments/downloads/{{.ID}}/seeding" hx-trigger="every 3s" hx-swap="outerHTML">
<div class="section-head"><h2>Раздача</h2><span class="hint">qBittorrent · источник продолжает раздаваться</span></div>
<div class="stat-row">
<div class="stat"><div class="v good">{{.Percent}}%</div><div class="k">скачано</div></div>
<div class="stat"><div class="v mono">{{.Ratio}}</div><div class="k">рейтинг</div></div>
<div class="stat"><div class="v mono">{{.Uploaded}}</div><div class="k">отдано</div></div>
<div class="stat"><div class="v mono">{{.Seeds}} / {{.Peers}}</div><div class="k">сиды / пиры</div></div>
<div class="stat"><div class="v mono">↑ {{.UpSpeed}}</div><div class="k">сейчас</div></div>
</div>
</div>{{else}}<div id="seeding-{{.ID}}"></div>{{end}}{{end}}