Приём (Ingest) стал быстрым: синхронно только парс magnet, синтез контекста из полей ссылки, атомарный дедуп и запись загрузки в новое состояние `catched` — ответ клиенту сразу. Медленный вывод имени (LLM) и добавление в qBittorrent вынесены в асинхронный шаг машины состояний, который двигает worker. - store: состояние `catched` (нетерминальное, активная группа); атомарный переход PromoteCatched (catched → downloading + display_name) с гардом state='catched' (ре-валидация после сетевых вызовов вне блокировки) - ingest: убраны namer/qbt из пути приёма; пишем `catched`, отвечаем сразу - worker.processCatched: вне w.mu выводит имя и qbt.Add, под w.mu — короткий переход; сбой add оставляет catched (ретрай тиком); предохранитель catch_timeout → failed(qbit_add)+notify; catched исключён из проверок пропажи - config: worker.catch_timeout (дефолт 10m) - веб-UI: бейдж catched, активная группа, самозавершающийся htmx-поллинг карточки/страницы до перехода в downloading; Telegram-текст без сырого catched - OpenSpec: дельты ingest/download-tracking/web-ui влиты в спеки, change заархивирован Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
37 lines
2.6 KiB
HTML
37 lines
2.6 KiB
HTML
{{define "card"}}
|
|
<article class="card" id="card-{{.ID}}"{{if .SelfPoll}} hx-get="/fragments/downloads/{{.ID}}/card" hx-trigger="every 3s" hx-swap="outerHTML"{{end}}>
|
|
<div class="card-top">
|
|
<div>
|
|
<div class="card-title"><a href="/download/{{.ID}}" style="color:inherit">{{.Title}}</a></div>
|
|
<div class="hash-row">
|
|
<span class="hash-label">ID:</span>
|
|
<span class="hash">{{.ID}}</span>
|
|
<button class="copy-btn" type="button" onclick="copyHash(this,'{{.ID}}')">⧉ копировать</button>
|
|
</div>
|
|
</div>
|
|
{{template "status_badge" .State}}
|
|
</div>
|
|
|
|
<div class="card-life">
|
|
<span>{{if .Added}}{{.Added}} <span class="ago">({{.AddedAgo}})</span>{{else}}—{{end}}</span>
|
|
<span class="sep">·</span>
|
|
<span>{{.Size}}</span>
|
|
<span class="sep">·</span>
|
|
<span title="рейтинг раздачи (отдано/скачано)">↑ {{.Ratio}}</span>
|
|
</div>
|
|
|
|
{{if .IsDownloading}}{{template "progress" .Progress}}{{end}}
|
|
|
|
<div class="card-foot">
|
|
{{if .ActionError}}<span class="card-meta" style="color:var(--st-err)">{{.ActionError}}</span>{{else}}<span class="card-meta">{{if .Note}}{{.Note}}{{else if .Error}}{{.Error}}{{end}}</span>{{end}}
|
|
<div class="card-acts">
|
|
{{if .Reviewable}}<a class="btn btn-primary btn-sm" href="/review/{{.ID}}">Ревью →</a>{{end}}
|
|
{{if .Undoable}}<form method="post" action="/ui/downloads/{{.ID}}/undo" hx-post="/ui/downloads/{{.ID}}/undo" hx-target="#card-{{.ID}}" hx-swap="outerHTML"><input type="hidden" name="surface" value="list"><button class="btn btn-sm btn-ghost" type="submit">Откатить</button></form>{{end}}
|
|
{{if .Relinkable}}<form method="post" action="/ui/downloads/{{.ID}}/relink" hx-post="/ui/downloads/{{.ID}}/relink" hx-target="#card-{{.ID}}" hx-swap="outerHTML"><input type="hidden" name="surface" value="list"><button class="btn btn-sm" type="submit">Привязать заново</button></form>{{end}}
|
|
{{if .Retriable}}<form method="post" action="/ui/downloads/{{.ID}}/retry" hx-post="/ui/downloads/{{.ID}}/retry" hx-target="#card-{{.ID}}" hx-swap="outerHTML"><input type="hidden" name="surface" value="list"><button class="btn btn-sm" type="submit">Повторить</button></form>{{end}}
|
|
{{if not .Terminal}}<form method="post" action="/ui/downloads/{{.ID}}/cancel" hx-post="/ui/downloads/{{.ID}}/cancel" hx-target="#card-{{.ID}}" hx-swap="outerHTML"><input type="hidden" name="surface" value="list"><button class="btn btn-sm btn-danger" type="submit">{{if .Reviewable}}Отклонить{{else}}Отменить{{end}}</button></form>{{end}}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
{{end}}
|