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:
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
||||
|
||||
import { state } from '../state.js';
|
||||
import { escapeHtml } from '../utils/format.js';
|
||||
|
||||
export function renderRequests() {
|
||||
const requestsList = document.getElementById('requests-list');
|
||||
const noRequests = document.getElementById('no-requests');
|
||||
|
||||
if (!requestsList) return;
|
||||
|
||||
const ombiRequests = state.ombiRequests || { movie: [], tv: [] };
|
||||
const allRequests = [
|
||||
...ombiRequests.movie.map(r => ({ ...r, mediaType: 'movie' })),
|
||||
...ombiRequests.tv.map(r => ({ ...r, mediaType: 'tv' }))
|
||||
];
|
||||
|
||||
requestsList.innerHTML = '';
|
||||
|
||||
if (allRequests.length === 0) {
|
||||
if (noRequests) noRequests.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
if (noRequests) noRequests.style.display = 'none';
|
||||
|
||||
allRequests.forEach(request => {
|
||||
const card = createRequestCard(request);
|
||||
requestsList.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function createRequestCard(request) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'request-card';
|
||||
|
||||
const typeIcon = document.createElement('span');
|
||||
typeIcon.className = `request-type-icon ${request.mediaType}`;
|
||||
typeIcon.textContent = request.mediaType === 'movie' ? '🎬' : '📺';
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'request-content';
|
||||
|
||||
const title = document.createElement('div');
|
||||
title.className = 'request-title';
|
||||
title.textContent = request.title || 'Unknown Title';
|
||||
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'request-meta';
|
||||
|
||||
const statusBadge = createStatusBadge(request);
|
||||
meta.appendChild(statusBadge);
|
||||
|
||||
if (request.year) {
|
||||
const year = document.createElement('span');
|
||||
year.className = 'request-year';
|
||||
year.textContent = request.year;
|
||||
meta.appendChild(year);
|
||||
}
|
||||
|
||||
if (request.requestedUser || request.requestedByAlias) {
|
||||
const user = document.createElement('span');
|
||||
user.className = 'request-user';
|
||||
user.textContent = `Requested by: ${request.requestedUser || request.requestedByAlias}`;
|
||||
meta.appendChild(user);
|
||||
}
|
||||
|
||||
if (request.quality) {
|
||||
const quality = document.createElement('span');
|
||||
quality.className = 'request-quality';
|
||||
quality.textContent = request.quality;
|
||||
meta.appendChild(quality);
|
||||
}
|
||||
|
||||
content.appendChild(title);
|
||||
content.appendChild(meta);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'request-actions';
|
||||
|
||||
if (request.theMovieDbId) {
|
||||
const ombiLink = document.createElement('a');
|
||||
ombiLink.className = 'request-link ombi-link';
|
||||
ombiLink.href = `${state.ombiBaseUrl}/details/${request.mediaType}/${request.theMovieDbId}`;
|
||||
ombiLink.target = '_blank';
|
||||
ombiLink.title = 'View in Ombi';
|
||||
|
||||
const ombiIcon = document.createElement('img');
|
||||
ombiIcon.src = '/images/ombi.svg';
|
||||
ombiIcon.alt = 'Ombi';
|
||||
ombiIcon.className = 'request-icon';
|
||||
|
||||
ombiLink.appendChild(ombiIcon);
|
||||
actions.appendChild(ombiLink);
|
||||
}
|
||||
|
||||
card.appendChild(typeIcon);
|
||||
card.appendChild(content);
|
||||
card.appendChild(actions);
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
function createStatusBadge(request) {
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'request-status-badge';
|
||||
|
||||
let status = 'unknown';
|
||||
let text = 'Unknown';
|
||||
|
||||
if (request.available) {
|
||||
status = 'available';
|
||||
text = 'Available';
|
||||
} else if (request.approved) {
|
||||
status = 'approved';
|
||||
text = 'Approved';
|
||||
} else if (request.denied) {
|
||||
status = 'denied';
|
||||
text = `Denied: ${request.deniedReason || 'No reason'}`;
|
||||
} else if (request.requested) {
|
||||
status = 'pending';
|
||||
text = 'Pending';
|
||||
}
|
||||
|
||||
badge.classList.add(status);
|
||||
badge.textContent = text;
|
||||
|
||||
return badge;
|
||||
}
|
||||
|
||||
export function setupRequestsTab() {
|
||||
// Listen for SSE updates
|
||||
if (state.sseSource) {
|
||||
state.sseSource.addEventListener('message', (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.ombiRequests) {
|
||||
state.ombiRequests = data.ombiRequests;
|
||||
renderRequests();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Also listen for custom event triggered from sse.js
|
||||
document.addEventListener('ombiRequestsUpdated', () => {
|
||||
renderRequests();
|
||||
});
|
||||
|
||||
// Initial render
|
||||
renderRequests();
|
||||
}
|
||||
Reference in New Issue
Block a user