feat: Add Ombi request filtering and search
Build and Push Docker Image / build (push) Successful in 1m29s
Docs Check / Markdown lint (push) Successful in 1m51s
Licence Check / Licence compatibility and copyright header verification (push) Failing after 2m3s
CI / Security audit (push) Successful in 2m54s
CI / Swagger Validation & Coverage (push) Successful in 3m6s
Docs Check / Mermaid diagram parse check (push) Successful in 3m13s
CI / Tests & coverage (push) Successful in 3m31s
Build and Push Docker Image / build (push) Successful in 1m29s
Docs Check / Markdown lint (push) Successful in 1m51s
Licence Check / Licence compatibility and copyright header verification (push) Failing after 2m3s
CI / Security audit (push) Successful in 2m54s
CI / Swagger Validation & Coverage (push) Successful in 3m6s
Docs Check / Mermaid diagram parse check (push) Successful in 3m13s
CI / Tests & coverage (push) Successful in 3m31s
- Add request filters UI (type, status, sort, search) - Implement dual-layer filtering (server + client) - Add ombiFilters utility for consistent filtering logic - Persist filter preferences in localStorage - Add SSE support for real-time Ombi request updates - Add webhook endpoints for Ombi integration - Update OpenAPI spec for new endpoints - Add unit tests for filter logic and UI - Add integration tests for Ombi routes
This commit is contained in:
+35
-42
@@ -2,12 +2,15 @@
|
||||
|
||||
import { state } from '../state.js';
|
||||
import { escapeHtml } from '../utils/format.js';
|
||||
import { applyRequestFilters, getRequestStatus } from '../utils/ombiFilters.js';
|
||||
|
||||
/**
|
||||
* Helper function to extract the username from an Ombi request object.
|
||||
* The Ombi API returns requestedUser as an OmbiStore.Entities.OmbiUser object,
|
||||
* not a string, so we need to extract the username from the object.
|
||||
*
|
||||
*
|
||||
* Must stay in sync with server/utils/ombiHelpers.js
|
||||
*
|
||||
* @param {Object} request - The Ombi request object
|
||||
* @returns {string} The extracted username, or empty string if not found
|
||||
*/
|
||||
@@ -38,16 +41,34 @@ export function renderRequests() {
|
||||
...ombiRequests.tv.map(r => ({ ...r, mediaType: 'tv' }))
|
||||
];
|
||||
|
||||
// Apply client-side filters, sorting, and search
|
||||
const filtered = applyRequestFilters(allRequests, {
|
||||
types: state.selectedRequestTypes,
|
||||
statuses: state.selectedRequestStatuses,
|
||||
sort: state.requestSortMode,
|
||||
search: state.requestSearchQuery
|
||||
});
|
||||
|
||||
requestsList.innerHTML = '';
|
||||
|
||||
if (allRequests.length === 0) {
|
||||
if (noRequests) noRequests.style.display = 'block';
|
||||
if (filtered.length === 0) {
|
||||
if (noRequests) {
|
||||
noRequests.style.display = 'block';
|
||||
const p = noRequests.querySelector('p');
|
||||
if (p) {
|
||||
// Differentiate between no data from Ombi vs filters excluded everything
|
||||
const hasAnyData = allRequests.length > 0;
|
||||
p.textContent = hasAnyData
|
||||
? 'No requests match your filters.'
|
||||
: 'No requests found.';
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (noRequests) noRequests.style.display = 'none';
|
||||
|
||||
allRequests.forEach(request => {
|
||||
filtered.forEach(request => {
|
||||
const card = createRequestCard(request);
|
||||
requestsList.appendChild(card);
|
||||
});
|
||||
@@ -102,7 +123,7 @@ function createRequestCard(request) {
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'request-actions';
|
||||
|
||||
if (request.theMovieDbId) {
|
||||
if (state.ombiBaseUrl && request.theMovieDbId) {
|
||||
const ombiLink = document.createElement('a');
|
||||
ombiLink.className = 'request-link ombi-link';
|
||||
ombiLink.href = `${state.ombiBaseUrl}/details/${request.mediaType}/${request.theMovieDbId}`;
|
||||
@@ -129,46 +150,18 @@ 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';
|
||||
}
|
||||
const status = getRequestStatus(request);
|
||||
const statusTexts = {
|
||||
available: 'Available',
|
||||
denied: `Denied: ${request.deniedReason || 'No reason'}`,
|
||||
approved: 'Approved',
|
||||
pending: 'Pending',
|
||||
unknown: 'Unknown'
|
||||
};
|
||||
|
||||
badge.classList.add(status);
|
||||
badge.textContent = text;
|
||||
badge.textContent = statusTexts[status] || 'Unknown';
|
||||
|
||||
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