Add download client ordering and filtering to active downloads list
All checks were successful
Build and Push Docker Image / build (push) Successful in 22s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m5s
CI / Security audit (push) Successful in 1m26s
CI / Tests & coverage (push) Successful in 1m44s

This commit is contained in:
2026-05-19 23:29:38 +01:00
parent 3e06bdf8cd
commit 720de6688b
6 changed files with 143 additions and 17 deletions

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2026 Gordon Bolton. MIT License.
let currentUser = null;
let downloads = [];
let downloadClients = []; // List of download clients from server (for ordering/filtering)
let selectedDownloadClient = localStorage.getItem('sofarr-download-client') || 'all'; // Selected client filter
let isAdmin = false;
let showAll = false;
let csrfToken = null; // double-submit CSRF token, sent as X-CSRF-Token on mutating requests
@@ -30,6 +32,7 @@ document.addEventListener('DOMContentLoaded', () => {
initThemeSwitcher();
initTabs();
initHistoryControls();
initDownloadClientFilter();
initWebhooks();
loadAppVersion();
@@ -118,6 +121,11 @@ function startSSE() {
currentUser = data.user;
isAdmin = !!data.isAdmin;
downloads = data.downloads;
// Store download clients and update filter dropdown
if (data.downloadClients) {
downloadClients = data.downloadClients;
updateDownloadClientFilter();
}
document.getElementById('currentUser').textContent = currentUser || '-';
renderDownloads();
hideError();
@@ -352,28 +360,44 @@ function formatEpisodeInfo(episodes) {
function renderDownloads() {
const downloadsList = document.getElementById('downloads-list');
const noDownloads = document.getElementById('no-downloads');
if (downloads.length === 0) {
// Filter downloads by selected client
let filteredDownloads = downloads;
if (selectedDownloadClient !== 'all') {
filteredDownloads = downloads.filter(d => d.instanceId === selectedDownloadClient);
}
// Sort downloads by client order (matching the order in downloadClients)
if (downloadClients.length > 0) {
const clientOrder = new Map(downloadClients.map((c, idx) => [c.id, idx]));
filteredDownloads = [...filteredDownloads].sort((a, b) => {
const orderA = clientOrder.get(a.instanceId) ?? Infinity;
const orderB = clientOrder.get(b.instanceId) ?? Infinity;
return orderA - orderB;
});
}
if (filteredDownloads.length === 0) {
noDownloads.style.display = 'block';
downloadsList.innerHTML = '';
return;
}
noDownloads.style.display = 'none';
// Get existing cards
const existingCards = new Map();
downloadsList.querySelectorAll('.download-card').forEach(card => {
existingCards.set(card.dataset.id, card);
});
// Track which downloads we've processed
const processedIds = new Set();
downloads.forEach(download => {
filteredDownloads.forEach(download => {
const id = download.title;
processedIds.add(id);
const existingCard = existingCards.get(id);
if (existingCard) {
// Update existing card
@@ -384,7 +408,7 @@ function renderDownloads() {
downloadsList.appendChild(card);
}
});
// Remove cards for downloads that no longer exist
existingCards.forEach((card, id) => {
if (!processedIds.has(id)) {
@@ -1022,6 +1046,51 @@ function initHistoryControls() {
}
}
// =============================================================================
// Download Client Filter
// =============================================================================
function initDownloadClientFilter() {
const filterSelect = document.getElementById('download-client-filter');
if (filterSelect) {
// Set initial value from localStorage
filterSelect.value = selectedDownloadClient;
filterSelect.addEventListener('change', () => {
selectedDownloadClient = filterSelect.value;
localStorage.setItem('sofarr-download-client', selectedDownloadClient);
renderDownloads();
});
}
}
function updateDownloadClientFilter() {
const filterSelect = document.getElementById('download-client-filter');
if (!filterSelect || downloadClients.length === 0) return;
// Save current selection
const currentValue = filterSelect.value;
// Clear existing options (except "All clients")
filterSelect.innerHTML = '<option value="all">All clients</option>';
// Add options for each download client
downloadClients.forEach(client => {
const option = document.createElement('option');
option.value = client.id;
option.textContent = client.name;
filterSelect.appendChild(option);
});
// Restore selection if still valid, otherwise default to 'all'
if (currentValue && (currentValue === 'all' || downloadClients.some(c => c.id === currentValue))) {
filterSelect.value = currentValue;
} else {
filterSelect.value = 'all';
selectedDownloadClient = 'all';
localStorage.setItem('sofarr-download-client', 'all');
}
}
function startHistoryRefresh() {
stopHistoryRefresh();
historyRefreshHandle = setInterval(() => loadHistory(), HISTORY_REFRESH_MS);

View File

@@ -144,6 +144,14 @@
<div class="tab-panel" id="tab-downloads">
<div class="downloads-container">
<div class="downloads-header">
<div class="downloads-controls">
<label class="download-client-label" for="download-client-filter">Download client:</label>
<select id="download-client-filter" class="download-client-select">
<option value="all">All clients</option>
</select>
</div>
</div>
<div id="no-downloads" class="no-downloads" style="display: none;">
<p>No downloads found for your user.</p>
<p>Make sure your shows and movies are tagged with your username in Sonarr/Radarr.</p>

View File

@@ -662,6 +662,42 @@ body {
padding: 0;
}
/* Downloads header and controls */
.downloads-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.downloads-controls {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: nowrap;
}
.download-client-label {
font-size: 0.85rem;
color: var(--text-secondary);
}
.download-client-select {
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface);
color: var(--text-primary);
font-size: 0.85rem;
cursor: pointer;
}
.download-client-select:focus {
outline: none;
border-color: var(--accent);
}
.history-header {
display: flex;
align-items: center;