web: new scans to interface
Local Docker Build (Dev) / build-dev (push) Successful in 4s

This commit is contained in:
2025-10-26 11:10:28 -04:00
parent f46f1df7cb
commit 18d1b45609
3 changed files with 647 additions and 2 deletions
+379
View File
@@ -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 = '<option value="">Select a predefined query...</option>';
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 = '<div class="loading">Executing query...</div>';
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 = `
<div class="query-info">
<strong>Query:</strong> ${response.query}<br>
<strong>Rows returned:</strong> ${response.row_count}
</div>
<div class="table-container">
<table class="results-table">
<thead>
<tr>
`;
response.columns.forEach(col => {
html += `<th>${col}</th>`;
});
html += '</tr></thead><tbody>';
response.rows.forEach(row => {
html += '<tr>';
response.columns.forEach(col => {
const value = row[col];
html += `<td>${value !== null ? value : '<em>NULL</em>'}</td>`;
});
html += '</tr>';
});
html += '</tbody></table></div>';
resultsContent.innerHTML = html;
} else {
// Non-SELECT query
resultsContent.innerHTML = `
<div class="query-info success">
<strong>Query:</strong> ${response.query}<br>
<strong>Result:</strong> ${response.message}<br>
<strong>Rows affected:</strong> ${response.rows_affected}
</div>
`;
}
} else {
resultsContent.innerHTML = `
<div class="query-info error">
<strong>Error:</strong> ${response.error}<br>
<strong>Query:</strong> ${response.query}
</div>
`;
}
} catch (error) {
resultsContent.innerHTML = `
<div class="query-info error">
<strong>Error:</strong> Failed to execute query: ${error.message}
</div>
`;
}
}
// NFO File Repair Functions
async function checkMissingNFODates() {
const resultsDiv = document.getElementById('nfo-scan-results');
const resultsContent = document.getElementById('nfo-scan-content');
try {
resultsContent.innerHTML = '<div class="loading">Scanning NFO files for missing dateadded elements...</div>';
resultsDiv.style.display = 'block';
const response = await apiCall('/api/admin/nfo/missing-dateadded');
if (response.success) {
let html = `
<div class="scan-summary">
<h4>NFO Scan Results</h4>
<p><strong>Total episodes checked:</strong> ${response.total_episodes_checked}</p>
<p><strong>Episodes missing dateadded in NFO:</strong> ${response.missing_dateadded_count}</p>
</div>
`;
if (response.missing_dateadded_count > 0) {
html += `
<div class="missing-episodes">
<h5>Episodes with missing dateadded (showing first 50):</h5>
<div class="table-container">
<table class="results-table">
<thead>
<tr>
<th>Series</th>
<th>Season</th>
<th>Episode</th>
<th>Database Date</th>
<th>Source</th>
<th>NFO Path</th>
</tr>
</thead>
<tbody>
`;
response.episodes.forEach(ep => {
html += `
<tr>
<td>${ep.imdb_id}</td>
<td>${ep.season}</td>
<td>${ep.episode}</td>
<td>${new Date(ep.dateadded).toLocaleString()}</td>
<td>${ep.source}</td>
<td><small>${ep.nfo_path}</small></td>
</tr>
`;
});
html += '</tbody></table></div></div>';
} else {
html += '<div class="success">✅ All NFO files have dateadded elements!</div>';
}
resultsContent.innerHTML = html;
} else {
resultsContent.innerHTML = `<div class="error">Error: ${response.message}</div>`;
}
} catch (error) {
resultsContent.innerHTML = `<div class="error">Failed to scan NFO files: ${error.message}</div>`;
}
}
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 = '<div class="loading">Fixing all missing dateadded elements in NFO files...</div>';
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 = `
<div class="fix-summary success">
<h4>NFO Fix Results</h4>
<p>✅ <strong>${response.updated_count}</strong> NFO files updated successfully</p>
<p><strong>Total episodes processed:</strong> ${response.total_episodes}</p>
<p><strong>Message:</strong> ${response.message}</p>
</div>
`;
if (response.errors && response.errors.length > 0) {
html += `
<div class="fix-errors">
<h5>Errors (first 10):</h5>
<ul>
`;
response.errors.forEach(error => {
html += `<li>${error}</li>`;
});
html += '</ul></div>';
}
resultsContent.innerHTML = html;
} else {
resultsContent.innerHTML = `<div class="error">Error: ${response.message}</div>`;
}
} catch (error) {
resultsContent.innerHTML = `<div class="error">Failed to fix NFO files: ${error.message}</div>`;
}
}
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 = '<div class="loading">Fixing NFO files for specified series...</div>';
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 = `
<div class="fix-summary success">
<h4>NFO Fix Results for Specific Series</h4>
<p>✅ <strong>${response.updated_count}</strong> NFO files updated successfully</p>
<p><strong>Series processed:</strong> ${idArray.join(', ')}</p>
<p><strong>Message:</strong> ${response.message}</p>
</div>
`;
resultsContent.innerHTML = html;
} else {
resultsContent.innerHTML = `<div class="error">Error: ${response.message}</div>`;
}
} catch (error) {
resultsContent.innerHTML = `<div class="error">Failed to fix NFO files: ${error.message}</div>`;
}
}
// Database Admin Functions
async function showEpisodesMissingNFODateadded() {
const resultsDiv = document.getElementById('admin-results');
const resultsContent = document.getElementById('admin-results-content');
try {
resultsContent.innerHTML = '<div class="loading">Checking for episodes missing NFO dateadded...</div>';
resultsDiv.style.display = 'block';
const response = await apiCall('/api/admin/nfo/missing-dateadded');
if (response.success) {
const html = `
<h4>Episodes Missing NFO dateadded</h4>
<p><strong>Found:</strong> ${response.missing_dateadded_count} episodes missing dateadded in NFO files</p>
<p><strong>Total checked:</strong> ${response.total_episodes_checked} episodes</p>
${response.missing_dateadded_count > 0 ?
'<p><strong>Recommendation:</strong> Use the "NFO File Repair" tool to fix these issues.</p>' :
'<p>✅ All NFO files are properly maintained!</p>'
}
`;
resultsContent.innerHTML = html;
} else {
resultsContent.innerHTML = `<div class="error">Error: ${response.message}</div>`;
}
} catch (error) {
resultsContent.innerHTML = `<div class="error">Failed to check NFO files: ${error.message}</div>`;
}
}
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 = '<div class="loading">Loading recent webhook activity...</div>';
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 = `
<h4>Recent Webhook Activity (Last 24 Hours)</h4>
<p><strong>Episodes processed:</strong> ${response.row_count}</p>
<div class="table-container">
<table class="results-table">
<thead>
<tr>
<th>IMDb ID</th>
<th>Season</th>
<th>Episode</th>
<th>Date Added</th>
<th>Source</th>
<th>Last Updated</th>
</tr>
</thead>
<tbody>
`;
response.rows.forEach(row => {
html += `
<tr>
<td>${row.imdb_id}</td>
<td>${row.season}</td>
<td>${row.episode}</td>
<td>${row.dateadded ? new Date(row.dateadded).toLocaleString() : 'None'}</td>
<td>${row.source}</td>
<td>${new Date(row.last_updated).toLocaleString()}</td>
</tr>
`;
});
html += '</tbody></table></div>';
resultsContent.innerHTML = html;
} else {
resultsContent.innerHTML = '<p>No recent webhook activity found.</p>';
}
} catch (error) {
resultsContent.innerHTML = `<div class="error">Failed to load webhook activity: ${error.message}</div>`;
}
}