diff --git a/webui/index.html b/webui/index.html index d42fa59d..cde26366 100644 --- a/webui/index.html +++ b/webui/index.html @@ -239,6 +239,10 @@ Library + + + Tools + Stats @@ -727,335 +731,18 @@ Tools & Operations - - - - Database Updater - ? - - Last Full Refresh: Never - - - Artists: - 0 - - - Albums: - 0 - - - Tracks: - 0 - - - Size: - 0.0 MB - - - - - Incremental Update - Full Refresh - - Update Database - - - Idle - - - - 0 / 0 artists (0.0%) - - - - - - Metadata Updater - ? - - Updates artist photos, genres, - and album art from Spotify. - - - 6 months - 3 months - 1 month - 2 weeks - 1 week - Full refresh - - Begin Update - - - Current Artist: Not - running - - - - - 0 / 0 artists (0.0%) - - - - - - - Quality Scanner - ? - - Scan library for tracks below quality preferences - - - Processed: - 0 - - - Quality Met: - 0 - - - Low Quality: - 0 - - - Matched: - 0 - - - - - Watchlist Artists Only - All Library Tracks - - Scan Library - - - Ready to scan - - - - - 0 / 0 tracks scanned - (0.0%) - - - - - - Duplicate Cleaner - ? - - Detect and remove duplicate tracks in Transfer folder - - - Files Scanned: - 0 - - - Duplicates Found: - 0 - - - Deleted: - 0 - - - Space Freed: - 0 MB - - - - Clean Duplicates - - - Ready to scan - - - - - 0 files scanned - (0.0%) - - - - - - Discovery Pool - - View and fix matched/failed discovery results across all mirrored playlists - - - Matched: - — - - - Failed: - — - - - - Open Discovery Pool - - - - - - Retag Tool - ? - - Fix metadata on previously downloaded albums & singles - - - Groups: - 0 - - - Tracks: - 0 - - - Artists: - 0 - - - Status: - Idle - - - - Open Retag Tool - - - Ready - - - - - 0 / 0 tracks (0.0%) - - - - - - Media Server Scan - ? - - Manually trigger Plex media library scan for music - - - Last Scan: - Never - - - Status: - Idle - - - - - 📡 - Scan Library - - - - Ready to scan - - - - - Waiting for scan - request - - - - - - Backup Manager - ? - - Create, download, restore and manage database backups - - - Last Backup: - Never - - - Backups: - 0 - - - Latest Size: - — - - - DB Size: - — - - - - Backup Now - - - - - - - - Metadata Cache - ? - - Cached API responses from Spotify & iTunes - - - Artists: - 0 - - - Albums: - 0 - - - Tracks: - 0 - - - Hits: - 0 - - - - Browse Cache - Cache Health - - - - - - - Download Blacklist - - Blocked sources that won't be used for future downloads - - - Blocked: - 0 - - - - View Blacklist + + + + + Database, scanning, backups, cache, maintenance & more + + Recent Activity @@ -6411,6 +6098,443 @@ + + + + + + + + + + Tools & Operations + + Database management, library scanning, metadata, backups + + + + + + + + + + Library Maintenance + Automated scanning, detection, and repair of library issues + + + + + + Enabled + + + + + Jobs + + Findings 0 + + History + + + + + Loading jobs... + + + + + + + + + All Jobs + + + All Severity + Info + Warning + + + Pending + All Status + Resolved + Dismissed + + + + + Select All + + + + Fix Selected + Dismiss Selected + Fix All + + Clear Findings + + + Loading findings... + + + + + + + Loading history... + + + + + + + Database & Scanning + + + + Database Updater + ? + + Last Full Refresh: Never + + + Artists: + 0 + + + Albums: + 0 + + + Tracks: + 0 + + + Size: + 0.0 MB + + + + + Incremental Update + Full Refresh + + Update Database + + + Idle + + + + 0 / 0 artists (0.0%) + + + + + + Metadata Updater + ? + + Updates artist photos, genres, + and album art from Spotify. + + + 6 months + 3 months + 1 month + 2 weeks + 1 week + Full refresh + + Begin Update + + + Current Artist: Not + running + + + + + 0 / 0 artists (0.0%) + + + + + + + Quality Scanner + ? + + Scan library for tracks below quality preferences + + + Processed: + 0 + + + Quality Met: + 0 + + + Low Quality: + 0 + + + Matched: + 0 + + + + + Watchlist Artists Only + All Library Tracks + + Scan Library + + + Ready to scan + + + + + 0 / 0 tracks scanned + (0.0%) + + + + + + Duplicate Cleaner + ? + + Detect and remove duplicate tracks in Transfer folder + + + Files Scanned: + 0 + + + Duplicates Found: + 0 + + + Deleted: + 0 + + + Space Freed: + 0 MB + + + + Clean Duplicates + + + Ready to scan + + + + + 0 files scanned + (0.0%) + + + + + + + Media Server Scan + ? + + Manually trigger Plex media library scan for music + + + Last Scan: + Never + + + Status: + Idle + + + + + 📡 + Scan Library + + + + Ready to scan + + + + Waiting for scan request + + + + + + + Metadata & Cache + + + + + Discovery Pool + + View and fix matched/failed discovery results across all mirrored playlists + + + Matched: + — + + + Failed: + — + + + + Open Discovery Pool + + + + + + Retag Tool + ? + + Fix metadata on previously downloaded albums & singles + + + Groups: + 0 + + + Tracks: + 0 + + + Artists: + 0 + + + Status: + Idle + + + + Open Retag Tool + + + Ready + + + + + 0 / 0 tracks (0.0%) + + + + + + + + Management + + + + + Backup Manager + ? + + Create, download, restore and manage database backups + + + Last Backup: + Never + + + Backups: + 0 + + + Latest Size: + — + + + DB Size: + — + + + + Backup Now + + + + + + + + Metadata Cache + ? + + Cached API responses from Spotify & iTunes + + + Artists: + 0 + + + Albums: + 0 + + + Tracks: + 0 + + + Hits: + 0 + + + + Browse Cache + Cache Health + + + + + + + Download Blacklist + + Blocked sources that won't be used for future downloads + + + Blocked: + 0 + + + + View Blacklist + + + + + + @@ -7497,89 +7621,6 @@ - - - - - - - - Library Maintenance - Scan, detect, and fix issues in your music library - - - - - - - Enabled - - × - - - - - Jobs - - Findings 0 - - History - - - - - Loading jobs... - - - - - - - - - - - - All Jobs - - - All Severity - Info - Warning - - - Pending - All Status - Resolved - Dismissed - - - - - Select All - - - - Fix Selected - Dismiss Selected - Fix All - - Clear Findings - - - Loading findings... - - - - - - - Loading history... - - - - - diff --git a/webui/static/helper.js b/webui/static/helper.js index 5a98a179..964ebd93 100644 --- a/webui/static/helper.js +++ b/webui/static/helper.js @@ -3602,6 +3602,7 @@ const WHATS_NEW = { '2.2': [ // --- April 15, 2026 --- { date: 'April 15, 2026' }, + { title: 'Tools Page', desc: 'All tool cards (Database Updater, Quality Scanner, Duplicate Cleaner, Retag, Backups, Cache, etc.) and Library Maintenance moved from the Dashboard to a dedicated Tools page in the sidebar. Dashboard shows a quick-link card', page: 'tools' }, { title: 'Watchlist & Wishlist Sidebar Pages', desc: 'Watchlist and Wishlist promoted from modals to full sidebar pages. All features preserved — artist grid, scan controls, batch operations, live activity, countdown timers, category cards with mosaic backgrounds. Header buttons now navigate to the pages', page: 'watchlist' }, { title: 'Picard-Style MusicBrainz Album Consistency', desc: 'Recording MBIDs now pulled from the matched release tracklist instead of independent searches. Batch-level artist name used for stable cache keys. Post-batch consistency pass rewrites album-level tags on all files to guarantee identical MusicBrainz IDs — prevents Navidrome album splits' }, { title: 'Fix Spotify API Leaking When Deezer/iTunes is Primary', desc: 'Spotify was being called for watchlist album scanning, similar artist discovery, repair jobs, and the Artists page search even when another source was set as primary. All data-fetching now respects the configured primary source. Spotify playlist sync is unaffected' }, diff --git a/webui/static/script.js b/webui/static/script.js index 7e050bc0..b5c34170 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -3048,6 +3048,9 @@ async function loadPageData(pageId) { // Load comparisons loadHydrabaseComparisons(); break; + case 'tools': + await initializeToolsPage(); + break; case 'watchlist': await initializeWatchlistPage(); break; @@ -25088,80 +25091,81 @@ function resetWishlistModalToIdleState() { } } -async function loadDashboardData() { - // Attach event listeners for the DB updater tool +let toolsPageState = { isInitialized: false }; + +async function initializeToolsPage() { + // Attach event listeners for tool buttons (idempotent — getElementById returns null if already wired) const updateButton = document.getElementById('db-update-button'); - if (updateButton) { + if (updateButton && !updateButton._toolsWired) { updateButton.addEventListener('click', handleDbUpdateButtonClick); + updateButton._toolsWired = true; } - // Attach event listeners for the metadata updater tool const metadataButton = document.getElementById('metadata-update-button'); - if (metadataButton) { + if (metadataButton && !metadataButton._toolsWired) { metadataButton.addEventListener('click', handleMetadataUpdateButtonClick); + metadataButton._toolsWired = true; } - // Check active media server and hide metadata updater if not Plex - await checkAndHideMetadataUpdaterForNonPlex(); - - // Check for ongoing metadata update and restore state - await checkAndRestoreMetadataUpdateState(); - - // Attach event listener for the quality scanner tool const qualityScanButton = document.getElementById('quality-scan-button'); - if (qualityScanButton) { + if (qualityScanButton && !qualityScanButton._toolsWired) { qualityScanButton.addEventListener('click', handleQualityScanButtonClick); + qualityScanButton._toolsWired = true; } - // Attach event listener for the duplicate cleaner tool const duplicateCleanButton = document.getElementById('duplicate-clean-button'); - if (duplicateCleanButton) { + if (duplicateCleanButton && !duplicateCleanButton._toolsWired) { duplicateCleanButton.addEventListener('click', handleDuplicateCleanButtonClick); + duplicateCleanButton._toolsWired = true; } - // Attach event listener for the retag tool const retagOpenButton = document.getElementById('retag-open-button'); - if (retagOpenButton) { + if (retagOpenButton && !retagOpenButton._toolsWired) { retagOpenButton.addEventListener('click', openRetagModal); + retagOpenButton._toolsWired = true; } - // Attach event listener for the media scan tool const mediaScanButton = document.getElementById('media-scan-button'); - if (mediaScanButton) { + if (mediaScanButton && !mediaScanButton._toolsWired) { mediaScanButton.addEventListener('click', handleMediaScanButtonClick); + mediaScanButton._toolsWired = true; } - // Check active media server and show media scan tool only for Plex - await checkAndShowMediaScanForPlex(); - - // Attach event listener for the backup manager const backupNowButton = document.getElementById('backup-now-button'); - if (backupNowButton) backupNowButton.addEventListener('click', handleBackupNowClick); - loadBackupList(); + if (backupNowButton && !backupNowButton._toolsWired) { + backupNowButton.addEventListener('click', handleBackupNowClick); + backupNowButton._toolsWired = true; + } - // Attach event listeners for tool help buttons + // Tool-specific init + await checkAndHideMetadataUpdaterForNonPlex(); + await checkAndRestoreMetadataUpdateState(); + await checkAndShowMediaScanForPlex(); + loadBackupList(); initializeToolHelpButtons(); - - // Initial load of retag stats loadRetagStats(); - - // Check for ongoing retag operation checkRetagStatus(); - - // Initial load of stats await fetchAndUpdateDbStats(); + loadDiscoveryPoolStats(); + loadMetadataCacheStats(); - // Start periodic refresh of stats (every 10 seconds) - stopDbStatsPolling(); // Ensure no duplicates + // Start polling (cleared when navigating away via loadPageData preamble) + stopDbStatsPolling(); dbStatsInterval = setInterval(fetchAndUpdateDbStats, 10000); - // Initial load of discovery pool stats for the tool card - loadDiscoveryPoolStats(); + // Check for ongoing operations + await checkAndUpdateDbProgress(); + await checkAndUpdateQualityScanProgress(); + await checkAndUpdateDuplicateCleanProgress(); - // Initial load of metadata cache stats + periodic refresh - loadMetadataCacheStats(); - setInterval(loadMetadataCacheStats, 15000); + // Initialize library maintenance section + updateRepairStatus(); + switchRepairTab('jobs'); + toolsPageState.isInitialized = true; +} + +async function loadDashboardData() { // Initial load of wishlist count await updateWishlistCount(); @@ -25186,15 +25190,6 @@ async function loadDashboardData() { // Start periodic toast checking (every 3 seconds) setInterval(checkForActivityToasts, 3000); - // Also check the status of any ongoing update when the page loads - await checkAndUpdateDbProgress(); - - // Check for any ongoing quality scanner when the page loads - await checkAndUpdateQualityScanProgress(); - - // Check for any ongoing duplicate cleaner when the page loads - await checkAndUpdateDuplicateCleanProgress(); - // Check for any active download processes that need rehydration await checkForActiveProcesses(); @@ -63191,10 +63186,12 @@ let _repairJobsCache = {}; // Cache job data for help modal * Open the Library Maintenance modal */ async function openRepairModal() { - const modal = document.getElementById('repair-modal'); - if (!modal) return; - modal.style.display = 'flex'; - document.body.style.overflow = 'hidden'; + navigateToPage('tools'); + // Scroll to maintenance section + setTimeout(() => { + const section = document.querySelector('.tools-maintenance-section'); + if (section) section.scrollIntoView({ behavior: 'smooth' }); + }, 100); _repairCurrentTab = 'jobs'; switchRepairTab('jobs'); // Load master toggle state @@ -63213,10 +63210,7 @@ async function openRepairModal() { } function closeRepairModal() { - const modal = document.getElementById('repair-modal'); - if (!modal) return; - modal.style.display = 'none'; - document.body.style.overflow = ''; + // No-op — repair content now lives on the tools page, no modal to close } async function toggleRepairMaster() { diff --git a/webui/static/style.css b/webui/static/style.css index 17a06e66..d24f99f7 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -55797,14 +55797,232 @@ body.reduce-effects *::after { } } +/* ═══════════════════════════════════════════════════════════════════ + DASHBOARD TOOLS LINK CARD + ═══════════════════════════════════════════════════════════════════ */ + +.dashboard-tools-link { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + background: rgba(255, 255, 255, 0.025); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 12px; + cursor: pointer; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); +} + +.dashboard-tools-link:hover { + background: rgba(255, 255, 255, 0.05); + border-color: rgba(var(--accent-rgb), 0.15); + transform: translateY(-1px); +} + +.dashboard-tools-link-content { + display: flex; + align-items: center; + gap: 12px; +} + +.dashboard-tools-link-title { + font-size: 14px; + color: rgba(255, 255, 255, 0.6); + font-weight: 500; +} + +/* ═══════════════════════════════════════════════════════════════════ + TOOLS PAGE + ═══════════════════════════════════════════════════════════════════ */ + +.tools-page-container { + padding: 28px 24px 30px; + margin: 20px; + background: linear-gradient(135deg, + rgba(20, 20, 20, 0.55) 0%, + rgba(12, 12, 12, 0.62) 100%); + border-radius: 24px; + border: 1px solid rgba(255, 255, 255, 0.08); + border-top: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.3), + 0 4px 16px rgba(0, 0, 0, 0.2), + inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +.tools-page-header { + padding: 20px 24px 18px; + margin: -28px -24px 24px -24px; + background: linear-gradient(180deg, + rgba(var(--accent-rgb), 0.08) 0%, + rgba(var(--accent-rgb), 0.03) 40%, + transparent 100%); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + border-top-left-radius: 24px; + border-top-right-radius: 24px; + position: relative; +} + +.tools-page-header::after { + content: ''; + position: absolute; + bottom: -1px; + left: 10%; + right: 10%; + height: 1px; + background: linear-gradient(90deg, + transparent 0%, + rgba(var(--accent-rgb), 0.2) 20%, + rgba(var(--accent-rgb), 0.35) 50%, + rgba(var(--accent-rgb), 0.2) 80%, + transparent 100%); +} + +.tools-page-title { + display: flex; + align-items: center; + gap: 10px; + font-size: 26px; + font-weight: 700; + color: #fff; + margin: 0; + letter-spacing: -0.5px; + font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif; +} + +.tools-page-subtitle { + font-size: 13px; + color: rgba(255, 255, 255, 0.4); + margin: 6px 0 0 0; + font-weight: 500; +} + +/* ── Library Maintenance hero section ── */ +.tools-maintenance-hero { + background: linear-gradient(135deg, + rgba(20, 20, 20, 0.95) 0%, + rgba(12, 12, 12, 0.98) 100%); + border: 1px solid rgba(255, 255, 255, 0.08); + border-top: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 16px; + padding: 20px 24px; + margin-bottom: 28px; + box-shadow: + 0 4px 16px rgba(0, 0, 0, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.06); + position: relative; + overflow: hidden; +} + +.tools-maintenance-hero::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, + transparent, + rgba(var(--accent-rgb), 0.5), + rgba(var(--accent-rgb), 0.8), + rgba(var(--accent-rgb), 0.5), + transparent); + box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.3); +} + +.tools-maintenance-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.tools-maintenance-header-left { + display: flex; + align-items: center; + gap: 14px; +} + +.tools-maintenance-logo { + width: 40px; + height: 40px; + border-radius: 10px; + object-fit: cover; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +.tools-maintenance-title { + font-size: 20px; + font-weight: 700; + color: #fff; + margin: 0 0 2px 0; + letter-spacing: -0.3px; +} + +.tools-maintenance-subtitle { + font-size: 12px; + color: rgba(255, 255, 255, 0.4); + margin: 0; + font-weight: 500; +} + +/* Repair tabs on the tools page */ +.tools-maintenance-hero .repair-tabs { + margin-bottom: 16px; +} + +.tools-maintenance-hero .repair-tab-content { + min-height: 200px; +} + +/* ── Tool card sections ── */ +.tools-section { + margin-bottom: 24px; +} + +.tools-section-title { + font-size: 13px; + font-weight: 600; + color: rgba(255, 255, 255, 0.35); + text-transform: uppercase; + letter-spacing: 1.2px; + margin: 0 0 12px 4px; + padding-bottom: 8px; + border-bottom: 1px solid rgba(255, 255, 255, 0.04); +} + +@media (max-width: 768px) { + .tools-page-container { + padding: 16px; + margin: 10px; + border-radius: 16px; + } + + .tools-page-header { + padding: 16px; + margin: -16px -16px 16px -16px; + border-top-left-radius: 16px; + border-top-right-radius: 16px; + } + + .tools-page-title { + font-size: 22px; + } + + .tools-maintenance-header { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } +} + /* ═══════════════════════════════════════════════════════════════════ WATCHLIST PAGE ═══════════════════════════════════════════════════════════════════ */ .watchlist-page-container { padding: 28px 24px 30px; - max-width: 1400px; - margin: 0 auto; + margin: 20px; background: linear-gradient(135deg, rgba(20, 20, 20, 0.55) 0%, rgba(12, 12, 12, 0.62) 100%); @@ -56216,8 +56434,7 @@ body.reduce-effects *::after { .wishlist-page-container { padding: 28px 24px 30px; - max-width: 1400px; - margin: 0 auto; + margin: 20px; background: linear-gradient(135deg, rgba(20, 20, 20, 0.55) 0%, rgba(12, 12, 12, 0.62) 100%); @@ -56534,6 +56751,7 @@ body.reduce-effects *::after { .watchlist-page-container, .wishlist-page-container { padding: 16px; + margin: 10px; border-radius: 16px; }
Last Full Refresh: Never
Idle
0 / 0 artists (0.0%)
Updates artist photos, genres, - and album art from Spotify.
Current Artist: Not - running
0 / 0 artists (0.0%) -
Scan library for tracks below quality preferences
Ready to scan
0 / 0 tracks scanned - (0.0%)
Detect and remove duplicate tracks in Transfer folder
0 files scanned - (0.0%)
View and fix matched/failed discovery results across all mirrored playlists
Fix metadata on previously downloaded albums & singles
Ready
0 / 0 tracks (0.0%)
Manually trigger Plex media library scan for music
Waiting for scan - request
Create, download, restore and manage database backups
Cached API responses from Spotify & iTunes
Blocked sources that won't be used for future downloads
Database management, library scanning, metadata, backups
Automated scanning, detection, and repair of library issues
Updates artist photos, genres, + and album art from Spotify.
Current Artist: Not + running
0 / 0 artists (0.0%) +
0 / 0 tracks scanned + (0.0%)
0 files scanned + (0.0%)
Waiting for scan request
Scan, detect, and fix issues in your music library