Files
kyleandClaude Opus 5.5 bf752a904f Add backend API, running status and 30-day run history
- FastAPI + SQLite backend: tenants, jobs and runs, with an idempotent
  /api/v1/ingest endpoint authenticated by per-collector API keys
- Runs store started_at and finished_at; "Last Run" is the most recent
  finish, and in-progress runs show as Running
- Job modal shows a 30-day daily strip, success rate, and a filterable
  run list with per-run messages
- nginx: HTTPS plus basic auth, ingest endpoint exempt; systemd unit
- manage.py for API keys, job cleanup and demo data; pytest suite

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 21:53:24 -06:00

44 lines
1.6 KiB
JavaScript

const dateTimeFmt = new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' });
const timeFmt = new Intl.DateTimeFormat(undefined, { timeStyle: 'medium' });
const relFmt = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });
const REL_UNITS = [
['year', 31_536_000], ['month', 2_592_000], ['week', 604_800],
['day', 86_400], ['hour', 3_600], ['minute', 60],
];
export function parseDate(value) {
if (!value) return null;
const d = new Date(value);
return Number.isNaN(d.getTime()) ? null : d;
}
export const formatDateTime = (d) => (d ? dateTimeFmt.format(d) : '—');
export const formatTime = (d) => timeFmt.format(d);
export function formatRelative(d, now = Date.now()) {
if (!d) return '';
const diffSec = Math.round((d.getTime() - now) / 1000);
for (const [unit, secs] of REL_UNITS) {
if (Math.abs(diffSec) >= secs) return relFmt.format(Math.round(diffSec / secs), unit);
}
return relFmt.format(diffSec, 'second');
}
export function formatDuration(totalSec) {
if (totalSec == null || Number.isNaN(Number(totalSec))) return null;
const s = Math.max(0, Math.round(totalSec));
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
const sec = s % 60;
return [h && `${h}h`, (h || m) && `${m}m`, `${sec}s`].filter(Boolean).join(' ');
}
export function formatBytes(bytes) {
if (bytes == null || Number.isNaN(Number(bytes))) return null;
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
let n = Number(bytes);
let i = 0;
while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
return `${n.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
}