feat: add Ombi requests tab and webhook panel integration

- Add Ombi requests tab UI with movie/TV request display
- Add showAll parameter support for Ombi requests (API and SSE)
- Add Ombi webhook panel with enable/test functionality
- Add Ombi webhook status endpoint with metrics
- Add Ombi webhook test endpoint
- Change GET /api/ombi/requests to use OmbiRetriever instead of cache
- Add Ombi webhook state and API functions to frontend
- Update SSE payload to include Ombi baseUrl and requests
This commit is contained in:
2026-05-21 20:59:06 +01:00
parent 884fb5285f
commit 1dccda529a
13 changed files with 1186 additions and 54 deletions
+21 -3
View File
@@ -5,7 +5,8 @@ const { initializeClients, getAllDownloads, getDownloadsByClientType } = require
const arrRetrieverRegistry = require('./arrRetrievers');
const {
getSonarrInstances,
getRadarrInstances
getRadarrInstances,
getOmbiInstances
} = require('./config');
const rawPollInterval = (process.env.POLL_INTERVAL || '').toLowerCase();
@@ -88,13 +89,14 @@ async function pollAllServices() {
const sonarrInstances = getSonarrInstances();
const radarrInstances = getRadarrInstances();
const ombiInstances = getOmbiInstances();
// Check webhook fallback: if no webhook events for WEBHOOK_FALLBACK_TIMEOUT, force full poll
const globalMetrics = cache.getGlobalWebhookMetrics();
const now = Date.now();
const lastWebhookTime = globalMetrics.lastGlobalWebhookTimestamp;
const fallbackTriggered = lastWebhookTime && (now - lastWebhookTime) > WEBHOOK_FALLBACK_TIMEOUT_MS;
if (fallbackTriggered) {
console.log(`[Poller] Webhook fallback triggered: no webhook events for ${WEBHOOK_FALLBACK_TIMEOUT_MINUTES} minutes, forcing full poll`);
}
@@ -102,6 +104,7 @@ async function pollAllServices() {
// Determine which instances should be polled based on webhook activity
const shouldPollSonarr = fallbackTriggered || !shouldSkipInstancePolling(sonarrInstances, 'sonarr');
const shouldPollRadarr = fallbackTriggered || !shouldSkipInstancePolling(radarrInstances, 'radarr');
const shouldPollOmbi = fallbackTriggered || !shouldSkipInstancePolling(ombiInstances, 'ombi');
// All fetches in parallel, each individually timed
const results = await Promise.all([
@@ -133,6 +136,10 @@ async function pollAllServices() {
const tagsByType = await arrRetrieverRegistry.getTagsByType();
return tagsByType.radarr || [];
}) : timed('Radarr Tags', async () => []),
shouldPollOmbi ? timed('Ombi Requests', async () => {
const ombiRequests = await arrRetrieverRegistry.getOmbiRequests();
return ombiRequests;
}) : timed('Ombi Requests', async () => ({ movie: [], tv: [] })),
]);
const [
@@ -140,7 +147,8 @@ async function pollAllServices() {
{ result: sonarrTagsResults }, { result: sonarrQueues },
{ result: sonarrHistories },
{ result: radarrQueues }, { result: radarrHistories },
{ result: radarrTagsResults }
{ result: radarrTagsResults },
{ result: ombiRequests }
] = results;
// Store per-task timings
@@ -282,6 +290,16 @@ async function pollAllServices() {
if (existingRadarrTags) cache.set('poll:radarr-tags', existingRadarrTags, cacheTTL);
}
// Ombi
if (shouldPollOmbi) {
cache.set('poll:ombi-requests', ombiRequests, cacheTTL);
logToFile(`[Poller] Ombi requests cached: ${ombiRequests.movie?.length || 0} movies, ${ombiRequests.tv?.length || 0} TV shows`);
} else {
// Extend TTL of existing cached data when polling is skipped
const existingOmbiRequests = cache.get('poll:ombi-requests');
if (existingOmbiRequests) cache.set('poll:ombi-requests', existingOmbiRequests, cacheTTL);
}
// qBittorrent (already set above in download clients section)
const elapsed = Date.now() - start;