Three-level dashboard driven by sidebar selection: - L1 (no selection): all-apps overview with health table, per-app charts - L2 (app selected): route performance table, error velocity, top errors - L3 (route selected): processor table, latency heatmap data, bottleneck KPI Backend: 3 new endpoints (timeseries/by-app, timeseries/by-route, errors/top), per-app SLA settings (app_settings table, V12 migration), exact SLA compliance from executions hypertable, error velocity with acceleration detection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
71 lines
2.4 KiB
TypeScript
71 lines
2.4 KiB
TypeScript
import type { AppSettings } from '../../api/queries/dashboard';
|
|
|
|
export type HealthStatus = 'success' | 'warning' | 'error';
|
|
|
|
const DEFAULT_SETTINGS: Pick<AppSettings, 'healthErrorWarn' | 'healthErrorCrit' | 'healthSlaWarn' | 'healthSlaCrit'> = {
|
|
healthErrorWarn: 1.0,
|
|
healthErrorCrit: 5.0,
|
|
healthSlaWarn: 99.0,
|
|
healthSlaCrit: 95.0,
|
|
};
|
|
|
|
export function computeHealthDot(
|
|
errorRate: number,
|
|
slaCompliance: number,
|
|
settings?: Partial<AppSettings> | null,
|
|
): HealthStatus {
|
|
const s = { ...DEFAULT_SETTINGS, ...settings };
|
|
const errorPct = errorRate * 100;
|
|
|
|
if (errorPct > s.healthErrorCrit || slaCompliance < s.healthSlaCrit) return 'error';
|
|
if (errorPct > s.healthErrorWarn || slaCompliance < s.healthSlaWarn) return 'warning';
|
|
return 'success';
|
|
}
|
|
|
|
export function formatThroughput(count: number, windowSeconds: number): string {
|
|
if (windowSeconds <= 0) return '0/s';
|
|
const tps = count / windowSeconds;
|
|
if (tps >= 1000) return `${(tps / 1000).toFixed(1)}k/s`;
|
|
if (tps >= 1) return `${tps.toFixed(0)}/s`;
|
|
return `${tps.toFixed(2)}/s`;
|
|
}
|
|
|
|
export function formatSlaCompliance(pct: number): string {
|
|
if (pct < 0) return '—';
|
|
return `${pct.toFixed(1)}%`;
|
|
}
|
|
|
|
export function trendIndicator(current: number, previous: number): { label: string; direction: 'up' | 'down' | 'flat' } {
|
|
if (previous === 0) return { label: '—', direction: 'flat' };
|
|
const delta = ((current - previous) / previous) * 100;
|
|
if (Math.abs(delta) < 0.5) return { label: '—', direction: 'flat' };
|
|
return {
|
|
label: `${delta > 0 ? '+' : ''}${delta.toFixed(1)}%`,
|
|
direction: delta > 0 ? 'up' : 'down',
|
|
};
|
|
}
|
|
|
|
export function trendArrow(trend: 'accelerating' | 'stable' | 'decelerating'): string {
|
|
switch (trend) {
|
|
case 'accelerating': return '\u25B2';
|
|
case 'decelerating': return '\u25BC';
|
|
default: return '\u2500\u2500';
|
|
}
|
|
}
|
|
|
|
export function formatDuration(ms: number): string {
|
|
if (ms < 1) return '<1ms';
|
|
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
return `${(ms / 1000).toFixed(2)}s`;
|
|
}
|
|
|
|
export function formatRelativeTime(isoString: string): string {
|
|
const diff = Date.now() - new Date(isoString).getTime();
|
|
const minutes = Math.floor(diff / 60_000);
|
|
if (minutes < 1) return 'just now';
|
|
if (minutes < 60) return `${minutes} min ago`;
|
|
const hours = Math.floor(minutes / 60);
|
|
if (hours < 24) return `${hours} hr ago`;
|
|
return `${Math.floor(hours / 24)} d ago`;
|
|
}
|