Update ReUI skill documentation to reflect the addition of 3 new components, increasing the total from 17 to 20. Adjust descriptions and examples accordingly, including updates to the data-grid API to align with TanStack Table v9. Ensure all references to components and their usage are consistent across the documentation.
Build and Push CFDM Docker Image / build-and-push (push) Successful in 2m2s
Build and Push CFDM Docker Image / create-release (push) Skipped
Build and Push CFDM Docker Image / update-wiki (push) Successful in 7s

This commit is contained in:
Denozordec
2026-08-07 09:46:30 +07:00
parent 721332e767
commit 3fb8480f8a
27 changed files with 444 additions and 108 deletions
+128 -2
View File
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { buildApp } from "../src/app.js";
import { loadConfig } from "../src/config.js";
import { repos } from "@cfdm/db";
import { createMemoryDb, repos, runMigrations } from "@cfdm/db";
async function authHeaders(app: Awaited<ReturnType<typeof buildApp>>) {
const config = loadConfig();
@@ -31,8 +31,14 @@ describe("service groups health enrichment", () => {
null,
"vpn.example.com",
);
repos.updateServiceGroup(app.db, group.id, "VPN", "vpn", null, "vpn.example.com", {
health_check_enabled: true,
health_check_type: "tcp",
health_check_port: 443,
});
const service = repos.createService(app.db, "Panel", "panel");
repos.setServiceGroup(app.db, service.id, group.id);
repos.setServiceEnabled(app.db, service.id, true);
const binding = repos.insertBinding(
app.db,
domain.id,
@@ -57,7 +63,7 @@ describe("service groups health enrichment", () => {
app.db,
"group",
group.id,
"5.6.7.8",
"1.2.3.4",
"up",
40,
0,
@@ -91,4 +97,124 @@ describe("service groups health enrichment", () => {
await app.close();
});
it("group badge ignores disabled service health and pool-only dead IPs", async () => {
const app = await buildApp({
config: { ...loadConfig(), staticDir: null },
memory: true,
});
const headers = await authHeaders(app);
const domain = repos.createDomain(app.db, null, "rkns.top", "zone-tg");
const group = repos.createServiceGroup(
app.db,
"TG Proxy",
"custom",
null,
"gt.rkns.top",
);
repos.updateServiceGroup(app.db, group.id, "TG Proxy", "custom", null, "gt.rkns.top", {
health_check_enabled: true,
health_check_type: "tcp",
health_check_port: 443,
});
const live = repos.createService(app.db, "Live", "live");
repos.setServiceGroup(app.db, live.id, group.id);
repos.setServiceEnabled(app.db, live.id, true);
repos.replaceServiceIps(app.db, live.id, ["10.0.0.1", "10.0.0.99"]);
const liveBinding = repos.insertBinding(app.db, domain.id, live.id, "gt", null);
repos.replaceBindingIpsWithMeta(app.db, liveBinding.id, [
{ ip: "10.0.0.1", weight: 1, priority: 1 },
]);
const dead = repos.createService(app.db, "Dead", "dead");
repos.setServiceGroup(app.db, dead.id, group.id);
repos.setServiceEnabled(app.db, dead.id, false);
const deadBinding = repos.insertBinding(app.db, domain.id, dead.id, "old", null);
repos.replaceBindingIpsWithMeta(app.db, deadBinding.id, [
{ ip: "10.0.0.2", weight: 1, priority: 1 },
]);
// Live binding + group FQDN are up; pool-only IP and disabled service are down.
repos.upsertIpHealthStatus(app.db, "binding", liveBinding.id, "10.0.0.1", "up", 20, 0, null);
repos.upsertIpHealthStatus(app.db, "group", group.id, "10.0.0.1", "up", 20, 0, null);
repos.upsertIpHealthStatus(app.db, "group", group.id, "10.0.0.99", "down", null, 5, "timeout");
repos.upsertIpHealthStatus(app.db, "binding", deadBinding.id, "10.0.0.2", "down", null, 5, "timeout");
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 }>;
};
const groupView = body.groups.find((g) => g.id === group.id);
expect(groupView?.health_status).toBe("up");
await app.close();
});
});
describe("group health check targets", () => {
it("probes binding IPs for group scope, not unused pool IPs", () => {
const { db, sqlite } = createMemoryDb();
runMigrations(sqlite);
const domain = repos.createDomain(db, null, "rkns.top", "zone-1");
const group = repos.createServiceGroup(db, "TG Proxy", "custom", null, "gt.rkns.top");
repos.updateServiceGroup(db, group.id, "TG Proxy", "custom", null, "gt.rkns.top", {
health_check_enabled: true,
health_check_type: "tcp",
health_check_port: 443,
});
const service = repos.createService(db, "Svc", "svc");
repos.setServiceGroup(db, service.id, group.id);
repos.setServiceEnabled(db, service.id, true);
repos.replaceServiceIps(db, service.id, ["10.0.0.1", "10.0.0.99"]);
const binding = repos.insertBinding(db, domain.id, service.id, "gt", null);
repos.replaceBindingIpsWithMeta(db, binding.id, [
{ ip: "10.0.0.1", weight: 1, priority: 1 },
]);
const targets = repos.listHealthCheckTargets(db);
const groupScope = targets.filter((t) => t.scope === "group" && t.ref_id === group.id);
expect(groupScope.map((t) => t.ip).sort()).toEqual(["10.0.0.1"]);
expect(groupScope.every((t) => t.hostname === "gt.rkns.top")).toBe(true);
});
it("pruneStaleIpHealthStatus drops pool-only and orphan group rows", () => {
const { db, sqlite } = createMemoryDb();
runMigrations(sqlite);
const domain = repos.createDomain(db, null, "rkns.top", "zone-1");
const group = repos.createServiceGroup(db, "TG Proxy", "custom", null, "gt.rkns.top");
repos.updateServiceGroup(db, group.id, "TG Proxy", "custom", null, "gt.rkns.top", {
health_check_enabled: true,
health_check_type: "tcp",
health_check_port: 443,
});
const service = repos.createService(db, "Svc", "svc");
repos.setServiceGroup(db, service.id, group.id);
repos.setServiceEnabled(db, service.id, true);
repos.replaceServiceIps(db, service.id, ["10.0.0.1", "10.0.0.99"]);
const binding = repos.insertBinding(db, domain.id, service.id, "gt", null);
repos.replaceBindingIpsWithMeta(db, binding.id, [
{ ip: "10.0.0.1", weight: 1, priority: 1 },
]);
repos.upsertIpHealthStatus(db, "group", group.id, "10.0.0.1", "up", 10, 0, null);
repos.upsertIpHealthStatus(db, "group", group.id, "10.0.0.99", "down", null, 3, "dead pool");
repos.upsertIpHealthStatus(db, "group", 999, "1.1.1.1", "down", null, 3, "orphan ref");
const targets = repos.listHealthCheckTargets(db);
const deleted = repos.pruneStaleIpHealthStatus(db, targets);
expect(deleted).toBeGreaterThanOrEqual(2);
const rows = repos.listIpHealthStatus(db, "group", group.id);
expect(rows.map((r) => r.ip)).toEqual(["10.0.0.1"]);
expect(repos.listIpHealthStatus(db, "group", 999)).toEqual([]);
});
});