changes to mobile styling in modal view, complete reword of backend worker system

This commit is contained in:
Bryan Van Deusen
2026-01-20 13:14:13 -05:00
parent 96f72718bd
commit cb3897c0b0
22 changed files with 2432 additions and 487 deletions
+304
View File
@@ -206,7 +206,140 @@
</div>
</div><!-- end settings-grid -->
<!-- Import Queue Status (Full Width) -->
<div class="settings-container" style="max-width: 1200px; margin: 2rem auto;">
<div class="settings-section">
<h2>Import Queue Status</h2>
<p class="settings-description">Monitor and control the Celery-based import queue system.</p>
<div id="queueStatusSection">
<div class="stats-grid" style="grid-template-columns: repeat(6, 1fr);">
<div class="stat-item">
<span class="stat-value" id="queuePending">-</span>
<span class="stat-label">Pending</span>
</div>
<div class="stat-item">
<span class="stat-value" id="queueQueued">-</span>
<span class="stat-label">Queued</span>
</div>
<div class="stat-item">
<span class="stat-value" id="queueProcessing">-</span>
<span class="stat-label">Processing</span>
</div>
<div class="stat-item">
<span class="stat-value" id="queueComplete">-</span>
<span class="stat-label">Complete</span>
</div>
<div class="stat-item">
<span class="stat-value" id="queueSkipped">-</span>
<span class="stat-label">Skipped</span>
</div>
<div class="stat-item">
<span class="stat-value" id="queueFailed">-</span>
<span class="stat-label">Failed</span>
</div>
</div>
<div id="activeBatchInfo" class="import-stats" style="margin-top: 1rem; display: none;">
<h3>Active Batch</h3>
<p>Processing <strong id="batchDir">-</strong></p>
<div class="batch-progress">
<div class="progress-bar">
<div class="progress-fill" id="batchProgressFill" style="width: 0%"></div>
</div>
<span id="batchProgressText">0 / 0</span>
</div>
</div>
<div class="queue-actions" style="margin-top: 1rem; display: flex; gap: 0.75rem; flex-wrap: wrap;">
<button id="triggerImportBtn" class="btn primary-btn">Trigger Import Scan</button>
<button id="retryFailedBtn" class="btn warning-btn">Retry Failed Tasks</button>
<button id="clearCompletedBtn" class="btn secondary-btn">Clear Completed</button>
<button id="regenMissingThumbsBtn" class="btn secondary-btn">Regenerate Missing Thumbnails</button>
<a href="/flower" target="_blank" class="btn secondary-btn" style="text-decoration: none;">Open Flower Dashboard</a>
</div>
<div id="celeryWorkerInfo" style="margin-top: 1rem;">
<p style="color: var(--text-muted); font-size: 0.85rem;">
Workers: <strong id="workerCount">-</strong> |
Active Tasks: <strong id="activeTasks">-</strong>
</p>
</div>
</div>
<!-- Recent Tasks Table -->
<div id="recentTasksSection" style="margin-top: 1.5rem;">
<h3>Recent Tasks</h3>
<div class="tasks-table-wrapper" style="max-height: 300px; overflow-y: auto;">
<table class="tasks-table">
<thead>
<tr>
<th>File</th>
<th>Type</th>
<th>Status</th>
<th>Error</th>
<th>Time</th>
</tr>
</thead>
<tbody id="recentTasksBody">
<tr><td colspan="5" style="text-align: center; color: var(--text-muted);">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<style>
.tasks-table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
}
.tasks-table th,
.tasks-table td {
padding: 0.5rem 0.75rem;
text-align: left;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.tasks-table th {
background: rgba(255, 255, 255, 0.05);
color: var(--text-dim);
font-weight: 500;
position: sticky;
top: 0;
}
.tasks-table td {
color: var(--text);
}
.tasks-table .status-pending { color: #9ca3af; }
.tasks-table .status-queued { color: #60a5fa; }
.tasks-table .status-processing { color: #fbbf24; }
.tasks-table .status-complete { color: #34d399; }
.tasks-table .status-skipped { color: #a78bfa; }
.tasks-table .status-failed { color: #f87171; }
.batch-progress {
display: flex;
align-items: center;
gap: 1rem;
margin-top: 0.5rem;
}
.progress-bar {
flex: 1;
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: var(--btn-primary);
transition: width 0.3s ease;
}
.queue-actions .btn {
min-width: auto;
white-space: nowrap;
}
.settings-description {
color: var(--text-muted);
margin-bottom: 1rem;
@@ -363,6 +496,177 @@
<script>
document.addEventListener('DOMContentLoaded', () => {
// ========================================
// Celery Queue Status
// ========================================
async function loadQueueStatus() {
try {
const r = await fetch('/api/import/status');
const data = await r.json();
if (data.ok) {
// Update status counts
document.getElementById('queuePending').textContent = data.status_counts.pending || 0;
document.getElementById('queueQueued').textContent = data.status_counts.queued || 0;
document.getElementById('queueProcessing').textContent = data.status_counts.processing || 0;
document.getElementById('queueComplete').textContent = data.status_counts.complete || 0;
document.getElementById('queueSkipped').textContent = data.status_counts.skipped || 0;
document.getElementById('queueFailed').textContent = data.status_counts.failed || 0;
// Update active batch info
const batchEl = document.getElementById('activeBatchInfo');
if (data.active_batch) {
batchEl.style.display = 'block';
document.getElementById('batchDir').textContent = data.active_batch.source_directory;
const processed = data.active_batch.processed_files || 0;
const total = data.active_batch.total_files || 1;
const pct = Math.round((processed / total) * 100);
document.getElementById('batchProgressFill').style.width = pct + '%';
document.getElementById('batchProgressText').textContent = `${processed} / ${total}`;
} else {
batchEl.style.display = 'none';
}
// Update recent tasks table
const tbody = document.getElementById('recentTasksBody');
if (data.recent_tasks && data.recent_tasks.length > 0) {
tbody.innerHTML = data.recent_tasks.map(t => `
<tr>
<td title="${t.source_path || ''}">${t.source_path || '-'}</td>
<td>${t.task_type || '-'}</td>
<td class="status-${t.status}">${t.status || '-'}</td>
<td title="${t.error_message || ''}">${t.error_message ? t.error_message.substring(0, 30) + '...' : '-'}</td>
<td>${t.created_at ? new Date(t.created_at).toLocaleString() : '-'}</td>
</tr>
`).join('');
} else {
tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; color: var(--text-muted);">No recent tasks</td></tr>';
}
}
} catch (e) {
console.error('Failed to load queue status:', e);
}
}
async function loadCeleryStatus() {
try {
const r = await fetch('/api/celery/status');
const data = await r.json();
if (data.ok) {
document.getElementById('workerCount').textContent = data.worker_count || 0;
document.getElementById('activeTasks').textContent = data.active_tasks || 0;
} else {
document.getElementById('workerCount').textContent = '?';
document.getElementById('activeTasks').textContent = '?';
}
} catch (e) {
document.getElementById('workerCount').textContent = 'offline';
document.getElementById('activeTasks').textContent = '-';
}
}
// Load queue status on page load and refresh periodically
loadQueueStatus();
loadCeleryStatus();
setInterval(loadQueueStatus, 5000); // Refresh every 5 seconds
setInterval(loadCeleryStatus, 10000); // Refresh every 10 seconds
// Trigger Import Scan
document.getElementById('triggerImportBtn').addEventListener('click', async () => {
const btn = document.getElementById('triggerImportBtn');
btn.disabled = true;
btn.textContent = 'Starting...';
try {
const r = await fetch('/api/import/trigger', { method: 'POST' });
const data = await r.json();
if (data.ok) {
alert('Import scan started! Task ID: ' + data.task_id);
loadQueueStatus();
} else {
alert('Failed to start import: ' + (data.error || 'Unknown error'));
}
} catch (e) {
alert('Failed to start import: ' + e.message);
} finally {
btn.disabled = false;
btn.textContent = 'Trigger Import Scan';
}
});
// Retry Failed Tasks
document.getElementById('retryFailedBtn').addEventListener('click', async () => {
const btn = document.getElementById('retryFailedBtn');
btn.disabled = true;
btn.textContent = 'Retrying...';
try {
const r = await fetch('/api/import/retry-failed', { method: 'POST' });
const data = await r.json();
if (data.ok) {
alert(`Retried ${data.retried} failed task(s).`);
loadQueueStatus();
} else {
alert('Failed to retry: ' + (data.error || 'Unknown error'));
}
} catch (e) {
alert('Failed to retry: ' + e.message);
} finally {
btn.disabled = false;
btn.textContent = 'Retry Failed Tasks';
}
});
// Clear Completed Tasks
document.getElementById('clearCompletedBtn').addEventListener('click', async () => {
const btn = document.getElementById('clearCompletedBtn');
btn.disabled = true;
btn.textContent = 'Clearing...';
try {
const r = await fetch('/api/import/clear-completed', { method: 'POST' });
const data = await r.json();
if (data.ok) {
alert(`Cleared ${data.deleted} completed/skipped task(s).`);
loadQueueStatus();
} else {
alert('Failed to clear: ' + (data.error || 'Unknown error'));
}
} catch (e) {
alert('Failed to clear: ' + e.message);
} finally {
btn.disabled = false;
btn.textContent = 'Clear Completed';
}
});
// Regenerate Missing Thumbnails
document.getElementById('regenMissingThumbsBtn').addEventListener('click', async () => {
const btn = document.getElementById('regenMissingThumbsBtn');
btn.disabled = true;
btn.textContent = 'Starting...';
try {
const r = await fetch('/api/thumbnails/regenerate-missing', { method: 'POST' });
const data = await r.json();
if (data.ok) {
alert('Missing thumbnail generation started! Task ID: ' + data.task_id);
} else {
alert('Failed to start: ' + (data.error || 'Unknown error'));
}
} catch (e) {
alert('Failed to start: ' + e.message);
} finally {
btn.disabled = false;
btn.textContent = 'Regenerate Missing Thumbnails';
}
});
// Load import settings
fetch('/api/import-settings')
.then(r => r.json())