feat(ui): add operator UI server, stores, and insights
This commit is contained in:
94
src/ui/runtime-events-store.ts
Normal file
94
src/ui/runtime-events-store.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import type { RuntimeEvent } from "../telemetry/runtime-events.js";
|
||||
|
||||
type RuntimeEventFilter = {
|
||||
sessionId?: string;
|
||||
types?: string[];
|
||||
severities?: Array<RuntimeEvent["severity"]>;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
function safeParseLine(line: string): RuntimeEvent | undefined {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed) as unknown;
|
||||
if (!parsed || typeof parsed !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const record = parsed as Partial<RuntimeEvent>;
|
||||
if (
|
||||
typeof record.id !== "string" ||
|
||||
typeof record.timestamp !== "string" ||
|
||||
typeof record.type !== "string" ||
|
||||
typeof record.severity !== "string" ||
|
||||
typeof record.message !== "string"
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return record as RuntimeEvent;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function readRuntimeEvents(logPath: string): Promise<RuntimeEvent[]> {
|
||||
const absolutePath = resolve(logPath);
|
||||
let content = "";
|
||||
|
||||
try {
|
||||
content = await readFile(absolutePath, "utf8");
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const parsed: RuntimeEvent[] = [];
|
||||
const lines = content.split(/\r?\n/);
|
||||
for (const line of lines) {
|
||||
const event = safeParseLine(line);
|
||||
if (event) {
|
||||
parsed.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
parsed.sort((left, right) => left.timestamp.localeCompare(right.timestamp));
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function filterRuntimeEvents(
|
||||
events: readonly RuntimeEvent[],
|
||||
filter: RuntimeEventFilter,
|
||||
): RuntimeEvent[] {
|
||||
const filtered: RuntimeEvent[] = [];
|
||||
const types = filter.types ? new Set(filter.types) : undefined;
|
||||
const severities = filter.severities ? new Set(filter.severities) : undefined;
|
||||
|
||||
for (const event of events) {
|
||||
if (filter.sessionId && event.sessionId !== filter.sessionId) {
|
||||
continue;
|
||||
}
|
||||
if (types && !types.has(event.type)) {
|
||||
continue;
|
||||
}
|
||||
if (severities && !severities.has(event.severity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filtered.push(event);
|
||||
}
|
||||
|
||||
if (!filter.limit || filter.limit < 1 || filtered.length <= filter.limit) {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
return filtered.slice(-filter.limit);
|
||||
}
|
||||
Reference in New Issue
Block a user