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
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:
+86
-2
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user