feat(gateway): add web UI with dashboard and chat interface

Refactor GatewayServer to serve HTTP and WebSocket on a shared
http.Server. Add static file serving with path traversal protection,
a dark-themed dashboard (system health, sessions, tools) and a
WebSocket chat interface with streaming tool events and markdown
rendering.
This commit is contained in:
William Valentin
2026-02-05 19:39:53 -08:00
parent f30a8bc318
commit 282a15d2b9
7 changed files with 1244 additions and 18 deletions
+28
View File
@@ -1,5 +1,6 @@
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import { WebSocket } from 'ws';
import { resolve } from 'path';
import { GatewayServer } from './server.js';
import type { GatewayServerConfig } from './server.js';
import type { GatewayResponse, GatewayError, GatewayEvent } from './protocol.js';
@@ -87,6 +88,7 @@ describe('GatewayServer integration', () => {
toolRegistry: mockToolRegistry as unknown as GatewayServerConfig['toolRegistry'],
toolExecutor: mockToolExecutor as unknown as GatewayServerConfig['toolExecutor'],
version: '0.1.0-test',
uiDir: resolve(import.meta.dirname, 'ui'),
});
await server.start();
});
@@ -185,4 +187,30 @@ describe('GatewayServer integration', () => {
expect(methods).toContain('tools.list');
expect(methods).toContain('tools.invoke');
});
// ── HTTP static file serving tests ────────────────────────────
it('serves index.html on HTTP GET /', async () => {
const res = await fetch(`http://127.0.0.1:${TEST_PORT}/`);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('text/html');
const body = await res.text();
expect(body).toContain('Flynn');
});
it('serves style.css on HTTP GET /style.css', async () => {
const res = await fetch(`http://127.0.0.1:${TEST_PORT}/style.css`);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('text/css');
});
it('returns 404 for unknown HTTP path', async () => {
const res = await fetch(`http://127.0.0.1:${TEST_PORT}/nonexistent`);
expect(res.status).toBe(404);
});
it('returns 404 for path traversal attempt', async () => {
const res = await fetch(`http://127.0.0.1:${TEST_PORT}/../../../etc/passwd`);
expect(res.status).toBe(404);
});
});