- 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]>
44 lines
1.6 KiB
JavaScript
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]}`;
|
|
} |