feat(api, web): integrate health status management for services and domains
Build, Test, and Push CFDM Docker Image / test (push) Successful in 10m17s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 1m48s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 6s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
Build, Test, and Push CFDM Docker Image / test (push) Successful in 10m17s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 1m48s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 6s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
- Added health status and latency fields to service and domain schemas, enhancing monitoring capabilities. - Implemented health status aggregation for services in the API, allowing for improved health checks and reporting. - Updated web components to display health status using HealthCheckBadge, improving user visibility of service health. - Refactored service and domain management components to incorporate health status in various views, enhancing overall functionality. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
64585ccd47
commit
1f710bbe8b
48 files changed
+4258
-246
No files matched your search
@@ -8,6 +8,7 @@ import type {
|
||||
HealthCheckScope,
|
||||
HealthCheckTarget,
|
||||
HealthCheckType,
|
||||
IpHealthState,
|
||||
IpHealthStatus,
|
||||
LbMode,
|
||||
Service,
|
||||
@@ -1466,6 +1467,152 @@ export function listIpHealthStatus(
|
||||
`);
|
||||
}
|
||||
|
||||
export type HealthAggregate = {
|
||||
health_status: IpHealthState;
|
||||
health_latency_ms: number | null;
|
||||
};
|
||||
|
||||
const UNKNOWN_HEALTH: HealthAggregate = {
|
||||
health_status: "unknown",
|
||||
health_latency_ms: null,
|
||||
};
|
||||
|
||||
function parseHealthAggregateRow(row: {
|
||||
health_status: string | null;
|
||||
health_latency_ms: number | null;
|
||||
}): HealthAggregate {
|
||||
const status = row.health_status;
|
||||
if (
|
||||
status === "up" ||
|
||||
status === "down" ||
|
||||
status === "degraded" ||
|
||||
status === "unknown"
|
||||
) {
|
||||
return {
|
||||
health_status: status,
|
||||
health_latency_ms: row.health_latency_ms ?? null,
|
||||
};
|
||||
}
|
||||
return UNKNOWN_HEALTH;
|
||||
}
|
||||
|
||||
const WORST_HEALTH_SQL = sql.raw(`CASE
|
||||
WHEN MAX(CASE
|
||||
WHEN status = 'down' THEN 3
|
||||
WHEN status = 'degraded' THEN 2
|
||||
WHEN status = 'up' THEN 1
|
||||
ELSE 0
|
||||
END) = 3 THEN 'down'
|
||||
WHEN MAX(CASE
|
||||
WHEN status = 'down' THEN 3
|
||||
WHEN status = 'degraded' THEN 2
|
||||
WHEN status = 'up' THEN 1
|
||||
ELSE 0
|
||||
END) = 2 THEN 'degraded'
|
||||
WHEN MAX(CASE
|
||||
WHEN status = 'down' THEN 3
|
||||
WHEN status = 'degraded' THEN 2
|
||||
WHEN status = 'up' THEN 1
|
||||
ELSE 0
|
||||
END) = 1 THEN 'up'
|
||||
ELSE 'unknown'
|
||||
END`);
|
||||
|
||||
/** Worst status across ip_health_status rows for each ref_id in a scope. */
|
||||
export function aggregateIpHealthByRefs(
|
||||
db: Db,
|
||||
scope: HealthCheckScope,
|
||||
refIds: number[],
|
||||
): Map<number, HealthAggregate> {
|
||||
const result = new Map<number, HealthAggregate>();
|
||||
if (refIds.length === 0) return result;
|
||||
const idList = sql.join(
|
||||
refIds.map((id) => sql`${id}`),
|
||||
sql`, `,
|
||||
);
|
||||
const rows = db.all<{
|
||||
ref_id: number;
|
||||
health_status: string | null;
|
||||
health_latency_ms: number | null;
|
||||
}>(sql`
|
||||
SELECT ref_id,
|
||||
${WORST_HEALTH_SQL} AS health_status,
|
||||
MAX(latency_ms) AS health_latency_ms
|
||||
FROM ip_health_status
|
||||
WHERE scope = ${scope} AND ref_id IN (${idList})
|
||||
GROUP BY ref_id
|
||||
`);
|
||||
for (const row of rows) {
|
||||
result.set(row.ref_id, parseHealthAggregateRow(row));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Worst binding-scope health rolled up per service_id. */
|
||||
export function aggregateIpHealthByServiceIds(
|
||||
db: Db,
|
||||
serviceIds: number[],
|
||||
): Map<number, HealthAggregate> {
|
||||
const result = new Map<number, HealthAggregate>();
|
||||
if (serviceIds.length === 0) return result;
|
||||
const idList = sql.join(
|
||||
serviceIds.map((id) => sql`${id}`),
|
||||
sql`, `,
|
||||
);
|
||||
const rows = db.all<{
|
||||
service_id: number;
|
||||
health_status: string | null;
|
||||
health_latency_ms: number | null;
|
||||
}>(sql`
|
||||
SELECT sb.service_id AS service_id,
|
||||
${WORST_HEALTH_SQL} AS health_status,
|
||||
MAX(ihs.latency_ms) AS health_latency_ms
|
||||
FROM ip_health_status ihs
|
||||
INNER JOIN service_bindings sb
|
||||
ON ihs.scope = 'binding' AND ihs.ref_id = sb.id
|
||||
WHERE sb.service_id IN (${idList})
|
||||
GROUP BY sb.service_id
|
||||
`);
|
||||
for (const row of rows) {
|
||||
result.set(row.service_id, parseHealthAggregateRow(row));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function mergeHealthAggregates(
|
||||
parts: Array<HealthAggregate | undefined | null>,
|
||||
): HealthAggregate {
|
||||
const rank: Record<IpHealthState, number> = {
|
||||
unknown: 0,
|
||||
up: 1,
|
||||
degraded: 2,
|
||||
down: 3,
|
||||
};
|
||||
let worst: HealthAggregate = UNKNOWN_HEALTH;
|
||||
let hasAny = false;
|
||||
for (const part of parts) {
|
||||
if (!part) continue;
|
||||
hasAny = true;
|
||||
if (rank[part.health_status] > rank[worst.health_status]) {
|
||||
worst = {
|
||||
health_status: part.health_status,
|
||||
health_latency_ms: part.health_latency_ms,
|
||||
};
|
||||
} else if (
|
||||
part.health_status === worst.health_status &&
|
||||
part.health_latency_ms != null &&
|
||||
(worst.health_latency_ms == null ||
|
||||
part.health_latency_ms > worst.health_latency_ms)
|
||||
) {
|
||||
worst = {
|
||||
health_status: worst.health_status,
|
||||
health_latency_ms: part.health_latency_ms,
|
||||
};
|
||||
}
|
||||
}
|
||||
return hasAny ? worst : UNKNOWN_HEALTH;
|
||||
}
|
||||
|
||||
export function getIpHealthStatusRow(
|
||||
db: Db,
|
||||
scope: HealthCheckScope,
|
||||
|
||||
Reference in new issue
Block a user