dimensionlab-website/src/lib/server/agent-config/index.ts

1292 lines
36 KiB
TypeScript

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<DashboardDocument>(
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<typeof AgentDashboardOperationSchema>;
export type AgentDashboardRequest = Static<typeof AgentDashboardRequestSchema>;
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<AgentDashboardOperation[]>(
AgentDashboardOperationsSchema,
);
const validateRequest = createAjv().compile<AgentDashboardRequest>(
AgentDashboardRequestSchema,
);
function schemaWithoutRootId<T>(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<Response> {
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<AgentDashboardOperation, { type: "add_section" }>;
type AddServiceOperation = Extract<AgentDashboardOperation, { type: "add_service" }>;
type AddMetricCardOperation = Extract<AgentDashboardOperation, { type: "add_metric_card" }>;
type ConnectDatasourceOperation = Extract<AgentDashboardOperation, { type: "connect_datasource" }>;
type SetStatusRuleOperation = Extract<AgentDashboardOperation, { type: "set_status_rule" }>;
type ArrangeItemOperation = Extract<AgentDashboardOperation, { type: "arrange_item" }>;
type RemoveItemOperation = Extract<AgentDashboardOperation, { type: "remove_item" }>;
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<T extends { id: string }>(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<T extends { id: string }>(
items: T[],
item: T,
position?: Static<typeof PositionSchema>,
): 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<typeof PositionSchema>,
): string[] {
const next = [...ids];
next.splice(resolveInsertionIndex(next, position), 0, id);
return next;
}
function resolveInsertionIndex(ids: string[], position?: Static<typeof PositionSchema>) {
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<string, unknown>;
const afterObject = after as Record<string, unknown>;
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<string, unknown> | 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<unknown> {
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: [],
};
}