From 18d1b4560981fa0b8092d0cdf5fb77b8e2710dee Mon Sep 17 00:00:00 2001 From: SBCrumb Date: Sun, 26 Oct 2025 11:10:28 -0400 Subject: [PATCH] web: new scans to interface --- nfoguard-web/static/css/styles.css | 192 ++++++++++++++- nfoguard-web/static/index.html | 78 +++++- nfoguard-web/static/js/app.js | 379 +++++++++++++++++++++++++++++ 3 files changed, 647 insertions(+), 2 deletions(-) diff --git a/nfoguard-web/static/css/styles.css b/nfoguard-web/static/css/styles.css index ad39481..79e36fb 100644 --- a/nfoguard-web/static/css/styles.css +++ b/nfoguard-web/static/css/styles.css @@ -886,4 +886,194 @@ body { .d-block { display: block; } .d-flex { display: flex; } .justify-content-between { justify-content: space-between; } -.align-items-center { align-items: center; } \ No newline at end of file +.align-items-center { align-items: center; } + +/* Database Admin Tools Styles */ +.query-results { + margin-top: 1rem; + padding: 1rem; + background: #f8f9fa; + border: 1px solid var(--border-color); + border-radius: 0.375rem; +} + +.query-info { + padding: 1rem; + border-radius: 0.375rem; + margin-bottom: 1rem; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.875rem; +} + +.query-info.success { + background-color: #d4edda; + border: 1px solid #c3e6cb; + color: #155724; +} + +.query-info.error { + background-color: #f8d7da; + border: 1px solid #f5c6cb; + color: #721c24; +} + +.results-table { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; + background: white; + border-radius: 0.375rem; + overflow: hidden; + box-shadow: var(--shadow); +} + +.results-table th { + background-color: var(--dark-color); + color: white; + padding: 0.75rem; + text-align: left; + font-weight: 600; +} + +.results-table td { + padding: 0.75rem; + border-bottom: 1px solid var(--border-color); + vertical-align: top; +} + +.results-table tr:hover { + background-color: #f8f9fa; +} + +.results-table td em { + color: var(--text-muted); + font-style: italic; +} + +.table-container { + max-height: 400px; + overflow-y: auto; + border: 1px solid var(--border-color); + border-radius: 0.375rem; +} + +.admin-tools { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.admin-tools .btn { + text-align: left; + justify-content: flex-start; +} + +.admin-results { + margin-top: 1rem; + padding: 1rem; + background: #f8f9fa; + border: 1px solid var(--border-color); + border-radius: 0.375rem; +} + +.scan-summary, +.fix-summary { + padding: 1rem; + border-radius: 0.375rem; + margin-bottom: 1rem; +} + +.scan-summary { + background-color: #e7f3ff; + border: 1px solid #b3d9ff; +} + +.fix-summary.success { + background-color: #d4edda; + border: 1px solid #c3e6cb; + color: #155724; +} + +.fix-errors { + background-color: #fff3cd; + border: 1px solid #ffeaa7; + padding: 1rem; + border-radius: 0.375rem; + margin-top: 1rem; +} + +.fix-errors ul { + margin: 0.5rem 0 0 1rem; +} + +.missing-episodes { + margin-top: 1rem; +} + +.loading { + text-align: center; + padding: 2rem; + color: var(--text-muted); + font-style: italic; +} + +.loading::before { + content: "⏳ "; +} + +.success { + background-color: #d4edda; + border: 1px solid #c3e6cb; + color: #155724; + padding: 1rem; + border-radius: 0.375rem; + margin: 1rem 0; +} + +.error { + background-color: #f8d7da; + border: 1px solid #f5c6cb; + color: #721c24; + padding: 1rem; + border-radius: 0.375rem; + margin: 1rem 0; +} + +/* Make text areas more distinctive */ +textarea { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.875rem; + resize: vertical; +} + +/* Button variations for admin tools */ +.btn.btn-info { + background-color: #17a2b8; + border-color: #17a2b8; + color: white; +} + +.btn.btn-info:hover { + background-color: #138496; + border-color: #117a8b; +} + +/* Responsive adjustments for admin tools */ +@media (max-width: 768px) { + .table-container { + max-height: 300px; + } + + .results-table { + font-size: 0.75rem; + } + + .results-table th, + .results-table td { + padding: 0.5rem; + } + + .admin-tools { + gap: 0.75rem; + } +} \ No newline at end of file diff --git a/nfoguard-web/static/index.html b/nfoguard-web/static/index.html index f434527..365a6fb 100644 --- a/nfoguard-web/static/index.html +++ b/nfoguard-web/static/index.html @@ -390,6 +390,82 @@ Refresh Stats + + +
+

Database Query Tool

+

Execute custom SQL queries on the NFOGuard database

+
+
+ + +
+
+ + + Query will be automatically limited to 100 results for safety +
+
+ + +
+ +
+ +
+ + +
+

NFO File Repair

+

Fix missing <dateadded> elements in NFO files

+
+ +
+ +
+ + +
+

Database Admin

+

Advanced database management tools

+
+ + + +
+ +
@@ -457,6 +533,6 @@
- + \ No newline at end of file diff --git a/nfoguard-web/static/js/app.js b/nfoguard-web/static/js/app.js index 14f72ff..fbb9535 100644 --- a/nfoguard-web/static/js/app.js +++ b/nfoguard-web/static/js/app.js @@ -1676,4 +1676,383 @@ function stopScanStatusPolling() { clearInterval(scanStatusInterval); scanStatusInterval = null; } +} + +// ============================================================================ +// DATABASE ADMIN TOOLS +// ============================================================================ + +// Initialize database admin tools +document.addEventListener('DOMContentLoaded', function() { + initializeDatabaseAdminTools(); +}); + +function initializeDatabaseAdminTools() { + // Load quick queries + loadQuickQueries(); + + // Initialize database query form + const dbQueryForm = document.getElementById('database-query-form'); + if (dbQueryForm) { + dbQueryForm.addEventListener('submit', function(e) { + e.preventDefault(); + executeDatabaseQuery(); + }); + } +} + +async function loadQuickQueries() { + try { + const response = await apiCall('/api/admin/database/quick-queries'); + const select = document.getElementById('quick-query-select'); + + if (response.success && select) { + // Clear existing options except first + select.innerHTML = ''; + + response.queries.forEach(query => { + const option = document.createElement('option'); + option.value = query.query; + option.textContent = `${query.name} - ${query.description}`; + select.appendChild(option); + }); + } + } catch (error) { + console.error('Failed to load quick queries:', error); + } +} + +function loadQuickQuery() { + const select = document.getElementById('quick-query-select'); + const textarea = document.getElementById('sql-query'); + + if (select && textarea && select.value) { + textarea.value = select.value; + } +} + +async function executeDatabaseQuery() { + const query = document.getElementById('sql-query').value.trim(); + const limit = document.getElementById('query-limit').value; + const resultsDiv = document.getElementById('query-results'); + const resultsContent = document.getElementById('query-results-content'); + + if (!query) { + alert('Please enter a SQL query'); + return; + } + + try { + resultsContent.innerHTML = '
Executing query...
'; + resultsDiv.style.display = 'block'; + + const response = await apiCall('/api/admin/database/query', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, limit: parseInt(limit) }) + }); + + if (response.success) { + if (response.columns && response.rows) { + // SELECT query results + let html = ` +
+ Query: ${response.query}
+ Rows returned: ${response.row_count} +
+
+ + + + `; + + response.columns.forEach(col => { + html += ``; + }); + html += ''; + + response.rows.forEach(row => { + html += ''; + response.columns.forEach(col => { + const value = row[col]; + html += ``; + }); + html += ''; + }); + + html += '
${col}
${value !== null ? value : 'NULL'}
'; + resultsContent.innerHTML = html; + } else { + // Non-SELECT query + resultsContent.innerHTML = ` +
+ Query: ${response.query}
+ Result: ${response.message}
+ Rows affected: ${response.rows_affected} +
+ `; + } + } else { + resultsContent.innerHTML = ` +
+ Error: ${response.error}
+ Query: ${response.query} +
+ `; + } + } catch (error) { + resultsContent.innerHTML = ` +
+ Error: Failed to execute query: ${error.message} +
+ `; + } +} + +// NFO File Repair Functions +async function checkMissingNFODates() { + const resultsDiv = document.getElementById('nfo-scan-results'); + const resultsContent = document.getElementById('nfo-scan-content'); + + try { + resultsContent.innerHTML = '
Scanning NFO files for missing dateadded elements...
'; + resultsDiv.style.display = 'block'; + + const response = await apiCall('/api/admin/nfo/missing-dateadded'); + + if (response.success) { + let html = ` +
+

NFO Scan Results

+

Total episodes checked: ${response.total_episodes_checked}

+

Episodes missing dateadded in NFO: ${response.missing_dateadded_count}

+
+ `; + + if (response.missing_dateadded_count > 0) { + html += ` +
+
Episodes with missing dateadded (showing first 50):
+
+ + + + + + + + + + + + + `; + + response.episodes.forEach(ep => { + html += ` + + + + + + + + + `; + }); + + html += '
SeriesSeasonEpisodeDatabase DateSourceNFO Path
${ep.imdb_id}${ep.season}${ep.episode}${new Date(ep.dateadded).toLocaleString()}${ep.source}${ep.nfo_path}
'; + } else { + html += '
✅ All NFO files have dateadded elements!
'; + } + + resultsContent.innerHTML = html; + } else { + resultsContent.innerHTML = `
Error: ${response.message}
`; + } + } catch (error) { + resultsContent.innerHTML = `
Failed to scan NFO files: ${error.message}
`; + } +} + +async function fixAllMissingNFODates() { + if (!confirm('This will update all NFO files that are missing dateadded elements. Continue?')) { + return; + } + + const resultsContent = document.getElementById('nfo-scan-content'); + + try { + resultsContent.innerHTML = '
Fixing all missing dateadded elements in NFO files...
'; + + const response = await apiCall('/api/admin/nfo/bulk-update', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ fix_all: true }) + }); + + if (response.success) { + let html = ` +
+

NFO Fix Results

+

${response.updated_count} NFO files updated successfully

+

Total episodes processed: ${response.total_episodes}

+

Message: ${response.message}

+
+ `; + + if (response.errors && response.errors.length > 0) { + html += ` +
+
Errors (first 10):
+
'; + } + + resultsContent.innerHTML = html; + } else { + resultsContent.innerHTML = `
Error: ${response.message}
`; + } + } catch (error) { + resultsContent.innerHTML = `
Failed to fix NFO files: ${error.message}
`; + } +} + +async function fixSpecificSeries() { + const imdbIds = prompt('Enter IMDb IDs to fix (comma-separated):\nExample: tt9111220,tt12327578'); + + if (!imdbIds) return; + + const idArray = imdbIds.split(',').map(id => id.trim()).filter(id => id); + + if (idArray.length === 0) { + alert('Please enter valid IMDb IDs'); + return; + } + + const resultsContent = document.getElementById('nfo-scan-content'); + + try { + resultsContent.innerHTML = '
Fixing NFO files for specified series...
'; + + const response = await apiCall('/api/admin/nfo/bulk-update', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ imdb_ids: idArray }) + }); + + if (response.success) { + let html = ` +
+

NFO Fix Results for Specific Series

+

${response.updated_count} NFO files updated successfully

+

Series processed: ${idArray.join(', ')}

+

Message: ${response.message}

+
+ `; + + resultsContent.innerHTML = html; + } else { + resultsContent.innerHTML = `
Error: ${response.message}
`; + } + } catch (error) { + resultsContent.innerHTML = `
Failed to fix NFO files: ${error.message}
`; + } +} + +// Database Admin Functions +async function showEpisodesMissingNFODateadded() { + const resultsDiv = document.getElementById('admin-results'); + const resultsContent = document.getElementById('admin-results-content'); + + try { + resultsContent.innerHTML = '
Checking for episodes missing NFO dateadded...
'; + resultsDiv.style.display = 'block'; + + const response = await apiCall('/api/admin/nfo/missing-dateadded'); + + if (response.success) { + const html = ` +

Episodes Missing NFO dateadded

+

Found: ${response.missing_dateadded_count} episodes missing dateadded in NFO files

+

Total checked: ${response.total_episodes_checked} episodes

+ ${response.missing_dateadded_count > 0 ? + '

Recommendation: Use the "NFO File Repair" tool to fix these issues.

' : + '

✅ All NFO files are properly maintained!

' + } + `; + resultsContent.innerHTML = html; + } else { + resultsContent.innerHTML = `
Error: ${response.message}
`; + } + } catch (error) { + resultsContent.innerHTML = `
Failed to check NFO files: ${error.message}
`; + } +} + +async function exportDatabaseReport() { + alert('Database export functionality will be implemented in a future update.'); +} + +async function showRecentWebhookActivity() { + const resultsDiv = document.getElementById('admin-results'); + const resultsContent = document.getElementById('admin-results-content'); + + try { + resultsContent.innerHTML = '
Loading recent webhook activity...
'; + resultsDiv.style.display = 'block'; + + const response = await apiCall('/api/admin/database/query', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + query: "SELECT * FROM episodes WHERE last_updated > NOW() - INTERVAL '24 hours' ORDER BY last_updated DESC", + limit: 20 + }) + }); + + if (response.success && response.rows) { + let html = ` +

Recent Webhook Activity (Last 24 Hours)

+

Episodes processed: ${response.row_count}

+
+ + + + + + + + + + + + + `; + + response.rows.forEach(row => { + html += ` + + + + + + + + + `; + }); + + html += '
IMDb IDSeasonEpisodeDate AddedSourceLast Updated
${row.imdb_id}${row.season}${row.episode}${row.dateadded ? new Date(row.dateadded).toLocaleString() : 'None'}${row.source}${new Date(row.last_updated).toLocaleString()}
'; + resultsContent.innerHTML = html; + } else { + resultsContent.innerHTML = '

No recent webhook activity found.

'; + } + } catch (error) { + resultsContent.innerHTML = `
Failed to load webhook activity: ${error.message}
`; + } } \ No newline at end of file