// Formatting of times, durations and labels. const MIN = 60 * 1000; const HOUR = 60 * MIN; const DAY = 24 * HOUR; // Compact age for list rows: "now", "4m", "3h", "2d". export function age(iso, now = Date.now()) { const ms = Math.max(0, now - Date.parse(iso)); if (ms < MIN) return 'now'; if (ms < HOUR) return `${Math.floor(ms / MIN)}m`; if (ms < DAY) return `${Math.floor(ms / HOUR)}h`; return `${Math.floor(ms / DAY)}d`; } // "4 min ago", "3 h ago", "yesterday"-free: stays unambiguous at 3am. export function ago(iso, now = Date.now()) { const a = age(iso, now); return a === 'now' ? 'just now' : `${a} ago`; } // Time remaining until iso, e.g. "1h 20m". export function until(iso, now = Date.now()) { return duration(Date.parse(iso) - now); } export function duration(ms) { ms = Math.max(0, ms); if (ms < MIN) return '<1m'; const d = Math.floor(ms / DAY); const h = Math.floor((ms % DAY) / HOUR); const m = Math.floor((ms % HOUR) / MIN); if (d) return h ? `${d}d ${h}h` : `${d}d`; if (h) return m ? `${h}h ${m}m` : `${h}h`; return `${m}m`; } const timeFmt = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }); const dayTimeFmt = new Intl.DateTimeFormat(undefined, { weekday: 'short', day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit', }); // Local timestamp; the date is dropped when it is today. export function when(iso) { const d = new Date(iso); const today = new Date(); if (d.toDateString() === today.toDateString()) return timeFmt.format(d); return dayTimeFmt.format(d); } export function isFuture(iso) { return iso != null && Date.parse(iso) > Date.now(); } // Local calendar dates, as the schedule stores them (YYYY-MM-DD). export function isoDate(d) { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${y}-${m}-${day}`; } export function mondayOf(d) { const r = new Date(d.getFullYear(), d.getMonth(), d.getDate()); r.setDate(r.getDate() - ((r.getDay() + 6) % 7)); return r; } export function addDays(d, n) { const r = new Date(d); r.setDate(r.getDate() + n); return r; } // ISO 8601 week number. export function isoWeek(d) { const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())); const day = t.getUTCDay() || 7; t.setUTCDate(t.getUTCDate() + 4 - day); const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1)); return Math.ceil(((t - yearStart) / DAY + 1) / 7); } export const STATUS_LABEL = { triggered: 'Triggered', acknowledged: 'Acknowledged', resolved: 'Resolved', snoozed: 'Snoozed', firing: 'Firing', }; export function severityClass(sev) { const s = (sev || '').toLowerCase(); if (s === 'critical' || s === 'page' || s === 'error') return 'sev-critical'; if (s === 'warning' || s === 'warn') return 'sev-warning'; if (s) return 'sev-info'; return ''; } // A one-line summary of the group labels, without the one the title already shows. export function labelSummary(labels, skip = 'alertname') { return Object.entries(labels || {}) .filter(([k]) => k !== skip) .map(([k, v]) => `${k}=${v}`) .join(' ยท '); } export function initial(name) { return (name || '?').trim().charAt(0) || '?'; }