Воркер ведёт 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>
94 lines
4.6 KiB
HTML
94 lines
4.6 KiB
HTML
<!doctype html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>jellybit — загрузка #{{.ID}}</title>
|
|
<link rel="stylesheet" href="{{asset "css/jellybit.css"}}">
|
|
<script src="{{asset "vendor/htmx.min.js"}}" defer></script>
|
|
<script src="{{asset "js/app.js"}}" defer></script>
|
|
</head>
|
|
<body>
|
|
|
|
{{template "header" ""}}
|
|
|
|
<main class="main">
|
|
<a class="back-link" href="/">← ко всем загрузкам</a>
|
|
|
|
{{if .Error}}<div class="result-banner" style="background:var(--st-err-bg);border-color:color-mix(in srgb,var(--st-err) 35%,transparent)"><h2 style="color:var(--st-err)">{{.Error}}</h2></div>{{end}}
|
|
|
|
<!-- Шапка -->
|
|
<div class="review-head" style="margin-bottom:var(--sp-4)">
|
|
<div>
|
|
<div class="review-title" style="font-size:var(--fs-xl)">{{.Title}}</div>
|
|
<div class="hash-row" style="margin-top:9px">
|
|
{{template "status_badge" .State}}
|
|
{{if .Infohash}}<span class="hash">{{.InfohashShort}}</span>
|
|
<button class="copy-btn" type="button" onclick="copyHash(this,'{{.Infohash}}')">⧉ копировать</button>{{end}}
|
|
</div>
|
|
{{if .Note}}<p class="muted" style="margin:var(--sp-2) 0 0">{{.Note}}</p>{{end}}
|
|
</div>
|
|
<div class="card-acts" style="align-items:center">
|
|
{{if .Reviewable}}<a class="btn btn-primary" href="/review/{{.ID}}">Ревью →</a>{{end}}
|
|
{{if .Undoable}}<form method="post" action="/ui/downloads/{{.ID}}/undo"><button class="btn btn-danger" type="submit">↩ Откатить</button></form>{{end}}
|
|
{{if .Relinkable}}<form method="post" action="/ui/downloads/{{.ID}}/relink"><button class="btn" type="submit">Привязать заново</button></form>{{end}}
|
|
{{if .Retriable}}<form method="post" action="/ui/downloads/{{.ID}}/retry"><button class="btn" type="submit">Повторить</button></form>{{end}}
|
|
{{if not .Terminal}}<form method="post" action="/ui/downloads/{{.ID}}/cancel"><button class="btn btn-ghost" type="submit">Отменить</button></form>{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Распознано как -->
|
|
<div class="section">
|
|
<div class="section-head"><h2>Распознано как</h2></div>
|
|
{{if .HasPlan}}
|
|
<div class="meta-card">
|
|
<div class="meta-body">
|
|
<p class="h">{{.RecTitle}}</p>
|
|
{{if .OriginalTitle}}<p class="o">{{.OriginalTitle}}</p>{{end}}
|
|
<dl class="kv" style="max-width:360px">
|
|
<dt>Тип</dt><dd>{{if .IsSeries}}сериал{{else if eq .MediaType "movie"}}фильм{{else}}{{.MediaType}}{{end}}</dd>
|
|
{{if .Year}}<dt>Год</dt><dd>{{.Year}}</dd>{{end}}
|
|
<dt>База</dt><dd>{{if .Provider}}{{.Provider}} · <span class="mono">{{.ProviderID}}</span>{{else if .NoBase}}без базы{{else}}—{{end}}</dd>
|
|
{{if .Confidence}}<dt>Уверенность</dt><dd class="mono">{{.Confidence}}</dd>{{end}}
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
{{else}}
|
|
<p class="muted" style="margin:0">Ещё не распознано.</p>
|
|
{{end}}
|
|
</div>
|
|
|
|
<!-- Файлы и раскладка -->
|
|
{{if .HasPlan}}
|
|
<div class="section">
|
|
<div class="section-head"><h2>Файлы и раскладка</h2><span class="hint">файл источника → целевой хардлинк</span></div>
|
|
{{template "layout_widget" .Files}}
|
|
<p class="faint" style="font-size:var(--fs-xs);margin:var(--sp-3) 0 0">
|
|
Целевые файлы связаны хардлинками — место не дублируется, исходник остаётся на раздаче.
|
|
</p>
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- Раздача (живая статистика; секция деградирует, если торрент не сидирует) -->
|
|
{{template "seeding" .Seeding}}
|
|
|
|
<!-- История -->
|
|
<div class="section">
|
|
<div class="section-head"><h2>История</h2></div>
|
|
<ul class="timeline">
|
|
{{if .CreatedAt}}<li class="tl-item is-done"><div class="tl-t">Добавлено</div><time>{{.CreatedAt}}</time></li>{{end}}
|
|
<li class="tl-item is-now"><div class="tl-t">{{badgeLabel .State}} <span class="badge st-{{.State}}">текущее</span></div>{{if .UpdatedAt}}<time>{{.UpdatedAt}}</time>{{end}}</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Контекст -->
|
|
{{if .Context}}
|
|
<div class="section" style="margin-bottom:0">
|
|
<div class="section-head"><h2>Контекст</h2><span class="hint">сообщение при добавлении</span></div>
|
|
<p style="margin:0;color:var(--text-dim)">«{{.Context}}»</p>
|
|
</div>
|
|
{{end}}
|
|
</main>
|
|
</body>
|
|
</html>
|