feat: high-availability page (/ha)

Platform-level HA surface: manager enable/disable toggle, status cards
(nodes, health checks, active alerts), manual failover with
confirmation, per-service failover policy CRUD with project/service
picker, active alert resolution, and health check result list.

Verified live: status, enable/disable, policy set/list/get/disable,
manual failover, alerts and health results endpoints. HA state is
in-memory — noted in roadmap alongside autoscaling persistence debt.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-17 18:17:49 +02:00
1 parent 44a1b09dad
commit d5c99acb8c
5 files changed
+496 -3

No files matched your search

+67
View File
@@ -1535,3 +1535,70 @@ export async function manualScaleService(serviceId: string, replicas: number, re
body: JSON.stringify({ replicas, reason }),
});
}
export type FailoverPolicy = components['schemas']['FailoverPolicy'];
export type HAStatus = {
enabled?: boolean;
nodes?: { total?: number; healthy?: number; unhealthy?: number };
health_checks?: { total?: number; healthy?: number; unhealthy?: number };
alerts?: { active?: number };
};
export type HAAlert = {
id?: string;
rule_id?: string;
status?: string;
severity?: string;
message?: string;
starts_at?: string;
ends_at?: string;
};
export type HAHealthResult = {
check_id?: string;
status?: string;
message?: string;
latency?: number;
timestamp?: string;
};
export async function getHAStatus(): Promise<HAStatus> {
const payload = await requestJson<{ status?: HAStatus }>('/ha/status');
return payload.status ?? {};
}
export async function setHAEnabled(enabled: boolean): Promise<void> {
await requestJson(enabled ? '/ha/enable' : '/ha/disable', { method: 'POST' });
}
export async function triggerFailover(reason: string): Promise<void> {
await requestJson('/ha/failover', { method: 'POST', body: JSON.stringify({ reason }) });
}
export async function listFailoverPolicies(): Promise<FailoverPolicy[]> {
const payload = await requestJson<{ policies?: FailoverPolicy[] }>('/ha/failover/policies');
return payload.policies ?? [];
}
export async function setFailoverPolicy(policy: FailoverPolicy): Promise<void> {
await requestJson('/ha/failover/policies', { method: 'POST', body: JSON.stringify(policy) });
}
export async function deleteFailoverPolicy(serviceId: string): Promise<void> {
await requestJson(`/ha/failover/policies/${encodeURIComponent(serviceId)}`, { method: 'DELETE' });
}
export async function listActiveAlerts(): Promise<HAAlert[]> {
const payload = await requestJson<{ alerts?: HAAlert[] }>('/ha/alerts/active');
return payload.alerts ?? [];
}
export async function resolveAlert(alertId: string): Promise<void> {
await requestJson(`/ha/alerts/${encodeURIComponent(alertId)}/resolve`, { method: 'POST' });
}
export async function listHealthResults(): Promise<HAHealthResult[]> {
const payload = await requestJson<{ results?: HAHealthResult[] }>('/ha/health/results');
return payload.results ?? [];
}