Sync from /srv/compose/unified-media-manager
This commit is contained in:
301
docs/UX-FLOWS.md
Normal file
301
docs/UX-FLOWS.md
Normal 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.
|
||||
Reference in New Issue
Block a user