Fix CSP violations and ignoreAvailable reference error
Some checks failed
Build and Push Docker Image / build (push) Successful in 27s
Licence Check / Licence compatibility and copyright header verification (push) Failing after 40s
CI / Security audit (push) Successful in 54s
CI / Tests & coverage (push) Successful in 1m7s

- Add .hidden utility class to style.css for CSP compliance
- Replace all inline style='display: none' with class='hidden' in HTML
- Update all UI modules to use classList.add/remove instead of style.display
- Fix ignoreAvailable reference error in history.js (use state.ignoreAvailable)
- Rebuild client bundle with vite
This commit is contained in:
2026-05-21 00:33:57 +01:00
parent ddebe96056
commit f02c30efde
10 changed files with 163 additions and 90 deletions

View File

@@ -11,7 +11,7 @@ export function fadeOutLogin() {
const login = document.getElementById('login-container'); const login = document.getElementById('login-container');
login.classList.add('fade-out'); login.classList.add('fade-out');
login.addEventListener('transitionend', () => { login.addEventListener('transitionend', () => {
login.style.display = 'none'; login.classList.add('hidden');
login.classList.remove('fade-out'); login.classList.remove('fade-out');
resolve(); resolve();
}, { once: true }); }, { once: true });
@@ -20,7 +20,7 @@ export function fadeOutLogin() {
export function showSplash() { export function showSplash() {
const splash = document.getElementById('splash-screen'); const splash = document.getElementById('splash-screen');
splash.style.display = 'flex'; splash.classList.remove('hidden');
splash.style.opacity = '1'; splash.style.opacity = '1';
splash.classList.remove('fade-out'); splash.classList.remove('fade-out');
} }
@@ -36,12 +36,12 @@ export function dismissSplash(startTime) {
// transitionend never fires (e.g. display was toggled in same frame) // transitionend never fires (e.g. display was toggled in same frame)
const TRANSITION_MS = 400; const TRANSITION_MS = 400;
const fallback = setTimeout(() => { const fallback = setTimeout(() => {
splash.style.display = 'none'; splash.classList.add('hidden');
resolve(); resolve();
}, TRANSITION_MS + 100); }, TRANSITION_MS + 100);
splash.addEventListener('transitionend', () => { splash.addEventListener('transitionend', () => {
clearTimeout(fallback); clearTimeout(fallback);
splash.style.display = 'none'; splash.classList.add('hidden');
resolve(); resolve();
}, { once: true }); }, { once: true });
}, remaining); }, remaining);
@@ -115,22 +115,27 @@ export async function handleLogoutClick() {
} }
export function showLogin() { export function showLogin() {
document.getElementById('login-container').style.display = 'flex'; document.getElementById('login-container').classList.remove('hidden');
document.getElementById('dashboard-container').style.display = 'none'; document.getElementById('dashboard-container').classList.add('hidden');
hideLoginError(); hideLoginError();
} }
export function showDashboard() { export function showDashboard() {
document.getElementById('login-container').style.display = 'none'; document.getElementById('login-container').classList.add('hidden');
document.getElementById('dashboard-container').style.display = 'block'; document.getElementById('dashboard-container').classList.remove('hidden');
document.getElementById('currentUser').textContent = state.currentUser.name || '-'; document.getElementById('currentUser').textContent = state.currentUser.name || '-';
// Always start with status panel hidden (guards against stale display value on re-login) // Always start with status panel hidden (guards against stale display value on re-login)
const sp = document.getElementById('status-panel'); const sp = document.getElementById('status-panel');
sp.style.display = 'none'; sp.classList.add('hidden');
// Also hide webhooks-section to keep them in sync (both show/hide together) // Also hide webhooks-section to keep them in sync (both show/hide together)
const webhooksSection = document.getElementById('webhooks-section'); const webhooksSection = document.getElementById('webhooks-section');
if (webhooksSection) webhooksSection.style.display = 'none'; if (webhooksSection) webhooksSection.classList.add('hidden');
document.getElementById('admin-controls').style.display = state.isAdmin ? 'flex' : 'none'; const adminControls = document.getElementById('admin-controls');
if (state.isAdmin) {
adminControls.classList.remove('hidden');
} else {
adminControls.classList.add('hidden');
}
// Note: webhooks-section visibility is controlled by toggleStatusPanel() // Note: webhooks-section visibility is controlled by toggleStatusPanel()
// Initialise days input from saved value // Initialise days input from saved value
const daysInput = document.getElementById('history-days'); const daysInput = document.getElementById('history-days');
@@ -141,31 +146,31 @@ export function showDashboard() {
export function showLoginError(message) { export function showLoginError(message) {
const errorDiv = document.getElementById('login-error'); const errorDiv = document.getElementById('login-error');
errorDiv.textContent = message; errorDiv.textContent = message;
errorDiv.style.display = 'block'; errorDiv.classList.remove('hidden');
} }
export function hideLoginError() { export function hideLoginError() {
const errorDiv = document.getElementById('login-error'); const errorDiv = document.getElementById('login-error');
errorDiv.style.display = 'none'; errorDiv.classList.add('hidden');
} }
export function showError(message) { export function showError(message) {
const errorDiv = document.getElementById('error-message'); const errorDiv = document.getElementById('error-message');
errorDiv.textContent = message; errorDiv.textContent = message;
errorDiv.style.display = 'block'; errorDiv.classList.remove('hidden');
} }
export function hideError() { export function hideError() {
const errorDiv = document.getElementById('error-message'); const errorDiv = document.getElementById('error-message');
errorDiv.style.display = 'none'; errorDiv.classList.add('hidden');
} }
export function showLoading() { export function showLoading() {
const loading = document.getElementById('loading'); const loading = document.getElementById('loading');
loading.style.display = 'block'; loading.classList.remove('hidden');
} }
export function hideLoading() { export function hideLoading() {
const loading = document.getElementById('loading'); const loading = document.getElementById('loading');
loading.style.display = 'none'; loading.classList.add('hidden');
} }

View File

@@ -75,12 +75,12 @@ export function renderDownloads() {
} }
if (filteredDownloads.length === 0) { if (filteredDownloads.length === 0) {
noDownloads.style.display = 'block'; noDownloads.classList.remove('hidden');
downloadsList.innerHTML = ''; downloadsList.innerHTML = '';
return; return;
} }
noDownloads.style.display = 'none'; noDownloads.classList.add('hidden');
// Get existing cards // Get existing cards
const existingCards = new Map(); const existingCards = new Map();

View File

@@ -13,17 +13,17 @@ export function initDownloadClientFilter() {
filterBtn.addEventListener('click', (e) => { filterBtn.addEventListener('click', (e) => {
e.stopPropagation(); e.stopPropagation();
filterDropdown.style.display = filterDropdown.style.display === 'block' ? 'none' : 'block'; filterDropdown.classList.toggle('open');
}); });
filterClose.addEventListener('click', () => { filterClose.addEventListener('click', () => {
filterDropdown.style.display = 'none'; filterDropdown.classList.remove('open');
}); });
// Close dropdown when clicking outside // Close dropdown when clicking outside
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
if (!filterDropdown.contains(e.target) && e.target !== filterBtn) { if (!filterDropdown.contains(e.target) && e.target !== filterBtn) {
filterDropdown.style.display = 'none'; filterDropdown.classList.remove('open');
} }
}); });

