diff --git a/public/index.html b/public/index.html index 3345067..d9f4216 100644 --- a/public/index.html +++ b/public/index.html @@ -152,12 +152,19 @@ pre.raw-line { background: var(--bs-tertiary-bg); padding: .75rem; border-radius +
| ID | +ID | Severity | Status | Message | @@ -180,6 +187,7 @@ pre.raw-line { background: var(--bs-tertiary-bg); padding: .75rem; border-radius||
|---|---|---|---|---|---|---|
| Loading... |
No results for "' + esc(query) + '"
No context found
'; + bootstrap.Modal.getOrCreateInstance(document.getElementById('contextModal')).show(); +} catch (e) { toast('Failed to load context', 'danger'); } +} + +async function exportLogs() { + const query = document.getElementById('logSearchInput').value.trim(); + if (!query) { toast('Search something first', 'warning'); return; } + const since = document.getElementById('logSince').value; + const until = document.getElementById('logUntil').value; + let url = '/logs/search?q=' + encodeURIComponent(query) + '&limit=10000'; + if (since) url += '&since=' + encodeURIComponent(since); + if (until) url += '&until=' + encodeURIComponent(until); + try { + const res = await api(url); + const entries = res.data || []; + if (!entries.length) { toast('No results to export', 'warning'); return; } + const csv = ['ID,Line,Source,Level,Created', ...entries.map(e => + [e.id, '"' + (e.line || '').replace(/"/g, '""') + '"', + e.source_name || '', e.level || '', e.created_at].join(','))].join('\n'); + const blob = new Blob([csv], { type: 'text/csv' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = 'logs.csv'; + a.click(); + toast('Exported ' + entries.length + ' rows'); + } catch (e) { toast('Export failed', 'danger'); } +} + +// --- BULK ALERT OPERATIONS --- +let selectedAlertIds = new Set(); + +function toggleAlertSelection(id) { + if (selectedAlertIds.has(id)) selectedAlertIds.delete(id); + else selectedAlertIds.add(id); + document.getElementById('bulkBar').classList.toggle('d-none', selectedAlertIds.size === 0); + document.getElementById('selectedCount').textContent = selectedAlertIds.size; +} + +function renderAlerts(query) { + let sorted = [...state.alerts]; + const field = state.sortField; + const dir = state.sortDir; + sorted.sort((a, b) => { + let va = a[field], vb = b[field]; + if (field === 'severity') { + const order = ['debug','info','notice','warning_low','warning','warning_high','error','critical_low','critical','critical_high','emergency']; + va = order.indexOf(va); + vb = order.indexOf(vb); + } else if (field === 'created_at') { + va = new Date(va).getTime(); + vb = new Date(vb).getTime(); + } else { + va = (va || '').toString().toLowerCase(); + vb = (vb || '').toString().toLowerCase(); + if (va < vb) return dir === 'asc' ? -1 : 1; + if (va > vb) return dir === 'asc' ? 1 : -1; + return 0; + } + return dir === 'asc' ? va - vb : vb - va; + }); + + const tbody = document.getElementById('alertsBody'); + if (!sorted.length) { + tbody.innerHTML = 'No alerts match those filters