mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
411b5f88a4
Introduce a per-indexer ratioLimit alongside seedingTimeMinutes to control torrent cleanup. Updates include: documentation (scheduler and settings pages), types and API (saved indexer config now includes ratioLimit), setup and management UI (new TorrentSeedingFields component, modal wiring, validation and handlers), and processor logic (cleanup-seeded-torrents now requires AND-semantics between time and ratio; 0 disables a criterion, both 0 = never cleaned, undefined client ratio with ratioLimit>0 = not met). Tests were added/updated to cover ratio-only, time+ratio, missing-ratio, and UI interactions. Default behavior: ratioLimit defaults to 0 (no ratio requirement).
448 lines
20 KiB
Markdown
448 lines
20 KiB
Markdown
# Settings Pages
|
|
|
|
**Status:** ✅ Implemented | ♻️ Refactored (Jan 2026)
|
|
|
|
Single tabbed interface for admins to view/modify system configuration post-setup with mandatory validation before saving.
|
|
|
|
## Architecture (Refactored Jan 2026)
|
|
|
|
**Original:** Monolithic 2,971-line component
|
|
**Current:** Modular architecture with 89% code reduction (2,971 → 325 lines)
|
|
|
|
**Structure:**
|
|
```
|
|
src/app/admin/settings/
|
|
├── page.tsx # Shell component (325 lines)
|
|
├── lib/
|
|
│ ├── types.ts # Shared TypeScript interfaces
|
|
│ └── helpers.ts # Business logic (206 lines)
|
|
├── hooks/
|
|
│ └── useSettings.ts # Global settings hook
|
|
└── tabs/ # Feature modules
|
|
├── LibraryTab/ # Plex/Audiobookshelf config
|
|
│ ├── LibraryTab.tsx
|
|
│ ├── useLibrarySettings.ts
|
|
│ ├── PlexSection.tsx
|
|
│ ├── AudiobookshelfSection.tsx
|
|
│ └── index.ts
|
|
├── AuthTab/ # Authentication (OIDC + Manual)
|
|
│ ├── AuthTab.tsx
|
|
│ ├── useAuthSettings.ts
|
|
│ ├── OIDCSection.tsx
|
|
│ ├── RegistrationSection.tsx
|
|
│ ├── PendingUsersTable.tsx
|
|
│ └── index.ts
|
|
├── IndexersTab/ # Prowlarr/indexers
|
|
│ ├── IndexersTab.tsx
|
|
│ ├── useIndexersSettings.ts
|
|
│ └── index.ts
|
|
├── DownloadTab/ # qBittorrent/Transmission/SABnzbd
|
|
│ ├── DownloadTab.tsx
|
|
│ ├── useDownloadSettings.ts
|
|
│ └── index.ts
|
|
├── PathsTab/ # Directory paths
|
|
│ ├── PathsTab.tsx
|
|
│ ├── usePathsSettings.ts
|
|
│ └── index.ts
|
|
├── EbookTab/ # E-book sidecar
|
|
│ ├── EbookTab.tsx
|
|
│ ├── useEbookSettings.ts
|
|
│ └── index.ts
|
|
└── BookDateTab/ # AI recommendations
|
|
├── BookDateTab.tsx
|
|
├── useBookDateSettings.ts
|
|
└── index.ts
|
|
```
|
|
|
|
**Benefits:**
|
|
- Single Responsibility: Each tab manages its own state/logic
|
|
- Testability: Individual tabs can be unit tested
|
|
- Maintainability: Changes to one feature don't affect others
|
|
- Performance: Lazy loading possible (future optimization)
|
|
- Reusability: Custom hooks can be used elsewhere
|
|
- Code Quality: Follows React best practices
|
|
|
|
## Sections
|
|
|
|
1. **Plex** - URL, token (masked), library ID, Audible region, filesystem scan trigger toggle
|
|
2. **Audiobookshelf** - URL, API token (masked), library ID, Audible region, filesystem scan trigger toggle
|
|
3. **Prowlarr** - URL, API key (masked), indexer selection with priority, seeding time, RSS monitoring toggle, **audiobook/ebook categories per indexer**
|
|
4. **Download Client** - Type (qBittorrent, Transmission, SABnzbd), URL, credentials (masked), custom download path (per-client relative sub-path with live preview)
|
|
5. **Paths** - Download + media directories, audiobook organization template, metadata tagging toggle, chapter merging toggle, Plex format coercion toggle
|
|
6. **E-book Sidecar** - Multi-source ebook downloads (Anna's Archive + Indexer Search), preferred format
|
|
7. **BookDate** - AI provider, API key (encrypted), model selection, library scope, custom prompt, swipe history
|
|
8. **Notifications** - Multiple backends (Discord, Pushover), event subscriptions, test functionality
|
|
|
|
## E-book Sidecar
|
|
|
|
**Purpose:** Configure ebook download sources and preferences to accompany audiobook downloads.
|
|
|
|
**Tab Structure (3 sections):**
|
|
|
|
1. **Anna's Archive Section**
|
|
- Enable toggle for Anna's Archive downloads
|
|
- Base URL (default: `https://annas-archive.gl`)
|
|
- FlareSolverr URL (optional, for Cloudflare bypass)
|
|
|
|
2. **Indexer Search Section**
|
|
- Enable toggle for indexer-based ebook search via Prowlarr
|
|
- Hint directing users to Indexers tab for category configuration
|
|
|
|
3. **General Settings Section** (visible when any source enabled)
|
|
- Preferred format: EPUB (recommended), PDF, MOBI, AZW3, Any
|
|
- Auto-grab toggle: Automatically create ebook requests after audiobook downloads
|
|
- Kindle fix toggle: Apply compatibility fixes to EPUB files (only visible when EPUB format selected)
|
|
|
|
**Configuration Keys:**
|
|
| Key | Default | Description |
|
|
|-----|---------|-------------|
|
|
| `ebook_annas_archive_enabled` | `false` | Enable Anna's Archive |
|
|
| `ebook_indexer_search_enabled` | `false` | Enable Indexer Search via Prowlarr |
|
|
| `ebook_sidecar_preferred_format` | `epub` | Preferred format |
|
|
| `ebook_auto_grab_enabled` | `true` | Auto-create ebook requests after audiobook downloads |
|
|
| `ebook_kindle_fix_enabled` | `false` | Apply Kindle compatibility fixes to EPUB files |
|
|
| `ebook_sidecar_base_url` | `https://annas-archive.gl` | Anna's Archive mirror |
|
|
| `ebook_sidecar_flaresolverr_url` | `` | FlareSolverr URL |
|
|
|
|
**Behavior:**
|
|
- If Anna's Archive enabled → Searches Anna's Archive first
|
|
- If Indexer Search enabled → Falls back to indexer search if Anna's Archive fails/disabled
|
|
- If both disabled → Ebook downloads completely off
|
|
- If auto-grab disabled → Manual "Fetch Ebook" button only (admin buttons still work)
|
|
- If Kindle fix enabled (and EPUB format) → Applies compatibility fixes during organization
|
|
|
|
## Indexer Categories (Tabbed)
|
|
|
|
**Purpose:** Configure separate category sets for audiobook and ebook searches per indexer.
|
|
|
|
**UI:** Edit Indexer modal has Categories section with two tabs:
|
|
- **AudioBook tab** - Categories for audiobook searches (default: `[3030]`)
|
|
- **EBook tab** - Categories for ebook searches (default: `[7020]`)
|
|
|
|
**Storage:** `prowlarr_indexers` JSON config stores:
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"name": "MyIndexer",
|
|
"audiobookCategories": [3030],
|
|
"ebookCategories": [7020],
|
|
...
|
|
}
|
|
```
|
|
|
|
## Auto-Search Behavior (Indexers tab)
|
|
|
|
**Purpose:** Control how ReadMeABook performs automatic indexer searches. Lives on the Indexers tab between the Prowlarr connection block and the IndexerManagement list.
|
|
|
|
**Toggle:** Skip unreleased books in automatic searches
|
|
- When ON: auto-search skips books whose release date is in the future. Those requests automatically start searching once the book is released. Manual searches are unaffected.
|
|
- When OFF: auto-search proceeds regardless of release date.
|
|
|
|
**Configuration Key:**
|
|
| Key | Default | Category | Description |
|
|
|-----|---------|----------|-------------|
|
|
| `indexer.skip_unreleased` | `true` (ON) | `indexer` | Skip auto-searches for books with future release dates |
|
|
|
|
**Read contract (consumed by background workers):**
|
|
- `value !== 'false'` → ON (skip enabled). Missing key OR any non-`'false'` value → ON.
|
|
- Only the exact string `'false'` disables the toggle. Workers MUST match this.
|
|
|
|
**API:** Persisted via `PUT /api/admin/settings/indexer-options`. Saved alongside Prowlarr connection + indexer config when the Indexers tab Save button is clicked.
|
|
|
|
## Audible Region
|
|
|
|
**Purpose:** Configure which Audible region to use for metadata and search to ensure accurate ASIN matching with your metadata engine.
|
|
|
|
**Configuration:**
|
|
- Key: `audible.region` (string, default: 'us')
|
|
- Supported regions: US, Canada, UK, Australia, India
|
|
- UI: Dropdown selector in Library tab (both Plex and Audiobookshelf settings)
|
|
- No validation required (immediate save)
|
|
|
|
**Why It Matters:**
|
|
- Each Audible region uses different ASINs for the same audiobook
|
|
- Users must match their RMAB region to their Plex/Audiobookshelf metadata engine region
|
|
- Mismatched regions cause poor search results and failed metadata matching
|
|
|
|
**Help Text:**
|
|
"Select the Audible region that matches your metadata engine (Audnexus/Audible Agent) configuration in [Plex/Audiobookshelf]. This ensures accurate book matching and metadata."
|
|
|
|
**Implementation:**
|
|
- Affects all Audible API calls (base URL changes per region)
|
|
- Affects all Audnexus API calls (region parameter added)
|
|
- Changes apply immediately on next API call (no restart required)
|
|
- **Automatic refresh**: Changing region automatically triggers `audible_refresh` job to fetch popular/new releases for the new region
|
|
- **Cache management**: ConfigService cache and AudibleService initialization are cleared when region changes
|
|
- **Smart re-initialization**: Service automatically detects region changes and re-initializes before each request
|
|
- See: `documentation/integrations/audible.md` for technical details
|
|
|
|
## Audiobook Organization Template
|
|
|
|
**Purpose:** Customize how audiobooks are organized within the media directory using variable-based templates.
|
|
|
|
**Configuration:**
|
|
- Key: `audiobook_path_template` (string, default: `{author}/{title} {asin}`)
|
|
- Variables: `{author}`, `{title}`, `{narrator}`, `{asin}`, `{year}`
|
|
- Optional variables (narrator, asin, year) removed if not available
|
|
- Template validated on test, shows preview examples
|
|
|
|
**UI (PathsTab):**
|
|
- Text input with monospace font
|
|
- Placeholder: `{author}/{title} {asin}`
|
|
- Variable reference panel showing all available variables
|
|
- Template validation on "Test Paths" with success/error feedback
|
|
- Preview examples showing 2-3 sample paths with actual data
|
|
|
|
**Validation:**
|
|
- Must contain at least `{author}` or `{title}` (required variables)
|
|
- Cannot be empty or only contain optional variables
|
|
- Invalid templates show error message
|
|
- Valid templates show preview paths
|
|
|
|
**Examples:**
|
|
- `{author}/{title} {asin}` → `Douglas Adams/The Hitchhiker's Guide to the Galaxy B0009JKV9W/`
|
|
- `{author}/{title} ({year})` → `Douglas Adams/The Hitchhiker's Guide to the Galaxy (2005)/`
|
|
- `{author}/{narrator}/{title}` → `Douglas Adams/Stephen Fry/The Hitchhiker's Guide to the Galaxy/`
|
|
|
|
## Filesystem Scan Trigger
|
|
|
|
**Purpose:** Trigger Plex/Audiobookshelf to scan filesystem after organizing files for users with disabled filesystem watchers.
|
|
|
|
**Configuration:**
|
|
- Plex: `plex.trigger_scan_after_import` (boolean, default: false)
|
|
- Audiobookshelf: `audiobookshelf.trigger_scan_after_import` (boolean, default: false)
|
|
|
|
**UI:**
|
|
- Checkbox toggle in both Plex and Audiobookshelf settings tabs
|
|
- Default: Unchecked (disabled)
|
|
- Help text: "Only enable this if you have [Plex/Audiobookshelf]'s filesystem watcher (automatic scanning) disabled. Most users should leave this disabled and rely on [Plex/Audiobookshelf]'s built-in automatic detection."
|
|
|
|
**Behavior:**
|
|
- When enabled: After `organize_files` job completes, RMAB triggers filesystem scan in media server
|
|
- When disabled: User relies on media server's filesystem watcher or manual scans
|
|
- Error handling: Scan failures logged but don't fail organize job (graceful degradation)
|
|
|
|
## Plex Format Coercion
|
|
|
|
**Purpose:** Rename audiobook files to Plex-recognized extensions (`.mp4` → `.m4b`, single-file `.m4a` → `.m4b`) before the library scan. Prevents Plex silently ignoring `.mp4` audiobooks. Rename-only — no transcoding. See: [phase3/file-organization.md](phase3/file-organization.md#plex-format-coercion).
|
|
|
|
**Configuration:**
|
|
- Key: `plex_format_coercion_enabled` (boolean, default: `true`)
|
|
- Read contract: `value !== 'false'` enables (default-on)
|
|
- Configurable in: Setup wizard (Paths step), Admin settings (Paths tab)
|
|
|
|
**UI:**
|
|
- Checkbox toggle in PathsTab, between metadata tagging and chapter merging
|
|
- Default: Checked (enabled)
|
|
- Label: "Coerce file formats for Plex compatibility"
|
|
- Sub-text: "Rename .mp4 audiobook files (and single-file .m4a) to .m4b before Plex scans. No re-encoding."
|
|
|
|
**Behavior:**
|
|
- When enabled: After organize, rename files per coercion table before scan trigger
|
|
- When disabled: Files left as-is (Plex may silently skip `.mp4`)
|
|
- Failure isolation: Rename errors logged but don't fail organize job
|
|
- Universal (Plex + ABS) — rename is lossless, no per-backend distinction
|
|
|
|
## Validation Flow
|
|
|
|
**Plex, Download Client, Paths:**
|
|
1. User modifies settings (URL, credentials, paths)
|
|
2. User clicks "Test Connection" or "Test Paths"
|
|
3. System validates settings
|
|
4. On success: "Save Changes" button enabled
|
|
5. On failure: Error shown, "Save Changes" remains disabled
|
|
|
|
**Prowlarr (special handling):**
|
|
1. **On tab load:** Current indexer configuration loaded from database automatically
|
|
2. **Changing indexer settings** (enable/disable, priority, seeding time, RSS):
|
|
- No test required
|
|
- Can save immediately if URL/API key unchanged
|
|
3. **Changing URL or API key:**
|
|
- Validation required before saving
|
|
- User clicks "Test Connection"
|
|
- On success: Indexers refresh automatically, "Save Changes" enabled
|
|
4. **Button text adapts:**
|
|
- "Test Connection" when URL/API key changed
|
|
- "Refresh Indexers" when connection info unchanged
|
|
|
|
**BookDate (Admin Settings):**
|
|
1. **On tab load:** Current BookDate global configuration loaded from database automatically
|
|
2. **Changing AI provider:** Resets model selection
|
|
3. **Test connection:** Required to fetch available models before saving
|
|
4. **Changing API key:** Must test connection to verify and fetch models
|
|
5. **Saving configuration:** Validates all fields (provider, API key, model)
|
|
6. **Note:** Library scope and custom prompt are now per-user settings (configured in BookDate page)
|
|
7. **Clear swipe history:** Confirmation dialog, removes ALL users' swipes and cached recommendations
|
|
8. No "Save Changes" button - uses dedicated "Save BookDate Configuration" button
|
|
9. Accessible to admins only
|
|
|
|
**BookDate (User Preferences - in `/bookdate` page):**
|
|
1. **Settings icon:** Opens modal with per-user preferences
|
|
2. **Library scope:** Full library or rated books only (default: full)
|
|
3. **Custom prompt:** Optional text (max 1000 chars, default: blank)
|
|
4. **Save:** Updates user preferences immediately
|
|
5. Accessible to all authenticated users
|
|
|
|
**Validation state resets when:**
|
|
- Plex: URL or token modified
|
|
- Prowlarr: URL or API key modified (NOT indexer config)
|
|
- Download Client: URL, username, or password modified
|
|
- Paths: Directory paths or template modified
|
|
|
|
## API Endpoints
|
|
|
|
**GET /api/admin/settings**
|
|
- Returns all config (passwords masked as ••••)
|
|
- Admin auth required
|
|
|
|
**GET /api/admin/settings/prowlarr/indexers**
|
|
- Returns current indexer configuration merged with available Prowlarr indexers
|
|
- Loads saved settings (enabled, priority, seeding time, RSS) from database
|
|
- Merges with live indexer list from Prowlarr
|
|
- Admin auth required
|
|
|
|
**PUT /api/admin/settings/plex**
|
|
- Updates Plex config
|
|
- Requires prior successful test if URL/token changed
|
|
|
|
**PUT /api/admin/settings/prowlarr**
|
|
- Updates Prowlarr URL and API key
|
|
- Requires prior successful test if values changed
|
|
|
|
**PUT /api/admin/settings/audible**
|
|
- Updates Audible region
|
|
- Body: `{ region: string }` (one of: us, ca, uk, au, in, es, fr)
|
|
- No validation required
|
|
|
|
**PUT /api/admin/settings/prowlarr/indexers**
|
|
- Updates indexer configuration (enabled, priority, seeding time, RSS)
|
|
- No test required if URL/API key unchanged
|
|
- Saves only enabled indexers to database
|
|
|
|
**GET /api/admin/settings/indexer-options**
|
|
- Returns `{ skipUnreleased: boolean }`
|
|
- Default ON: missing or non-`'false'` value resolves to `true`
|
|
- Admin auth required
|
|
|
|
**PUT /api/admin/settings/indexer-options**
|
|
- Updates indexer-wide auto-search options
|
|
- Body: `{ skipUnreleased: boolean }` (strict boolean validation)
|
|
- Persists `indexer.skip_unreleased` (category: `indexer`)
|
|
- No connection test required
|
|
|
|
**PUT /api/admin/settings/download-client**
|
|
- Updates download client config
|
|
- Requires prior successful test if credentials changed
|
|
|
|
**PUT /api/admin/settings/paths**
|
|
- Updates paths and audiobook organization template
|
|
- Requires prior successful test if paths or template changed
|
|
- Body: `{ downloadDir, mediaDir, audiobookPathTemplate, metadataTaggingEnabled, chapterMergingEnabled }`
|
|
|
|
**Test Endpoints (authenticated, handle masked values):**
|
|
- POST /api/admin/settings/test-plex - Tests Plex connection, uses stored token if masked, returns libraries
|
|
- POST /api/admin/settings/test-prowlarr - Tests connection, uses stored API key if masked, returns indexers
|
|
- POST /api/admin/settings/test-download-client - Tests qBittorrent/Transmission, uses stored password if masked
|
|
- POST /api/setup/test-paths - Validates paths writable and template format, returns `{success, message, templateValidation: {isValid, error?, previewPaths?}}`
|
|
|
|
**BookDate Endpoints:**
|
|
- GET /api/bookdate/config - Get global BookDate configuration (API key excluded, admin only)
|
|
- POST /api/bookdate/config - Save/update global BookDate configuration (admin only)
|
|
- POST /api/bookdate/test-connection - Test AI provider connection and fetch available models
|
|
- DELETE /api/bookdate/swipes - Clear ALL users' swipe history and cached recommendations (admin only)
|
|
- GET /api/bookdate/preferences - Get user's preferences (libraryScope, customPrompt)
|
|
- PUT /api/bookdate/preferences - Update user's preferences (all authenticated users)
|
|
|
|
## Features
|
|
|
|
- Password visibility toggle
|
|
- Mandatory "Test Connection" buttons per tab
|
|
- "Save Changes" disabled until current tab validated
|
|
- Test result display (success/error messages)
|
|
- Toast notifications for save confirmations
|
|
- Form validation with Zod schemas
|
|
- Reuses setup wizard connection test endpoints
|
|
- Visual warning when validation required
|
|
|
|
## Security
|
|
|
|
- Admin role required
|
|
- Passwords never returned in GET (masked)
|
|
- Connection tests validate before saving
|
|
- HTTPS required in production
|
|
|
|
## Validation
|
|
|
|
**Plex:** Valid HTTP/HTTPS URL, non-empty token, library ID selected
|
|
**Prowlarr:** Valid URL, non-empty API key, ≥1 indexer configured, priority 1-25, seedingTimeMinutes ≥0, ratioLimit ≥0 (torrents only; decimal, `0` = no requirement), rssEnabled boolean
|
|
**Download Client:** Valid URL, credentials required, type must be 'qbittorrent', 'transmission', or 'sabnzbd'
|
|
**Paths:** Absolute paths, exist or creatable, writable, cannot be same directory, template must contain `{author}` or `{title}`
|
|
|
|
## Tech Stack
|
|
|
|
- React Hook Form
|
|
- Zod validation
|
|
- Tab/sidebar navigation
|
|
- Toast notifications
|
|
|
|
## Fixed Issues ✅
|
|
|
|
**1. Settings Save Without Validation**
|
|
- Issue: Users could save invalid/broken settings (wrong URLs, bad credentials, invalid paths)
|
|
- Cause: No validation enforcement before save
|
|
- Fix: Added mandatory "Test Connection"/"Test Paths" buttons per tab, disabled "Save Changes" until validated
|
|
- Behavior: Now matches wizard flow - test first, then save
|
|
|
|
**2. Testing with Masked Credentials**
|
|
- Issue: Test connection failed because it was testing with masked `••••` values instead of actual credentials
|
|
- Cause: Test endpoints didn't handle masked values, tried to authenticate with literal `••••••••`
|
|
- Fix: Created authenticated test endpoints that read actual values from database when masked values detected
|
|
- Endpoints: `/api/admin/settings/test-plex`, `/test-prowlarr`, `/test-download-client`
|
|
- Behavior: Users can test without re-entering unchanged passwords
|
|
|
|
**3. Indexer Configuration Workflow**
|
|
- Issue: Indexer settings required re-testing before saving, current settings weren't loading, workflow confusing
|
|
- Cause: Indexers only loaded after test, changing any indexer setting invalidated connection
|
|
- Fix:
|
|
- Load current indexer config from database on tab load (GET `/api/admin/settings/prowlarr/indexers`)
|
|
- Track which values changed (URL/API key vs indexer config)
|
|
- Only require test if URL/API key changed
|
|
- Allow saving indexer config changes without re-testing connection
|
|
- Button text adapts: "Test Connection" vs "Refresh Indexers"
|
|
- Behavior: Natural workflow - see current settings, modify indexers, save immediately
|
|
|
|
## Notifications
|
|
|
|
**Purpose:** Configure notification backends to receive alerts for audiobook request events.
|
|
|
|
**Configuration:**
|
|
- Multiple backends per type (Discord, Pushover)
|
|
- Per-backend event subscriptions (4 events)
|
|
- Encrypted sensitive values (webhook URLs, API keys)
|
|
- Enable/disable toggle per backend
|
|
|
|
**UI (NotificationsTab):**
|
|
- Type selector cards: Discord (indigo "D"), Pushover (blue "P")
|
|
- Grid layout for configured backends (3 columns)
|
|
- Card shows: type icon, name, enabled status, event count
|
|
- Edit/delete actions per card
|
|
|
|
**Modal (NotificationConfigModal):**
|
|
- Type-specific forms (Discord: webhook/username/avatar, Pushover: keys/priority)
|
|
- Event subscription checkboxes (4 events)
|
|
- Enable/disable toggle
|
|
- Test button (sends sample notification)
|
|
- Password masking for sensitive values
|
|
|
|
**Event Types:**
|
|
- Request Pending Approval - Admin approval required
|
|
- Request Approved - Approved (manual or auto)
|
|
- Request Available - Available in library
|
|
- Request Error - Failed at any stage
|
|
|
|
**Validation:**
|
|
- Name required
|
|
- Discord: webhook URL required
|
|
- Pushover: user key + app token required
|
|
- At least one event selected
|