181 lines
5.3 KiB
TypeScript
181 lines
5.3 KiB
TypeScript
export type ServiceStatus = 'healthy' | 'degraded' | 'down' | 'maintenance';
|
|
export type OverallStatus = 'healthy' | 'degraded' | 'critical';
|
|
|
|
export interface OpsService {
|
|
id: string;
|
|
name: string;
|
|
group: string;
|
|
target: string;
|
|
status: ServiceStatus;
|
|
latency: number;
|
|
version?: string;
|
|
message?: string;
|
|
lastChecked: string;
|
|
}
|
|
|
|
export interface OpsStatusInput {
|
|
overall: OverallStatus;
|
|
timestamp: string;
|
|
services: OpsService[];
|
|
}
|
|
|
|
export interface InventoryService extends OpsService {
|
|
description: string;
|
|
management: 'docker' | 'vm';
|
|
exposure: 'internal' | 'public';
|
|
port?: number;
|
|
restartable: boolean;
|
|
}
|
|
|
|
export interface InventoryDataInput {
|
|
timestamp: string;
|
|
counts: {
|
|
services: number;
|
|
healthy: number;
|
|
degraded: number;
|
|
down: number;
|
|
hostTools: number;
|
|
};
|
|
services: InventoryService[];
|
|
hostTools: unknown[];
|
|
}
|
|
|
|
export interface ValkeyDataInput {
|
|
timestamp: string;
|
|
pattern: string;
|
|
limit: number;
|
|
summary: {
|
|
ping: string;
|
|
dbsize: number;
|
|
matchedKeys: number;
|
|
version: string;
|
|
usedMemoryHuman: string;
|
|
usedMemoryPeakHuman: string;
|
|
};
|
|
keys: unknown[];
|
|
}
|
|
|
|
export interface OpsCockpitTile {
|
|
label: string;
|
|
value: string;
|
|
detail: string;
|
|
tone: 'success' | 'warning' | 'danger' | 'neutral';
|
|
}
|
|
|
|
export interface OpsCockpitAction {
|
|
serviceId?: string;
|
|
action: string;
|
|
detail: string;
|
|
severity: 'critical' | 'warning' | 'info';
|
|
}
|
|
|
|
export interface OpsCockpit {
|
|
headline: string;
|
|
summary: string;
|
|
tiles: OpsCockpitTile[];
|
|
priorityActions: OpsCockpitAction[];
|
|
}
|
|
|
|
export function buildOpsCockpit(input: {
|
|
status: OpsStatusInput | null;
|
|
inventory: InventoryDataInput | null;
|
|
valkey: ValkeyDataInput | null;
|
|
}): OpsCockpit {
|
|
const { status, inventory, valkey } = input;
|
|
|
|
if (!status && !inventory && !valkey) {
|
|
return {
|
|
headline: 'Waiting for live ops telemetry',
|
|
summary: 'Refresh Mission Control to collect service, inventory, and cache health.',
|
|
tiles: [
|
|
{ label: 'Services', value: '--', detail: 'No sample yet', tone: 'neutral' },
|
|
{ label: 'Cache keys', value: '--', detail: 'Valkey not loaded', tone: 'neutral' },
|
|
{
|
|
label: 'Restartable issues',
|
|
value: '--',
|
|
detail: 'Inventory not loaded',
|
|
tone: 'neutral',
|
|
},
|
|
],
|
|
priorityActions: [
|
|
{
|
|
action: 'Refresh telemetry',
|
|
detail: 'Load the latest service and cache status before taking action.',
|
|
severity: 'info',
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
const unhealthyServices = status?.services.filter(service => service.status !== 'healthy') ?? [];
|
|
const restartableIssues = unhealthyServices.filter(service =>
|
|
inventory?.services.some(inv => inv.id === service.id && inv.restartable)
|
|
);
|
|
const cacheHealthy = valkey?.summary.ping === 'PONG';
|
|
const criticalCount = unhealthyServices.filter(service => service.status === 'down').length;
|
|
const degradedCount = unhealthyServices.filter(service => service.status === 'degraded').length;
|
|
|
|
const priorityActions: OpsCockpitAction[] = restartableIssues.map(service => ({
|
|
serviceId: service.id,
|
|
action: 'Restart service',
|
|
detail: `${service.name} is ${service.status}${service.message ? ` — ${service.message}` : ''}`,
|
|
severity: service.status === 'down' ? 'critical' : 'warning',
|
|
}));
|
|
|
|
if (!cacheHealthy && valkey) {
|
|
priorityActions.push({
|
|
action: 'Inspect Valkey',
|
|
detail: `Cache ping returned ${valkey.summary.ping}; inspect hot keys and dependent services.`,
|
|
severity: 'warning',
|
|
});
|
|
}
|
|
|
|
if (priorityActions.length === 0) {
|
|
priorityActions.push({
|
|
action: 'Review deploy readiness',
|
|
detail: 'All loaded systems are healthy; check recent errors before starting a deployment.',
|
|
severity: 'info',
|
|
});
|
|
}
|
|
|
|
const overall =
|
|
status?.overall ??
|
|
(criticalCount > 0 ? 'critical' : degradedCount > 0 ? 'degraded' : 'healthy');
|
|
const headline =
|
|
overall === 'critical'
|
|
? `Critical ops attention needed (${criticalCount} down)`
|
|
: overall === 'degraded'
|
|
? `Ops degraded (${degradedCount} warning${degradedCount === 1 ? '' : 's'})`
|
|
: 'Ops cockpit healthy';
|
|
|
|
return {
|
|
headline,
|
|
summary: `${inventory?.counts.healthy ?? 0}/${inventory?.counts.services ?? status?.services.length ?? 0} services healthy · Valkey ${cacheHealthy ? 'ready' : 'needs review'}`,
|
|
tiles: [
|
|
{
|
|
label: 'Healthy services',
|
|
value: String(
|
|
inventory?.counts.healthy ??
|
|
status?.services.filter(s => s.status === 'healthy').length ??
|
|
0
|
|
),
|
|
detail: `${inventory?.counts.services ?? status?.services.length ?? 0} tracked`,
|
|
tone: criticalCount > 0 ? 'danger' : degradedCount > 0 ? 'warning' : 'success',
|
|
},
|
|
{
|
|
label: 'Cache keys',
|
|
value: String(valkey?.summary.dbsize ?? 0),
|
|
detail: valkey ? `${valkey.summary.usedMemoryHuman} used` : 'Valkey not loaded',
|
|
tone: cacheHealthy ? 'success' : 'warning',
|
|
},
|
|
{
|
|
label: 'Restartable issues',
|
|
value: String(restartableIssues.length),
|
|
detail: restartableIssues.length ? 'Safe action available' : 'No restart needed',
|
|
tone: restartableIssues.length ? 'danger' : 'success',
|
|
},
|
|
],
|
|
priorityActions,
|
|
};
|
|
}
|