From 2b8d5306f4bdb60000ccddecb67c19e9d4e7262b Mon Sep 17 00:00:00 2001 From: Admin9705 <9705@duck.com> Date: Thu, 19 Feb 2026 11:49:30 -0500 Subject: [PATCH] Refactor --- frontend/static/css/requestarr-users.css | 162 ++++++------------ frontend/static/css/sidebar.css | 24 ++- frontend/static/js/app.js | 19 +- frontend/static/js/dist/bundle-app.js | 19 +- frontend/static/js/dist/requestarr-bundle.js | 138 ++++++++++----- .../features/requestarr/requestarr-content.js | 14 +- .../requestarr/requestarr-services.js | 110 ++++++++---- .../requestarr/requestarr-settings.js | 14 +- frontend/templates/components/scripts.html | 4 +- frontend/templates/components/sidebar.html | 48 ++++-- src/primary/utils/database.py | 32 +++- 11 files changed, 336 insertions(+), 248 deletions(-) diff --git a/frontend/static/css/requestarr-users.css b/frontend/static/css/requestarr-users.css index fc57f78e..f0f2c804 100644 --- a/frontend/static/css/requestarr-users.css +++ b/frontend/static/css/requestarr-users.css @@ -337,130 +337,70 @@ /* ── Services Page ────────────────────────────────────────── */ -.reqservices-section { - margin-bottom: 32px; +/* ── Services Page (uses shared instance-card system from instances-card.css) ── */ +/* Matches Media Hunt Instances bordered-section design exactly */ + +.reqservices-group { + background: rgba(15, 23, 42, 0.4); + border: 1px solid rgba(148, 163, 184, 0.08); + border-radius: 12px; + padding: 24px; + margin: 12px 0 20px 0; } -.reqservices-section-header { +.reqservices-group .profiles-header { display: flex; - align-items: center; + align-items: flex-start; justify-content: space-between; - margin-bottom: 12px; + gap: 16px; + margin-bottom: 4px; } -.reqservices-section-title { - font-size: 1.15rem; - font-weight: 600; - color: var(--text-primary); +.reqservices-group .profiles-header h3 { margin: 0; - display: flex; - align-items: center; - gap: 8px; -} - -.reqservices-section-desc { - font-size: 0.85rem; - color: var(--text-muted); - margin-bottom: 16px; -} - -.reqservices-card { - display: flex; - align-items: center; - justify-content: space-between; - padding: 14px 16px; - background: var(--glass-bg); - border: 1px solid var(--glass-border); - border-radius: var(--radius-md); - margin-bottom: 8px; - backdrop-filter: blur(var(--glass-blur)); -} - -.reqservices-card-left { - display: flex; - align-items: center; - gap: 12px; -} - -.reqservices-card-icon { - width: 36px; - height: 36px; - border-radius: var(--radius-sm); - display: flex; - align-items: center; - justify-content: center; - font-size: 1rem; -} - -.reqservices-card-icon.movies { - background: rgba(234, 179, 8, 0.12); - color: #eab308; -} - -.reqservices-card-icon.tv { - background: rgba(99, 102, 241, 0.12); - color: #818cf8; -} - -.reqservices-card-info { - display: flex; - flex-direction: column; - gap: 2px; -} - -.reqservices-card-name { - font-weight: 500; - color: var(--text-primary); - font-size: 0.9rem; -} - -.reqservices-card-type { - font-size: 0.78rem; - color: var(--text-muted); -} - -.reqservices-card-badges { - display: flex; - gap: 6px; - align-items: center; -} - -.reqservices-badge { - padding: 2px 8px; - border-radius: 10px; - font-size: 0.7rem; + padding: 0 0 12px 0; + font-size: 1.1rem; font-weight: 600; + color: #f1f5f9; + border-bottom: 1px solid rgba(148, 163, 184, 0.12); } -.reqservices-badge-default { - background: rgba(34, 197, 94, 0.15); - color: #22c55e; +.reqservices-group .profiles-help { + color: rgba(255, 255, 255, 0.55); + font-size: 13px; + margin: 12px 0 0; } -.reqservices-badge-4k { - background: rgba(234, 179, 8, 0.15); +.reqservices-group .instances-card-grid { + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 14px; + margin-top: 16px; + margin-bottom: 0; +} + +.reqservices-group .instances-card-grid .add-instance-card { + min-height: 100px; +} + +.reqservices-group .instances-card-grid .add-instance-card .add-icon { + font-size: 1.5rem; +} + +.reqservices-group .instances-card-grid .add-instance-card .add-text { + font-size: 0.85rem; +} + +/* 4K inline badge (next to instance name in card header) */ +.reqservices-badge-4k-inline { + font-size: 0.65rem; + background: rgba(234, 179, 8, 0.25); color: #eab308; -} - -.reqservices-card-actions { - display: flex; - gap: 6px; - align-items: center; -} - -.reqservices-empty { - padding: 32px; - text-align: center; - color: var(--text-muted); - border: 2px dashed var(--border-color); - border-radius: var(--radius-lg); -} - -.reqservices-empty i { - font-size: 2rem; - margin-bottom: 8px; - display: block; - opacity: 0.5; + padding: 2px 7px; + border-radius: 12px; + text-transform: uppercase; + font-weight: 700; + letter-spacing: 0.5px; + margin-left: 6px; } /* ── Plex Import Modal ────────────────────────────────────── */ diff --git a/frontend/static/css/sidebar.css b/frontend/static/css/sidebar.css index 27f22609..6c103640 100644 --- a/frontend/static/css/sidebar.css +++ b/frontend/static/css/sidebar.css @@ -382,7 +382,8 @@ #movie-hunt-activity-sub, #media-hunt-config-sub, #index-master-sub, -#index-master-view-sub { +#index-master-view-sub, +#requestarr-user-support-sub { display: none; margin-top: 0; } @@ -391,13 +392,15 @@ #movie-hunt-activity-sub.expanded, #media-hunt-config-sub.expanded, #index-master-sub.expanded, -#index-master-view-sub.expanded { +#index-master-view-sub.expanded, +#requestarr-user-support-sub.expanded { display: block; } #movie-hunt-collection-sub .nav-item-sub, #movie-hunt-activity-sub .nav-item-sub, #media-hunt-config-sub .nav-item-sub, +#requestarr-user-support-sub .nav-item-sub, #index-master-sub .nav-item-sub, #index-master-view-sub .nav-item-sub { margin-top: 0; @@ -427,14 +430,27 @@ } /* Back button styling in config sub-group */ -.media-hunt-config-back { +.media-hunt-config-back, +.requestarr-user-support-back { opacity: 0.7; font-size: 0.85em; } -.media-hunt-config-back:hover { +.media-hunt-config-back:hover, +.requestarr-user-support-back:hover { opacity: 1; } +/* When user-support sub-group is expanded, hide main Requests items + other toggles */ +#sidebar-group-requests.user-support-view > #requestarrDiscoverNav, +#sidebar-group-requests.user-support-view > #requestarrTVNav, +#sidebar-group-requests.user-support-view > #requestarrMoviesNav, +#sidebar-group-requests.user-support-view > #requestarrHiddenNav, +#sidebar-group-requests.user-support-view > #requestarrSmartHuntSettingsNav, +#sidebar-group-requests.user-support-view > #requestarrSettingsNav, +#sidebar-group-requests.user-support-view > #requestarrUserSupportToggle { + display: none !important; +} + /* ===== APP ICONS ===== */ .app-icon-img { width: 20px; diff --git a/frontend/static/js/app.js b/frontend/static/js/app.js index 68947850..13041023 100644 --- a/frontend/static/js/app.js +++ b/frontend/static/js/app.js @@ -186,7 +186,7 @@ let huntarrUI = { } else if (this.currentSection === 'movie-hunt-home' || this.currentSection === 'movie-hunt-collection' || this.currentSection === 'media-hunt-collection' || this.currentSection === 'activity-queue' || this.currentSection === 'activity-history' || this.currentSection === 'activity-blocklist' || this.currentSection === 'activity-logs' || this.currentSection === 'logs-media-hunt' || this.currentSection === 'settings-clients' || this.currentSection === 'movie-hunt-instance-editor') { console.log('[huntarrUI] Initialization - showing movie hunt sidebar'); this.showMovieHuntSidebar(); - } else if (this.currentSection === 'requestarr' || this.currentSection === 'requestarr-discover' || this.currentSection === 'requestarr-movies' || this.currentSection === 'requestarr-tv' || this.currentSection === 'requestarr-hidden' || this.currentSection === 'requestarr-settings' || this.currentSection === 'requestarr-smarthunt-settings' || this.currentSection === 'requestarr-users' || this.currentSection === 'requestarr-services') { + } else if (this.currentSection === 'requestarr' || this.currentSection === 'requestarr-discover' || this.currentSection === 'requestarr-movies' || this.currentSection === 'requestarr-tv' || this.currentSection === 'requestarr-hidden' || this.currentSection === 'requestarr-settings' || this.currentSection === 'requestarr-smarthunt-settings' || this.currentSection === 'requestarr-users' || this.currentSection === 'requestarr-services' || this.currentSection === 'requestarr-requests') { if (this._enableRequestarr === false) { console.log('[huntarrUI] Requestarr disabled - redirecting to home'); this.switchSection('home'); @@ -294,14 +294,18 @@ let huntarrUI = { .then(r => r.ok ? r.json() : null) .then(data => { var badge = document.getElementById('requestarr-pending-badge'); - if (!badge) return; + var mirrors = document.querySelectorAll('.requestarr-pending-badge-mirror'); var count = (data && data.count) || 0; - if (count > 0) { - badge.textContent = count > 99 ? '99+' : String(count); - badge.style.display = ''; - } else { - badge.style.display = 'none'; + var text = count > 99 ? '99+' : String(count); + var show = count > 0; + if (badge) { + badge.textContent = text; + badge.style.display = show ? '' : 'none'; } + mirrors.forEach(function(m) { + m.textContent = text; + m.style.display = show ? '' : 'none'; + }); }) .catch(function() {}); }, @@ -347,6 +351,7 @@ let huntarrUI = { 'requestarrUsersNav', 'requestarrServicesNav', 'requestarrSettingsNav', + 'requestarrUserSupportToggle', ]; hideNavItems.forEach(function(id) { var el = document.getElementById(id); diff --git a/frontend/static/js/dist/bundle-app.js b/frontend/static/js/dist/bundle-app.js index 7a52d846..a3660747 100644 --- a/frontend/static/js/dist/bundle-app.js +++ b/frontend/static/js/dist/bundle-app.js @@ -188,7 +188,7 @@ let huntarrUI = { } else if (this.currentSection === 'movie-hunt-home' || this.currentSection === 'movie-hunt-collection' || this.currentSection === 'media-hunt-collection' || this.currentSection === 'activity-queue' || this.currentSection === 'activity-history' || this.currentSection === 'activity-blocklist' || this.currentSection === 'activity-logs' || this.currentSection === 'logs-media-hunt' || this.currentSection === 'settings-clients' || this.currentSection === 'movie-hunt-instance-editor') { console.log('[huntarrUI] Initialization - showing movie hunt sidebar'); this.showMovieHuntSidebar(); - } else if (this.currentSection === 'requestarr' || this.currentSection === 'requestarr-discover' || this.currentSection === 'requestarr-movies' || this.currentSection === 'requestarr-tv' || this.currentSection === 'requestarr-hidden' || this.currentSection === 'requestarr-settings' || this.currentSection === 'requestarr-smarthunt-settings' || this.currentSection === 'requestarr-users' || this.currentSection === 'requestarr-services') { + } else if (this.currentSection === 'requestarr' || this.currentSection === 'requestarr-discover' || this.currentSection === 'requestarr-movies' || this.currentSection === 'requestarr-tv' || this.currentSection === 'requestarr-hidden' || this.currentSection === 'requestarr-settings' || this.currentSection === 'requestarr-smarthunt-settings' || this.currentSection === 'requestarr-users' || this.currentSection === 'requestarr-services' || this.currentSection === 'requestarr-requests') { if (this._enableRequestarr === false) { console.log('[huntarrUI] Requestarr disabled - redirecting to home'); this.switchSection('home'); @@ -296,14 +296,18 @@ let huntarrUI = { .then(r => r.ok ? r.json() : null) .then(data => { var badge = document.getElementById('requestarr-pending-badge'); - if (!badge) return; + var mirrors = document.querySelectorAll('.requestarr-pending-badge-mirror'); var count = (data && data.count) || 0; - if (count > 0) { - badge.textContent = count > 99 ? '99+' : String(count); - badge.style.display = ''; - } else { - badge.style.display = 'none'; + var text = count > 99 ? '99+' : String(count); + var show = count > 0; + if (badge) { + badge.textContent = text; + badge.style.display = show ? '' : 'none'; } + mirrors.forEach(function(m) { + m.textContent = text; + m.style.display = show ? '' : 'none'; + }); }) .catch(function() {}); }, @@ -349,6 +353,7 @@ let huntarrUI = { 'requestarrUsersNav', 'requestarrServicesNav', 'requestarrSettingsNav', + 'requestarrUserSupportToggle', ]; hideNavItems.forEach(function(id) { var el = document.getElementById(id); diff --git a/frontend/static/js/dist/requestarr-bundle.js b/frontend/static/js/dist/requestarr-bundle.js index d4958d95..8ed6e32a 100644 --- a/frontend/static/js/dist/requestarr-bundle.js +++ b/frontend/static/js/dist/requestarr-bundle.js @@ -1686,14 +1686,16 @@ class RequestarrSettings { try { const _ts = Date.now(); - const [movieHuntResponse, radarrResponse, sonarrResponse] = await Promise.all([ + const [movieHuntResponse, radarrResponse, tvHuntResponse, sonarrResponse] = await Promise.all([ fetch(`./api/requestarr/instances/movie_hunt?t=${_ts}`, { cache: 'no-store' }), fetch(`./api/requestarr/instances/radarr?t=${_ts}`, { cache: 'no-store' }), + fetch(`./api/requestarr/instances/tv_hunt?t=${_ts}`, { cache: 'no-store' }), fetch(`./api/requestarr/instances/sonarr?t=${_ts}`, { cache: 'no-store' }) ]); const movieHuntData = await movieHuntResponse.json(); const radarrData = await radarrResponse.json(); + const tvHuntData = await tvHuntResponse.json(); const sonarrData = await sonarrResponse.json(); const instanceOptions = []; @@ -1717,6 +1719,16 @@ class RequestarrSettings { } }); + // TV Hunt instances + (tvHuntData.instances || []).forEach(instance => { + if (instance && instance.name) { + instanceOptions.push({ + value: `tv_hunt::${instance.name}`, + label: `TV Hunt \u2013 ${instance.name}` + }); + } + }); + (sonarrData.instances || []).forEach(instance => { if (instance && instance.name) { instanceOptions.push({ @@ -3497,12 +3509,10 @@ class RequestarrContent { try { const _ts = Date.now(); const [thResponse, sonarrResponse] = await Promise.all([ - fetch(`./api/tv-hunt/instances?t=${_ts}`, { cache: 'no-store' }), + fetch(`./api/requestarr/instances/tv_hunt?t=${_ts}`, { cache: 'no-store' }), fetch(`./api/requestarr/instances/sonarr?t=${_ts}`, { cache: 'no-store' }) ]); - let thData = await thResponse.json(); - if (!thResponse.ok || thData.error) thData = { instances: [] }; - else thData = { instances: (thData.instances || []).filter(i => i.enabled !== false) }; + const thData = await thResponse.json(); const sonarrData = await sonarrResponse.json(); const allInstances = [ @@ -3775,15 +3785,13 @@ class RequestarrContent { select.innerHTML = ''; try { - // Fetch TV Hunt from Media Hunt API (canonical); Sonarr from requestarr + // Fetch TV Hunt and Sonarr from requestarr (filtered by services config) const _ts = Date.now(); const [thResponse, sonarrResponse] = await Promise.all([ - fetch(`./api/tv-hunt/instances?t=${_ts}`, { cache: 'no-store' }), + fetch(`./api/requestarr/instances/tv_hunt?t=${_ts}`, { cache: 'no-store' }), fetch(`./api/requestarr/instances/sonarr?t=${_ts}`, { cache: 'no-store' }) ]); - let thData = await thResponse.json(); - if (!thResponse.ok || thData.error) thData = { instances: [] }; - else thData = { instances: (thData.instances || []).filter(i => i.enabled !== false) }; + const thData = await thResponse.json(); const sonarrData = await sonarrResponse.json(); const thInstances = (thData.instances || []).map(inst => ({ @@ -7456,6 +7464,8 @@ window.RequestarrUsers = { * Manages which instances are available for media requests. * Movies = Radarr + Movie Hunt instances * TV = Sonarr + TV Hunt instances + * + * Uses the same instance-card design system as Media Hunt Instances. */ window.RequestarrServices = { @@ -7495,27 +7505,38 @@ window.RequestarrServices = { const movieServices = this.services.filter(s => s.service_type === 'movies'); const tvServices = this.services.filter(s => s.service_type === 'tv'); - container.innerHTML = ` - ${this._renderSection('Movies', 'movies', movieServices, 'fas fa-film')} - ${this._renderSection('TV', 'tv', tvServices, 'fas fa-tv')} - `; + container.innerHTML = + this._renderSection('Movies', 'movies', movieServices, 'fa-film') + + this._renderSection('TV', 'tv', tvServices, 'fa-tv'); + + // Wire up click handlers on the grids + this._wireGrid('reqservices-movies-grid', 'movies'); + this._wireGrid('reqservices-tv-grid', 'tv'); }, - _renderSection(title, type, services, icon) { - const cardsHtml = services.length ? services.map(s => this._renderCard(s, type)).join('') : - `
No ${title.toLowerCase()} services configured
-Add an instance to enable ${title.toLowerCase()} requests
-Configure your ${title.toLowerCase()} servers below. You can connect multiple servers and mark defaults.
+Configure your ${title.toLowerCase()} server${services.length !== 1 ? 's' : ''} below. You can connect multiple servers and mark defaults.
- ${cardsHtml} +No ${title.toLowerCase()} services configured
-Add an instance to enable ${title.toLowerCase()} requests
-Configure your ${title.toLowerCase()} servers below. You can connect multiple servers and mark defaults.
+Configure your ${title.toLowerCase()} server${services.length !== 1 ? 's' : ''} below. You can connect multiple servers and mark defaults.
- ${cardsHtml} +