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
+112 -33
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2026 Gordon Bolton. MIT License.
import { state } from '../state.js';
import { fetchWebhookStatus as apiFetchWebhookStatus, enableSonarrWebhook as apiEnableSonarrWebhook, enableRadarrWebhook as apiEnableRadarrWebhook, testSonarrWebhook as apiTestSonarrWebhook, testRadarrWebhook as apiTestRadarrWebhook } from '../api.js';
import { fetchWebhookStatus as apiFetchWebhookStatus, enableSonarrWebhook as apiEnableSonarrWebhook, enableRadarrWebhook as apiEnableRadarrWebhook, enableOmbiWebhook as apiEnableOmbiWebhook, testSonarrWebhook as apiTestSonarrWebhook, testRadarrWebhook as apiTestRadarrWebhook, testOmbiWebhook as apiTestOmbiWebhook } from '../api.js';
import { formatTimeAgo } from '../utils/format.js';
export function initWebhooks() {
@@ -13,8 +13,10 @@ export function initWebhooks() {
document.getElementById('webhooks-header').addEventListener('click', toggleWebhookSection);
document.getElementById('enable-sonarr-webhook').addEventListener('click', enableSonarrWebhook);
document.getElementById('enable-radarr-webhook').addEventListener('click', enableRadarrWebhook);
document.getElementById('enable-ombi-webhook').addEventListener('click', enableOmbiWebhook);
document.getElementById('test-sonarr-webhook').addEventListener('click', testSonarrWebhook);
document.getElementById('test-radarr-webhook').addEventListener('click', testRadarrWebhook);
document.getElementById('test-ombi-webhook').addEventListener('click', testOmbiWebhook);
}
export function toggleWebhookSection() {
@@ -58,9 +60,9 @@ export function renderWebhookStatus() {
const sonarrTriggers = document.getElementById('sonarr-triggers');
const sonarrStats = document.getElementById('sonarr-stats');
sonarrStatus.textContent = sonarrWebhook.enabled ? '● Enabled' : '○ Disabled';
sonarrStatus.className = 'status-indicator ' + (sonarrWebhook.enabled ? 'enabled' : 'disabled');
if (sonarrWebhook.enabled) {
sonarrStatus.textContent = state.sonarrWebhook.enabled ? '● Enabled' : '○ Disabled';
sonarrStatus.className = 'status-indicator ' + (state.sonarrWebhook.enabled ? 'enabled' : 'disabled');
if (state.sonarrWebhook.enabled) {
sonarrEnableBtn.classList.add('hidden');
sonarrTestBtn.classList.remove('hidden');
sonarrTriggers.classList.remove('hidden');
@@ -70,22 +72,22 @@ export function renderWebhookStatus() {
sonarrTriggers.classList.add('hidden');
}
if (sonarrWebhook.enabled) {
document.getElementById('sonarr-onGrab').textContent = sonarrWebhook.triggers.onGrab ? '✓' : '✗';
document.getElementById('sonarr-onGrab').className = 'trigger-value ' + (sonarrWebhook.triggers.onGrab ? 'active' : 'inactive');
document.getElementById('sonarr-onDownload').textContent = sonarrWebhook.triggers.onDownload ? '✓' : '✗';
document.getElementById('sonarr-onDownload').className = 'trigger-value ' + (sonarrWebhook.triggers.onDownload ? 'active' : 'inactive');
document.getElementById('sonarr-onImport').textContent = sonarrWebhook.triggers.onImport ? '✓' : '✗';
document.getElementById('sonarr-onImport').className = 'trigger-value ' + (sonarrWebhook.triggers.onImport ? 'active' : 'inactive');
document.getElementById('sonarr-onUpgrade').textContent = sonarrWebhook.triggers.onUpgrade ? '✓' : '✗';
document.getElementById('sonarr-onUpgrade').className = 'trigger-value ' + (sonarrWebhook.triggers.onUpgrade ? 'active' : 'inactive');
if (state.sonarrWebhook.enabled) {
document.getElementById('sonarr-onGrab').textContent = state.sonarrWebhook.triggers.onGrab ? '✓' : '✗';
document.getElementById('sonarr-onGrab').className = 'trigger-value ' + (state.sonarrWebhook.triggers.onGrab ? 'active' : 'inactive');
document.getElementById('sonarr-onDownload').textContent = state.sonarrWebhook.triggers.onDownload ? '✓' : '✗';
document.getElementById('sonarr-onDownload').className = 'trigger-value ' + (state.sonarrWebhook.triggers.onDownload ? 'active' : 'inactive');
document.getElementById('sonarr-onImport').textContent = state.sonarrWebhook.triggers.onImport ? '✓' : '✗';
document.getElementById('sonarr-onImport').className = 'trigger-value ' + (state.sonarrWebhook.triggers.onImport ? 'active' : 'inactive');
document.getElementById('sonarr-onUpgrade').textContent = state.sonarrWebhook.triggers.onUpgrade ? '✓' : '✗';
document.getElementById('sonarr-onUpgrade').className = 'trigger-value ' + (state.sonarrWebhook.triggers.onUpgrade ? 'active' : 'inactive');
}
if (sonarrWebhook.stats) {
if (state.sonarrWebhook.stats) {
sonarrStats.classList.remove('hidden');
document.getElementById('sonarr-events').textContent = sonarrWebhook.stats.eventsReceived ?? 0;
document.getElementById('sonarr-polls').textContent = sonarrWebhook.stats.pollsSkipped ?? 0;
document.getElementById('sonarr-last').textContent = formatTimeAgo(sonarrWebhook.stats.lastWebhookTimestamp);
document.getElementById('sonarr-events').textContent = state.sonarrWebhook.stats.eventsReceived ?? 0;
document.getElementById('sonarr-polls').textContent = state.sonarrWebhook.stats.pollsSkipped ?? 0;
document.getElementById('sonarr-last').textContent = formatTimeAgo(state.sonarrWebhook.stats.lastWebhookTimestamp);
} else {
sonarrStats.classList.add('hidden');
}
@@ -97,9 +99,9 @@ export function renderWebhookStatus() {
const radarrTriggers = document.getElementById('radarr-triggers');
const radarrStats = document.getElementById('radarr-stats');
radarrStatus.textContent = radarrWebhook.enabled ? '● Enabled' : '○ Disabled';
radarrStatus.className = 'status-indicator ' + (radarrWebhook.enabled ? 'enabled' : 'disabled');
if (radarrWebhook.enabled) {
radarrStatus.textContent = state.radarrWebhook.enabled ? '● Enabled' : '○ Disabled';
radarrStatus.className = 'status-indicator ' + (state.radarrWebhook.enabled ? 'enabled' : 'disabled');
if (state.radarrWebhook.enabled) {
radarrEnableBtn.classList.add('hidden');
radarrTestBtn.classList.remove('hidden');
radarrTriggers.classList.remove('hidden');
@@ -109,25 +111,66 @@ export function renderWebhookStatus() {
radarrTriggers.classList.add('hidden');
}
if (radarrWebhook.enabled) {
document.getElementById('radarr-onGrab').textContent = radarrWebhook.triggers.onGrab ? '✓' : '✗';
document.getElementById('radarr-onGrab').className = 'trigger-value ' + (radarrWebhook.triggers.onGrab ? 'active' : 'inactive');
document.getElementById('radarr-onDownload').textContent = radarrWebhook.triggers.onDownload ? '✓' : '✗';
document.getElementById('radarr-onDownload').className = 'trigger-value ' + (radarrWebhook.triggers.onDownload ? 'active' : 'inactive');
document.getElementById('radarr-onImport').textContent = radarrWebhook.triggers.onImport ? '✓' : '✗';
document.getElementById('radarr-onImport').className = 'trigger-value ' + (radarrWebhook.triggers.onImport ? 'active' : 'inactive');
document.getElementById('radarr-onUpgrade').textContent = radarrWebhook.triggers.onUpgrade ? '✓' : '✗';
document.getElementById('radarr-onUpgrade').className = 'trigger-value ' + (radarrWebhook.triggers.onUpgrade ? 'active' : 'inactive');
if (state.radarrWebhook.enabled) {
document.getElementById('radarr-onGrab').textContent = state.radarrWebhook.triggers.onGrab ? '✓' : '✗';
document.getElementById('radarr-onGrab').className = 'trigger-value ' + (state.radarrWebhook.triggers.onGrab ? 'active' : 'inactive');
document.getElementById('radarr-onDownload').textContent = state.radarrWebhook.triggers.onDownload ? '✓' : '✗';
document.getElementById('radarr-onDownload').className = 'trigger-value ' + (state.radarrWebhook.triggers.onDownload ? 'active' : 'inactive');
document.getElementById('radarr-onImport').textContent = state.radarrWebhook.triggers.onImport ? '✓' : '✗';
document.getElementById('radarr-onImport').className = 'trigger-value ' + (state.radarrWebhook.triggers.onImport ? 'active' : 'inactive');
document.getElementById('radarr-onUpgrade').textContent = state.radarrWebhook.triggers.onUpgrade ? '✓' : '✗';
document.getElementById('radarr-onUpgrade').className = 'trigger-value ' + (state.radarrWebhook.triggers.onUpgrade ? 'active' : 'inactive');
}
if (radarrWebhook.stats) {
if (state.radarrWebhook.stats) {
radarrStats.classList.remove('hidden');
document.getElementById('radarr-events').textContent = radarrWebhook.stats.eventsReceived ?? 0;
document.getElementById('radarr-polls').textContent = radarrWebhook.stats.pollsSkipped ?? 0;
document.getElementById('radarr-last').textContent = formatTimeAgo(radarrWebhook.stats.lastWebhookTimestamp);
document.getElementById('radarr-events').textContent = state.radarrWebhook.stats.eventsReceived ?? 0;
document.getElementById('radarr-polls').textContent = state.radarrWebhook.stats.pollsSkipped ?? 0;
document.getElementById('radarr-last').textContent = formatTimeAgo(state.radarrWebhook.stats.lastWebhookTimestamp);
} else {
radarrStats.classList.add('hidden');
}
// Ombi
const ombiStatus = document.getElementById('ombi-status');
const ombiEnableBtn = document.getElementById('enable-ombi-webhook');
const ombiTestBtn = document.getElementById('test-ombi-webhook');
const ombiTriggers = document.getElementById('ombi-triggers');
const ombiStats = document.getElementById('ombi-stats');
ombiStatus.textContent = state.ombiWebhook.enabled ? '● Enabled' : '○ Disabled';
ombiStatus.className = 'status-indicator ' + (state.ombiWebhook.enabled ? 'enabled' : 'disabled');
if (state.ombiWebhook.enabled) {
ombiEnableBtn.classList.add('hidden');
ombiTestBtn.classList.remove('hidden');
ombiTriggers.classList.remove('hidden');
} else {
ombiEnableBtn.classList.remove('hidden');
ombiTestBtn.classList.add('hidden');
ombiTriggers.classList.add('hidden');
}
if (state.ombiWebhook.enabled) {
document.getElementById('ombi-requestAvailable').textContent = state.ombiWebhook.triggers.requestAvailable ? '✓' : '✗';
document.getElementById('ombi-requestAvailable').className = 'trigger-value ' + (state.ombiWebhook.triggers.requestAvailable ? 'active' : 'inactive');
document.getElementById('ombi-requestApproved').textContent = state.ombiWebhook.triggers.requestApproved ? '✓' : '✗';
document.getElementById('ombi-requestApproved').className = 'trigger-value ' + (state.ombiWebhook.triggers.requestApproved ? 'active' : 'inactive');
document.getElementById('ombi-requestDeclined').textContent = state.ombiWebhook.triggers.requestDeclined ? '✓' : '✗';
document.getElementById('ombi-requestDeclined').className = 'trigger-value ' + (state.ombiWebhook.triggers.requestDeclined ? 'active' : 'inactive');
document.getElementById('ombi-requestPending').textContent = state.ombiWebhook.triggers.requestPending ? '✓' : '✗';
document.getElementById('ombi-requestPending').className = 'trigger-value ' + (state.ombiWebhook.triggers.requestPending ? 'active' : 'inactive');
document.getElementById('ombi-requestProcessing').textContent = state.ombiWebhook.triggers.requestProcessing ? '✓' : '✗';
document.getElementById('ombi-requestProcessing').className = 'trigger-value ' + (state.ombiWebhook.triggers.requestProcessing ? 'active' : 'inactive');
}
if (state.ombiWebhook.stats) {
ombiStats.classList.remove('hidden');
document.getElementById('ombi-events').textContent = state.ombiWebhook.stats.eventsReceived ?? 0;
document.getElementById('ombi-polls').textContent = state.ombiWebhook.stats.pollsSkipped ?? 0;
document.getElementById('ombi-last').textContent = formatTimeAgo(state.ombiWebhook.stats.lastWebhookTimestamp);
} else {
ombiStats.classList.add('hidden');
}
}
export async function enableSonarrWebhook() {
@@ -198,12 +241,48 @@ export async function testRadarrWebhook() {
}
}
export async function enableOmbiWebhook() {
setWebhookLoading(true);
try {
const result = await apiEnableOmbiWebhook();
if (!result.success) {
console.error('Failed to enable Ombi webhook:', result.error);
alert('Failed to enable Ombi webhook. Check console for details.');
}
} catch (err) {
console.error('Failed to enable Ombi webhook:', err);
alert('Failed to enable Ombi webhook. Check console for details.');
} finally {
setWebhookLoading(false);
}
}
export async function testOmbiWebhook() {
setWebhookLoading(true);
try {
const result = await apiTestOmbiWebhook();
if (result.success) {
alert('Ombi webhook test sent successfully!');
} else {
console.error('Failed to test Ombi webhook:', result.error);
alert('Failed to test Ombi webhook. Check console for details.');
}
} catch (err) {
console.error('Failed to test Ombi webhook:', err);
alert('Failed to test Ombi webhook. Check console for details.');
} finally {
setWebhookLoading(false);
}
}
export function setWebhookLoading(loading) {
state.webhookLoading = loading;
document.getElementById('enable-sonarr-webhook').disabled = loading;
document.getElementById('enable-radarr-webhook').disabled = loading;
document.getElementById('enable-ombi-webhook').disabled = loading;
document.getElementById('test-sonarr-webhook').disabled = loading;
document.getElementById('test-radarr-webhook').disabled = loading;
document.getElementById('test-ombi-webhook').disabled = loading;
const loadingEl = document.getElementById('webhook-loading');
if (loading) {
loadingEl.classList.remove('hidden');