d3d085d614
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
321 lines
22 KiB
HTML
321 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>sofarr - Your Downloads Dashboard</title>
|
||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
|
||
<link rel="apple-touch-icon" sizes="192x192" href="favicon-192.png">
|
||
<meta name="theme-color" content="#1a1a2e">
|
||
<link rel="stylesheet" href="style.css">
|
||
</head>
|
||
<body>
|
||
<!-- Splash Screen -->
|
||
<div id="splash-screen" class="splash-screen">
|
||
<img src="images/sofarr-flashscreen.png" alt="sofarr" class="splash-logo">
|
||
</div>
|
||
|
||
<div class="app">
|
||
<!-- Login Form -->
|
||
<div id="login-container" class="login-container hidden">
|
||
<div class="login-box">
|
||
<img src="images/sofarr-flashscreen.png" alt="sofarr" class="login-logo">
|
||
<p class="login-subtitle">Login with your Emby credentials</p>
|
||
<form id="login-form">
|
||
<div class="form-group">
|
||
<label for="username">Username:</label>
|
||
<input type="text" id="username" name="username" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="password">Password:</label>
|
||
<input type="password" id="password" name="password" required>
|
||
</div>
|
||
<div class="form-group form-group--checkbox">
|
||
<label class="checkbox-label">
|
||
<input type="checkbox" id="remember-me" name="rememberMe">
|
||
<span>Keep me logged in</span>
|
||
</label>
|
||
</div>
|
||
<button type="submit" class="login-btn">Login</button>
|
||
</form>
|
||
<div id="login-error" class="error-message hidden"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Dashboard -->
|
||
<div id="dashboard-container" class="dashboard-container hidden">
|
||
<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>
|
||
<div class="header-controls">
|
||
<div class="theme-switcher">
|
||
<button class="theme-btn active" data-theme="light">Light</button>
|
||
<button class="theme-btn" data-theme="dark">Dark</button>
|
||
<button class="theme-btn" data-theme="mono">Mono</button>
|
||
</div>
|
||
<div id="admin-controls" class="admin-controls hidden">
|
||
<label class="toggle-label">
|
||
<input type="checkbox" id="show-all-toggle">
|
||
<span>Show all users</span>
|
||
</label>
|
||
<button id="status-btn" class="status-btn">Status</button>
|
||
</div>
|
||
<div class="user-info">
|
||
<span class="user-label">Current User:</span>
|
||
<span class="user-name" id="currentUser">-</span>
|
||
<button id="logout-btn" class="logout-btn">Logout</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div id="status-panel" class="status-panel hidden">
|
||
<!-- Status content gets rendered here -->
|
||
<div id="status-content"><p class="status-loading">Loading status...</p></div>
|
||
</div>
|
||
|
||
<!-- Webhooks Configuration Panel (sibling to status-panel) -->
|
||
<div class="webhooks-section hidden" id="webhooks-section">
|
||
<div class="webhooks-header" id="webhooks-header">
|
||
<h2>⚡ Webhooks Configuration</h2>
|
||
<span class="webhooks-toggle" id="webhooks-toggle">▼</span>
|
||
</div>
|
||
<div class="webhooks-content hidden" id="webhooks-content">
|
||
<div id="webhook-loading" class="webhook-loading hidden">Loading webhook status...</div>
|
||
|
||
<!-- Sonarr Webhook -->
|
||
<div class="webhook-instance">
|
||
<h3>Sonarr</h3>
|
||
<div class="webhook-status">
|
||
<span class="status-indicator" id="sonarr-status">○ Disabled</span>
|
||
<button id="enable-sonarr-webhook" class="enable-webhook-btn hidden">Enable Sofarr Webhooks</button>
|
||
<button id="test-sonarr-webhook" class="test-webhook-btn hidden">Test</button>
|
||
</div>
|
||
<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 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 Upgrade</span><span class="trigger-value" id="sonarr-onUpgrade">✗</span></div>
|
||
</div>
|
||
<div class="webhook-stats hidden" id="sonarr-stats">
|
||
<div class="webhook-stats-title">Statistics</div>
|
||
<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">Polls Skipped</span><span class="webhook-stat-value" id="sonarr-polls">0</span></div>
|
||
<div class="webhook-stat"><span class="webhook-stat-label">Last Event</span><span class="webhook-stat-value" id="sonarr-last">Never</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Radarr Webhook -->
|
||
<div class="webhook-instance">
|
||
<h3>Radarr</h3>
|
||
<div class="webhook-status">
|
||
<span class="status-indicator" id="radarr-status">○ Disabled</span>
|
||
<button id="enable-radarr-webhook" class="enable-webhook-btn hidden">Enable Sofarr Webhooks</button>
|
||
<button id="test-radarr-webhook" class="test-webhook-btn hidden">Test</button>
|
||
</div>
|
||
<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 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 Upgrade</span><span class="trigger-value" id="radarr-onUpgrade">✗</span></div>
|
||
</div>
|
||
<div class="webhook-stats hidden" id="radarr-stats">
|
||
<div class="webhook-stats-title">Statistics</div>
|
||
<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">Polls Skipped</span><span class="webhook-stat-value" id="radarr-polls">0</span></div>
|
||
<div class="webhook-stat"><span class="webhook-stat-label">Last Event</span><span class="webhook-stat-value" id="radarr-last">Never</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Ombi Webhook -->
|
||
<div class="webhook-instance">
|
||
<h3>Ombi</h3>
|
||
<div class="webhook-status">
|
||
<span class="status-indicator" id="ombi-status">○ Disabled</span>
|
||
<button id="enable-ombi-webhook" class="enable-webhook-btn hidden">Enable Sofarr Webhooks</button>
|
||
<button id="test-ombi-webhook" class="test-webhook-btn hidden">Test</button>
|
||
</div>
|
||
<div class="webhook-triggers hidden" id="ombi-triggers">
|
||
<div class="trigger-item"><span class="trigger-label">Request Available</span><span class="trigger-value" id="ombi-requestAvailable">✗</span></div>
|
||
<div class="trigger-item"><span class="trigger-label">Request Approved</span><span class="trigger-value" id="ombi-requestApproved">✗</span></div>
|
||
<div class="trigger-item"><span class="trigger-label">Request Declined</span><span class="trigger-value" id="ombi-requestDeclined">✗</span></div>
|
||
<div class="trigger-item"><span class="trigger-label">Request Pending</span><span class="trigger-value" id="ombi-requestPending">✗</span></div>
|
||
<div class="trigger-item"><span class="trigger-label">Request Processing</span><span class="trigger-value" id="ombi-requestProcessing">✗</span></div>
|
||
</div>
|
||
<div class="webhook-stats hidden" id="ombi-stats">
|
||
<div class="webhook-stats-title">Statistics</div>
|
||
<div class="webhook-stats-grid">
|
||
<div class="webhook-stat"><span class="webhook-stat-label">Events Received</span><span class="webhook-stat-value" id="ombi-events">0</span></div>
|
||
<div class="webhook-stat"><span class="webhook-stat-label">Polls Skipped</span><span class="webhook-stat-value" id="ombi-polls">0</span></div>
|
||
<div class="webhook-stat"><span class="webhook-stat-label">Last Event</span><span class="webhook-stat-value" id="ombi-last">Never</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="error-message" class="error-message hidden"></div>
|
||
|
||
<div id="loading" class="loading hidden">Loading downloads...</div>
|
||
|
||
<div class="main-tabs">
|
||
<div class="tab-bar">
|
||
<button class="tab-btn active" data-tab="downloads">Active Downloads</button>
|
||
<button class="tab-btn" data-tab="requests">Requests</button>
|
||
<button class="tab-btn" data-tab="history">Recently Completed</button>
|
||
</div>
|
||
|
||
<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>
|
||
<div class="download-client-filter" id="download-client-filter">
|
||
<button class="download-client-dropdown-btn" id="download-client-dropdown-btn" type="button" aria-expanded="false">
|
||
<span id="download-client-selected-text">All clients</span>
|
||
<span class="dropdown-arrow">▼</span>
|
||
</button>
|
||
<div class="download-client-dropdown" id="download-client-dropdown">
|
||
<div class="download-client-dropdown-header">
|
||
<button class="download-client-dropdown-btn-small" id="download-client-select-all" type="button">Select All</button>
|
||
<button class="download-client-dropdown-btn-small" id="download-client-deselect-all" type="button">Deselect All</button>
|
||
</div>
|
||
<div class="download-client-options" id="download-client-options">
|
||
<!-- Options will be populated by JavaScript -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="no-downloads" class="no-downloads hidden">
|
||
<p>No downloads found for your user.</p>
|
||
<p>Make sure your shows and movies are tagged with your username in Sonarr/Radarr.</p>
|
||
</div>
|
||
<div id="downloads-list" class="downloads-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-panel hidden" id="tab-requests">
|
||
<div class="requests-container">
|
||
<div class="requests-header">
|
||
<div class="requests-controls">
|
||
<!-- Media Type Filter -->
|
||
<div class="request-filter" id="request-type-filter">
|
||
<label class="request-filter-label">Type:</label>
|
||
<button class="request-filter-btn" id="request-type-filter-btn" type="button" aria-expanded="false">
|
||
<span id="request-type-selected-text">All</span>
|
||
<span class="dropdown-arrow">▼</span>
|
||
</button>
|
||
<div class="request-filter-dropdown" id="request-type-filter-dropdown">
|
||
<div class="request-filter-dropdown-header">
|
||
<button class="request-filter-dropdown-btn-small" id="request-type-select-all" type="button">Select All</button>
|
||
<button class="request-filter-dropdown-btn-small" id="request-type-deselect-all" type="button">Deselect All</button>
|
||
</div>
|
||
<div class="request-filter-options" id="request-type-options">
|
||
<div class="request-filter-option" data-value="movie">
|
||
<input type="checkbox" id="request-type-movie" class="request-filter-checkbox" checked>
|
||
<label for="request-type-movie">Movies</label>
|
||
</div>
|
||
<div class="request-filter-option" data-value="tv">
|
||
<input type="checkbox" id="request-type-tv" class="request-filter-checkbox" checked>
|
||
<label for="request-type-tv">TV Shows</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Status Filter -->
|
||
<div class="request-filter" id="request-status-filter">
|
||
<label class="request-filter-label">Status:</label>
|
||
<button class="request-filter-btn" id="request-status-filter-btn" type="button" aria-expanded="false">
|
||
<span id="request-status-selected-text">All</span>
|
||
<span class="dropdown-arrow">▼</span>
|
||
</button>
|
||
<div class="request-filter-dropdown" id="request-status-filter-dropdown">
|
||
<div class="request-filter-dropdown-header">
|
||
<button class="request-filter-dropdown-btn-small" id="request-status-select-all" type="button">Select All</button>
|
||
<button class="request-filter-dropdown-btn-small" id="request-status-deselect-all" type="button">Deselect All</button>
|
||
</div>
|
||
<div class="request-filter-options" id="request-status-options">
|
||
<div class="request-filter-option" data-value="pending">
|
||
<input type="checkbox" id="request-status-pending" class="request-filter-checkbox">
|
||
<label for="request-status-pending">Pending</label>
|
||
</div>
|
||
<div class="request-filter-option" data-value="approved">
|
||
<input type="checkbox" id="request-status-approved" class="request-filter-checkbox">
|
||
<label for="request-status-approved">Approved</label>
|
||
</div>
|
||
<div class="request-filter-option" data-value="available">
|
||
<input type="checkbox" id="request-status-available" class="request-filter-checkbox">
|
||
<label for="request-status-available">Available</label>
|
||
</div>
|
||
<div class="request-filter-option" data-value="denied">
|
||
<input type="checkbox" id="request-status-denied" class="request-filter-checkbox">
|
||
<label for="request-status-denied">Denied</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sort Dropdown -->
|
||
<div class="request-sort">
|
||
<label class="request-filter-label" for="request-sort-select">Sort:</label>
|
||
<select id="request-sort-select" class="request-sort-select">
|
||
<option value="requestedDate_desc">Newest to oldest</option>
|
||
<option value="requestedDate_asc">Oldest to newest</option>
|
||
<option value="title_asc">A–Z</option>
|
||
<option value="title_desc">Z–A</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Search Input -->
|
||
<div class="request-search">
|
||
<input type="text" id="request-search-input" class="request-search-input" placeholder="Search by title...">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="no-requests" class="no-requests hidden">
|
||
<p>No requests found.</p>
|
||
</div>
|
||
<div id="requests-list" class="requests-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-panel hidden" id="tab-history">
|
||
<div class="history-container" id="history-container">
|
||
<div class="history-header">
|
||
<div class="history-controls">
|
||
<label class="history-days-label" for="history-days">Last</label>
|
||
<input type="number" id="history-days" class="history-days-input" value="7" min="1" max="90">
|
||
<span class="history-days-label">days</span>
|
||
<button id="history-refresh-btn" class="history-refresh-btn" title="Refresh history">↻</button>
|
||
<label class="history-toggle-label" id="ignore-available-label" data-tooltip="Hide failed downloads where the item is already available on disk (i.e. a failed upgrade attempt)">
|
||
<input type="checkbox" id="ignore-available-toggle">
|
||
<span>Hide upgrade failures</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div id="history-loading" class="history-loading hidden">Loading history...</div>
|
||
<div id="history-error" class="history-error hidden"></div>
|
||
<div id="no-history" class="no-history hidden">
|
||
<p>No completed downloads found in this period.</p>
|
||
</div>
|
||
<div id="history-list" class="history-list"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<footer class="app-footer">
|
||
<p>Ensure your media is tagged with your username in Sonarr/Radarr to match downloads to users.</p>
|
||
<a href="https://git.i3omb.com/Gandalf/sofarr" target="_blank" rel="noopener noreferrer" class="app-version" id="app-version"></a>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="app.js"></script>
|
||
</body>
|
||
</html>
|