diff --git a/src/lib/model/index.ts b/src/lib/model/index.ts index 5ae349a..1a028cc 100644 --- a/src/lib/model/index.ts +++ b/src/lib/model/index.ts @@ -1,11 +1,6 @@ export { DASHBOARD_SCHEMA_VERSION, DashboardDocumentSchema, - DatasourceReferenceSchema, - ServiceEntrySchema, - ServiceGroupSchema, - TelemetryCardSchema, - ThresholdSchema, dashboardDocumentJsonSchema, type DashboardDocument, type DashboardModule, diff --git a/src/lib/model/schema.ts b/src/lib/model/schema.ts index 86e3ab7..e0215a4 100644 --- a/src/lib/model/schema.ts +++ b/src/lib/model/schema.ts @@ -27,7 +27,7 @@ const LinkSchema = Type.Object( { additionalProperties: false }, ); -export const StaticDatasourceSchema = Type.Object( +const StaticDatasourceSchema = Type.Object( { type: Type.Literal("static"), label: Type.Optional(Type.String({ minLength: 1 })), @@ -36,7 +36,7 @@ export const StaticDatasourceSchema = Type.Object( { additionalProperties: false }, ); -export const PlaceholderDatasourceSchema = Type.Object( +const PlaceholderDatasourceSchema = Type.Object( { type: Type.Literal("placeholder"), reason: Type.String({ minLength: 1 }), @@ -44,7 +44,7 @@ export const PlaceholderDatasourceSchema = Type.Object( { additionalProperties: false }, ); -export const ExternalDatasourceSchema = Type.Object( +const ExternalDatasourceSchema = Type.Object( { type: Type.Literal("external"), adapter: Type.Union([ @@ -58,7 +58,7 @@ export const ExternalDatasourceSchema = Type.Object( { additionalProperties: false }, ); -export const DatasourceReferenceSchema = Type.Union([ +const DatasourceReferenceSchema = Type.Union([ StaticDatasourceSchema, PlaceholderDatasourceSchema, ExternalDatasourceSchema, @@ -98,13 +98,13 @@ const TextMetricValueSchema = Type.Object( { additionalProperties: false }, ); -export const MetricValueSchema = Type.Union([ +const MetricValueSchema = Type.Union([ PercentMetricValueSchema, NonNegativeMetricValueSchema, TextMetricValueSchema, ]); -export const ThresholdSchema = Type.Object( +const ThresholdSchema = Type.Object( { warning: Type.Optional(Type.Number({ minimum: 0 })), danger: Type.Optional(Type.Number({ minimum: 0 })), diff --git a/src/lib/server/agent-config/agent-config.test.ts b/src/lib/server/agent-config/agent-config.test.ts deleted file mode 100644 index 87449e8..0000000 --- a/src/lib/server/agent-config/agent-config.test.ts +++ /dev/null @@ -1,595 +0,0 @@ -import { existsSync, rmSync } from "node:fs"; -import { mkdtemp } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { afterEach, describe, expect, test } from "vitest"; -import type { DashboardDocument } from "$lib/model"; -import { genericDashboardFixture } from "$lib/model/fixtures/generic"; -import { createDashboardStore, type DashboardStore } from "$lib/server/db/dashboard-store"; -import { - AgentConfigAuthorizationError, - authorizeAgentConfigRequest, - handleAgentDashboardRequest, - previewDashboardChanges, - publishDashboardChanges, - rollbackDashboardRevision, - type AgentDashboardOperation, - type JsonPatchOperation, -} from "."; - -const stores: DashboardStore[] = []; -const tempRoots: string[] = []; - -afterEach(() => { - stores.splice(0).forEach((store) => store.close()); - tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true })); -}); - -describe("agent dashboard configuration API", () => { - test("previews typed dashboard operations with an RFC 6902-compatible patch", () => { - const operations = exampleOperations(); - - const result = previewDashboardChanges(genericDashboardFixture, operations); - - expect(result.ok).toBe(true); - if (!result.ok) throw new Error("expected preview success"); - expect(result.document.serviceGroups[0]?.id).toBe("edge"); - expect(result.document.serviceGroups[0]?.services[0]).toMatchObject({ - id: "edge-router", - datasource: { - type: "external", - adapter: "http-status", - reference: "GET https://edge.example.test/api/status", - }, - }); - expect(result.document.layout.telemetry).toEqual([ - "service-uptime", - "edge-latency", - "queue-depth", - ]); - expect(genericDashboardFixture.serviceGroups.map((group) => group.id)).toEqual([ - "core-services", - ]); - expect(result.patch.length).toBeGreaterThan(0); - expect(result.patch.every((operation) => operation.path.startsWith("/"))).toBe(true); - expect(result.patch.map((operation) => operation.op)).toContain("add"); - }); - - test("returns structured repairable errors for invalid operations", () => { - const result = previewDashboardChanges(genericDashboardFixture, [ - { - type: "add_service", - groupId: "missing-group", - service: exampleService(), - }, - ]); - - expect(result.ok).toBe(false); - if (result.ok) throw new Error("expected preview failure"); - expect(result.errors).toEqual([ - expect.objectContaining({ - code: "group_not_found", - operationIndex: 0, - path: "/serviceGroups", - }), - ]); - }); - - test("rejects unsupported target-specific mutations", () => { - const datasourceResult = previewDashboardChanges(genericDashboardFixture, [ - { - type: "connect_datasource", - target: { kind: "statusItem", stripId: "runtime", id: "status" }, - datasource: { - type: "external", - adapter: "http-status", - reference: "GET https://status.example.test/api", - }, - }, - ]); - expect(datasourceResult.ok).toBe(false); - if (datasourceResult.ok) throw new Error("expected datasource failure"); - expect(datasourceResult.errors[0]).toMatchObject({ - code: "unsupported_target", - operationIndex: 0, - path: "/statusStrips", - }); - - const thresholdResult = previewDashboardChanges(genericDashboardFixture, [ - { - type: "set_status_rule", - target: { kind: "service", id: "identity" }, - thresholds: { warning: 1 }, - }, - ]); - expect(thresholdResult.ok).toBe(false); - if (thresholdResult.ok) throw new Error("expected threshold failure"); - expect(thresholdResult.errors[0]).toMatchObject({ - code: "unsupported_target", - operationIndex: 0, - path: "/serviceGroups", - }); - }); - - test("removes status strip items through the shared remove operation", () => { - const result = previewDashboardChanges(genericDashboardFixture, [ - { - type: "remove_item", - target: { kind: "statusItem", stripId: "runtime", id: "sync" }, - }, - ]); - - expect(result.ok).toBe(true); - if (!result.ok) throw new Error("expected remove success"); - expect(result.document.statusStrips[0]?.items.map((item) => item.id)).toEqual([ - "status", - ]); - expect(result.patch.map((operation) => operation.op)).toContain("remove"); - }); - - test("returns RFC 6902-applicable patches for multiple array removals", () => { - const result = previewDashboardChanges(genericDashboardFixture, [ - { - type: "remove_item", - target: { kind: "statusItem", stripId: "runtime", id: "status" }, - }, - { - type: "remove_item", - target: { kind: "statusItem", stripId: "runtime", id: "sync" }, - }, - ]); - - expect(result.ok).toBe(true); - if (!result.ok) throw new Error("expected remove success"); - expect( - result.patch - .filter((operation) => operation.op === "remove") - .map((operation) => operation.path), - ).toEqual(["/statusStrips/0/items/1", "/statusStrips/0/items/0"]); - expect(applyJsonPatch(genericDashboardFixture, result.patch)).toMatchObject({ - statusStrips: [{ items: [] }], - }); - }); - - test("rejects ambiguous service and status targets", () => { - const duplicateDocument = documentWithDuplicateNestedIds(); - - const connect = previewDashboardChanges(duplicateDocument, [ - { - type: "connect_datasource", - target: { kind: "service", id: "identity" }, - datasource: { - type: "external", - adapter: "http-status", - reference: "GET https://identity.example.test/health", - }, - }, - ]); - expect(connect.ok).toBe(false); - if (connect.ok) throw new Error("expected ambiguous service failure"); - expect(connect.errors[0]).toMatchObject({ - code: "ambiguous_target", - operationIndex: 0, - path: "/serviceGroups", - }); - - const remove = previewDashboardChanges(duplicateDocument, [ - { - type: "remove_item", - target: { kind: "service", id: "identity" }, - }, - ]); - expect(remove.ok).toBe(false); - if (remove.ok) throw new Error("expected ambiguous removal failure"); - expect(remove.errors[0]).toMatchObject({ - code: "ambiguous_target", - operationIndex: 0, - path: "/serviceGroups", - }); - - const status = previewDashboardChanges(duplicateDocument, [ - { - type: "set_status_rule", - target: { kind: "statusItem", id: "status" }, - value: "Healthy", - }, - ]); - expect(status.ok).toBe(false); - if (status.ok) throw new Error("expected ambiguous status failure"); - expect(status.errors[0]).toMatchObject({ - code: "ambiguous_target", - operationIndex: 0, - path: "/statusStrips", - }); - }); - - test("rejects create_dashboard when it is not the first operation", () => { - const result = previewDashboardChanges(genericDashboardFixture, [ - { - type: "remove_item", - target: { kind: "telemetry", id: "queue-depth" }, - }, - { - type: "create_dashboard", - document: genericDashboardFixture, - }, - ]); - - expect(result.ok).toBe(false); - if (result.ok) throw new Error("expected sequence failure"); - expect(result.errors[0]).toMatchObject({ - code: "invalid_operation_sequence", - operationIndex: 1, - path: "/1", - }); - }); - - test("publishes valid operations as a persisted dashboard revision", async () => { - const { dbPath, store } = await createTestStore(); - const seed = store.seedDashboardIfEmpty(genericDashboardFixture, { - actor: "seed", - message: "initial dashboard", - }); - - const result = publishDashboardChanges(store, exampleOperations(), { - actor: "agent", - message: "add edge router", - }); - - expect(result.ok).toBe(true); - if (!result.ok) throw new Error("expected publish success"); - expect(existsSync(dbPath)).toBe(true); - expect(result.revision.operation).toBe("commit"); - expect(result.revision.actor).toBe("agent"); - expect(result.revision.message).toBe("add edge router"); - expect(result.previousRevisionId).toBe(seed.id); - expect(result.patch.length).toBeGreaterThan(0); - expect(store.getActiveDashboard()?.document.serviceGroups[0]?.id).toBe("edge"); - expect(store.listRevisions()).toHaveLength(2); - }); - - test("does not publish invalid operations", async () => { - const { store } = await createTestStore(); - const seed = store.seedDashboardIfEmpty(genericDashboardFixture, { - actor: "seed", - }); - - const result = publishDashboardChanges(store, [ - { - type: "remove_item", - target: { kind: "telemetry", id: "missing-metric" }, - }, - ]); - - expect(result.ok).toBe(false); - if (result.ok) throw new Error("expected publish failure"); - expect(result.errors[0]).toMatchObject({ - code: "item_not_found", - operationIndex: 0, - }); - expect(store.getActiveDashboard()?.currentRevisionId).toBe(seed.id); - expect(store.listRevisions()).toHaveLength(1); - }); - - test("rolls back through the agent-safe revision path", async () => { - const { store } = await createTestStore(); - const seed = store.seedDashboardIfEmpty(genericDashboardFixture, { - actor: "seed", - }); - const publish = publishDashboardChanges(store, exampleOperations(), { - actor: "agent", - }); - expect(publish.ok).toBe(true); - - const rollback = rollbackDashboardRevision(store, seed.id, { - actor: "agent", - message: "restore previous dashboard", - }); - - expect(rollback.operation).toBe("rollback"); - expect(rollback.actor).toBe("agent"); - expect(rollback.sourceRevisionId).toBe(seed.id); - expect(store.getActiveDashboard()?.document.metadata.title).toBe("Operations Console"); - expect(store.listRevisions().map((revision) => revision.operation)).toEqual([ - "rollback", - "commit", - "seed", - ]); - }); - - test("authenticates agent configuration requests with a shared token", () => { - const authorized = new Request("https://dimensionlab.test/api/agent/dashboard", { - headers: { authorization: "Bearer shared-secret" }, - }); - const rejected = new Request("https://dimensionlab.test/api/agent/dashboard"); - - expect(authorizeAgentConfigRequest(authorized, "shared-secret")).toEqual({ - ok: true, - }); - expect(() => authorizeAgentConfigRequest(rejected, "shared-secret")).toThrow( - AgentConfigAuthorizationError, - ); - expect(() => authorizeAgentConfigRequest(authorized, "")).toThrow( - AgentConfigAuthorizationError, - ); - }); - - test("handles preview, publish, and rollback requests through the HTTP adapter", async () => { - const { store } = await createTestStore(); - const seed = store.seedDashboardIfEmpty(genericDashboardFixture, { - actor: "seed", - }); - - const preview = await handleAgentDashboardRequest( - jsonRequest({ - action: "preview_changes", - operations: exampleOperations(), - }), - { store, token: "shared-secret" }, - ); - expect(preview.status).toBe(200); - expect(await preview.json()).toMatchObject({ - ok: true, - action: "preview_changes", - patch: expect.any(Array), - }); - expect(store.listRevisions()).toHaveLength(1); - - const publish = await handleAgentDashboardRequest( - jsonRequest({ - action: "publish_changes", - actor: "agent", - message: "publish edge router", - operations: exampleOperations(), - }), - { store, token: "shared-secret" }, - ); - expect(publish.status).toBe(200); - expect(await publish.json()).toMatchObject({ - ok: true, - action: "publish_changes", - revision: { operation: "commit", actor: "agent" }, - }); - expect(store.listRevisions()).toHaveLength(2); - - const rollback = await handleAgentDashboardRequest( - jsonRequest({ - action: "rollback_revision", - actor: "agent", - revisionId: seed.id, - }), - { store, token: "shared-secret" }, - ); - expect(rollback.status).toBe(200); - expect(await rollback.json()).toMatchObject({ - ok: true, - action: "rollback_revision", - revision: { operation: "rollback", sourceRevisionId: seed.id }, - }); - }); - - test("returns a structured error for unknown rollback revisions", async () => { - const { store } = await createTestStore(); - store.seedDashboardIfEmpty(genericDashboardFixture, { - actor: "seed", - }); - - const rollback = await handleAgentDashboardRequest( - jsonRequest({ - action: "rollback_revision", - actor: "agent", - revisionId: "missing-revision", - }), - { store, token: "shared-secret" }, - ); - - expect(rollback.status).toBe(404); - expect(await rollback.json()).toMatchObject({ - ok: false, - errors: [ - { - code: "revision_not_found", - path: "/revisionId", - }, - ], - }); - }); - - test("previews create_dashboard requests against an empty store", async () => { - const { store } = await createTestStore(); - - const preview = await handleAgentDashboardRequest( - jsonRequest({ - action: "preview_changes", - operations: [ - { - type: "create_dashboard", - document: genericDashboardFixture, - }, - ], - }), - { store, token: "shared-secret" }, - ); - - expect(preview.status).toBe(200); - expect(await preview.json()).toMatchObject({ - ok: true, - action: "preview_changes", - document: { - metadata: { - title: "Operations Console", - }, - }, - }); - expect(store.listRevisions()).toHaveLength(0); - }); -}); - -async function createTestStore() { - const root = await mkdtemp(join(tmpdir(), "dimensionlab-agent-config-")); - tempRoots.push(root); - const dbPath = join(root, "dashboard.sqlite"); - const store = createDashboardStore({ - databaseUrl: `file:${dbPath}`, - }); - stores.push(store); - - return { dbPath, store }; -} - -function exampleOperations(): AgentDashboardOperation[] { - return [ - { - type: "add_section", - section: { - id: "edge", - title: "Edge", - layout: "list", - }, - }, - { - type: "add_service", - groupId: "edge", - service: exampleService(), - }, - { - type: "add_metric_card", - card: { - id: "edge-latency", - label: "Edge Latency", - value: { kind: "latency", value: 12, precision: 0 }, - severity: "ok", - detail: "p95", - datasource: { - type: "external", - adapter: "prometheus", - reference: "histogram_quantile(0.95, edge_request_duration_seconds_bucket)", - }, - }, - position: { afterId: "service-uptime" }, - }, - { - type: "connect_datasource", - target: { - kind: "service", - groupId: "edge", - id: "edge-router", - }, - datasource: { - type: "external", - adapter: "http-status", - reference: "GET https://edge.example.test/api/status", - }, - }, - { - type: "set_status_rule", - target: { kind: "telemetry", id: "edge-latency" }, - thresholds: { warning: 50, danger: 100 }, - }, - { - type: "arrange_item", - area: "serviceGroups", - id: "edge", - index: 0, - }, - ]; -} - -function exampleService() { - return { - id: "edge-router", - label: "Edge Router", - description: "Ingress and routing", - icon: "mdi:router-network", - severity: "ok" as const, - detail: "pending datasource", - datasource: { type: "placeholder" as const, reason: "health adapter pending" }, - link: { - href: "https://edge.example.test", - label: "Open Edge Router", - external: true, - }, - }; -} - -function documentWithDuplicateNestedIds(): DashboardDocument { - const document = structuredClone(genericDashboardFixture); - document.layout.serviceGroups.push("secondary-services"); - document.serviceGroups.push({ - id: "secondary-services", - title: "Secondary Services", - layout: "list", - services: [ - { - ...document.serviceGroups[0].services[0], - label: "Shadow Identity", - }, - ], - }); - document.layout.statusStrips.push("secondary-runtime"); - document.statusStrips.push({ - id: "secondary-runtime", - items: [ - { - ...document.statusStrips[0].items[0], - value: "Healthy", - }, - ], - }); - return document; -} - -function applyJsonPatch(value: T, patch: JsonPatchOperation[]): T { - const next = structuredClone(value); - for (const operation of patch) { - const { parent, key } = jsonPointerTarget(next, operation.path); - if (operation.op === "remove") { - if (Array.isArray(parent)) { - parent.splice(Number(key), 1); - } else { - delete parent[key]; - } - } else if (operation.op === "add") { - if (Array.isArray(parent)) { - parent.splice(Number(key), 0, operation.value); - } else { - parent[key] = operation.value; - } - } else if (operation.op === "replace") { - if (Array.isArray(parent)) { - parent[Number(key)] = operation.value; - } else { - parent[key] = operation.value; - } - } - } - return next; -} - -function jsonPointerTarget(value: unknown, path: string) { - const segments = path - .split("/") - .slice(1) - .map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")); - const key = segments.pop(); - if (key === undefined) throw new Error(`Invalid JSON pointer: ${path}`); - - let parent = value as Record | unknown[]; - for (const segment of segments) { - parent = Array.isArray(parent) - ? (parent[Number(segment)] as Record | unknown[]) - : (parent[segment] as Record | unknown[]); - } - return { parent, key }; -} - -function jsonRequest(body: unknown) { - return new Request("https://dimensionlab.test/api/agent/dashboard", { - body: JSON.stringify(body), - headers: { - authorization: "Bearer shared-secret", - "content-type": "application/json", - }, - method: "POST", - }); -} diff --git a/src/lib/server/agent-config/index.ts b/src/lib/server/agent-config/index.ts deleted file mode 100644 index ffa7dd8..0000000 --- a/src/lib/server/agent-config/index.ts +++ /dev/null @@ -1,1292 +0,0 @@ -import Ajv, { type ErrorObject } from "ajv"; -import addFormats from "ajv-formats"; -import { Type, type Static } from "@sinclair/typebox"; -import { - DashboardDocumentSchema, - DatasourceReferenceSchema, - ServiceEntrySchema, - TelemetryCardSchema, - ThresholdSchema, - validateDashboardDocument, - type DashboardDocument, - type DashboardModule, - type DashboardValidationFailure, - type DatasourceReference, - type ServiceEntry, - type ServiceGroup, - type StatusItem, - type TelemetryCard, -} from "$lib/model"; -import { - createDashboardStore, - DashboardRevisionNotFoundError, - type DashboardRevision, - type DashboardStore, - type DashboardWriteMetadata, -} from "$lib/server/db/dashboard-store"; - -const IdentifierSchema = Type.String({ - minLength: 1, - pattern: "^[a-z0-9][a-z0-9-_.:]*$", -}); - -const PositionSchema = Type.Object( - { - index: Type.Optional(Type.Integer({ minimum: 0 })), - beforeId: Type.Optional(IdentifierSchema), - afterId: Type.Optional(IdentifierSchema), - }, - { additionalProperties: false, minProperties: 1 }, -); - -const AgentTargetSchema = Type.Union([ - Type.Object( - { - kind: Type.Literal("telemetry"), - id: IdentifierSchema, - }, - { additionalProperties: false }, - ), - Type.Object( - { - kind: Type.Literal("service"), - groupId: Type.Optional(IdentifierSchema), - id: IdentifierSchema, - }, - { additionalProperties: false }, - ), - Type.Object( - { - kind: Type.Literal("serviceGroup"), - id: IdentifierSchema, - }, - { additionalProperties: false }, - ), - Type.Object( - { - kind: Type.Literal("statusItem"), - stripId: Type.Optional(IdentifierSchema), - id: IdentifierSchema, - }, - { additionalProperties: false }, - ), - Type.Object( - { - kind: Type.Literal("module"), - id: IdentifierSchema, - }, - { additionalProperties: false }, - ), -]); - -const AddSectionOperationSchema = Type.Object( - { - type: Type.Literal("add_section"), - section: Type.Object( - { - id: IdentifierSchema, - title: Type.String({ minLength: 1 }), - layout: Type.Optional(Type.Union([Type.Literal("list"), Type.Literal("grid")])), - }, - { additionalProperties: false }, - ), - position: Type.Optional(PositionSchema), - }, - { additionalProperties: false }, -); - -const AddServiceOperationSchema = Type.Object( - { - type: Type.Literal("add_service"), - groupId: IdentifierSchema, - service: ServiceEntrySchema, - position: Type.Optional(PositionSchema), - }, - { additionalProperties: false }, -); - -const AddMetricCardOperationSchema = Type.Object( - { - type: Type.Literal("add_metric_card"), - card: TelemetryCardSchema, - position: Type.Optional(PositionSchema), - }, - { additionalProperties: false }, -); - -const ConnectDatasourceOperationSchema = Type.Object( - { - type: Type.Literal("connect_datasource"), - target: AgentTargetSchema, - datasource: DatasourceReferenceSchema, - }, - { additionalProperties: false }, -); - -const SetStatusRuleOperationSchema = Type.Object( - { - type: Type.Literal("set_status_rule"), - target: AgentTargetSchema, - thresholds: Type.Optional(ThresholdSchema), - severity: Type.Optional( - Type.Union([ - Type.Literal("neutral"), - Type.Literal("ok"), - Type.Literal("warning"), - Type.Literal("danger"), - Type.Literal("stale"), - Type.Literal("unavailable"), - ]), - ), - detail: Type.Optional(Type.String()), - value: Type.Optional(Type.String()), - }, - { additionalProperties: false, minProperties: 3 }, -); - -const ArrangeItemOperationSchema = Type.Object( - { - type: Type.Literal("arrange_item"), - area: Type.Union([ - Type.Literal("telemetry"), - Type.Literal("serviceGroups"), - Type.Literal("statusStrips"), - Type.Literal("modules"), - ]), - id: IdentifierSchema, - index: Type.Integer({ minimum: 0 }), - }, - { additionalProperties: false }, -); - -const RemoveItemOperationSchema = Type.Object( - { - type: Type.Literal("remove_item"), - target: AgentTargetSchema, - }, - { additionalProperties: false }, -); - -const NestedDashboardDocumentSchema = Type.Unsafe( - schemaWithoutRootId(DashboardDocumentSchema), -); - -const CreateDashboardOperationSchema = Type.Object( - { - type: Type.Literal("create_dashboard"), - document: NestedDashboardDocumentSchema, - }, - { additionalProperties: false }, -); - -export const AgentDashboardOperationSchema = Type.Union([ - CreateDashboardOperationSchema, - AddSectionOperationSchema, - AddServiceOperationSchema, - AddMetricCardOperationSchema, - ConnectDatasourceOperationSchema, - SetStatusRuleOperationSchema, - ArrangeItemOperationSchema, - RemoveItemOperationSchema, -]); - -export const AgentDashboardOperationsSchema = Type.Array(AgentDashboardOperationSchema, { - minItems: 1, -}); - -const PreviewRequestSchema = Type.Object( - { - action: Type.Literal("preview_changes"), - operations: AgentDashboardOperationsSchema, - }, - { additionalProperties: false }, -); - -const PublishRequestSchema = Type.Object( - { - action: Type.Literal("publish_changes"), - actor: Type.Optional(Type.String({ minLength: 1 })), - message: Type.Optional(Type.String({ minLength: 1 })), - operations: AgentDashboardOperationsSchema, - }, - { additionalProperties: false }, -); - -const RollbackRequestSchema = Type.Object( - { - action: Type.Literal("rollback_revision"), - actor: Type.Optional(Type.String({ minLength: 1 })), - message: Type.Optional(Type.String({ minLength: 1 })), - revisionId: Type.String({ minLength: 1 }), - }, - { additionalProperties: false }, -); - -export const AgentDashboardRequestSchema = Type.Union([ - PreviewRequestSchema, - PublishRequestSchema, - RollbackRequestSchema, -]); - -export type AgentDashboardOperation = Static; -export type AgentDashboardRequest = Static; - -export interface JsonPatchAddOperation { - op: "add"; - path: string; - value: unknown; -} - -export interface JsonPatchRemoveOperation { - op: "remove"; - path: string; -} - -export interface JsonPatchReplaceOperation { - op: "replace"; - path: string; - value: unknown; -} - -export type JsonPatchOperation = - | JsonPatchAddOperation - | JsonPatchRemoveOperation - | JsonPatchReplaceOperation; - -export interface AgentConfigError { - code: string; - message: string; - operationIndex?: number; - path: string; - details?: unknown; -} - -export type AgentPreviewResult = - | { - ok: true; - document: DashboardDocument; - patch: JsonPatchOperation[]; - } - | { - ok: false; - errors: AgentConfigError[]; - }; - -export type AgentPublishResult = - | { - ok: true; - document: DashboardDocument; - patch: JsonPatchOperation[]; - previousRevisionId: string | null; - revision: DashboardRevision; - } - | { - ok: false; - errors: AgentConfigError[]; - }; - -export interface AgentDashboardHandlerOptions { - store?: DashboardStore; - token?: string; -} - -export class AgentConfigAuthorizationError extends Error { - constructor() { - super("Unauthorized dashboard configuration request"); - this.name = "AgentConfigAuthorizationError"; - } -} - -const validateOperations = createAjv().compile( - AgentDashboardOperationsSchema, -); -const validateRequest = createAjv().compile( - AgentDashboardRequestSchema, -); - -function schemaWithoutRootId(schema: T): T { - const clone = JSON.parse(JSON.stringify(schema)) as T & { $id?: string }; - delete clone.$id; - return clone; -} - -function createAjv() { - return addFormats( - new Ajv({ - allErrors: true, - strict: false, - strictNumbers: true, - }), - ); -} - -export function previewDashboardChanges( - document: DashboardDocument, - operations: AgentDashboardOperation[], -): AgentPreviewResult { - const operationValidation = validateAgentOperations(operations); - if (operationValidation.length) return { ok: false, errors: operationValidation }; - - const before = structuredClone(document); - let next = structuredClone(document); - const errors: AgentConfigError[] = []; - - operations.forEach((operation, operationIndex) => { - if (errors.length) return; - const result = applyOperation(next, operation, operationIndex); - if (result.ok) { - next = result.document; - } else { - errors.push(...result.errors); - } - }); - - if (errors.length) return { ok: false, errors }; - - const validation = validateDashboardDocument(next); - if (!validation.valid) { - return { - ok: false, - errors: dashboardValidationErrors(validation), - }; - } - - return { - ok: true, - document: validation.data, - patch: createJsonPatch(before, validation.data), - }; -} - -export function publishDashboardChanges( - store: DashboardStore, - operations: AgentDashboardOperation[], - metadata: DashboardWriteMetadata = {}, -): AgentPublishResult { - const active = store.getActiveDashboard(); - if (!active) { - const create = operations[0]; - if (!create || create.type !== "create_dashboard") { - return { - ok: false, - errors: [ - { - code: "no_active_dashboard", - message: "No active dashboard exists; start with create_dashboard", - path: "/", - }, - ], - }; - } - } - - const base = active?.document || emptyDashboardDocument(); - const preview = previewDashboardChanges(base, operations); - if (!preview.ok) return preview; - - const revision = store.commitDashboard(preview.document, metadata); - return { - ok: true, - document: revision.document, - patch: preview.patch, - previousRevisionId: active?.currentRevisionId || null, - revision, - }; -} - -export function rollbackDashboardRevision( - store: DashboardStore, - revisionId: string, - metadata: DashboardWriteMetadata = {}, -) { - return store.rollbackToRevision(revisionId, metadata); -} - -export function authorizeAgentConfigRequest( - request: Request, - token = process.env.AGENT_CONFIG_TOKEN, -): { ok: true } { - if (!token) throw new AgentConfigAuthorizationError(); - - const authorization = request.headers.get("authorization") || ""; - const bearer = authorization.match(/^Bearer\s+(.+)$/i)?.[1]; - const headerToken = request.headers.get("x-agent-config-token"); - if (bearer === token || headerToken === token) return { ok: true }; - - throw new AgentConfigAuthorizationError(); -} - -export async function handleAgentDashboardRequest( - request: Request, - options: AgentDashboardHandlerOptions = {}, -): Promise { - const ownsStore = !options.store; - const store = options.store || createDashboardStore(); - - try { - authorizeAgentConfigRequest(request, options.token); - const body = await parseRequestBody(request); - const requestValidationErrors = validateAgentRequest(body); - if (requestValidationErrors.length) { - return Response.json( - { ok: false, errors: requestValidationErrors }, - { status: 400 }, - ); - } - - const agentRequest = body as AgentDashboardRequest; - if (agentRequest.action === "rollback_revision") { - const revision = rollbackDashboardRevision(store, agentRequest.revisionId, { - actor: agentRequest.actor || "agent", - message: agentRequest.message, - }); - return Response.json({ - ok: true, - action: agentRequest.action, - revision: serializeRevision(revision), - }); - } - - if (agentRequest.action === "preview_changes") { - const active = store.getActiveDashboard(); - const base = active?.document || previewBaseDocument(agentRequest.operations); - if (!base) { - return Response.json( - { - ok: false, - errors: [ - { - code: "no_active_dashboard", - message: "No active dashboard exists to preview against", - path: "/", - }, - ], - }, - { status: 409 }, - ); - } - const preview = previewDashboardChanges(base, agentRequest.operations); - return Response.json( - { - ...preview, - action: agentRequest.action, - }, - { status: preview.ok ? 200 : 422 }, - ); - } - - const publish = publishDashboardChanges(store, agentRequest.operations, { - actor: agentRequest.actor || "agent", - message: agentRequest.message, - }); - return Response.json( - { - ...publish, - action: agentRequest.action, - revision: publish.ok ? serializeRevision(publish.revision) : undefined, - }, - { status: publish.ok ? 200 : 422 }, - ); - } catch (error) { - if (error instanceof AgentConfigAuthorizationError) { - return Response.json( - { - ok: false, - errors: [ - { - code: "unauthorized", - message: error.message, - path: "/", - }, - ], - }, - { status: 401 }, - ); - } - - if (error instanceof SyntaxError) { - return Response.json( - { - ok: false, - errors: [ - { - code: "invalid_json", - message: error.message, - path: "/", - }, - ], - }, - { status: 400 }, - ); - } - - if (error instanceof DashboardRevisionNotFoundError) { - return Response.json( - { - ok: false, - errors: [ - { - code: "revision_not_found", - message: error.message, - path: "/revisionId", - }, - ], - }, - { status: 404 }, - ); - } - - throw error; - } finally { - if (ownsStore) store.close(); - } -} - -function validateAgentOperations(operations: unknown): AgentConfigError[] { - if (!validateOperations(operations)) { - return schemaErrors(validateOperations.errors || [], "invalid_operation_schema"); - } - - const createDashboardIndices = operations.flatMap((operation, index) => - operation.type === "create_dashboard" ? [index] : [], - ); - if (createDashboardIndices.length > 1) { - const operationIndex = createDashboardIndices[1]; - return [ - { - code: "invalid_operation_sequence", - message: "create_dashboard can only appear once", - operationIndex, - path: `/${operationIndex}`, - }, - ]; - } - if (createDashboardIndices[0] !== undefined && createDashboardIndices[0] !== 0) { - const operationIndex = createDashboardIndices[0]; - return [ - { - code: "invalid_operation_sequence", - message: "create_dashboard must be the first operation", - operationIndex, - path: `/${operationIndex}`, - }, - ]; - } - - return []; -} - -function validateAgentRequest(value: unknown): AgentConfigError[] { - if (validateRequest(value)) return []; - return schemaErrors(validateRequest.errors || [], "invalid_request_schema"); -} - -function schemaErrors(errors: ErrorObject[], code: string): AgentConfigError[] { - return errors.map((error) => ({ - code, - message: error.message || "is invalid", - operationIndex: operationIndexFromPath(error.instancePath), - path: error.instancePath || "/", - details: error.params, - })); -} - -function dashboardValidationErrors( - failure: DashboardValidationFailure, -): AgentConfigError[] { - return failure.errors.map((message, index) => ({ - code: "dashboard_validation_failed", - message, - path: failure.details[index]?.instancePath || "/", - details: failure.details[index]?.params, - })); -} - -function operationIndexFromPath(path: string): number | undefined { - const segments = path.split("/").filter(Boolean); - const operationsIndex = segments.indexOf("operations"); - const candidate = - operationsIndex >= 0 ? segments[operationsIndex + 1] : segments[0]; - const index = Number(candidate); - return Number.isInteger(index) ? index : undefined; -} - -type ApplyResult = - | { ok: true; document: DashboardDocument } - | { ok: false; errors: AgentConfigError[] }; - -function applyOperation( - document: DashboardDocument, - operation: AgentDashboardOperation, - operationIndex: number, -): ApplyResult { - const next = structuredClone(document); - - switch (operation.type) { - case "create_dashboard": - return { ok: true, document: structuredClone(operation.document) }; - case "add_section": - return addSection(next, operation, operationIndex); - case "add_service": - return addService(next, operation, operationIndex); - case "add_metric_card": - return addMetricCard(next, operation, operationIndex); - case "connect_datasource": - return connectDatasource(next, operation, operationIndex); - case "set_status_rule": - return setStatusRule(next, operation, operationIndex); - case "arrange_item": - return arrangeItem(next, operation, operationIndex); - case "remove_item": - return removeItem(next, operation, operationIndex); - } -} - -type AddSectionOperation = Extract; -type AddServiceOperation = Extract; -type AddMetricCardOperation = Extract; -type ConnectDatasourceOperation = Extract; -type SetStatusRuleOperation = Extract; -type ArrangeItemOperation = Extract; -type RemoveItemOperation = Extract; - -function addSection( - document: DashboardDocument, - operation: AddSectionOperation, - operationIndex: number, -): ApplyResult { - if (document.serviceGroups.some((group) => group.id === operation.section.id)) { - return failure( - "duplicate_id", - `Service group already exists: ${operation.section.id}`, - operationIndex, - "/serviceGroups", - ); - } - - const group: ServiceGroup = { - ...operation.section, - services: [], - }; - document.serviceGroups.push(group); - document.layout.serviceGroups = insertId( - document.layout.serviceGroups, - group.id, - operation.position, - ); - return { ok: true, document }; -} - -function addService( - document: DashboardDocument, - operation: AddServiceOperation, - operationIndex: number, -): ApplyResult { - const group = document.serviceGroups.find((item) => item.id === operation.groupId); - if (!group) { - return failure( - "group_not_found", - `Service group not found: ${operation.groupId}`, - operationIndex, - "/serviceGroups", - ); - } - - if ( - document.serviceGroups.some((item) => - item.services.some((service) => service.id === operation.service.id), - ) - ) { - return failure( - "duplicate_id", - `Service already exists: ${operation.service.id}`, - operationIndex, - "/serviceGroups", - ); - } - - group.services = insertItem(group.services, operation.service, operation.position); - return { ok: true, document }; -} - -function addMetricCard( - document: DashboardDocument, - operation: AddMetricCardOperation, - operationIndex: number, -): ApplyResult { - if (document.telemetry.some((card) => card.id === operation.card.id)) { - return failure( - "duplicate_id", - `Telemetry card already exists: ${operation.card.id}`, - operationIndex, - "/telemetry", - ); - } - - document.telemetry.push(operation.card); - document.layout.telemetry = insertId( - document.layout.telemetry, - operation.card.id, - operation.position, - ); - return { ok: true, document }; -} - -function connectDatasource( - document: DashboardDocument, - operation: ConnectDatasourceOperation, - operationIndex: number, -): ApplyResult { - if (!supportsDatasource(operation.target.kind)) { - return failure( - "unsupported_target", - `Target does not support datasources: ${operation.target.kind}`, - operationIndex, - targetPath(operation.target), - ); - } - - const targetResolution = resolveTarget(document, operation.target, operationIndex); - if (!targetResolution.ok) { - return { ok: false, errors: targetResolution.errors }; - } - - const target = targetResolution.target; - (target as DatasourceAgentTarget).datasource = operation.datasource; - return { ok: true, document }; -} - -function setStatusRule( - document: DashboardDocument, - operation: SetStatusRuleOperation, - operationIndex: number, -): ApplyResult { - if (operation.target.kind === "serviceGroup") { - return failure( - "unsupported_target", - `Target does not support status rules: ${operation.target.kind}`, - operationIndex, - targetPath(operation.target), - ); - } - if (operation.thresholds && operation.target.kind !== "telemetry") { - return failure( - "unsupported_target", - "Thresholds can only be set on telemetry targets", - operationIndex, - targetPath(operation.target), - ); - } - if (operation.detail !== undefined && !supportsDetail(operation.target.kind)) { - return failure( - "unsupported_target", - "Detail can only be set on telemetry, service, or module targets", - operationIndex, - targetPath(operation.target), - ); - } - if (operation.value !== undefined && !supportsStringValue(operation.target.kind)) { - return failure( - "unsupported_target", - "String values can only be set on status item or module targets", - operationIndex, - targetPath(operation.target), - ); - } - - const targetResolution = resolveTarget(document, operation.target, operationIndex); - if (!targetResolution.ok) { - return { ok: false, errors: targetResolution.errors }; - } - - const target = targetResolution.target; - if (operation.thresholds && operation.target.kind === "telemetry") { - (target as TelemetryCard).thresholds = operation.thresholds; - } - if (operation.severity) { - target.severity = operation.severity; - } - if (operation.detail !== undefined && supportsDetail(operation.target.kind)) { - (target as DetailAgentTarget).detail = operation.detail; - } - if (operation.value !== undefined && supportsStringValue(operation.target.kind)) { - (target as StringValueAgentTarget).value = operation.value; - } - - return { ok: true, document }; -} - -function arrangeItem( - document: DashboardDocument, - operation: ArrangeItemOperation, - operationIndex: number, -): ApplyResult { - const list = layoutList(document, operation.area); - if (!list.includes(operation.id)) { - return failure( - "item_not_found", - `Layout item not found: ${operation.id}`, - operationIndex, - `/layout/${operation.area}`, - ); - } - - const without = list.filter((id) => id !== operation.id); - const index = Math.min(operation.index, without.length); - without.splice(index, 0, operation.id); - setLayoutList(document, operation.area, without); - syncCollectionOrder(document, operation.area, without); - return { ok: true, document }; -} - -function removeItem( - document: DashboardDocument, - operation: RemoveItemOperation, - operationIndex: number, -): ApplyResult { - if (operation.target.kind === "telemetry") { - if (!document.telemetry.some((card) => card.id === operation.target.id)) { - return failure( - "item_not_found", - `Telemetry card not found: ${operation.target.id}`, - operationIndex, - "/telemetry", - ); - } - document.telemetry = document.telemetry.filter((card) => card.id !== operation.target.id); - document.layout.telemetry = document.layout.telemetry.filter((id) => id !== operation.target.id); - return { ok: true, document }; - } - - if (operation.target.kind === "serviceGroup") { - if (!document.serviceGroups.some((group) => group.id === operation.target.id)) { - return failure( - "item_not_found", - `Service group not found: ${operation.target.id}`, - operationIndex, - "/serviceGroups", - ); - } - document.serviceGroups = document.serviceGroups.filter((group) => group.id !== operation.target.id); - document.layout.serviceGroups = document.layout.serviceGroups.filter((id) => id !== operation.target.id); - return { ok: true, document }; - } - - if (operation.target.kind === "service") { - const matches = findServices(document, operation.target.id, operation.target.groupId); - if (!matches.length) { - return failure( - "item_not_found", - `Service not found: ${operation.target.id}`, - operationIndex, - "/serviceGroups", - ); - } - if (!operation.target.groupId && matches.length > 1) { - return failure( - "ambiguous_target", - `Service exists in multiple groups: ${operation.target.id}`, - operationIndex, - "/serviceGroups", - ); - } - - const match = matches[0]; - match.group.services = match.group.services.filter((service) => service.id !== operation.target.id); - return { ok: true, document }; - } - - if (operation.target.kind === "module") { - if (!(document.modules || []).some((module) => module.id === operation.target.id)) { - return failure( - "item_not_found", - `Module not found: ${operation.target.id}`, - operationIndex, - "/modules", - ); - } - document.modules = (document.modules || []).filter((module) => module.id !== operation.target.id); - document.layout.modules = (document.layout.modules || []).filter((id) => id !== operation.target.id); - return { ok: true, document }; - } - - if (operation.target.kind === "statusItem") { - const matches = findStatusItems(document, operation.target.id, operation.target.stripId); - if (!matches.length) { - return failure( - "item_not_found", - `Status item not found: ${operation.target.id}`, - operationIndex, - "/statusStrips", - ); - } - if (!operation.target.stripId && matches.length > 1) { - return failure( - "ambiguous_target", - `Status item exists in multiple strips: ${operation.target.id}`, - operationIndex, - "/statusStrips", - ); - } - - const [match] = matches; - match.strip.items = match.strip.items.filter((item) => item.id !== operation.target.id); - return { ok: true, document }; - } - - return failure("unsupported_target", "Cannot remove target kind", operationIndex, "/"); -} - -function resolveTarget( - document: DashboardDocument, - target: ConnectDatasourceOperation["target"], - operationIndex: number, -): ResolveTargetResult { - if (target.kind === "telemetry") { - const card = document.telemetry.find((item) => item.id === target.id); - if (!card) { - return resolutionFailure( - "item_not_found", - `Telemetry card not found: ${target.id}`, - operationIndex, - "/telemetry", - ); - } - return { ok: true, target: card }; - } - if (target.kind === "service") { - const matches = findServices(document, target.id, target.groupId); - if (!matches.length) { - return resolutionFailure( - "item_not_found", - `Service not found: ${target.id}`, - operationIndex, - "/serviceGroups", - ); - } - if (!target.groupId && matches.length > 1) { - return resolutionFailure( - "ambiguous_target", - `Service exists in multiple groups: ${target.id}`, - operationIndex, - "/serviceGroups", - ); - } - return { ok: true, target: matches[0].service }; - } - if (target.kind === "statusItem") { - const matches = findStatusItems(document, target.id, target.stripId); - if (!matches.length) { - return resolutionFailure( - "item_not_found", - `Status item not found: ${target.id}`, - operationIndex, - "/statusStrips", - ); - } - if (!target.stripId && matches.length > 1) { - return resolutionFailure( - "ambiguous_target", - `Status item exists in multiple strips: ${target.id}`, - operationIndex, - "/statusStrips", - ); - } - return { ok: true, target: matches[0].item }; - } - if (target.kind === "module") { - const module = (document.modules || []).find((item) => item.id === target.id); - if (!module) { - return resolutionFailure( - "item_not_found", - `Module not found: ${target.id}`, - operationIndex, - "/modules", - ); - } - return { ok: true, target: module }; - } - return resolutionFailure( - "item_not_found", - `Target not found: ${target.id}`, - operationIndex, - targetPath(target), - ); -} - -type ResolveTargetResult = - | { ok: true; target: ResolvedAgentTarget } - | { ok: false; errors: AgentConfigError[] }; -type ResolvedAgentTarget = TelemetryCard | ServiceEntry | StatusItem | DashboardModule; -type DatasourceAgentTarget = TelemetryCard | ServiceEntry | DashboardModule; -type DetailAgentTarget = TelemetryCard | ServiceEntry | DashboardModule; -type StringValueAgentTarget = StatusItem | DashboardModule; - -type DatasourceTargetKind = Extract< - ConnectDatasourceOperation["target"]["kind"], - "telemetry" | "service" | "module" ->; - -function supportsDatasource( - kind: ConnectDatasourceOperation["target"]["kind"], -): kind is DatasourceTargetKind { - return kind === "telemetry" || kind === "service" || kind === "module"; -} - -type DetailTargetKind = Extract< - SetStatusRuleOperation["target"]["kind"], - "telemetry" | "service" | "module" ->; - -function supportsDetail( - kind: SetStatusRuleOperation["target"]["kind"], -): kind is DetailTargetKind { - return kind === "telemetry" || kind === "service" || kind === "module"; -} - -type StringValueTargetKind = Extract< - SetStatusRuleOperation["target"]["kind"], - "statusItem" | "module" ->; - -function supportsStringValue( - kind: SetStatusRuleOperation["target"]["kind"], -): kind is StringValueTargetKind { - return kind === "statusItem" || kind === "module"; -} - -function findServices(document: DashboardDocument, id: string, groupId?: string) { - const groups = groupId - ? document.serviceGroups.filter((group) => group.id === groupId) - : document.serviceGroups; - return groups.flatMap((group) => - group.services.flatMap((service) => - service.id === id ? [{ group, service }] : [], - ), - ); -} - -function findStatusItems(document: DashboardDocument, id: string, stripId?: string) { - const strips = stripId - ? document.statusStrips.filter((strip) => strip.id === stripId) - : document.statusStrips; - return strips.flatMap((strip) => - strip.items.flatMap((item) => (item.id === id ? [{ strip, item }] : [])), - ); -} - -function resolutionFailure( - code: string, - message: string, - operationIndex: number, - path: string, -): ResolveTargetResult { - return { - ok: false, - errors: [ - { - code, - message, - operationIndex, - path, - }, - ], - }; -} - -function targetPath(target: ConnectDatasourceOperation["target"]): string { - if (target.kind === "telemetry") return "/telemetry"; - if (target.kind === "service") return "/serviceGroups"; - if (target.kind === "serviceGroup") return "/serviceGroups"; - if (target.kind === "statusItem") return "/statusStrips"; - return "/modules"; -} - -function layoutList(document: DashboardDocument, area: ArrangeItemOperation["area"]) { - if (area === "modules") return document.layout.modules || []; - return document.layout[area]; -} - -function setLayoutList( - document: DashboardDocument, - area: ArrangeItemOperation["area"], - value: string[], -) { - if (area === "modules") { - document.layout.modules = value; - return; - } - document.layout[area] = value; -} - -function syncCollectionOrder( - document: DashboardDocument, - area: ArrangeItemOperation["area"], - orderedIds: string[], -) { - if (area === "telemetry") { - document.telemetry = orderByIds(document.telemetry, orderedIds); - return; - } - if (area === "serviceGroups") { - document.serviceGroups = orderByIds(document.serviceGroups, orderedIds); - return; - } - if (area === "statusStrips") { - document.statusStrips = orderByIds(document.statusStrips, orderedIds); - return; - } - document.modules = orderByIds(document.modules || [], orderedIds); -} - -function orderByIds(items: T[], orderedIds: string[]): T[] { - const order = new Map(orderedIds.map((id, index) => [id, index])); - return [...items].sort( - (left, right) => - (order.get(left.id) ?? Number.MAX_SAFE_INTEGER) - - (order.get(right.id) ?? Number.MAX_SAFE_INTEGER), - ); -} - -function insertItem( - items: T[], - item: T, - position?: Static, -): T[] { - const next = [...items]; - next.splice(resolveInsertionIndex(next.map((entry) => entry.id), position), 0, item); - return next; -} - -function insertId( - ids: string[], - id: string, - position?: Static, -): string[] { - const next = [...ids]; - next.splice(resolveInsertionIndex(next, position), 0, id); - return next; -} - -function resolveInsertionIndex(ids: string[], position?: Static) { - if (!position) return ids.length; - if (typeof position.index === "number") return Math.min(position.index, ids.length); - if (position.beforeId) { - const beforeIndex = ids.indexOf(position.beforeId); - return beforeIndex >= 0 ? beforeIndex : ids.length; - } - if (position.afterId) { - const afterIndex = ids.indexOf(position.afterId); - return afterIndex >= 0 ? afterIndex + 1 : ids.length; - } - return ids.length; -} - -function failure( - code: string, - message: string, - operationIndex: number, - path: string, -): ApplyResult { - return { - ok: false, - errors: [ - { - code, - message, - operationIndex, - path, - }, - ], - }; -} - -function createJsonPatch( - before: unknown, - after: unknown, - path = "", -): JsonPatchOperation[] { - if (before === undefined) return [{ op: "add", path: path || "/", value: after }]; - if (after === undefined) return [{ op: "remove", path: path || "/" }]; - if (Object.is(before, after)) return []; - - if (!isObjectLike(before) || !isObjectLike(after)) { - return [{ op: "replace", path: path || "/", value: after }]; - } - - if (Array.isArray(before) || Array.isArray(after)) { - if (!Array.isArray(before) || !Array.isArray(after)) { - return [{ op: "replace", path: path || "/", value: after }]; - } - const operations: JsonPatchOperation[] = []; - const sharedLength = Math.min(before.length, after.length); - for (let index = 0; index < sharedLength; index += 1) { - operations.push( - ...createJsonPatch(before[index], after[index], `${path}/${index}`), - ); - } - for (let index = before.length - 1; index >= after.length; index -= 1) { - operations.push({ op: "remove", path: `${path}/${index}` }); - } - for (let index = before.length; index < after.length; index += 1) { - operations.push({ op: "add", path: `${path}/${index}`, value: after[index] }); - } - return operations; - } - - const beforeObject = before as Record; - const afterObject = after as Record; - const keys = new Set([...Object.keys(beforeObject), ...Object.keys(afterObject)]); - return [...keys].flatMap((key) => - createJsonPatch(beforeObject[key], afterObject[key], `${path}/${escapeJsonPointer(key)}`), - ); -} - -function isObjectLike(value: unknown): value is Record | unknown[] { - return typeof value === "object" && value !== null; -} - -function escapeJsonPointer(value: string): string { - return value.replace(/~/g, "~0").replace(/\//g, "~1"); -} - -async function parseRequestBody(request: Request): Promise { - const text = await request.text(); - return text ? JSON.parse(text) : {}; -} - -function previewBaseDocument(operations: AgentDashboardOperation[]): DashboardDocument | null { - return operations[0]?.type === "create_dashboard" ? emptyDashboardDocument() : null; -} - -function serializeRevision(revision: DashboardRevision) { - return { - id: revision.id, - dashboardId: revision.dashboardId, - schemaVersion: revision.schemaVersion, - actor: revision.actor, - message: revision.message, - operation: revision.operation, - sourceRevisionId: revision.sourceRevisionId, - createdAt: revision.createdAt.toISOString(), - }; -} - -function emptyDashboardDocument(): DashboardDocument { - return { - schemaVersion: "dashboard.v1", - metadata: { - title: "Untitled Dashboard", - }, - layout: { - telemetry: [], - serviceGroups: [], - statusStrips: [], - modules: [], - }, - telemetry: [], - serviceGroups: [], - statusStrips: [], - modules: [], - }; -} diff --git a/src/routes/api/agent/dashboard/+server.ts b/src/routes/api/agent/dashboard/+server.ts deleted file mode 100644 index ddfbef4..0000000 --- a/src/routes/api/agent/dashboard/+server.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { handleAgentDashboardRequest } from "$lib/server/agent-config"; -import type { RequestHandler } from "./$types"; - -export const POST: RequestHandler = ({ request }) => handleAgentDashboardRequest(request);