110 lines
3.8 KiB
TypeScript
110 lines
3.8 KiB
TypeScript
/** Formatting helpers for numbers, bytes, durations and timestamps. */
|
|
|
|
export function formatInt(value: number | null | undefined): string {
|
|
if (value === null || value === undefined || Number.isNaN(value)) return "—";
|
|
return value.toLocaleString("en-US");
|
|
}
|
|
|
|
export function formatBytes(value: number | null | undefined): string {
|
|
if (value === null || value === undefined || Number.isNaN(value)) return "—";
|
|
if (value < 1024) return `${value} B`;
|
|
const units = ["KiB", "MiB", "GiB", "TiB"];
|
|
let size = value;
|
|
let unit = "B";
|
|
for (const next of units) {
|
|
if (size < 1024) break;
|
|
size /= 1024;
|
|
unit = next;
|
|
}
|
|
return `${size >= 100 ? size.toFixed(0) : size.toFixed(1)} ${unit}`;
|
|
}
|
|
|
|
export function formatDurationMs(value: number | null | undefined): string {
|
|
if (value === null || value === undefined || Number.isNaN(value)) return "—";
|
|
if (value < 1000) return `${Math.round(value)} ms`;
|
|
const totalSeconds = Math.floor(value / 1000);
|
|
const hours = Math.floor(totalSeconds / 3600);
|
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
const seconds = totalSeconds % 60;
|
|
if (hours > 0) return `${hours}h ${String(minutes).padStart(2, "0")}m`;
|
|
if (minutes > 0) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
const fraction = value / 1000;
|
|
return `${fraction >= 10 ? fraction.toFixed(0) : fraction.toFixed(1)}s`;
|
|
}
|
|
|
|
/** Parse an ISO/RFC3339 timestamp; returns null when unparseable. */
|
|
export function parseTime(value: string | null | undefined): Date | null {
|
|
if (!value) return null;
|
|
const date = new Date(value);
|
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
}
|
|
|
|
/** "2026-07-17 01:46:55 UTC" — stable, timezone-explicit rendering. */
|
|
export function formatUtc(value: string | null | undefined): string {
|
|
const date = parseTime(value);
|
|
if (!date) return value ? String(value) : "—";
|
|
const pad = (n: number) => String(n).padStart(2, "0");
|
|
return (
|
|
`${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ` +
|
|
`${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())} UTC`
|
|
);
|
|
}
|
|
|
|
export function formatRelative(value: string | null | undefined): string {
|
|
const date = parseTime(value);
|
|
if (!date) return "—";
|
|
const deltaMs = Date.now() - date.getTime();
|
|
const future = deltaMs < 0;
|
|
const abs = Math.abs(deltaMs);
|
|
const minutes = Math.floor(abs / 60_000);
|
|
const hours = Math.floor(abs / 3_600_000);
|
|
const days = Math.floor(abs / 86_400_000);
|
|
let text: string;
|
|
if (abs < 60_000) text = "just now";
|
|
else if (minutes < 60) text = `${minutes}m`;
|
|
else if (hours < 24) text = `${hours}h`;
|
|
else text = `${days}d`;
|
|
if (text === "just now") return text;
|
|
return future ? `in ${text}` : `${text} ago`;
|
|
}
|
|
|
|
export function formatPercent(part: number, total: number): string {
|
|
if (!total) return "—";
|
|
return `${((part / total) * 100).toFixed(1)}%`;
|
|
}
|
|
|
|
/** Truncate a long identifier for display, keeping head and tail. */
|
|
export function truncateMiddle(value: string, max = 32): string {
|
|
if (value.length <= max) return value;
|
|
const head = Math.ceil((max - 1) / 2);
|
|
const tail = Math.floor((max - 1) / 2);
|
|
return `${value.slice(0, head)}…${value.slice(value.length - tail)}`;
|
|
}
|
|
|
|
/** Human label for an object type code (accepts both report-stream and projection vocabularies). */
|
|
export function objectTypeLabel(type: string | null | undefined): string {
|
|
switch ((type ?? "").toLowerCase()) {
|
|
case "roa":
|
|
return "ROA";
|
|
case "mft":
|
|
case "manifest":
|
|
return "Manifest";
|
|
case "crl":
|
|
return "CRL";
|
|
case "cer":
|
|
case "certificate":
|
|
return "Certificate";
|
|
case "asa":
|
|
case "aspa":
|
|
return "ASPA";
|
|
case "gbr":
|
|
return "Ghostbusters";
|
|
case "ee":
|
|
return "EE Certificate";
|
|
case "other":
|
|
return "Other";
|
|
default:
|
|
return type ? type.toUpperCase() : "—";
|
|
}
|
|
}
|