/** * Small store that exposes counts for sidebar nav badges. * * Values reflect the last successful poll. Individual sources fail * independently — a failure keeps the previous value and flips `stale` true * so the UI can dim the badge if desired. The poller is intentionally * forgiving: if the user is unauthenticated or the backend isn't ready, * it silently retries on the next tick. */ import { writable, type Readable } from 'svelte/store'; import * as api from '$lib/api'; import { isAuthenticated } from '$lib/auth'; export interface NavCounts { projects: number | null; sites: number | null; stacks: number | null; proxies: number | null; /** Error-severity events only; dashboard surfaces total separately. */ eventsErrors: number | null; } const EMPTY: NavCounts = { projects: null, sites: null, stacks: null, proxies: null, eventsErrors: null }; const store = writable(EMPTY); export const navCounts: Readable = { subscribe: store.subscribe }; let pollTimer: ReturnType | null = null; let inFlight = false; async function refreshOnce(): Promise { if (inFlight || !isAuthenticated()) return; inFlight = true; try { const [projects, sites, stacks, proxies, eventStats] = await Promise.allSettled([ api.listProjects(), api.listStaticSites(), api.listStacks(), api.listProxyRoutes(), api.fetchEventLogStats() ]); store.update((prev) => ({ projects: projects.status === 'fulfilled' ? projects.value.length : prev.projects, sites: sites.status === 'fulfilled' ? sites.value.length : prev.sites, stacks: stacks.status === 'fulfilled' ? stacks.value.length : prev.stacks, proxies: proxies.status === 'fulfilled' ? proxies.value.length : prev.proxies, eventsErrors: eventStats.status === 'fulfilled' ? eventStats.value.error : prev.eventsErrors })); } finally { inFlight = false; } } /** * Start periodic polling of nav counts. Safe to call repeatedly — * subsequent calls are no-ops until `stopNavCountsPolling()` is called. */ export function startNavCountsPolling(intervalMs = 60_000): void { if (pollTimer) return; void refreshOnce(); pollTimer = setInterval(() => void refreshOnce(), intervalMs); } export function stopNavCountsPolling(): void { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } } /** Trigger an out-of-band refresh (e.g. after a mutation). */ export function refreshNavCounts(): void { void refreshOnce(); }