fix: resolve Ombi webhook race condition and add Ombi webhook status metrics to status panel
CI / Security audit (push) Successful in 1m45s
Build and Push Docker Image / build (push) Successful in 1m43s
Licence Check / Licence compatibility and copyright header verification (push) Has been cancelled
CI / Tests & coverage (push) Has been cancelled
CI / Swagger Validation & Coverage (push) Has been cancelled
CI / Security audit (push) Successful in 1m45s
Build and Push Docker Image / build (push) Successful in 1m43s
Licence Check / Licence compatibility and copyright header verification (push) Has been cancelled
CI / Tests & coverage (push) Has been cancelled
CI / Swagger Validation & Coverage (push) Has been cancelled
This commit is contained in:
@@ -118,18 +118,22 @@ export function renderStatusPanel(data, panel) {
|
|||||||
const wh = data.webhooks;
|
const wh = data.webhooks;
|
||||||
const sonarrEnabled = wh.sonarr?.enabled ? '●' : '○';
|
const sonarrEnabled = wh.sonarr?.enabled ? '●' : '○';
|
||||||
const radarrEnabled = wh.radarr?.enabled ? '●' : '○';
|
const radarrEnabled = wh.radarr?.enabled ? '●' : '○';
|
||||||
|
const ombiEnabled = wh.ombi?.enabled ? '●' : '○';
|
||||||
const sonarrEvents = wh.sonarr?.eventsReceived || 0;
|
const sonarrEvents = wh.sonarr?.eventsReceived || 0;
|
||||||
const radarrEvents = wh.radarr?.eventsReceived || 0;
|
const radarrEvents = wh.radarr?.eventsReceived || 0;
|
||||||
|
const ombiEvents = wh.ombi?.eventsReceived || 0;
|
||||||
const sonarrPolls = wh.sonarr?.pollsSkipped || 0;
|
const sonarrPolls = wh.sonarr?.pollsSkipped || 0;
|
||||||
const radarrPolls = wh.radarr?.pollsSkipped || 0;
|
const radarrPolls = wh.radarr?.pollsSkipped || 0;
|
||||||
|
const ombiPolls = wh.ombi?.pollsSkipped || 0;
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
<div class="status-card">
|
<div class="status-card">
|
||||||
<div class="status-card-title">Webhooks</div>
|
<div class="status-card-title">Webhooks</div>
|
||||||
<div class="status-row"><span>Sonarr</span><span>${sonarrEnabled} ${wh.sonarr?.enabled ? 'Enabled' : 'Disabled'}</span></div>
|
<div class="status-row"><span>Sonarr</span><span>${sonarrEnabled} ${wh.sonarr?.enabled ? 'Enabled' : 'Disabled'}</span></div>
|
||||||
<div class="status-row"><span>Radarr</span><span>${radarrEnabled} ${wh.radarr?.enabled ? 'Enabled' : 'Disabled'}</span></div>
|
<div class="status-row"><span>Radarr</span><span>${radarrEnabled} ${wh.radarr?.enabled ? 'Enabled' : 'Disabled'}</span></div>
|
||||||
<div class="status-row status-row-sub"><span>Events</span><span>S:${sonarrEvents} R:${radarrEvents}</span></div>
|
<div class="status-row"><span>Ombi</span><span>${ombiEnabled} ${wh.ombi?.enabled ? 'Enabled' : 'Disabled'}</span></div>
|
||||||
<div class="status-row status-row-sub"><span>Polls skipped</span><span>S:${sonarrPolls} R:${radarrPolls}</span></div>
|
<div class="status-row status-row-sub"><span>Events</span><span>S:${sonarrEvents} R:${radarrEvents} O:${ombiEvents}</span></div>
|
||||||
|
<div class="status-row status-row-sub"><span>Polls skipped</span><span>S:${sonarrPolls} R:${radarrPolls} O:${ombiPolls}</span></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-4
@@ -4,9 +4,9 @@ const router = express.Router();
|
|||||||
const requireAuth = require('../middleware/requireAuth');
|
const requireAuth = require('../middleware/requireAuth');
|
||||||
const cache = require('../utils/cache');
|
const cache = require('../utils/cache');
|
||||||
const { getLastPollTimings, POLLING_ENABLED } = require('../utils/poller');
|
const { getLastPollTimings, POLLING_ENABLED } = require('../utils/poller');
|
||||||
const { getSonarrInstances, getRadarrInstances } = require('../utils/config');
|
const { getSonarrInstances, getRadarrInstances, getOmbiInstances } = require('../utils/config');
|
||||||
const { getGlobalWebhookMetrics } = require('../utils/cache');
|
const { getGlobalWebhookMetrics } = require('../utils/cache');
|
||||||
const { checkWebhookConfigured, aggregateMetrics } = require('../services/WebhookStatus');
|
const { checkWebhookConfigured, checkOmbiWebhookConfigured, aggregateMetrics } = require('../services/WebhookStatus');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @openapi
|
* @openapi
|
||||||
@@ -121,6 +121,7 @@ router.get('/', requireAuth, async (req, res) => {
|
|||||||
// Check webhook configuration for each service
|
// Check webhook configuration for each service
|
||||||
const sonarrInstances = getSonarrInstances();
|
const sonarrInstances = getSonarrInstances();
|
||||||
const radarrInstances = getRadarrInstances();
|
const radarrInstances = getRadarrInstances();
|
||||||
|
const ombiInstances = getOmbiInstances();
|
||||||
|
|
||||||
const sonarrWebhookConfigured = sonarrInstances.length > 0
|
const sonarrWebhookConfigured = sonarrInstances.length > 0
|
||||||
? await checkWebhookConfigured(sonarrInstances[0], 'Sonarr')
|
? await checkWebhookConfigured(sonarrInstances[0], 'Sonarr')
|
||||||
@@ -128,15 +129,21 @@ router.get('/', requireAuth, async (req, res) => {
|
|||||||
const radarrWebhookConfigured = radarrInstances.length > 0
|
const radarrWebhookConfigured = radarrInstances.length > 0
|
||||||
? await checkWebhookConfigured(radarrInstances[0], 'Radarr')
|
? await checkWebhookConfigured(radarrInstances[0], 'Radarr')
|
||||||
: false;
|
: false;
|
||||||
|
const ombiWebhookConfigured = ombiInstances.length > 0
|
||||||
|
? await checkOmbiWebhookConfigured(ombiInstances[0])
|
||||||
|
: false;
|
||||||
|
|
||||||
// Find Sonarr and Radarr metrics from instances
|
// Find Sonarr, Radarr, and Ombi metrics from instances
|
||||||
const sonarrMetrics = {};
|
const sonarrMetrics = {};
|
||||||
const radarrMetrics = {};
|
const radarrMetrics = {};
|
||||||
|
const ombiMetrics = {};
|
||||||
for (const [url, metrics] of Object.entries(webhookMetrics.instances || {})) {
|
for (const [url, metrics] of Object.entries(webhookMetrics.instances || {})) {
|
||||||
if (url.includes('sonarr')) {
|
if (url.includes('sonarr')) {
|
||||||
sonarrMetrics[url] = metrics;
|
sonarrMetrics[url] = metrics;
|
||||||
} else if (url.includes('radarr')) {
|
} else if (url.includes('radarr')) {
|
||||||
radarrMetrics[url] = metrics;
|
radarrMetrics[url] = metrics;
|
||||||
|
} else if (url.includes('ombi') || (ombiInstances.length > 0 && url === ombiInstances[0].url)) {
|
||||||
|
ombiMetrics[url] = metrics;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +163,8 @@ router.get('/', requireAuth, async (req, res) => {
|
|||||||
cache: cacheStats,
|
cache: cacheStats,
|
||||||
webhooks: {
|
webhooks: {
|
||||||
sonarr: aggregateMetrics(sonarrMetrics, sonarrWebhookConfigured),
|
sonarr: aggregateMetrics(sonarrMetrics, sonarrWebhookConfigured),
|
||||||
radarr: aggregateMetrics(radarrMetrics, radarrWebhookConfigured)
|
radarr: aggregateMetrics(radarrMetrics, radarrWebhookConfigured),
|
||||||
|
ombi: aggregateMetrics(ombiMetrics, ombiWebhookConfigured)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -259,6 +259,8 @@ async function processWebhookEvent(serviceType, eventType) {
|
|||||||
const ombiInstances = getOmbiInstances();
|
const ombiInstances = getOmbiInstances();
|
||||||
|
|
||||||
if (affectsOmbi) {
|
if (affectsOmbi) {
|
||||||
|
// Add a 2000ms delay to resolve the race condition where Ombi fires the webhook before committing to DB
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
const ombiRequests = await arrRetrieverRegistry.getOmbiRequests(true);
|
const ombiRequests = await arrRetrieverRegistry.getOmbiRequests(true);
|
||||||
cache.set('poll:ombi-requests', ombiRequests, CACHE_TTL);
|
cache.set('poll:ombi-requests', ombiRequests, CACHE_TTL);
|
||||||
logToFile(`[Webhook] Refreshed poll:ombi-requests (${ombiRequests.movie?.length || 0} movies, ${ombiRequests.tv?.length || 0} TV shows)`);
|
logToFile(`[Webhook] Refreshed poll:ombi-requests (${ombiRequests.movie?.length || 0} movies, ${ombiRequests.tv?.length || 0} TV shows)`);
|
||||||
|
|||||||
@@ -49,7 +49,26 @@ function aggregateMetrics(metricsMap, configured) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Sofarr webhook is configured in an Ombi instance.
|
||||||
|
* @param {Object} instance - The Ombi instance config
|
||||||
|
* @returns {Promise<boolean>} true if webhook is configured
|
||||||
|
*/
|
||||||
|
async function checkOmbiWebhookConfigured(instance) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${instance.url}/api/v1/Settings/notifications/webhook`, {
|
||||||
|
headers: { 'ApiKey': instance.apiKey },
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
return !!(response.data && response.data.enabled);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`[WebhookStatus] Failed to check Ombi webhook config: ${err.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
checkWebhookConfigured,
|
checkWebhookConfigured,
|
||||||
|
checkOmbiWebhookConfigured,
|
||||||
aggregateMetrics
|
aggregateMetrics
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user