working on backend
This commit is contained in:
@@ -16,6 +16,7 @@ const app = {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
try { setupGlobalErrorHandlers(); } catch (_) {}
|
||||
initializeApp();
|
||||
try { initializeBatchProgressUI(); } catch (_) {}
|
||||
});
|
||||
|
||||
// Theme Management (centralized)
|
||||
@@ -123,6 +124,141 @@ async function initializeApp() {
|
||||
console.log('Delphi Database System initialized');
|
||||
}
|
||||
|
||||
// Live Batch Progress (Admin Overview)
|
||||
function initializeBatchProgressUI() {
|
||||
const listEl = document.getElementById('batchProgressList');
|
||||
const emptyEl = document.getElementById('batchProgressEmpty');
|
||||
const refreshBtn = document.getElementById('refreshBatchesBtn');
|
||||
if (!listEl || !emptyEl) return;
|
||||
|
||||
const subscriptions = new Map();
|
||||
|
||||
function percent(progress) {
|
||||
if (!progress || !progress.total_files) return 0;
|
||||
const done = Number(progress.processed_files || 0);
|
||||
const total = Number(progress.total_files || 0);
|
||||
return Math.max(0, Math.min(100, Math.round((done / total) * 100)));
|
||||
}
|
||||
|
||||
function renderRow(progress) {
|
||||
const pid = progress.batch_id;
|
||||
const pct = percent(progress);
|
||||
const status = String(progress.status || '').toUpperCase();
|
||||
const current = progress.current_file || '';
|
||||
const success = progress.successful_files || 0;
|
||||
const failed = progress.failed_files || 0;
|
||||
const total = progress.total_files || 0;
|
||||
|
||||
return (
|
||||
`<div class="border border-neutral-200 dark:border-neutral-700 rounded-lg p-3" data-batch="${pid}">
|
||||
<div class="flex items-center justify-between gap-3 mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs px-2 py-0.5 rounded bg-neutral-100 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300">${pid}</span>
|
||||
<span class="text-xs font-medium ${status === 'COMPLETED' ? 'text-green-600 dark:text-green-400' : status === 'FAILED' ? 'text-red-600 dark:text-red-400' : status === 'CANCELLED' ? 'text-neutral-500' : 'text-amber-600 dark:text-amber-400'}">${status}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-neutral-500 dark:text-neutral-400">${success}/${total} ✓ • ${failed} ✕</span>
|
||||
<button class="text-xs px-2 py-1 rounded bg-neutral-100 dark:bg-neutral-800 hover:bg-neutral-200 dark:hover:bg-neutral-700 text-neutral-700 dark:text-neutral-300" data-action="cancel" ${status==='RUNNING'||status==='PENDING' ? '' : 'disabled'}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full h-2 bg-neutral-100 dark:bg-neutral-800 rounded">
|
||||
<div class="h-2 rounded ${status==='FAILED'? 'bg-red-500' : status==='CANCELLED' ? 'bg-neutral-500' : 'bg-primary-500'}" style="width:${pct}%"></div>
|
||||
</div>
|
||||
<div class="mt-2 flex items-center justify-between text-xs text-neutral-600 dark:text-neutral-400">
|
||||
<span>${pct}%</span>
|
||||
<span>${current ? 'Current: '+current : ''}</span>
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchActiveBatches() {
|
||||
const resp = await window.http.wrappedFetch('/api/billing/statements/batch-list');
|
||||
if (!resp.ok) return [];
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
function updateEmptyState() {
|
||||
const hasRows = listEl.children.length > 0;
|
||||
emptyEl.style.display = hasRows ? 'none' : '';
|
||||
}
|
||||
|
||||
function upsertRow(data) {
|
||||
const pid = data && data.batch_id ? data.batch_id : null;
|
||||
if (!pid) return;
|
||||
let row = listEl.querySelector(`[data-batch="${pid}"]`);
|
||||
const html = renderRow(data);
|
||||
if (row) {
|
||||
row.outerHTML = html;
|
||||
} else {
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = html;
|
||||
listEl.prepend(container.firstChild);
|
||||
}
|
||||
updateEmptyState();
|
||||
}
|
||||
|
||||
async function cancelBatch(batchId) {
|
||||
try {
|
||||
const resp = await window.http.wrappedFetch(`/api/billing/statements/batch-progress/${encodeURIComponent(batchId)}`, { method: 'DELETE' });
|
||||
if (!resp.ok) {
|
||||
throw await window.http.toError(resp, 'Failed to cancel batch');
|
||||
}
|
||||
// Let stream update the row; no-op here
|
||||
} catch (e) {
|
||||
console.warn('Cancel failed', e);
|
||||
try { alert(window.http.formatAlert(e, 'Cancel failed')); } catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
function attachRowHandlers() {
|
||||
listEl.addEventListener('click', function(ev){
|
||||
const btn = ev.target.closest('[data-action="cancel"]');
|
||||
if (!btn) return;
|
||||
const row = ev.target.closest('[data-batch]');
|
||||
if (!row) return;
|
||||
const pid = row.getAttribute('data-batch');
|
||||
cancelBatch(pid);
|
||||
});
|
||||
}
|
||||
|
||||
async function subscribeTo(pid) {
|
||||
if (!window.progress || typeof window.progress.subscribe !== 'function') return;
|
||||
if (subscriptions.has(pid)) return;
|
||||
const unsub = window.progress.subscribe(pid, function(progress){
|
||||
if (!progress) return;
|
||||
upsertRow(progress);
|
||||
const status = String(progress.status || '').toUpperCase();
|
||||
if (status === 'COMPLETED' || status === 'FAILED' || status === 'CANCELLED') {
|
||||
// Auto-unsubscribe once terminal
|
||||
const fn = subscriptions.get(pid);
|
||||
if (fn) { try { fn(); } catch (_) {} }
|
||||
subscriptions.delete(pid);
|
||||
}
|
||||
}, function(err){
|
||||
// Non-fatal; polling fallback is handled inside subscribe()
|
||||
console.debug('progress stream issue', err && err.message ? err.message : err);
|
||||
});
|
||||
subscriptions.set(pid, unsub);
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const batches = await fetchActiveBatches();
|
||||
if (!Array.isArray(batches)) return;
|
||||
if (batches.length === 0) updateEmptyState();
|
||||
for (const pid of batches) {
|
||||
subscribeTo(pid);
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshBtn) {
|
||||
refreshBtn.addEventListener('click', function(){ refresh(); });
|
||||
}
|
||||
|
||||
attachRowHandlers();
|
||||
refresh();
|
||||
}
|
||||
|
||||
// Form validation
|
||||
function initializeFormValidation() {
|
||||
// Native validation handling
|
||||
|
||||
Reference in New Issue
Block a user