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
@@ -0,0 +1,94 @@
import { describe, expect, it } from "vitest";
import { buildApp } from "../src/app.js";
import { loadConfig } from "../src/config.js";
import { repos } from "@cfdm/db";
async function authHeaders(app: Awaited<ReturnType<typeof buildApp>>) {
const config = loadConfig();
const res = await app.inject({
method: "POST",
url: "/api/v1/auth/login",
payload: { username: config.adminUsername, password: "admin" },
});
expect(res.statusCode).toBe(200);
const { token } = res.json() as { token: string };
return { authorization: `Bearer ${token}` };
}
describe("service groups health enrichment", () => {
it("GET /service-groups returns health_status from ip_health_status", async () => {
const app = await buildApp({
config: { ...loadConfig(), staticDir: null },
memory: true,
});
const headers = await authHeaders(app);
const domain = repos.createDomain(app.db, null, "example.com", "zone-1");
const group = repos.createServiceGroup(
app.db,
"VPN",
"vpn",
null,
"vpn.example.com",
);
const service = repos.createService(app.db, "Panel", "panel");
repos.setServiceGroup(app.db, service.id, group.id);
const binding = repos.insertBinding(
app.db,
domain.id,
service.id,
"panel",
null,
);
repos.replaceBindingIpsWithMeta(app.db, binding.id, [
{ ip: "1.2.3.4", weight: 1, priority: 1 },
]);
repos.upsertIpHealthStatus(
app.db,
"binding",
binding.id,
"1.2.3.4",
"degraded",
120,
1,
null,
);
repos.upsertIpHealthStatus(
app.db,
"group",
group.id,
"5.6.7.8",
"up",
40,
0,
null,
);
const res = await app.inject({
method: "GET",
url: "/api/v1/service-groups",
headers,
});
expect(res.statusCode).toBe(200);
const body = res.json() as {
groups: Array<{
id: number;
health_status: string;
health_latency_ms: number | null;
services: Array<{
id: number;
health_status: string;
health_latency_ms: number | null;
}>;
}>;
};
const groupView = body.groups.find((g) => g.id === group.id);
expect(groupView).toBeDefined();
expect(groupView!.services[0]?.health_status).toBe("degraded");
expect(groupView!.services[0]?.health_latency_ms).toBe(120);
// group worst = degraded (from service) over up (group scope)
expect(groupView!.health_status).toBe("degraded");
await app.close();
});
});