feat(ui): add operator UI server, stores, and insights

This commit is contained in:
2026-02-23 18:49:53 -05:00
parent 8100f4d1c6
commit cf386e1aaa
18 changed files with 3252 additions and 17 deletions

90
src/ui/http-utils.ts Normal file
View File

@@ -0,0 +1,90 @@
import { createReadStream } from "node:fs";
import { stat } from "node:fs/promises";
import { extname, resolve } from "node:path";
import type { IncomingMessage, ServerResponse } from "node:http";
const CONTENT_TYPES: Record<string, string> = {
".html": "text/html; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".css": "text/css; charset=utf-8",
".json": "application/json; charset=utf-8",
".svg": "image/svg+xml",
};
export function sendJson(response: ServerResponse, statusCode: number, body: unknown): void {
const payload = JSON.stringify(body);
response.statusCode = statusCode;
response.setHeader("Content-Type", "application/json; charset=utf-8");
response.end(payload);
}
export function sendText(response: ServerResponse, statusCode: number, body: string): void {
response.statusCode = statusCode;
response.setHeader("Content-Type", "text/plain; charset=utf-8");
response.end(body);
}
export async function parseJsonBody<T>(request: IncomingMessage): Promise<T> {
const chunks: Buffer[] = [];
await new Promise<void>((resolveBody, rejectBody) => {
request.on("data", (chunk: Buffer) => {
chunks.push(chunk);
});
request.on("end", () => resolveBody());
request.on("error", rejectBody);
});
const body = Buffer.concat(chunks).toString("utf8").trim();
if (!body) {
throw new Error("Request body is required.");
}
return JSON.parse(body) as T;
}
export function methodNotAllowed(response: ServerResponse): void {
sendJson(response, 405, {
ok: false,
error: "Method not allowed.",
});
}
export function notFound(response: ServerResponse): void {
sendJson(response, 404, {
ok: false,
error: "Not found.",
});
}
export async function serveStaticFile(input: {
response: ServerResponse;
filePath: string;
}): Promise<boolean> {
try {
const absolutePath = resolve(input.filePath);
const fileStats = await stat(absolutePath);
if (!fileStats.isFile()) {
return false;
}
const extension = extname(absolutePath).toLowerCase();
const contentType = CONTENT_TYPES[extension] ?? "application/octet-stream";
input.response.statusCode = 200;
input.response.setHeader("Content-Type", contentType);
await new Promise<void>((resolveStream, rejectStream) => {
const stream = createReadStream(absolutePath);
stream.on("error", rejectStream);
stream.on("end", () => resolveStream());
stream.pipe(input.response);
});
return true;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
return false;
}
throw error;
}
}