// Accès aux routes exposées par le serveur axum et petits utilitaires partagés // par les web components du dashboard. export const endpoints = { jobs: "/jobs", reviews: "/reviews", lifecycle: "/events", jobEvents: (id) => `/jobs/${id}/events`, }; async function getJson(url) { const response = await fetch(url, { headers: { accept: "application/json" } }); if (!response.ok) { throw new Error(`${url} → ${response.status}`); } return response.json(); } export const fetchJobs = () => getJson(endpoints.jobs); export const fetchReviews = () => getJson(endpoints.reviews); // Ouvre un flux SSE et route les évènements nommés vers les callbacks fournis. // Les évènements du serveur sont nommés (`lifecycle`, `progress`, `gap`), donc // `addEventListener("message")` ne suffit pas : il faut écouter par nom. export function subscribe(url, handlers = {}) { const source = new EventSource(url); const onData = (name, handler) => { if (handler) { source.addEventListener(name, (event) => handler(JSON.parse(event.data))); } }; if (handlers.onOpen) source.addEventListener("open", () => handlers.onOpen()); if (handlers.onError) source.addEventListener("error", () => handlers.onError()); onData("lifecycle", handlers.onLifecycle); onData("progress", handlers.onProgress); if (handlers.onGap) { source.addEventListener("gap", (event) => handlers.onGap(Number(event.data))); } return source; } export function escapeHtml(value) { return String(value ?? "").replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'", })[char]); } // Horodatage relatif (« il y a 3 min »), tolérant sur les valeurs absentes. export function relativeTime(value) { if (!value) return "—"; const then = Date.parse(value); if (Number.isNaN(then)) return "—"; const seconds = Math.round((then - Date.now()) / 1000); const steps = [ ["year", 60 * 60 * 24 * 365], ["month", 60 * 60 * 24 * 30], ["day", 60 * 60 * 24], ["hour", 60 * 60], ["minute", 60], ["second", 1], ]; const formatter = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); for (const [unit, size] of steps) { if (Math.abs(seconds) >= size || unit === "second") { return formatter.format(Math.round(seconds / size), unit); } } return "—"; }