- выбор → поимённое подтверждение → отчёт: пачка до 20 загрузок, гарды входа на обеих границах, потолок времени и остановка после трёх подряд отказов внешнего сервиса - допуск полного удаления сведён в единую точку store.State.CanDelete() — worker, страница загрузки и Telegram больше не держат своих перечней
60 lines
4.8 KiB
Markdown
60 lines
4.8 KiB
Markdown
## Why
|
||
|
||
Удалить раздачу вместе с файлами сегодня можно только со страницы одной
|
||
загрузки, из её danger-секции. Чтобы освободить место после десяти закрытых
|
||
раздач, человек десять раз проходит путь «список → карточка → подтверждение».
|
||
Работа механическая, а цена ошибки высокая: на каждом проходе он подтверждает
|
||
необратимое действие заново и легко теряет, какие раздачи уже снёс.
|
||
|
||
## What Changes
|
||
|
||
- Появляется отдельная страница веб-UI «Удаление», доступная из шапки. На ней
|
||
перечислены только те загрузки, для которых удаление с файлами разрешено
|
||
поштучно (`done`, `orphaned`, `target_missing`), у каждой строки — чекбокс.
|
||
- Одна кнопка «Удалить выбранные» ведёт на **страницу подтверждения**, где
|
||
выбранные раздачи названы поимённо. Удаление уходит только оттуда: POST без
|
||
признака подтверждения отклоняется, ни одного удаления не делает.
|
||
- Результат показывается поимённо: что удалено и что отказало, с причиной
|
||
каждого отказа. Отказ на одной загрузке не отменяет остальных.
|
||
- Групповой путь прав не расширяет: каждая выбранная загрузка проходит ту же
|
||
проверку состояния, что и поштучное удаление, и отклоняется тем же конфликтом.
|
||
- Условие «удаление разрешено» переезжает в единую точку домена
|
||
(`store.State`): сейчас оно записано дважды — в проверке воркера и в сборке
|
||
представления страницы загрузки, — а групповая страница была бы третьим
|
||
местом.
|
||
- Страница намеренно **не самообновляется**: живая перерисовка карточек стёрла
|
||
бы выбор человека. Это и есть причина, по которой режим выбора не вешается на
|
||
основной список.
|
||
|
||
Не входит: групповое закрытие без файлов (`Dismiss`), автоматическая чистка по
|
||
сроку хранения, изменение самой операции удаления одной загрузки.
|
||
|
||
## Capabilities
|
||
|
||
### New Capabilities
|
||
|
||
Новых нет: групповое удаление — это новая поверхность существующего поведения,
|
||
а не новое поведение системы.
|
||
|
||
### Modified Capabilities
|
||
|
||
- `web-ui`: добавляется страница группового удаления с обязательным поимённым
|
||
подтверждением и поимённым отчётом об исходе; ссылка на неё в шапке.
|
||
- `state-reconciliation`: требование «Полное удаление загрузки пользователем»
|
||
уточняется — подтверждение может быть одно на пачку, но остаётся обязательным
|
||
и поимённым, а условия допустимости у каждой загрузки прежние и проверяются
|
||
для каждой отдельно.
|
||
|
||
## Impact
|
||
|
||
- `internal/httpapi`: страница выбора, страница подтверждения, обработчик
|
||
группового удаления и страница результата; ссылка в шапке.
|
||
- `web/templates`: новый шаблон страницы и её партиалы,
|
||
`partials/header.html`.
|
||
- `internal/store`: единая точка «в этом состоянии удаление разрешено».
|
||
- `internal/worker`: `Reviewer.Delete` зовётся по каждой выбранной загрузке;
|
||
сама операция не меняется.
|
||
- qBittorrent `torrents/delete` с `deleteFiles=true` — внешний сервис,
|
||
необратимая операция; число вызовов за один запрос человека растёт с одного
|
||
до числа выбранных.
|