Веб-UI: основной идентификатор карточек — download.id вместо infohash

В карточке списка и шапке /download/{id} показываем и копируем download.id
(ULID) — тот же ключ, что в логах (download_id), удобно грепать. Infohash
остаётся в блоке «Информация о торренте». Поиск по списку расширен: матчит
любой идентификатор (download.id ИЛИ infohash), плюс название/контекст.
Удалены осиротевшие поля Infohash/InfohashShort и хелпер shortenHash.

Дельта web-ui влита в спеки, change заархивирован.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-04 09:47:27 +03:00
co-authored by Claude Opus 4.8
parent 80e725eb9a
commit 9ed732e49e
12 changed files with 220 additions and 64 deletions
+25 -29
View File
@@ -12,19 +12,17 @@ import (
// --- Страница просмотра одной загрузки ---
type downloadDetailView struct {
ID string
Title string
SourceType string // тип источника (magnet/torrent/url) — блок «Информация о торренте»
SourceFull string // полный источник (magnet) — блок «Информация о торренте»
Infohash string // первый хеш (для шапки)
InfohashShort string
Infohashes []string // все хеши загрузки (блок «Информация о торренте»)
Context string
State string
Error string
Note string
CreatedAt string
UpdatedAt string
ID string
Title string
SourceType string // тип источника (magnet/torrent/url) — блок «Информация о торренте»
SourceFull string // полный источник (magnet) — блок «Информация о торренте»
Infohashes []string // все хеши загрузки (блок «Информация о торренте»)
Context string
State string
Error string
Note string
CreatedAt string
UpdatedAt string
// Распознавание (если есть план).
HasPlan bool
@@ -84,22 +82,20 @@ func (s *server) handleDownload(w http.ResponseWriter, r *http.Request) {
d := rd.Download
view := downloadDetailView{
ID: id,
Title: detailTitle(d, rd),
SourceType: string(d.SourceType),
SourceFull: d.SourceRef,
Infohash: d.PrimaryInfohash(),
InfohashShort: shortenHash(d.PrimaryInfohash()),
Infohashes: d.HashList(),
Context: d.Context,
State: string(d.State),
Error: d.ErrorMsg.String,
Note: desyncNote(d.State),
CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt,
Terminal: d.State.IsTerminal(),
Reviewable: d.State == store.StateReview || d.State == store.StateDeferred,
Undoable: d.State == store.StateDone,
ID: id,
Title: detailTitle(d, rd),
SourceType: string(d.SourceType),
SourceFull: d.SourceRef,
Infohashes: d.HashList(),
Context: d.Context,
State: string(d.State),
Error: d.ErrorMsg.String,
Note: desyncNote(d.State),
CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt,
Terminal: d.State.IsTerminal(),
Reviewable: d.State == store.StateReview || d.State == store.StateDeferred,
Undoable: d.State == store.StateDone,
Relinkable: d.State == store.StateReverted || d.State == store.StateCancelled ||
d.State == store.StateTargetMissing,
Retriable: d.State == store.StateFailed || d.State == store.StateStuck,
-12
View File
@@ -181,8 +181,6 @@ type pageLink struct {
type downloadView struct {
ID string
Title string // отображаемый заголовок карточки
Infohash string // полный (для копирования)
InfohashShort string // усечённый (для показа)
Context string
State string
Error string
@@ -524,8 +522,6 @@ func toView(d store.Download) downloadView {
return downloadView{
ID: d.ID,
Title: downloadTitle(d),
Infohash: d.PrimaryInfohash(),
InfohashShort: shortenHash(d.PrimaryInfohash()),
Context: d.Context,
State: state,
Error: d.ErrorMsg.String,
@@ -560,14 +556,6 @@ func oneLine(s string) string {
return strings.Join(strings.Fields(s), " ")
}
// shortenHash усекает infohash до вида "a1b2c3d4e5…f0" для компактного показа.
func shortenHash(h string) string {
if len(h) <= 12 {
return h
}
return h[:10] + "…" + h[len(h)-2:]
}
// desyncNote — пояснение состояния рассинхрона для UI (см. state-reconciliation).
func desyncNote(s store.State) string {
switch s {
+4 -3
View File
@@ -41,7 +41,7 @@ func statesInGroup(g StateGroup) []State {
// ListFilter — параметры страницы списка загрузок.
type ListFilter struct {
Group StateGroup // группа состояний ("" / all — без сужения)
Query string // поиск по названию/infohash/контексту (подстрока)
Query string // поиск по id/названию/infohash/контексту (подстрока)
IncludeDeleted bool // показывать состояние deleted (только под all)
Limit int // размер страницы
Offset int // смещение страницы
@@ -71,12 +71,13 @@ func listWhere(f ListFilter) (string, []any) {
// ICU в чистом modernc-драйвере нет. Достаточно для инфохэша/латиницы;
// кириллические названия ищем как введены.
like := "%" + q + "%"
conds = append(conds, "(source_ref LIKE ? COLLATE NOCASE "+
conds = append(conds, "(download.id LIKE ? COLLATE NOCASE "+
"OR source_ref LIKE ? COLLATE NOCASE "+
"OR display_name LIKE ? COLLATE NOCASE "+
"OR context LIKE ? COLLATE NOCASE "+
"OR EXISTS (SELECT 1 FROM download_infohash dh "+
"WHERE dh.download_id = download.id AND dh.infohash LIKE ? COLLATE NOCASE))")
args = append(args, like, like, like, like)
args = append(args, like, like, like, like, like)
}
if len(conds) == 0 {