65bae95e3c
Add gauge widget (recharts RadialBarChart with configurable threshold bands, scalar-only per SC-111) and mean widget (client-side average over range-query window, scalar-only per SC-114). Extract shared _instant_query helper from the metric path; _fetch_gauge and _fetch_mean dispatch in PrometheusWidgetSource.fetch(). Both new widget kinds declared in integrations/prometheus.py and frontend registry. Backend: 305 pytest pass, ruff clean. Frontend: 136 vitest pass, build+lint green.
140 lines
3.3 KiB
TypeScript
140 lines
3.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
BUILTIN_WIDGETS,
|
|
SERVICE_REGISTRY,
|
|
getBuiltinBinding,
|
|
getServiceBinding,
|
|
resolveWidget,
|
|
} from "./registry";
|
|
import type { ServiceInstance, WidgetInstance } from "../types";
|
|
|
|
describe("service registry", () => {
|
|
it("registers the backend service types", () => {
|
|
expect(Object.keys(SERVICE_REGISTRY).sort()).toEqual([
|
|
"alertmanager",
|
|
"grafana",
|
|
"jellyfin",
|
|
"nextcloud",
|
|
"prometheus",
|
|
"ssh_tasks",
|
|
]);
|
|
});
|
|
|
|
it("binds widget kinds per service", () => {
|
|
expect(SERVICE_REGISTRY.grafana.widgets.map((w) => w.kind)).toEqual([
|
|
"link",
|
|
]);
|
|
expect(SERVICE_REGISTRY.prometheus.widgets.map((w) => w.kind)).toEqual([
|
|
"metric",
|
|
"chart",
|
|
"gauge",
|
|
"mean",
|
|
]);
|
|
expect(SERVICE_REGISTRY.alertmanager.widgets.map((w) => w.kind)).toEqual([
|
|
"active_alerts",
|
|
]);
|
|
expect(SERVICE_REGISTRY.ssh_tasks.widgets.map((w) => w.kind)).toEqual([
|
|
"task_output",
|
|
]);
|
|
expect(SERVICE_REGISTRY.nextcloud.widgets).toEqual([]);
|
|
});
|
|
|
|
it("registers the two built-in widget kinds", () => {
|
|
expect(Object.keys(BUILTIN_WIDGETS).sort()).toEqual(["backups", "static"]);
|
|
});
|
|
|
|
it("resolves a service-bound widget via the services list", () => {
|
|
const widget: WidgetInstance = {
|
|
id: "w1",
|
|
service_id: "s1",
|
|
widget_kind: "link",
|
|
title: "Dashboard",
|
|
config: {},
|
|
enabled: true,
|
|
sort_order: 0,
|
|
created_at: 0,
|
|
updated_at: 0,
|
|
};
|
|
const services: ServiceInstance[] = [
|
|
{
|
|
id: "s1",
|
|
service_type: "grafana",
|
|
name: "Grafana",
|
|
config: { base_url: "https://grafana.example.com" },
|
|
secrets_set: { api_key: true },
|
|
enabled: true,
|
|
created_at: 0,
|
|
updated_at: 0,
|
|
},
|
|
];
|
|
const resolved = resolveWidget(widget, services);
|
|
expect(resolved).toBeDefined();
|
|
expect(resolved?.refreshIntervalMs).toBe(0);
|
|
});
|
|
|
|
it("resolves an alertmanager active_alerts widget", () => {
|
|
const widget: WidgetInstance = {
|
|
id: "wam",
|
|
service_id: "sam",
|
|
widget_kind: "active_alerts",
|
|
title: "Alerts",
|
|
config: {},
|
|
enabled: true,
|
|
sort_order: 0,
|
|
created_at: 0,
|
|
updated_at: 0,
|
|
};
|
|
const services: ServiceInstance[] = [
|
|
{
|
|
id: "sam",
|
|
service_type: "alertmanager",
|
|
name: "Alertmanager",
|
|
config: { base_url: "http://am:9093" },
|
|
secrets_set: {},
|
|
enabled: true,
|
|
created_at: 0,
|
|
updated_at: 0,
|
|
},
|
|
];
|
|
const resolved = resolveWidget(widget, services);
|
|
expect(resolved).toBeDefined();
|
|
expect(resolved?.refreshIntervalMs).toBe(30_000);
|
|
});
|
|
|
|
it("resolves a built-in widget without a service", () => {
|
|
const widget: WidgetInstance = {
|
|
id: "w2",
|
|
service_id: null,
|
|
widget_kind: "static",
|
|
title: "Note",
|
|
config: { text: "hi" },
|
|
enabled: true,
|
|
sort_order: 0,
|
|
created_at: 0,
|
|
updated_at: 0,
|
|
};
|
|
const resolved = resolveWidget(widget, []);
|
|
expect(resolved).toBeDefined();
|
|
});
|
|
|
|
it("returns undefined for an unknown widget kind", () => {
|
|
const widget: WidgetInstance = {
|
|
id: "w3",
|
|
service_id: null,
|
|
widget_kind: "bogus",
|
|
title: "x",
|
|
config: {},
|
|
enabled: true,
|
|
sort_order: 0,
|
|
created_at: 0,
|
|
updated_at: 0,
|
|
};
|
|
expect(resolveWidget(widget, [])).toBeUndefined();
|
|
});
|
|
|
|
it("lookups return undefined for unknown types", () => {
|
|
expect(getServiceBinding("nope")).toBeUndefined();
|
|
expect(getBuiltinBinding("nope")).toBeUndefined();
|
|
});
|
|
});
|