From c2c720cd8b7d96952628e0f178037b407e13894c Mon Sep 17 00:00:00 2001 From: SBCrumb Date: Mon, 3 Nov 2025 09:30:50 -0500 Subject: [PATCH] update to web interface --- api/web_routes.py | 10 ++- nfoguard-web/api/web_routes.py | 25 +++++-- nfoguard-web/static/index.html | 52 ++++++++++---- nfoguard-web/static/js/app.js | 120 +++++++++++++++++++++++++++++++-- 4 files changed, 181 insertions(+), 26 deletions(-) diff --git a/api/web_routes.py b/api/web_routes.py index faa86fc..41281e8 100644 --- a/api/web_routes.py +++ b/api/web_routes.py @@ -45,16 +45,24 @@ def map_source_to_description(source: str) -> str: elif "omdb:" in source_lower: return "OMDb Release" + # Sonarr sources + elif "sonarr:" in source_lower: + return "Sonarr API" + # Manual and other sources elif "manual" in source_lower: return "Manual Entry" elif "digital_release" in source_lower: return "Digital Release" + elif "nfo_file_existing" in source_lower: + return "NFO File (Legacy)" elif "nfo:" in source_lower: return "NFO File" elif "webhook:" in source_lower: return "Webhook/API" - + elif "database" in source_lower: + return "Database" + # Fallback for unknown patterns return source.title() diff --git a/nfoguard-web/api/web_routes.py b/nfoguard-web/api/web_routes.py index aa8bfd6..42d3b64 100644 --- a/nfoguard-web/api/web_routes.py +++ b/nfoguard-web/api/web_routes.py @@ -48,16 +48,24 @@ def map_source_to_description(source: str) -> str: elif "omdb:" in source_lower: return "OMDb Release" + # Sonarr sources + elif "sonarr:" in source_lower: + return "Sonarr API" + # Manual and other sources elif "manual" in source_lower: return "Manual Entry" elif "digital_release" in source_lower: return "Digital Release" + elif "nfo_file_existing" in source_lower: + return "NFO File (Legacy)" elif "nfo:" in source_lower: return "NFO File" elif "webhook:" in source_lower: return "Webhook/API" - + elif "database" in source_lower: + return "Database" + # Fallback for unknown patterns return source.title() @@ -468,11 +476,18 @@ async def get_dashboard_stats(dependencies: dict): cursor.execute("SELECT COUNT(*) FROM episodes WHERE source = 'no_valid_date_source'") episodes_no_valid_source = db._get_first_value(cursor.fetchone()) - - # Recent activity (last 7 days) + + # Recent activity (last 7 days) - count items processed, not history events cursor.execute(""" - SELECT COUNT(*) FROM processing_history - WHERE processed_at > NOW() - INTERVAL '7 days' + SELECT COUNT(*) FROM ( + SELECT imdb_id FROM movies + WHERE created_at > NOW() - INTERVAL '7 days' + OR updated_at > NOW() - INTERVAL '7 days' + UNION + SELECT DISTINCT imdb_id FROM episodes + WHERE created_at > NOW() - INTERVAL '7 days' + OR updated_at > NOW() - INTERVAL '7 days' + ) AS recent_items """) recent_activity = db._get_first_value(cursor.fetchone()) diff --git a/nfoguard-web/static/index.html b/nfoguard-web/static/index.html index 9e59a36..d67590e 100644 --- a/nfoguard-web/static/index.html +++ b/nfoguard-web/static/index.html @@ -139,16 +139,30 @@
- +
- - - - - - - + + + + + + + @@ -196,14 +210,24 @@
-
TitleIMDb IDMovie ReleasedDate Added to LibrarySourceDate TypeVideo File + Title + + IMDb ID + + Movie Released + + Date Added to Library + + Source + + Date Type + + Video File + Actions
+
- - - - - + + + + + diff --git a/nfoguard-web/static/js/app.js b/nfoguard-web/static/js/app.js index 883d7ee..378b0b8 100644 --- a/nfoguard-web/static/js/app.js +++ b/nfoguard-web/static/js/app.js @@ -514,17 +514,27 @@ function showEpisodesModal(data) {
-
Series TitleIMDb IDEpisodesWith DatesWith Video + Series Title + + IMDb ID + + Episodes + + With Dates + + With Video + Actions
+
- - - - - + + + + + @@ -1952,3 +1962,101 @@ function updatePopulateProgress(status) { } } } + +// ==================== Table Sorting Functions ==================== + +let sortDirections = {}; // Track sort direction for each table column + +function sortTable(tableBodyId, columnIndex, dataType) { + const tbody = document.getElementById(tableBodyId); + if (!tbody) return; + + const sortKey = `${tableBodyId}-${columnIndex}`; + + // Toggle sort direction + if (!sortDirections[sortKey]) { + sortDirections[sortKey] = 'asc'; + } else { + sortDirections[sortKey] = sortDirections[sortKey] === 'asc' ? 'desc' : 'asc'; + } + + const rows = Array.from(tbody.querySelectorAll('tr')); + + rows.sort((a, b) => { + const aCell = a.cells[columnIndex]; + const bCell = b.cells[columnIndex]; + + if (!aCell || !bCell) return 0; + + let aValue = aCell.textContent.trim(); + let bValue = bCell.textContent.trim(); + + // Handle MISSING values - always sort to bottom + if (aValue === 'MISSING') return 1; + if (bValue === 'MISSING') return -1; + + // Handle different data types + if (dataType === 'date') { + // Parse dates for comparison + aValue = aValue === '-' ? '' : aValue; + bValue = bValue === '-' ? '' : bValue; + + if (!aValue && !bValue) return 0; + if (!aValue) return 1; + if (!bValue) return -1; + + const aDate = new Date(aValue); + const bDate = new Date(bValue); + + if (sortDirections[sortKey] === 'asc') { + return aDate - bDate; + } else { + return bDate - aDate; + } + } else if (dataType === 'number') { + const aNum = parseFloat(aValue) || 0; + const bNum = parseFloat(bValue) || 0; + + if (sortDirections[sortKey] === 'asc') { + return aNum - bNum; + } else { + return bNum - aNum; + } + } else { + // Text comparison + if (sortDirections[sortKey] === 'asc') { + return aValue.localeCompare(bValue); + } else { + return bValue.localeCompare(aValue); + } + } + }); + + // Re-append sorted rows + rows.forEach(row => tbody.appendChild(row)); + + // Update sort icons + updateSortIcons(tableBodyId, columnIndex, sortDirections[sortKey]); +} + +function updateSortIcons(tableBodyId, activeColumn, direction) { + // Find the table and update icons + const tbody = document.getElementById(tableBodyId); + if (!tbody) return; + + const table = tbody.closest('table'); + if (!table) return; + + const headers = table.querySelectorAll('th.sortable'); + headers.forEach((header, index) => { + const icon = header.querySelector('i'); + if (!icon) return; + + // +1 to account for checkbox column + if (index + 1 === activeColumn) { + icon.className = direction === 'asc' ? 'fas fa-sort-up' : 'fas fa-sort-down'; + } else { + icon.className = 'fas fa-sort'; + } + }); +}
EpisodeAiredDate AddedSourceVideo + Episode + + Aired + + Date Added + + Source + + Video + Actions