Sync from /srv/compose/unified-media-manager

This commit is contained in:
Christopher Mayor
2026-04-24 10:45:19 -07:00
commit 7dbd00e537
132 changed files with 25394 additions and 0 deletions

301
docs/UX-FLOWS.md Normal file
View File

@@ -0,0 +1,301 @@
# UMM UX Flows — Implemented & Tested
**Last verified:** 2026-04-22 (deployed build, backend healthy)
## Navigation
| Route | Page | Nav Label |
|-------|------|-----------|
| `/` | Dashboard | Dashboard |
| `/library` | Library | Library |
| `/library/:type/:id` | Media Detail | (via Library click) |
| `/discover` | Discover | Discover |
| `/calendar` | Calendar | Calendar |
| `/queue` | Download Queue | Queue |
| `/search` | Search Indexers | Search |
| `/activity` | Activity | Activity |
| `/requests` | Requests | Requests |
| `/blocklist` | Blocklist | Blocklist |
| `/settings` | Settings | Settings |
---
## Page-by-Page Flows
### Dashboard (`/`)
**API:** `GET /api/dashboard` (200 OK)
- 8 stat cards: Movies, TV Series, Music, Books, Active Downloads, Missing, Upgrades Available, Total Storage
- Auto-fetches on mount
- Skeleton loading while fetching
- Error banner with retry on failure
### Library (`/library`)
**API:** `GET /api/media?page=1&page_size=50` (200 OK)
- Search input with 300ms debounce
- Type filter: All, Movie, Series, Music, Book, Audiobook, Podcast, Photo, Other
- Status filter: All, Available, Unavailable, Downloading, Failed
- Paginated table (50 per page)
- Each row: Title (click → detail), Type, Status badge, Quality, Monitor toggle
- Monitor toggle: optimistic update, rollback on error with toast
- Empty state messages for no results vs empty library
### Media Detail (`/library/:type/:id`)
**API:** `GET /api/media/:type/:id/detail` (200 OK)
5 tabs (4 for non-series):
**Overview tab:**
- Poster image (or letter placeholder)
- Title, year, original title
- Status badge, monitored indicator
- TMDB rating, genres
- Quality profile name
- Current quality / desired quality
- Synopsis text
**Search tab:**
- Auto-searches indexers for media title
- Results via `ReleaseSearchResults` component
- Sortable by quality, size, seeders, age
**Files tab:**
- File table: name, quality, size, source, codec, subtitles
- Per-file subtitle badges (language code, SDH, Forced, source)
- "Search" button per file → subtitle search modal
- "Download" button per subtitle result
- "Extract All Subtitles" button (extracts embedded subs)
**Episodes tab (series only):**
- Episodes grouped by season
- Columns: episode #, title, status, monitored, has file, quality
**History tab:**
- Timeline with color-coded badges: Grab, Import, Download, Failed, Upgrade, Blocked, Error
- Relative timestamps (Xm ago, Xh ago, Xd ago)
**Header actions:**
- "Refresh Metadata" button → `POST /api/media/:type/:id/refresh-metadata`
- "Delete" button → confirmation modal → `DELETE /api/media/:type/:id` → redirect to Library
### Discover (`/discover`)
**API:** `GET /api/discover/trending?type=movie&page=1` (401 — requires API key auth)
**API:** `GET /api/discover/popular?type=movie&page=1` (401 — requires API key auth)
- Trending / Popular tab toggle
- Movie / Series type filter
- Poster grid with hover overlay (title, year, rating, "Add to Library" button)
- Green checkmark badge for items already in library
- "Add to Library" → `POST /api/discover/add` → toast feedback
- Paginated (prev/next, up to page 10)
**Note:** Requires API key authentication. Frontend does not currently send API keys, so this flow will 401 in production.
### Calendar (`/calendar`)
**API:** `GET /api/calendar?month=2026-04` (200 OK)
- Monthly grid (6 rows × 7 cols)
- Navigation: prev/next month, "Today" button
- Events color-coded by type: movie (indigo), series (emerald), music (amber), audiobook (rose), podcast (violet)
- Click event → navigate to media detail
- "Today" date highlighted with indigo ring
- "+N more" for days with >3 events
### Download Queue (`/queue`)
**API:** `GET /api/queue` (200 OK)
- Tabs: All, Downloading, Pending, Failed, Imported, History
- Auto-refresh every 5 seconds
- Per-item: release title, status badge, download client, quality, size
- Progress bar for downloading items
- Error message display for failed items
- Actions: Cancel (with confirmation modal), Retry, Retry All Failed, Clear Completed
- "Check for Completed Downloads" → `POST /api/imports/trigger`
- Import History tab with pagination (`GET /api/imports/history`)
### Search Indexers (`/search`)
**API:** `GET /api/releases/search?query=...` (500 — "no enabled indexers available" when none configured)
- Free-text search across all enabled indexers
- Sortable columns: Quality, Title, Size, Indexer, Seeders, Age
- "Select & Grab" button per result → opens media selector modal
- Modal: debounced library search
- Select media item → `POST /api/releases/grab` → toast
- Empty state for no query, no results, loading skeleton
**Note:** Returns 500 when no indexers are configured. This is expected behavior.
### Activity (`/activity`)
**API:** `GET /api/activity?page=1&page_size=50` (200 OK)
- Paginated event log (50 per page)
- Type filter dropdown: All, Grabs, Imports, Downloads, Failures, Upgrades, Safety Blocks, Errors
- Color-coded badges: Grab (blue), Import/Download (green), Failed/Error (red), Upgrade (purple), Blocked (orange)
- Relative timestamps
### Requests (`/requests`)
**API:** `GET /api/requests?page=1&page_size=20` (401 — requires API key auth)
- Filter tabs: All, Pending, Approved, Fulfilled, Rejected
- "+ New Request" modal:
- Title (required), Type (7 options), Year, Quality Profile dropdown, Root Folder dropdown
- Submit → `POST /api/requests`
- Per-request card: title, year, status badge, requester, time ago, quality profile, root folder
- Actions for pending: Approve (green) → `PUT /api/requests/:id/approve`, Reject (red, with confirmation) → `PUT /api/requests/:id/reject`
- Withdraw link for pending/approved → `DELETE /api/requests/:id`
**Note:** Requires API key authentication. Frontend does not currently send API keys.
### Blocklist (`/blocklist`)
**API:** `GET /api/blocklist?page=1&page_size=50` (200 OK)
- Paginated table (50 per page)
- Checkboxes for bulk selection (select all toggle)
- Per-row: release title, indexer, quality, reason, date, delete button
- Bulk "Delete Selected" → `DELETE /api/blocklist/:id` per item
- "Clear Expired" → `DELETE /api/blocklist/expired`
- "Clear All" → confirmation modal → `DELETE /api/blocklist`
### Settings (`/settings`)
8 sections in 2-column grid layout:
#### Notifications
**API:** `GET /api/notifications/channels` (200 OK)
- List channels with enable/disable dot, name, type badge (webhook/telegram), config preview
- Inline edit: name, URL/bot token/chat ID, event type checkboxes (8 types), save/cancel/test/delete
- Add channel: name, type selector, URL or bot token + chat ID, event type checkboxes
- Test button → `POST /api/notifications/channels/:id/test`
#### Indexers
**API:** `GET /api/indexers` (200 OK)
- List with enable/disable dot, name, implementation badge (torznab/newznab/cardigann), URL
- Inline edit: name, URL, API key (masked), enable toggle, save/cancel/test/delete
- Add indexer: name, implementation selector (Newznab, Torznab, Cardigann)
- Cardigann: YAML textarea, validate button → `POST /api/indexers/validate-cardigann`, settings fields from definition
- Test → `POST /api/indexers/:id/test`
#### Download Clients
**API:** `GET /api/download-clients` (200 OK)
- List with enable/disable dot, name, implementation, URL
- Inline edit: name, URL, API key, category, priority, enable toggle, save/cancel/test/delete
- Add: name, implementation (SABnzbd, qBittorrent), URL, API key
- Test → `POST /api/download-clients/:id/test`
#### Quality Profiles
**API:** `GET /api/quality-profiles` (200 OK)
- List with name, media type badges
- Inline edit: name, cutoff quality, allowed qualities (comma-sep), save/cancel/delete
- Add: name, media types (comma-sep), cutoff quality, allowed qualities
#### Root Folders
**API:** `GET /api/root-folders` (200 OK)
- List with path, media type, free space
- Inline edit: path, media type (delete + re-create pattern), save/cancel/delete
- Add: path, media type
#### Tags
**API:** `GET /api/tags` (200 OK — fixed)
- List with name, color swatch
- Inline edit: name, color, save/cancel/delete
- Add: name, color (hex)
#### Tasks
**API:** `GET /api/workers` (401 — requires API key auth, different from `/api/tasks`)
- Scheduled tasks list with name, cron expression, enabled toggle, last run, next run
- Enable/disable toggle per task
**Note:** Frontend calls `/api/tasks` but router registers `/api/workers`. May result in 404.
#### Metadata
**API:** `POST /api/media/refresh-all` (200 OK, no auth required)
- Description text about refresh scope
- "Refresh All Metadata" button → confirmation modal → POST
- Loading state while refreshing
---
## Shared Components
| Component | Used By | Purpose |
|-----------|---------|---------|
| `Toast` | All pages | Success/error feedback (top-right, auto-dismiss) |
| `ConfirmModal` | Queue, Blocklist, Media Detail, Settings, Requests | Confirmation for destructive/batch actions |
| `Pagination` | Library, Queue, Activity, Blocklist, Requests | Page navigation with total/pages display |
| `StatusBadge` | Library, Queue, Media Detail, Requests | Color-coded status labels |
| `ErrorBanner` | All pages | Error display with retry button |
| `Loading` | Dashboard, Queue, Activity, Blocklist, Settings | Skeleton/spinner loading states |
| `ReleaseSearchResults` | Media Detail, Search | Release result table with grab actions |
---
## API Test Results (2026-04-22)
### Working (200 OK)
| Endpoint | Status | Notes |
|----------|--------|-------|
| `GET /health/live` | 200 | `{"status":"alive"}` |
| `GET /api/dashboard` | 200 | Stats returned, all zeros (empty DB) |
| `GET /api/media` | 200 | Paginated, empty |
| `GET /api/queue` | 200 | Empty |
| `GET /api/blocklist` | 200 | Paginated, empty |
| `GET /api/activity` | 200 | Paginated, empty |
| `GET /api/indexers` | 200 | Empty |
| `GET /api/download-clients` | 200 | Empty |
| `GET /api/quality-profiles` | 200 | Empty |
| `GET /api/root-folders` | 200 | Empty |
| `GET /api/tags` | 200 | Empty (fixed: removed `created_at` column ref) |
| `GET /api/notifications/channels` | 200 | Empty |
| `GET /api/calendar` | 200 | Empty events |
| `GET /api/search` | 200 | Empty results |
| `GET /api/imports/history` | 200 | Empty |
| `POST /api/media/refresh-all` | 200 | Triggers refresh |
### Auth-Required (401 Unauthorized)
| Endpoint | Notes |
|----------|-------|
| `GET /api/requests` | API key auth middleware |
| `GET /api/discover/trending` | API key auth middleware |
| `GET /api/discover/popular` | API key auth middleware |
| `GET /api/workers` | API key auth middleware |
**Root cause:** Frontend `client.ts` does not send `X-API-Key` header or `api_key` query param. These flows (Requests, Discover, Tasks) will 401 in production unless auth is configured or the frontend is updated to pass API keys.
### Expected Errors (500)
| Endpoint | Error | Cause |
|----------|-------|-------|
| `GET /api/releases/search` | "no enabled indexers available" | No indexers configured yet |
---
## Known Issues
1. **Auth-protected endpoints unreachable from frontend** — Requests, Discover, and Tasks pages will show errors because the API client doesn't pass API keys. The `newAPIKeyAuth` middleware requires `X-API-Key` header or `api_key` query param.
2. **Tasks page route mismatch** — Frontend calls `/api/tasks` but router registers workers at `/api/workers`. Needs alignment.
3. **No indexers configured** — Release search (used by Search page and Media Detail Search tab) returns 500. Expected until indexers are added via Settings.
4. **Frontend CORS** — CORS is configured to allow `cfg.FrontendURL` only. Requests from other origins will be rejected.