Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec9b1c6d94 | |||
| 3f8970ea99 | |||
| 9491948ec9 |
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
|
||||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.7.10] - 2026-05-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Ombi webhook race condition fix** — Introduced a 2000ms delay in the webhook background worker for Ombi events before fetching new requests. This resolves a race condition where Ombi triggers the webhook before its database has committed the new request, which previously caused the request to be missing from the subsequent API fetch. Resolves Gitea Issue [#42](https://git.i3omb.com/Gandalf/sofarr/issues/42).
|
||||
- **Ombi webhook statistics in status panel** — Integrated Ombi webhook metrics into the admin status panel. The backend now retrieves the Ombi webhook configuration status and aggregates its events received and polls skipped metrics. The frontend displays these metrics (consistently formatted as `O:<count>`) under the Webhooks status card.
|
||||
|
||||
---
|
||||
|
||||
## [1.7.9] - 2026-05-23
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -118,18 +118,22 @@ export function renderStatusPanel(data, panel) {
|
||||
const wh = data.webhooks;
|
||||
const sonarrEnabled = wh.sonarr?.enabled ? '●' : '○';
|
||||
const radarrEnabled = wh.radarr?.enabled ? '●' : '○';
|
||||
const ombiEnabled = wh.ombi?.enabled ? '●' : '○';
|
||||
const sonarrEvents = wh.sonarr?.eventsReceived || 0;
|
||||
const radarrEvents = wh.radarr?.eventsReceived || 0;
|
||||
const ombiEvents = wh.ombi?.eventsReceived || 0;
|
||||
const sonarrPolls = wh.sonarr?.pollsSkipped || 0;
|
||||
const radarrPolls = wh.radarr?.pollsSkipped || 0;
|
||||
const ombiPolls = wh.ombi?.pollsSkipped || 0;
|
||||
|
||||
html += `
|
||||
<div class="status-card">
|
||||
<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>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 status-row-sub"><span>Polls skipped</span><span>S:${sonarrPolls} R:${radarrPolls}</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>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>`;
|
||||
}
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sofarr",
|
||||
"version": "1.7.9",
|
||||
"version": "1.7.10",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sofarr",
|
||||
"version": "1.7.9",
|
||||
"version": "1.7.10",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sofarr",
|
||||
"version": "1.7.9",
|
||||
"version": "1.7.10",
|
||||
"description": "A personal media download dashboard that shows your downloads 'so far' while you relax on the sofa waiting for your *arr services to finish",
|
||||
"main": "server/index.js",
|
||||
"scripts": {
|
||||
|
||||
+12
-4
@@ -4,9 +4,9 @@ const router = express.Router();
|
||||
const requireAuth = require('../middleware/requireAuth');
|
||||
const cache = require('../utils/cache');
|
||||
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 { checkWebhookConfigured, aggregateMetrics } = require('../services/WebhookStatus');
|
||||
const { checkWebhookConfigured, checkOmbiWebhookConfigured, aggregateMetrics } = require('../services/WebhookStatus');
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
@@ -121,6 +121,7 @@ router.get('/', requireAuth, async (req, res) => {
|
||||
// Check webhook configuration for each service
|
||||
const sonarrInstances = getSonarrInstances();
|
||||
const radarrInstances = getRadarrInstances();
|
||||
const ombiInstances = getOmbiInstances();
|
||||
|
||||
const sonarrWebhookConfigured = sonarrInstances.length > 0
|
||||
? await checkWebhookConfigured(sonarrInstances[0], 'Sonarr')
|
||||
@@ -128,15 +129,21 @@ router.get('/', requireAuth, async (req, res) => {
|
||||
const radarrWebhookConfigured = radarrInstances.length > 0
|
||||
? await checkWebhookConfigured(radarrInstances[0], 'Radarr')
|
||||
: 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 radarrMetrics = {};
|
||||
const ombiMetrics = {};
|
||||
for (const [url, metrics] of Object.entries(webhookMetrics.instances || {})) {
|
||||
if (url.includes('sonarr')) {
|
||||
sonarrMetrics[url] = metrics;
|
||||
} else if (url.includes('radarr')) {
|
||||
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,
|
||||
webhooks: {
|
||||
sonarr: aggregateMetrics(sonarrMetrics, sonarrWebhookConfigured),
|
||||
radarr: aggregateMetrics(radarrMetrics, radarrWebhookConfigured)
|
||||
radarr: aggregateMetrics(radarrMetrics, radarrWebhookConfigured),
|
||||
ombi: aggregateMetrics(ombiMetrics, ombiWebhookConfigured)
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -259,6 +259,8 @@ async function processWebhookEvent(serviceType, eventType) {
|
||||
const ombiInstances = getOmbiInstances();
|
||||
|
||||
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);
|
||||
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)`);
|
||||
|
||||
@@ -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 = {
|
||||
checkWebhookConfigured,
|
||||
checkOmbiWebhookConfigured,
|
||||
aggregateMetrics
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user