View File

@@ -24,11 +24,11 @@ export function initHistoryControls() {
refreshBtn.addEventListener('click', () => loadHistory(true)); refreshBtn.addEventListener('click', () => loadHistory(true));
} }
if (ignoreToggle) { if (ignoreToggle) {
ignoreToggle.checked = ignoreAvailable; ignoreToggle.checked = state.ignoreAvailable;
ignoreToggle.addEventListener('change', () => { ignoreToggle.addEventListener('change', () => {
ignoreAvailable = ignoreToggle.checked; state.ignoreAvailable = ignoreToggle.checked;
saveIgnoreAvailable(ignoreAvailable); saveIgnoreAvailable(state.ignoreAvailable);
renderHistory(lastHistoryItems); renderHistory(state.lastHistoryItems);
}); });
} }
@@ -53,8 +53,8 @@ export function stopHistoryRefresh() {
export function clearHistory() { export function clearHistory() {
state.lastHistoryItems = []; state.lastHistoryItems = [];
document.getElementById('history-list').innerHTML = ''; document.getElementById('history-list').innerHTML = '';
document.getElementById('no-history').style.display = 'none'; document.getElementById('no-history').classList.add('hidden');
document.getElementById('history-error').style.display = 'none'; document.getElementById('history-error').classList.add('hidden');
} }
export async function loadHistory(forceRefresh = false) { export async function loadHistory(forceRefresh = false) {
@@ -63,24 +63,24 @@ export async function loadHistory(forceRefresh = false) {
const errorEl = document.getElementById('history-error'); const errorEl = document.getElementById('history-error');
const noHistoryEl = document.getElementById('no-history'); const noHistoryEl = document.getElementById('no-history');
loadingEl.style.display = 'block'; loadingEl.classList.remove('hidden');
errorEl.style.display = 'none'; errorEl.classList.add('hidden');
noHistoryEl.style.display = 'none'; noHistoryEl.classList.add('hidden');
try { try {
const result = await apiLoadHistory(forceRefresh); const result = await apiLoadHistory(forceRefresh);
loadingEl.style.display = 'none'; loadingEl.classList.add('hidden');
if (result.success) { if (result.success) {
state.lastHistoryItems = result.history; state.lastHistoryItems = result.history;
renderHistory(state.lastHistoryItems); renderHistory(state.lastHistoryItems);
} else { } else {
errorEl.textContent = result.error || 'Failed to load history.'; errorEl.textContent = result.error || 'Failed to load history.';
errorEl.style.display = 'block'; errorEl.classList.remove('hidden');
} }
} catch (err) { } catch (err) {
loadingEl.style.display = 'none'; loadingEl.classList.add('hidden');
errorEl.textContent = 'Failed to load history.'; errorEl.textContent = 'Failed to load history.';
errorEl.style.display = 'block'; errorEl.classList.remove('hidden');
console.error('[History] Load error:', err); console.error('[History] Load error:', err);
} }
} }
@@ -93,10 +93,10 @@ export function renderHistory(items) {
? items.filter(item => !(item.outcome === 'failed' && item.availableForUpgrade)) ? items.filter(item => !(item.outcome === 'failed' && item.availableForUpgrade))
: items; : items;
if (!visible.length) { if (!visible.length) {
noHistoryEl.style.display = 'block'; noHistoryEl.classList.remove('hidden');
return; return;
} }
noHistoryEl.style.display = 'none'; noHistoryEl.classList.add('hidden');
visible.forEach(item => listEl.appendChild(createHistoryCard(item))); visible.forEach(item => listEl.appendChild(createHistoryCard(item)));
} }

View File

@@ -7,24 +7,24 @@ import { fetchWebhookStatus } from './webhooks.js';
export async function toggleStatusPanel() { export async function toggleStatusPanel() {
const panel = document.getElementById('status-panel'); const panel = document.getElementById('status-panel');
const webhooksSection = document.getElementById('webhooks-section'); const webhooksSection = document.getElementById('webhooks-section');
if (panel.style.display !== 'none') { if (!panel.classList.contains('hidden')) {
// Close both panels (webhooks is a sibling, hide it too) // Close both panels (webhooks is a sibling, hide it too)
panel.style.display = 'none'; panel.classList.add('hidden');
if (webhooksSection) webhooksSection.style.display = 'none'; if (webhooksSection) webhooksSection.classList.add('hidden');
if (state.statusRefreshHandle) { clearInterval(state.statusRefreshHandle); state.statusRefreshHandle = null; } if (state.statusRefreshHandle) { clearInterval(state.statusRefreshHandle); state.statusRefreshHandle = null; }
return; return;
} }
// Open status panel and webhooks section (siblings) // Open status panel and webhooks section (siblings)
panel.style.display = 'block'; panel.classList.remove('hidden');
// Show webhooks section for admin users (collapsed by default) // Show webhooks section for admin users (collapsed by default)
if (webhooksSection && state.isAdmin) { if (webhooksSection && state.isAdmin) {
webhooksSection.style.display = 'block'; webhooksSection.classList.remove('hidden');
state.webhookSectionExpanded = false; state.webhookSectionExpanded = false;
document.getElementById('webhooks-content').style.display = 'none'; document.getElementById('webhooks-content').classList.add('hidden');
document.getElementById('webhooks-toggle').classList.remove('expanded'); document.getElementById('webhooks-toggle').classList.remove('expanded');
await fetchWebhookStatus(); await fetchWebhookStatus();
} else if (webhooksSection) { } else if (webhooksSection) {
webhooksSection.style.display = 'none'; webhooksSection.classList.add('hidden');
} }
refreshStatusPanel(); refreshStatusPanel();
if (state.statusRefreshHandle) clearInterval(state.statusRefreshHandle); if (state.statusRefreshHandle) clearInterval(state.statusRefreshHandle);
@@ -32,9 +32,9 @@ export async function toggleStatusPanel() {
} }
export function closeStatusPanel() { export function closeStatusPanel() {
document.getElementById('status-panel').style.display = 'none'; document.getElementById('status-panel').classList.add('hidden');
const webhooksSection = document.getElementById('webhooks-section'); const webhooksSection = document.getElementById('webhooks-section');
if (webhooksSection) webhooksSection.style.display = 'none'; if (webhooksSection) webhooksSection.classList.add('hidden');
if (state.statusRefreshHandle) { clearInterval(state.statusRefreshHandle); state.statusRefreshHandle = null; } if (state.statusRefreshHandle) { clearInterval(state.statusRefreshHandle); state.statusRefreshHandle = null; }
} }
@@ -42,7 +42,7 @@ export async function refreshStatusPanel() {
const panel = document.getElementById('status-panel'); const panel = document.getElementById('status-panel');
const contentDiv = document.getElementById('status-content'); const contentDiv = document.getElementById('status-content');
console.log('[Status] panel found:', !!panel, 'contentDiv found:', !!contentDiv, 'panel display:', panel?.style?.display); console.log('[Status] panel found:', !!panel, 'contentDiv found:', !!contentDiv, 'panel display:', panel?.style?.display);
if (!panel || panel.style.display === 'none') return; if (!panel || panel.classList.contains('hidden')) return;
console.log('[Status] Refreshing status panel...'); console.log('[Status] Refreshing status panel...');
try { try {
const result = await apiRefreshStatusPanel(); const result = await apiRefreshStatusPanel();

View File

@@ -26,20 +26,20 @@ export function initTabs() {
export function activateTab(tab) { export function activateTab(tab) {
const downloadsTab = document.getElementById('downloads-tab'); const downloadsTab = document.getElementById('downloads-tab');
const historyTab = document.getElementById('history-tab'); const historyTab = document.getElementById('history-tab');
const downloadsSection = document.getElementById('downloads-section'); const downloadsSection = document.getElementById('tab-downloads');
const historySection = document.getElementById('history-section'); const historySection = document.getElementById('tab-history');
if (tab === 'downloads') { if (tab === 'downloads') {
downloadsTab.classList.add('active'); downloadsTab.classList.add('active');
historyTab.classList.remove('active'); historyTab.classList.remove('active');
downloadsSection.style.display = 'block'; downloadsSection.classList.remove('hidden');
historySection.style.display = 'none'; historySection.classList.add('hidden');
saveActiveTab('downloads'); saveActiveTab('downloads');
} else if (tab === 'history') { } else if (tab === 'history') {
historyTab.classList.add('active'); historyTab.classList.add('active');
downloadsTab.classList.remove('active'); downloadsTab.classList.remove('active');
historySection.style.display = 'block'; historySection.classList.remove('hidden');
downloadsSection.style.display = 'none'; downloadsSection.classList.add('hidden');
saveActiveTab('history'); saveActiveTab('history');
loadHistory(); loadHistory();
} }

View File

@@ -22,7 +22,11 @@ export function toggleWebhookSection() {
const content = document.getElementById('webhooks-content'); const content = document.getElementById('webhooks-content');
const toggle = document.getElementById('webhooks-toggle'); const toggle = document.getElementById('webhooks-toggle');
content.style.display = state.webhookSectionExpanded ? '' : 'none'; if (state.webhookSectionExpanded) {
content.classList.remove('hidden');
} else {
content.classList.add('hidden');
}
toggle.classList.toggle('expanded', state.webhookSectionExpanded); toggle.classList.toggle('expanded', state.webhookSectionExpanded);
if (state.webhookSectionExpanded) { if (state.webhookSectionExpanded) {
@@ -32,7 +36,7 @@ export function toggleWebhookSection() {
export async function fetchWebhookStatus() { export async function fetchWebhookStatus() {
const loadingEl = document.getElementById('webhook-loading'); const loadingEl = document.getElementById('webhook-loading');
loadingEl.style.display = ''; loadingEl.classList.remove('hidden');
try { try {
const result = await apiFetchWebhookStatus(); const result = await apiFetchWebhookStatus();
@@ -42,7 +46,7 @@ export async function fetchWebhookStatus() {
} catch (err) { } catch (err) {
console.error('Failed to fetch webhook status:', err); console.error('Failed to fetch webhook status:', err);
} finally { } finally {
loadingEl.style.display = 'none'; loadingEl.classList.add('hidden');
} }
} }
@@ -56,9 +60,15 @@ export function renderWebhookStatus() {
sonarrStatus.textContent = sonarrWebhook.enabled ? '● Enabled' : '○ Disabled'; sonarrStatus.textContent = sonarrWebhook.enabled ? '● Enabled' : '○ Disabled';
sonarrStatus.className = 'status-indicator ' + (sonarrWebhook.enabled ? 'enabled' : 'disabled'); sonarrStatus.className = 'status-indicator ' + (sonarrWebhook.enabled ? 'enabled' : 'disabled');
sonarrEnableBtn.style.display = sonarrWebhook.enabled ? 'none' : ''; if (sonarrWebhook.enabled) {
sonarrTestBtn.style.display = sonarrWebhook.enabled ? '' : 'none'; sonarrEnableBtn.classList.add('hidden');
sonarrTriggers.style.display = sonarrWebhook.enabled ? '' : 'none'; sonarrTestBtn.classList.remove('hidden');
sonarrTriggers.classList.remove('hidden');
} else {
sonarrEnableBtn.classList.remove('hidden');
sonarrTestBtn.classList.add('hidden');
sonarrTriggers.classList.add('hidden');
}
if (sonarrWebhook.enabled) { if (sonarrWebhook.enabled) {
document.getElementById('sonarr-onGrab').textContent = sonarrWebhook.triggers.onGrab ? '✓' : '✗'; document.getElementById('sonarr-onGrab').textContent = sonarrWebhook.triggers.onGrab ? '✓' : '✗';
@@ -72,12 +82,12 @@ export function renderWebhookStatus() {
} }
if (sonarrWebhook.stats) { if (sonarrWebhook.stats) {
sonarrStats.style.display = ''; sonarrStats.classList.remove('hidden');
document.getElementById('sonarr-events').textContent = sonarrWebhook.stats.eventsReceived ?? 0; document.getElementById('sonarr-events').textContent = sonarrWebhook.stats.eventsReceived ?? 0;
document.getElementById('sonarr-polls').textContent = sonarrWebhook.stats.pollsSkipped ?? 0; document.getElementById('sonarr-polls').textContent = sonarrWebhook.stats.pollsSkipped ?? 0;
document.getElementById('sonarr-last').textContent = formatTimeAgo(sonarrWebhook.stats.lastWebhookTimestamp); document.getElementById('sonarr-last').textContent = formatTimeAgo(sonarrWebhook.stats.lastWebhookTimestamp);
} else { } else {
sonarrStats.style.display = 'none'; sonarrStats.classList.add('hidden');
} }
// Radarr // Radarr
@@ -89,9 +99,15 @@ export function renderWebhookStatus() {
radarrStatus.textContent = radarrWebhook.enabled ? '● Enabled' : '○ Disabled'; radarrStatus.textContent = radarrWebhook.enabled ? '● Enabled' : '○ Disabled';
radarrStatus.className = 'status-indicator ' + (radarrWebhook.enabled ? 'enabled' : 'disabled'); radarrStatus.className = 'status-indicator ' + (radarrWebhook.enabled ? 'enabled' : 'disabled');
radarrEnableBtn.style.display = radarrWebhook.enabled ? 'none' : ''; if (radarrWebhook.enabled) {
radarrTestBtn.style.display = radarrWebhook.enabled ? '' : 'none'; radarrEnableBtn.classList.add('hidden');
radarrTriggers.style.display = radarrWebhook.enabled ? '' : 'none'; radarrTestBtn.classList.remove('hidden');
radarrTriggers.classList.remove('hidden');
} else {
radarrEnableBtn.classList.remove('hidden');
radarrTestBtn.classList.add('hidden');
radarrTriggers.classList.add('hidden');
}
if (radarrWebhook.enabled) { if (radarrWebhook.enabled) {
document.getElementById('radarr-onGrab').textContent = radarrWebhook.triggers.onGrab ? '✓' : '✗'; document.getElementById('radarr-onGrab').textContent = radarrWebhook.triggers.onGrab ? '✓' : '✗';
@@ -105,12 +121,12 @@ export function renderWebhookStatus() {
} }
if (radarrWebhook.stats) { if (radarrWebhook.stats) {
radarrStats.style.display = ''; radarrStats.classList.remove('hidden');
document.getElementById('radarr-events').textContent = radarrWebhook.stats.eventsReceived ?? 0; document.getElementById('radarr-events').textContent = radarrWebhook.stats.eventsReceived ?? 0;
document.getElementById('radarr-polls').textContent = radarrWebhook.stats.pollsSkipped ?? 0; document.getElementById('radarr-polls').textContent = radarrWebhook.stats.pollsSkipped ?? 0;
document.getElementById('radarr-last').textContent = formatTimeAgo(radarrWebhook.stats.lastWebhookTimestamp); document.getElementById('radarr-last').textContent = formatTimeAgo(radarrWebhook.stats.lastWebhookTimestamp);
} else { } else {
radarrStats.style.display = 'none'; radarrStats.classList.add('hidden');
} }
} }
@@ -188,5 +204,10 @@ export function setWebhookLoading(loading) {
document.getElementById('enable-radarr-webhook').disabled = loading; document.getElementById('enable-radarr-webhook').disabled = loading;
document.getElementById('test-sonarr-webhook').disabled = loading; document.getElementById('test-sonarr-webhook').disabled = loading;
document.getElementById('test-radarr-webhook').disabled = loading; document.getElementById('test-radarr-webhook').disabled = loading;
document.getElementById('webhook-loading').style.display = loading ? '' : 'none'; const loadingEl = document.getElementById('webhook-loading');
if (loading) {
loadingEl.classList.remove('hidden');
} else {
loadingEl.classList.add('hidden');
}
} }

42
public/app.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -18,7 +18,7 @@
<div class="app"> <div class="app">
<!-- Login Form --> <!-- Login Form -->
<div id="login-container" class="login-container" style="display: none;"> <div id="login-container" class="login-container hidden">
<div class="login-box"> <div class="login-box">
<img src="images/sofarr-flashscreen.png" alt="sofarr" class="login-logo"> <img src="images/sofarr-flashscreen.png" alt="sofarr" class="login-logo">
<p class="login-subtitle">Login with your Emby credentials</p> <p class="login-subtitle">Login with your Emby credentials</p>
@@ -39,12 +39,12 @@
</div> </div>
<button type="submit" class="login-btn">Login</button> <button type="submit" class="login-btn">Login</button>
</form> </form>
<div id="login-error" class="error-message" style="display: none;"></div> <div id="login-error" class="error-message hidden"></div>
</div> </div>
</div> </div>
<!-- Dashboard --> <!-- Dashboard -->
<div id="dashboard-container" class="dashboard-container" style="display: none;"> <div id="dashboard-container" class="dashboard-container hidden">
<header class="app-header"> <header class="app-header">
<h1><a href="#" class="title-link" id="title-home-link"><img src="favicon-192.png" alt="" class="title-logo">sofarr</a></h1> <h1><a href="#" class="title-link" id="title-home-link"><img src="favicon-192.png" alt="" class="title-logo">sofarr</a></h1>
<div class="header-controls"> <div class="header-controls">
@@ -53,7 +53,7 @@
<button class="theme-btn" data-theme="dark">Dark</button> <button class="theme-btn" data-theme="dark">Dark</button>
<button class="theme-btn" data-theme="mono">Mono</button> <button class="theme-btn" data-theme="mono">Mono</button>
</div> </div>
<div id="admin-controls" class="admin-controls" style="display: none;"> <div id="admin-controls" class="admin-controls hidden">
<label class="toggle-label"> <label class="toggle-label">
<input type="checkbox" id="show-all-toggle"> <input type="checkbox" id="show-all-toggle">
<span>Show all users</span> <span>Show all users</span>
@@ -68,35 +68,35 @@
</div> </div>
</header> </header>
<div id="status-panel" class="status-panel" style="display: none;"> <div id="status-panel" class="status-panel hidden">
<!-- Status content gets rendered here --> <!-- Status content gets rendered here -->
<div id="status-content"><p class="status-loading">Loading status...</p></div> <div id="status-content"><p class="status-loading">Loading status...</p></div>
</div> </div>
<!-- Webhooks Configuration Panel (sibling to status-panel) --> <!-- Webhooks Configuration Panel (sibling to status-panel) -->
<div class="webhooks-section" id="webhooks-section" style="display: none;"> <div class="webhooks-section hidden" id="webhooks-section">
<div class="webhooks-header" id="webhooks-header"> <div class="webhooks-header" id="webhooks-header">
<h2>⚡ Webhooks Configuration</h2> <h2>⚡ Webhooks Configuration</h2>
<span class="webhooks-toggle" id="webhooks-toggle"></span> <span class="webhooks-toggle" id="webhooks-toggle"></span>
</div> </div>
<div class="webhooks-content" id="webhooks-content" style="display: none;"> <div class="webhooks-content hidden" id="webhooks-content">
<div id="webhook-loading" class="webhook-loading" style="display: none;">Loading webhook status...</div> <div id="webhook-loading" class="webhook-loading hidden">Loading webhook status...</div>
<!-- Sonarr Webhook --> <!-- Sonarr Webhook -->
<div class="webhook-instance"> <div class="webhook-instance">
<h3>Sonarr</h3> <h3>Sonarr</h3>
<div class="webhook-status"> <div class="webhook-status">
<span class="status-indicator" id="sonarr-status">○ Disabled</span> <span class="status-indicator" id="sonarr-status">○ Disabled</span>
<button id="enable-sonarr-webhook" class="enable-webhook-btn" style="display: none;">Enable Sofarr Webhooks</button> <button id="enable-sonarr-webhook" class="enable-webhook-btn hidden">Enable Sofarr Webhooks</button>
<button id="test-sonarr-webhook" class="test-webhook-btn" style="display: none;">Test</button> <button id="test-sonarr-webhook" class="test-webhook-btn hidden">Test</button>
</div> </div>
<div class="webhook-triggers" id="sonarr-triggers" style="display: none;"> <div class="webhook-triggers hidden" id="sonarr-triggers">
<div class="trigger-item"><span class="trigger-label">On Grab</span><span class="trigger-value" id="sonarr-onGrab"></span></div> <div class="trigger-item"><span class="trigger-label">On Grab</span><span class="trigger-value" id="sonarr-onGrab"></span></div>
<div class="trigger-item"><span class="trigger-label">On Download</span><span class="trigger-value" id="sonarr-onDownload"></span></div> <div class="trigger-item"><span class="trigger-label">On Download</span><span class="trigger-value" id="sonarr-onDownload"></span></div>
<div class="trigger-item"><span class="trigger-label">On Import</span><span class="trigger-value" id="sonarr-onImport"></span></div> <div class="trigger-item"><span class="trigger-label">On Import</span><span class="trigger-value" id="sonarr-onImport"></span></div>
<div class="trigger-item"><span class="trigger-label">On Upgrade</span><span class="trigger-value" id="sonarr-onUpgrade"></span></div> <div class="trigger-item"><span class="trigger-label">On Upgrade</span><span class="trigger-value" id="sonarr-onUpgrade"></span></div>
</div> </div>
<div class="webhook-stats" id="sonarr-stats" style="display: none;"> <div class="webhook-stats hidden" id="sonarr-stats">
<div class="webhook-stats-title">Statistics</div> <div class="webhook-stats-title">Statistics</div>
<div class="webhook-stats-grid"> <div class="webhook-stats-grid">
<div class="webhook-stat"><span class="webhook-stat-label">Events Received</span><span class="webhook-stat-value" id="sonarr-events">0</span></div> <div class="webhook-stat"><span class="webhook-stat-label">Events Received</span><span class="webhook-stat-value" id="sonarr-events">0</span></div>
@@ -111,16 +111,16 @@
<h3>Radarr</h3> <h3>Radarr</h3>
<div class="webhook-status"> <div class="webhook-status">
<span class="status-indicator" id="radarr-status">○ Disabled</span> <span class="status-indicator" id="radarr-status">○ Disabled</span>
<button id="enable-radarr-webhook" class="enable-webhook-btn" style="display: none;">Enable Sofarr Webhooks</button> <button id="enable-radarr-webhook" class="enable-webhook-btn hidden">Enable Sofarr Webhooks</button>
<button id="test-radarr-webhook" class="test-webhook-btn" style="display: none;">Test</button> <button id="test-radarr-webhook" class="test-webhook-btn hidden">Test</button>
</div> </div>
<div class="webhook-triggers" id="radarr-triggers" style="display: none;"> <div class="webhook-triggers hidden" id="radarr-triggers">
<div class="trigger-item"><span class="trigger-label">On Grab</span><span class="trigger-value" id="radarr-onGrab"></span></div> <div class="trigger-item"><span class="trigger-label">On Grab</span><span class="trigger-value" id="radarr-onGrab"></span></div>
<div class="trigger-item"><span class="trigger-label">On Download</span><span class="trigger-value" id="radarr-onDownload"></span></div> <div class="trigger-item"><span class="trigger-label">On Download</span><span class="trigger-value" id="radarr-onDownload"></span></div>
<div class="trigger-item"><span class="trigger-label">On Import</span><span class="trigger-value" id="radarr-onImport"></span></div> <div class="trigger-item"><span class="trigger-label">On Import</span><span class="trigger-value" id="radarr-onImport"></span></div>
<div class="trigger-item"><span class="trigger-label">On Upgrade</span><span class="trigger-value" id="radarr-onUpgrade"></span></div> <div class="trigger-item"><span class="trigger-label">On Upgrade</span><span class="trigger-value" id="radarr-onUpgrade"></span></div>
</div> </div>
<div class="webhook-stats" id="radarr-stats" style="display: none;"> <div class="webhook-stats hidden" id="radarr-stats">
<div class="webhook-stats-title">Statistics</div> <div class="webhook-stats-title">Statistics</div>
<div class="webhook-stats-grid"> <div class="webhook-stats-grid">
<div class="webhook-stat"><span class="webhook-stat-label">Events Received</span><span class="webhook-stat-value" id="radarr-events">0</span></div> <div class="webhook-stat"><span class="webhook-stat-label">Events Received</span><span class="webhook-stat-value" id="radarr-events">0</span></div>
@@ -132,9 +132,9 @@
</div> </div>
</div> </div>
<div id="error-message" class="error-message" style="display: none;"></div> <div id="error-message" class="error-message hidden"></div>
<div id="loading" class="loading" style="display: none;">Loading downloads...</div> <div id="loading" class="loading hidden">Loading downloads...</div>
<div class="main-tabs"> <div class="main-tabs">
<div class="tab-bar"> <div class="tab-bar">
@@ -164,7 +164,7 @@
</div> </div>
</div> </div>
</div> </div>
<div id="no-downloads" class="no-downloads" style="display: none;"> <div id="no-downloads" class="no-downloads hidden">
<p>No downloads found for your user.</p> <p>No downloads found for your user.</p>
<p>Make sure your shows and movies are tagged with your username in Sonarr/Radarr.</p> <p>Make sure your shows and movies are tagged with your username in Sonarr/Radarr.</p>
</div> </div>
@@ -172,7 +172,7 @@
</div> </div>
</div> </div>
<div class="tab-panel" id="tab-history" style="display: none;"> <div class="tab-panel hidden" id="tab-history">
<div class="history-container" id="history-container"> <div class="history-container" id="history-container">
<div class="history-header"> <div class="history-header">
<div class="history-controls"> <div class="history-controls">
@@ -186,9 +186,9 @@
</label> </label>
</div> </div>
</div> </div>
<div id="history-loading" class="history-loading" style="display: none;">Loading history...</div> <div id="history-loading" class="history-loading hidden">Loading history...</div>
<div id="history-error" class="history-error" style="display: none;"></div> <div id="history-error" class="history-error hidden"></div>
<div id="no-history" class="no-history" style="display: none;"> <div id="no-history" class="no-history hidden">
<p>No completed downloads found in this period.</p> <p>No completed downloads found in this period.</p>
</div> </div>
<div id="history-list" class="history-list"></div> <div id="history-list" class="history-list"></div>

View File

@@ -1,3 +1,8 @@
/* ===== Utility Classes ===== */
.hidden {
display: none !important;
}
/* ===== Splash Screen ===== */ /* ===== Splash Screen ===== */
.splash-screen { .splash-screen {
position: fixed; position: fixed;