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

- 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 <[email protected]>
This commit is contained in:
Denozordec
2026-07-17 02:55:49 +07:00
co-authored by Cursor
parent 64585ccd47
commit 1f710bbe8b
48 changed files with 4258 additions and 246 deletions
@@ -340,23 +340,45 @@ async function buildView(db: Db, serviceId: number): Promise<ServiceView> {
updated_at: service.updated_at,
ips,
domains: domainViews,
health_status: "unknown",
health_latency_ms: null,
};
}
function attachServiceHealth(
db: Db,
views: ServiceView[],
): ServiceView[] {
const healthByService = repos.aggregateIpHealthByServiceIds(
db,
views.map((v) => v.id),
);
return views.map((view) => {
const health = healthByService.get(view.id);
return {
...view,
health_status: health?.health_status ?? "unknown",
health_latency_ms: health?.health_latency_ms ?? null,
};
});
}
export async function listViews(db: Db): Promise<ServiceView[]> {
return Promise.all(
const views = await Promise.all(
repos.listServices(db).map((s) => buildView(db, s.id)),
);
return attachServiceHealth(db, views);
}
export async function getView(db: Db, id: number): Promise<ServiceView> {
repos.getService(db, id);
return buildView(db, id);
const [view] = attachServiceHealth(db, [await buildView(db, id)]);
return view!;
}
export async function listGroupViews(db: Db): Promise<ServiceGroupsResponse> {
const groups = repos.listServiceGroups(db);
const groupViews = await Promise.all(
const groupViewsRaw = await Promise.all(
groups.map(async (group) => {
const services = repos.listServicesByGroup(db, group.id);
const serviceViews = await Promise.all(
@@ -367,10 +389,52 @@ export async function listGroupViews(db: Db): Promise<ServiceGroupsResponse> {
);
const ungroupedServices = repos.listUngroupedServices(db);
const ungrouped = await Promise.all(
const ungroupedRaw = await Promise.all(
ungroupedServices.map((s) => buildView(db, s.id)),
);
const allServiceViews = [
...groupViewsRaw.flatMap((g) => g.services),
...ungroupedRaw,
];
const withHealth = attachServiceHealth(db, allServiceViews);
const healthById = new Map(withHealth.map((v) => [v.id, v]));
const groupHealthById = repos.aggregateIpHealthByRefs(
db,
"group",
groups.map((g) => g.id),
);
const groupViews = groupViewsRaw.map((group) => {
const services = group.services.map(
(s) => healthById.get(s.id) ?? { ...s, health_status: "unknown" as const, health_latency_ms: null },
);
const groupScopeHealth = groupHealthById.get(group.id);
const merged = repos.mergeHealthAggregates([
groupScopeHealth,
...services.map((s) => ({
health_status: s.health_status,
health_latency_ms: s.health_latency_ms,
})),
]);
return {
...group,
services,
health_status: merged.health_status,
health_latency_ms: merged.health_latency_ms,
};
});
const ungrouped = ungroupedRaw.map(
(s) =>
healthById.get(s.id) ?? {
...s,
health_status: "unknown" as const,
health_latency_ms: null,
},
);
return { groups: groupViews, ungrouped };
}
@@ -1224,7 +1288,8 @@ export async function updateConfig(
void syncServiceToVpsTracker(db, id, removedBindingIds);
return buildView(db, id);
const [view] = attachServiceHealth(db, [await buildView(db, id)]);
return view!;
}
export async function createGroup(
@@ -1318,12 +1383,18 @@ export async function toggleService(
if (!enabled) {
await cleanupServiceDnsOnly(db, cf, serviceId);
await syncGroupDomainForService(db, cf, serviceId);
return buildView(db, serviceId);
const [disabledView] = attachServiceHealth(db, [
await buildView(db, serviceId),
]);
return disabledView!;
}
await syncServiceBindingsToDns(db, cf, serviceId);
await syncGroupDomainForService(db, cf, serviceId);
return buildView(db, serviceId);
const [enabledView] = attachServiceHealth(db, [
await buildView(db, serviceId),
]);
return enabledView!;
}
export async function toggleGroup(