Home is the one overview now: the brain graph flat across the top, the flow switches, then the health sections. The /brain and /health routes and their sidebar entries are gone. - charts report their cursor and clicks, so hovering one filters the list beside it to that minute and a click pins it until Escape or Clear - chart values round to about three significant digits, the legend mounts under the plot so it can wrap without leaving the card, and axis ticks shorten past a thousand - the embedded brain leaves the wheel to the page rather than zooming - number fields no longer draw their up/down spinner (NOTEPAD)
100 lines
3.4 KiB
TypeScript
100 lines
3.4 KiB
TypeScript
import { ObservabilityService } from "@/client"
|
|
|
|
/** The live view; anything older is the collector's rollups. */
|
|
export const HOURS = 24
|
|
|
|
/** Another tab can change things, and the engine fails flows on its own. */
|
|
const REFRESH = 30_000
|
|
|
|
/**
|
|
* How deep the run and failure lists are read.
|
|
*
|
|
* Deeper than they are shown: picking a minute off a chart filters these rows
|
|
* client-side, and a list holding only the newest handful would have nothing
|
|
* to find for any minute but the current one.
|
|
*
|
|
* ponytail: the runs endpoint caps at 200 and takes no time range, so a busy
|
|
* engine still only covers its last minute or so. A `since` parameter is what
|
|
* would let a moment on the chart reach the whole day.
|
|
*/
|
|
const RUN_DEPTH = 200
|
|
const EVENT_DEPTH = 100
|
|
|
|
export const healthKeys = {
|
|
all: ["observability"] as const,
|
|
summary: ["observability", "summary"] as const,
|
|
events: ["observability", "events"] as const,
|
|
}
|
|
|
|
/** The resting surface these screens are built from. */
|
|
export const CARD = "rounded-lg border border-border bg-card p-4 shadow-e1"
|
|
|
|
export const summaryQueryOptions = () => ({
|
|
queryKey: healthKeys.summary,
|
|
queryFn: () => ObservabilityService.readSummary(),
|
|
refetchInterval: 10_000,
|
|
})
|
|
|
|
export const timeseriesQueryOptions = () => ({
|
|
queryKey: ["observability", "timeseries", HOURS] as const,
|
|
queryFn: () => ObservabilityService.readTimeseries({ hours: HOURS }),
|
|
refetchInterval: REFRESH,
|
|
})
|
|
|
|
export const flowRollupsQueryOptions = () => ({
|
|
queryKey: ["observability", "flows", HOURS] as const,
|
|
queryFn: () => ObservabilityService.readFlowRollups({ hours: HOURS }),
|
|
refetchInterval: REFRESH,
|
|
})
|
|
|
|
export const runsQueryOptions = () => ({
|
|
queryKey: ["observability", "runs"] as const,
|
|
queryFn: () => ObservabilityService.readRuns({ limit: RUN_DEPTH }),
|
|
refetchInterval: REFRESH,
|
|
})
|
|
|
|
export const failuresQueryOptions = () => ({
|
|
queryKey: [...healthKeys.events, "failure"] as const,
|
|
queryFn: () =>
|
|
ObservabilityService.readEvents({ kind: "failure", limit: EVENT_DEPTH }),
|
|
refetchInterval: REFRESH,
|
|
})
|
|
|
|
export const auditQueryOptions = () => ({
|
|
queryKey: [...healthKeys.events, "audit"] as const,
|
|
queryFn: () => ObservabilityService.readEvents({ kind: "audit", limit: 15 }),
|
|
refetchInterval: REFRESH,
|
|
})
|
|
|
|
export const deadLetterQueryOptions = () => ({
|
|
queryKey: ["observability", "dead-letter"] as const,
|
|
queryFn: () => ObservabilityService.readDeadLetters({ limit: 20 }),
|
|
refetchInterval: REFRESH,
|
|
})
|
|
|
|
/** How long ago something happened, in the coarsest unit that still says it. */
|
|
export function ago(ts: string | number | null | undefined): string {
|
|
if (!ts) return "—"
|
|
const stamp = typeof ts === "number" ? ts * 1000 : Date.parse(ts)
|
|
const seconds = Math.max(0, (Date.now() - stamp) / 1000)
|
|
if (seconds < 90) return `${Math.round(seconds)}s ago`
|
|
if (seconds < 5400) return `${Math.round(seconds / 60)}m ago`
|
|
if (seconds < 172800) return `${Math.round(seconds / 3600)}h ago`
|
|
return `${Math.round(seconds / 86400)}d ago`
|
|
}
|
|
|
|
/**
|
|
* The minute a timestamp belongs to, as unix seconds.
|
|
*
|
|
* Minutes are the granularity the engine stores its metrics at, so this is
|
|
* also the finest moment a chart can be asked about.
|
|
*/
|
|
export const minuteOf = (ts: string) => Math.floor(Date.parse(ts) / 60000) * 60
|
|
|
|
/** A moment as a clock reading, which is how the charts label their axis. */
|
|
export const clock = (ts: number) =>
|
|
new Date(ts * 1000).toLocaleTimeString([], {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
})
|