// KPI Alert Dashboard — vanilla JS, compiled by Vite into /static/assets/js/iris/kpi_dashboard.js
import '../css/kpi_dashboard.css';
const API_BASE = '/kpi-dashboard/api';
const state = {
page: 1,
perPage: 20,
sortBy: 'alert_id',
sortDir: 'desc',
filterTitle: '',
filterOwner: '',
total: 0,
selected: new Set(),
};
// Debounce helper for filter inputs
let _debounceTimer = null;
function debouncedLoad() {
clearTimeout(_debounceTimer);
_debounceTimer = setTimeout(() => {
state.page = 1;
loadAlerts();
}, 350);
}
async function loadAlerts() {
state.filterTitle = document.getElementById('filter-title').value;
state.filterOwner = document.getElementById('filter-owner').value;
const params = new URLSearchParams({
page: state.page,
per_page: state.perPage,
sort_by: state.sortBy,
sort_dir: state.sortDir,
});
if (state.filterTitle) params.set('filter_title', state.filterTitle);
if (state.filterOwner) params.set('filter_owner', state.filterOwner);
const body = document.getElementById('alerts-body');
body.innerHTML = '
| Loading... |
';
try {
const res = await fetch(`${API_BASE}/alerts?${params}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
const alertsData = json.data?.alerts ?? {};
const alerts = alertsData.data ?? [];
state.total = alertsData.total ?? alerts.length;
renderTable(alerts);
updatePageInfo();
updateSelectionLabel();
} catch (err) {
body.innerHTML = `| Error: ${err.message} |
`;
}
}
function renderKpiBar(kpi) {
if (!kpi) return '—';
const containerW = 100;
const fillW = Math.round((kpi.elapsed_pct / 100) * containerW);
const fillColor = kpi.resolved ? '#6b7280'
: kpi.elapsed_pct >= 75 ? '#ef4444'
: kpi.elapsed_pct >= 50 ? '#f97316'
: kpi.elapsed_pct >= 25 ? '#eab308'
: '#22c55e';
const segs = (kpi.segments || []).map(s =>
``
).join('');
return `
`;
}
function severityBadge(severity) {
if (!severity) return '';
const name = (severity.severity_name || '').toLowerCase();
const cls = name === 'high' ? 'badge-high' : name === 'medium' ? 'badge-medium' : 'badge-low';
return `${severity.severity_name || ''}`;
}
function statusDot(status) {
if (!status) return '';
const name = (status.status_name || '').toLowerCase();
const cls = name === 'closed' ? 'dot-closed' : 'dot-open';
return ``;
}
function fmtDate(str) {
if (!str) return '—';
try {
return new Date(str).toLocaleString();
} catch {
return str;
}
}
function renderTable(alerts) {
const body = document.getElementById('alerts-body');
if (!alerts.length) {
body.innerHTML = '| No alerts found. |
';
return;
}
body.innerHTML = alerts.map(alert => {
const id = alert.alert_id ?? '';
const checked = state.selected.has(id) ? 'checked' : '';
const owner = (alert.owner || {}).user_name || '—';
const classification = (alert.classification || {}).name || '—';
const closedAt = alert.alert_close_timestamp ? fmtDate(alert.alert_close_timestamp) : '—';
return `
|
${statusDot(alert.status)} |
${owner} |
${id} |
${alert.alert_title || '—'} |
${classification} |
${severityBadge(alert.severity)} |
${fmtDate(alert.alert_creation_time)} |
${renderKpiBar(alert.kpi)} |
${fmtDate(alert.alert_source_event_time)} |
${closedAt} |
— |
`;
}).join('');
}
function toggleAll(chk) {
document.querySelectorAll('.row-chk').forEach(el => {
el.checked = chk.checked;
const id = Number(el.dataset.id);
if (chk.checked) state.selected.add(id);
else state.selected.delete(id);
});
updateSelectionLabel();
}
function toggleRow(el) {
const id = Number(el.dataset.id);
if (el.checked) state.selected.add(id);
else state.selected.delete(id);
updateSelectionLabel();
}
function updateSelectionLabel() {
// selection label removed from UI
}
function updatePageInfo() {
const el = document.getElementById('page-info');
if (el) {
const totalPages = Math.max(1, Math.ceil(state.total / state.perPage));
el.textContent = `Page ${state.page} of ${totalPages} (${state.total} total)`;
}
}
function prevPage() {
if (state.page > 1) {
state.page--;
refreshActive();
}
}
function nextPage() {
const totalPages = Math.ceil(state.total / state.perPage);
if (state.page < totalPages) {
state.page++;
refreshActive();
}
}
function sortBy(col) {
if (state.sortBy === col) {
state.sortDir = state.sortDir === 'asc' ? 'desc' : 'asc';
} else {
state.sortBy = col;
state.sortDir = 'desc';
}
state.page = 1;
loadAlerts();
}
async function assignSelected() {
if (!state.selected.size) {
alert('No alerts selected.');
return;
}
const ownerIdStr = prompt('Enter owner user ID to assign:');
if (!ownerIdStr) return;
const ownerId = parseInt(ownerIdStr, 10);
if (isNaN(ownerId)) {
alert('Invalid owner ID.');
return;
}
const ids = Array.from(state.selected);
try {
await Promise.all(ids.map(id =>
fetch(`${API_BASE}/alerts/${id}/assign`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ owner_id: ownerId }),
})
));
state.selected.clear();
loadAlerts();
} catch (err) {
alert(`Assignment failed: ${err.message}`);
}
}
function exportCsv() {
const params = new URLSearchParams({
sort_by: state.sortBy,
sort_dir: state.sortDir,
});
if (state.filterTitle) params.set('filter_title', state.filterTitle);
if (state.filterOwner) params.set('filter_owner', state.filterOwner);
window.location.href = `${API_BASE}/alerts/export-csv?${params}`;
}
let activeTab = 'alerts';
function switchTab(tab) {
activeTab = tab;
document.getElementById('tab-alerts').classList.toggle('active', tab === 'alerts');
document.getElementById('tab-cases').classList.toggle('active', tab === 'cases');
state.page = 1;
activeTab === 'alerts' ? loadAlerts() : loadCases();
}
async function loadCases() {
const params = new URLSearchParams({
page: state.page,
per_page: state.perPage,
sort_by: state.sortBy,
sort_dir: state.sortDir,
});
const body = document.getElementById('alerts-body');
body.innerHTML = '| Loading... |
';
try {
const res = await fetch(`${API_BASE}/cases?${params}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
const casesData = json.data?.cases ?? {};
const cases = casesData.data ?? [];
state.total = casesData.total ?? cases.length;
renderCasesTable(cases);
updatePageInfo();
} catch (err) {
body.innerHTML = `| Error: ${err.message} |
`;
}
}
function renderCasesTable(cases) {
const body = document.getElementById('alerts-body');
if (!cases.length) {
body.innerHTML = '| No cases found. |
';
return;
}
body.innerHTML = cases.map(c => {
const owner = (c.owner || {}).user_name || '—';
const stateName = (c.state || {}).state_name || '—';
const closed = c.close_date ? fmtDate(c.close_date) : '—';
return `
|
${statusDot({status_name: stateName})} |
${owner} |
${c.case_id ?? ''} |
${c.case_name || '—'} |
${c.case_soc_id || '—'} |
— |
${fmtDate(c.open_date)} |
${renderKpiBar(c.kpi)} |
— |
${closed} |
— |
`;
}).join('');
}
function openAlertDetail(alertId, event) {
// Don't navigate when clicking the checkbox
if (event && event.target.type === 'checkbox') return;
window.location.href = `/alerts?alert_ids=${alertId}&cid=1`;
}
function refreshActive() {
activeTab === 'alerts' ? loadAlerts() : loadCases();
}
// Auto-load on page ready
document.addEventListener('DOMContentLoaded', loadAlerts);
setInterval(refreshActive, 60_000);
// Expose for inline onclick handlers
window.debouncedLoad = debouncedLoad;
window.loadAlerts = loadAlerts;
window.toggleAll = toggleAll;
window.toggleRow = toggleRow;
window.assignSelected = assignSelected;
window.exportCsv = exportCsv;
window.prevPage = prevPage;
window.nextPage = nextPage;
window.sortBy = sortBy;
window.switchTab = switchTab;
window.openAlertDetail = openAlertDetail;