This commit is contained in:
@@ -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>`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user