a182a93950
Build / build (push) Successful in 10m29s
Nav & UI polish
- Sidebar nav items show monospace count badges (projects, sites, stacks,
proxies). Events badge shows error count only, styled red as actionable
- New $lib/stores/navCounts.ts polls all counts in parallel every 60s and
refreshes on route change so badges track mutations
- Login page gets a dynamic forge backdrop: rotating conic glow, drifting
embers, dot-grid texture, vignette — all pure CSS, reduced-motion safe
- main element gets scrollbar-gutter: stable so Settings tab switching no
longer shifts horizontally when content heights differ
Events i18n
- events.source.* dictionary rewritten to match actually-emitted backend
sources (deploy, static_site, stale_scanner, stale_cleanup, admin);
dead keys (container, proxy, system) removed
- EventLogFilter.allSources + /events default sources state updated to match
- Localize "{N} total" via events.totalCount in the page hero toolbar
Backend
- Stage API accepts enable_proxy on create/update (defaults to true) so
proxy registration can be opted out per stage
Concurrency
- api.ts: queued request waiters no longer double-increment the inflight
counter; releasing a slot hands it off directly
Reactive effects
- project detail / env / volumes pages wrap side-effect calls in untrack()
to prevent $effect feedback loops when their loaders mutate tracked state
84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
/**
|
|
* 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<NavCounts>(EMPTY);
|
|
|
|
export const navCounts: Readable<NavCounts> = { subscribe: store.subscribe };
|
|
|
|
let pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
let inFlight = false;
|
|
|
|
async function refreshOnce(): Promise<void> {
|
|
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();
|
|
}
|