267 lines
11 KiB
Markdown
267 lines
11 KiB
Markdown
# Telegram UX
|
||
|
||
Archive Control adds a separate handler/module to the existing bot. Existing
|
||
commands and conversations retain their current behavior.
|
||
|
||
## Authorization and shared state
|
||
|
||
The handler accepts private-chat users through the existing `@username`
|
||
allowlist for compatibility. A configured numeric Telegram user ID may pin an
|
||
entry; when pinned, both the ID and current allowlist rule must match. This
|
||
mitigates username reassignment while permitting a staged migration.
|
||
|
||
Jobs, placements, events, queues, and routes are shared administrative state and
|
||
store no initiating-user identity. UI session state records only what is needed
|
||
to resume the conversation/view and expires after 24 hours by default.
|
||
|
||
## Entry point and navigation
|
||
|
||
The existing “Archive/Unarchive Resources” entry opens one message:
|
||
|
||
```text
|
||
What do you want to do?
|
||
|
||
[ Archive ] [ Unarchive ]
|
||
[ Evict Cache ] [ Job Status ]
|
||
```
|
||
|
||
All subsequent pages edit this message. `Cancel` closes the active selection
|
||
flow. `Back` returns one level while retaining validated filters/selections.
|
||
Callback payloads contain opaque session/action IDs, not resource names or
|
||
paths, and are validated against persisted session revision and expiry.
|
||
|
||
## Archive and unarchive flow
|
||
|
||
### 1. Select source resource
|
||
|
||
Control queries relevant online nodes on demand. It groups identical resource
|
||
and selection replicas into one display choice while showing distinct
|
||
selections separately. The list is stable-sorted and paged:
|
||
|
||
```text
|
||
Which cached resource do you want to archive? (page 1/3)
|
||
Send a message to filter resources by name.
|
||
Active filters: `documentary`, `2025`
|
||
Send another message to narrow the results further.
|
||
|
||
1. `10.0 GB` Resource A — cache-1
|
||
2. `840 MB` Resource B — cache-2
|
||
|
||
[ 1 ] [ 2 ]
|
||
[ Prev ] [ Next ]
|
||
[ Remove filters ] [ Cancel ]
|
||
```
|
||
|
||
Size is selected-complete logical content, not total torrent size. Every
|
||
filterable page explicitly says that a new message applies a filter. New
|
||
messages add literal, case-insensitive filters and narrow the current results;
|
||
Remove filters resets the chain. The one-entry tree is filterable by path or
|
||
display name in the same way. If no item remains, the message explains whether
|
||
no source is online, no selected-complete data exists, or filters eliminated
|
||
all results.
|
||
|
||
### 2. Select source replica and target
|
||
|
||
If multiple identical-selection replicas exist, control may choose a healthy
|
||
source automatically using deterministic availability/route/free-space rules;
|
||
otherwise it lists the source replicas. One eligible target is selected
|
||
automatically; multiple targets are paged. Offline targets are not offered.
|
||
|
||
A resource already on a target is still eligible when the requested selection
|
||
has an unverified delta. The UI says that it will extend an existing placement.
|
||
|
||
### 3. Confirm or choose one entry
|
||
|
||
Before showing confirmation, control obtains fresh full trees from source and
|
||
target and calculates the transfer delta.
|
||
|
||
```text
|
||
Archive Resource A from cache-1 to archive-2?
|
||
Requested: 7 files, 10.0 GB
|
||
Already verified there: 2 files, 3.1 GB
|
||
Will transfer: 5 files, 6.9 GB
|
||
|
||
[ Confirm ]
|
||
[ Choose one entry… ] [ Cancel ]
|
||
```
|
||
|
||
Choose one entry opens a paged/filterable tree. Only source-selected-complete
|
||
files/directories are enabled. Selecting a directory expands it to descendant
|
||
file indices, returns to a freshly recomputed confirmation, and clearly states
|
||
the chosen subtree. The protocol supports multiple entries from v1, but the
|
||
initial UI exposes one.
|
||
|
||
Confirmation uses a persisted, single-use nonce bound to session revision,
|
||
operation, resource, source, target, selection, fingerprints, and placement
|
||
generation. Double taps or stale callbacks cannot create a second job.
|
||
|
||
## Evict Cache flow
|
||
|
||
The first page lists current cache placements. Selecting one triggers fresh
|
||
queries to the cache and relevant online archive nodes. Each row/confirmation
|
||
shows coverage:
|
||
|
||
- enabled when the union of verified archives covers every selected cache file;
|
||
- disabled with missing file count/size and unavailable archive explanation
|
||
otherwise.
|
||
|
||
There is no override button. Confirmation creates a durable three-step
|
||
eviction job; it never clears an archive placement or a transfer history row.
|
||
|
||
## Job Status
|
||
|
||
Status pages show live and retained jobs with operation, resource, endpoints,
|
||
state, current-step and overall percentages, bytes/s where meaningful, and
|
||
failure reason.
|
||
Routine edits are coalesced to one per five seconds by default. Step changes,
|
||
stall transitions, failures, commit, cleanup-required, and terminal states edit
|
||
immediately subject to Telegram rate-limit backoff.
|
||
|
||
State symbols:
|
||
|
||
| Symbol | Meaning |
|
||
| --- | --- |
|
||
| 🟡 | queued/not started |
|
||
| 🔵 | active, waiting, or stalled |
|
||
| 🟠 | committed but cleanup required |
|
||
| 🟢 | succeeded |
|
||
| 🔴 | failed or cancelled |
|
||
|
||
Transfer jobs display five numbered steps: Source Stage/HardLink, Syncthing,
|
||
Target Merge/Copy, QB Verification, and Staging Cleanup. Route setup and
|
||
preflight are named, unnumbered pre-steps. Eviction displays Verify Archive
|
||
Coverage, QB Remove Entry, and Safe File Unlink.
|
||
|
||
A stalled job keeps its last overall percentage:
|
||
|
||
```text
|
||
🔵 Archive: Resource A — cache-1 → archive-2
|
||
[2/5 Syncthing: 63.42%, overall 32.68%, stalled, last progress 38m ago]
|
||
Waiting indefinitely; retry occurs when progress/service connectivity returns.
|
||
```
|
||
|
||
Failure messages include stable phase and clear reason, while hiding secrets
|
||
and host-only paths.
|
||
|
||
## Clear and remove behavior
|
||
|
||
`Clear Finished` hides only green successful jobs and leaves a 30-day default
|
||
tombstone. Orange Cleanup Required is not clearable as finished.
|
||
|
||
`Remove One` pauses status auto-refresh, retains the current page’s numbered
|
||
job rows, and replaces the buttons with current-page job numbers and Cancel.
|
||
A successful green row hides immediately. Other confirmation pages identify
|
||
the resource, operation, source/target or cache node, state, overall progress,
|
||
and only then the internal job ID. A queued job confirmation deletes only its
|
||
queue record/reservation. An active, failed-with-residue, or cancelling job
|
||
requires a second confirmation and begins/continues compensation; its row
|
||
remains until safe cleanup is known. A committed job can only retry staging
|
||
cleanup. No history action deletes a cache placement; that is exclusively
|
||
Evict Cache.
|
||
|
||
Archive Control messages use Telegram HTML formatting with all dynamic resource
|
||
names, paths, node IDs, filter strings, progress details, and failure reasons
|
||
escaped before rendering.
|
||
|
||
## Restart and rate-limit behavior
|
||
|
||
Sessions, pages, filters, chosen entries, confirmation bindings, and live status
|
||
views are persisted. On bot restart, active status messages resume updates when
|
||
the stored chat/message remains editable; otherwise users can reopen Job Status
|
||
without losing job state. “message is not modified” is treated as success.
|
||
Telegram retry-after values pause/coalesce edits without affecting orchestration.
|
||
|
||
## Interactive test entry
|
||
|
||
An opt-in `Archive Control (Test)` entry reuses this same handler with an
|
||
independent callback namespace and SQLite database. Its scripted backend
|
||
provides comprehensive, empty, and slow-route scenarios without contacting
|
||
control/client daemons, qBittorrent, or Syncthing. The production entry,
|
||
callback namespace, database, jobs, placements, and other bot features are
|
||
unchanged.
|
||
|
||
The test entry exposes administrator-only `/actest` controls to reset a
|
||
scenario, move a synthetic job through displayed states, fail the next selected
|
||
backend call, complete a slow route, inspect recent trace events, or export the
|
||
complete JSONL trace. The trace contains callback and filter inputs, session
|
||
state transitions, exact message/keyboard renders, backend results/errors, and
|
||
job transitions, so a UI report can be reproduced without live storage nodes.
|
||
|
||
### `/actest` operator guide
|
||
|
||
Run `/actest` only in a private chat as an authorized bot manager. The test UI
|
||
must be enabled, and all commands affect only the isolated dummy database.
|
||
Sending `/actest` without arguments is equivalent to `/actest status`.
|
||
|
||
| Command | Effect |
|
||
| --- | --- |
|
||
| `/actest status` | Show the current scenario, route flags, synthetic job count, trace count, and test database path. |
|
||
| `/actest reset [scenario]` | Clear dummy sessions, jobs, faults, and traces, then load `comprehensive`, `empty`, or `route_slow`. The configured scenario is used when omitted. |
|
||
| `/actest scenario [scenario]` | Alias of `reset`; it does not preserve the previous dummy jobs or sessions. |
|
||
| `/actest advance <job-id> <state>` | Move one synthetic job directly to a display state and persist the transition. |
|
||
| `/actest fail-next <method> [message]` | Make exactly the next matching dummy backend call fail with the supplied message, then disarm the fault. |
|
||
| `/actest route-ready` | Complete the pending dummy route used by `route_slow`. |
|
||
| `/actest trace [limit]` | Show the most recent trace events in chronological order; the limit defaults to 10 and is bounded to 1–500. |
|
||
| `/actest export` | Send the complete append-only trace as `archive-control-telegram-trace.jsonl`. |
|
||
|
||
Valid `advance` states are:
|
||
|
||
```text
|
||
queued preparing running waiting stalled cancelling
|
||
cleanup_required succeeded failed cancelled
|
||
```
|
||
|
||
The internal job ID is shown on the final line of a Remove One confirmation.
|
||
Press Cancel after copying it if the purpose is only to drive the job with
|
||
`advance`. It is also present in `trace` backend results and the JSONL export.
|
||
|
||
Valid `fail-next` methods are:
|
||
|
||
```text
|
||
list_operation_resources
|
||
list_clients
|
||
get_resource_tree
|
||
preview_transfer
|
||
preview_eviction
|
||
create_job
|
||
list_jobs
|
||
clear_finished_jobs
|
||
hide_job
|
||
cancel_job
|
||
```
|
||
|
||
For example:
|
||
|
||
```text
|
||
/actest reset comprehensive
|
||
/actest fail-next preview_transfer simulated planning failure
|
||
```
|
||
|
||
The next archive or unarchive preview fails visibly with that reason; a later
|
||
preview proceeds normally. To exercise slow route provisioning:
|
||
|
||
```text
|
||
/actest reset route_slow
|
||
# Start an archive flow and choose a source/target.
|
||
# The preview reports that route provisioning has started.
|
||
/actest route-ready
|
||
# Reopen/retry the archive flow; the preview can now complete.
|
||
```
|
||
|
||
A useful review sequence is:
|
||
|
||
1. Run `/actest reset comprehensive`, then reopen `Archive Control (Test)`;
|
||
reset intentionally makes pre-reset inline buttons stale.
|
||
2. Walk through Archive, Unarchive, Evict Cache, filtering, selective entry,
|
||
pagination, Job Status, Remove One, and cancellation without confirming
|
||
destructive-looking choices unless that behavior is under review.
|
||
3. Copy a job ID from Remove One and use `/actest advance` to inspect waiting,
|
||
stalled, failed, cleanup-required, and terminal rendering.
|
||
4. Arm one `fail-next` point and repeat its matching UI action.
|
||
5. Run `/actest trace 50` for a quick diagnosis or `/actest export` when
|
||
reporting a reproducible UI problem.
|
||
|
||
Scenario state, synthetic jobs, and traces survive bot restarts. Only `reset`
|
||
clears them. None of these commands starts the production control daemon,
|
||
contacts storage nodes, or mutates production jobs or placements.
|