feat: Add Ombi request filtering and search
Build and Push Docker Image / build (push) Successful in 1m29s
Docs Check / Markdown lint (push) Successful in 1m51s
Licence Check / Licence compatibility and copyright header verification (push) Failing after 2m3s
CI / Security audit (push) Successful in 2m54s
CI / Swagger Validation & Coverage (push) Successful in 3m6s
Docs Check / Mermaid diagram parse check (push) Successful in 3m13s
CI / Tests & coverage (push) Successful in 3m31s

- Add request filters UI (type, status, sort, search)
- Implement dual-layer filtering (server + client)
- Add ombiFilters utility for consistent filtering logic
- Persist filter preferences in localStorage
- Add SSE support for real-time Ombi request updates
- Add webhook endpoints for Ombi integration
- Update OpenAPI spec for new endpoints
- Add unit tests for filter logic and UI
- Add integration tests for Ombi routes
This commit is contained in:
2026-05-22 12:31:31 +01:00
parent dbf45ec31d
commit d3d085d614
17 changed files with 1695 additions and 83 deletions
+86 -2
View File
@@ -51,6 +51,7 @@ flowchart TB
dash["Dashboard Cards"]
status["Status Panel\n(Admin only)"]
history["History Tab"]
requests["Requests Tab\n+ Filters / Search"]
webhooks["Webhook Config"]
swagger["Swagger UI\n/api/swagger"]
end
@@ -62,6 +63,7 @@ flowchart TB
dash_r["Dashboard Routes\n/api/dashboard (SSE /stream)"]
stat_r["Status Routes\n/api/status"]
wh_r["Webhook Routes\n/api/webhook/sonarr|radarr"]
ombi_r["Ombi Routes\n/api/ombi"]
hist_r["History Routes\n/api/history"]
proxy_r["Proxy Routes\n/api/sonarr · /api/radarr\n/api/sabnzbd · /api/emby"]
@@ -82,12 +84,14 @@ flowchart TB
rtorrent["rTorrent"]
transmission["Transmission"]
emby["Emby / Jellyfin"]
ombi["Ombi"]
end
login -->|"POST /api/auth/login"| auth_r
dash -->|"GET /api/dashboard/stream (SSE)"| dash_r
status -->|"GET /api/status"| stat_r
history -->|"GET /api/history/recent"| hist_r
requests -->|"GET /api/ombi/requests"| ombi_r
auth_r --> tokenstore
auth_r -->|"authenticate"| emby
@@ -97,13 +101,14 @@ flowchart TB
stat_r --> cache
wh_r --> cache
wh_r --> paldra
ombi_r --> paldra
hist_r --> cache
proxy_r -->|"proxy"| sonarr & radarr & sab & emby
poller --> pdca & paldra
poller --> cache
pdca -->|"HTTP/API"| sab & qbt & rtorrent & transmission
paldra -->|"HTTP/API"| sonarr & radarr
paldra -->|"HTTP/API"| sonarr & radarr & ombi
sonarr & radarr -->|"POST /api/webhook/*"| wh_r
```
@@ -130,6 +135,7 @@ Express Server (:3001)
├── /api/dashboard → requireAuth → read cache → DownloadBuilder → SSE/JSON
├── /api/status → requireAuth → admin cache/polling/webhook status
├── /api/history → requireAuth → historyFetcher (5 min cache) → filter + dedup
├── /api/ombi → requireAuth → PALDRA → filter/sort/search → JSON
├── /api/sonarr|radarr → requireAuth → verifyCsrf → proxy to *arr API
└── /api/sabnzbd|emby → requireAuth → verifyCsrf → proxy
@@ -300,6 +306,17 @@ arrRetrieverRegistry = {
}
```
#### Ombi retriever
The `OmbiRetriever` (in `server/clients/OmbiClient.js`) fetches from:
| Task | Endpoint | Notes |
|------|----------|-------|
| Movie requests | `GET /api/v1/Request/movie` | Returns full movie request objects |
| TV requests | `GET /api/v1/Request/tv` | Returns full TV request objects |
Results are cached under `poll:ombi` and broadcast via SSE as `ombiRequests: { movie, tv }`. The client applies the same `ombiFilters.js` logic used by the server route, keeping behaviour consistent across both layers.
Each result element is `{ instance: instanceId, data: <arr API response> }`, allowing callers to look up instance credentials from `config.js`.
#### Retriever API Calls
@@ -539,7 +556,12 @@ The browser's native `EventSource` API handles reconnection automatically on net
id: string, // Instance identifier
name: string, // Instance display name
type: string // Client type ('sabnzbd', 'qbittorrent', 'transmission', 'rtorrent')
}[]
}[],
ombiRequests: { // Raw Ombi movie + TV requests (client applies filters)
movie: OmbiRequest[],
tv: OmbiRequest[]
},
ombiBaseUrl: string // Ombi instance base URL for deep links
}
```
@@ -631,6 +653,67 @@ Matched download objects include `client`, `instanceId`, and `instanceName` fiel
| `addedOn` | number/null | (qBittorrent) Unix timestamp when torrent was added |
| `availableForUpgrade` | boolean/undefined | (History) `true` when outcome is `failed` but content is on disk |
### 5.5 Ombi Request Filtering
The Ombi Requests tab displays movie and TV requests from Ombi. Filtering, sorting, and text search are applied **server-side** on the REST endpoint (`GET /api/ombi/requests`) and **client-side** on every SSE update. This dual-layer approach ensures external API consumers receive pre-filtered data while the SPA remains responsive without extra round-trips.
```mermaid
sequenceDiagram
participant Client as Browser (Requests Tab)
participant SSE as SSE /api/dashboard/stream
participant Route as /api/ombi/requests
participant Filters as ombiFilters (shared)
participant PALDRA as PALDRA Registry
participant Ombi as Ombi API
Note over Client: Initial load
Client->>Route: GET /api/ombi/requests?type=…&status=…&sort=…&search=…
Route->>PALDRA: getOmbiRequests()
PALDRA->>Ombi: GET /api/v1/Request/movie + /tv
Ombi-->>PALDRA: raw request arrays
PALDRA-->>Route: { movie: [], tv: [] }
Route->>Filters: applyRequestFilters()
Filters-->>Route: filtered & sorted requests
Route-->>Client: { requests: { movie, tv }, total }
Note over Client: Real-time updates
SSE->>Client: push raw ombiRequests + ombiBaseUrl
Client->>Filters: applyRequestFilters() (same code)
Filters-->>Client: filtered & sorted requests
Client->>Client: renderRequests()
```
#### Query parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `type` | `movie` \| `tv` \| `all` | `all` | Media type filter (multi-select) |
| `status` | `pending` \| `approved` \| `available` \| `denied` | — | Request status filter (multi-select) |
| `sort` | `requestedDate_desc` \| `requestedDate_asc` \| `title_asc` \| `title_desc` | `requestedDate_desc` | Sort mode |
| `search` | string | — | Case-insensitive title substring |
| `showAll` | `'true'` \| `'false'` | `'false'` | Admin only: show all users' requests |
#### Status priority
The same `getRequestStatus()` function runs on both server and client:
1. `available` — if `available === true`
2. `denied` — if `denied === true`
3. `approved` — if `approved === true`
4. `pending` — if `requested === true`
5. `unknown` — fallback
#### Persistence
Filter and sort preferences are persisted in `localStorage` under the following keys:
| Key | Content |
|-----|---------|
| `sofarr-request-types` | `['movie', 'tv']` or subset |
| `sofarr-request-statuses` | `['pending', 'approved', 'available', 'denied']` or subset |
| `sofarr-request-sort` | `requestedDate_desc`, `requestedDate_asc`, `title_asc`, `title_desc` |
| `sofarr-request-search` | Free-text query string |
---
## 6. Caching and Smart Polling
@@ -667,6 +750,7 @@ class MemoryCache {
| `poll:radarr-history` | `{ records }` — lightweight | `POLL_INTERVAL × 3` |
| `poll:radarr-tags` | `[{ instance, data: [{id, label}] }]` | `POLL_INTERVAL × 3` |
| `poll:qbittorrent` | `[torrent, …]` | `POLL_INTERVAL × 3` |
| `poll:ombi` | `{ movie: [], tv: [] }` | `POLL_INTERVAL × 3` |
| `history:sonarr` | `[record, …]` flat array with `_instanceUrl`/`_instanceName` | 5 min |
| `history:radarr` | `[record, …]` flat array with `_instanceUrl`/`_instanceName` | 5 min |
| `emby:users` | `Map<lowerName, displayName>` | 60 s |