8cf19f58ab
Add a local WebUI dashboard for monitoring Pulse runtime state. Run `upulse ui` to start at http://localhost:3140. Features: - Dashboard: daemon status, last tick, code rev, recent events timeline - Vitals: sense key selection, canvas line charts, raw data view - Rules: engine rule files in onion order - Deploy: promote/rollback history with active version badge - Event detail: click any event to view CAS object data Technical: - Zero dependencies: Bun HTTP server + single-file embedded HTML - Linear-inspired dark theme, monospace accents - 7 API endpoints reading from PulseStore - Auto-polling (5s) on dashboard - localhost-only, read-only, no auth needed Files: - commands/ui.ts: CLI command registration - ui/server.ts: Bun HTTP server + API routes - ui/dashboard.ts: embedded HTML/CSS/JS (874 lines) - ui/server.test.ts: 8 API tests Closes #50
47 lines
1.4 KiB
TypeScript
47 lines
1.4 KiB
TypeScript
/**
|
|
* commands/ui.ts — upulse ui [--port] [--no-open]
|
|
*
|
|
* Start a local WebUI dashboard for monitoring Pulse.
|
|
*/
|
|
|
|
import type { Command } from 'commander';
|
|
import { loadConfig, resolveDir } from '../config.js';
|
|
import { createUIServer } from '../ui/server.js';
|
|
|
|
export function registerUICommand(program: Command): void {
|
|
program
|
|
.command('ui')
|
|
.description('Start local WebUI dashboard')
|
|
.option('--port <number>', 'HTTP port', '3140')
|
|
.option('--no-open', 'Do not open browser automatically')
|
|
.action(async (opts: { port: string; open: boolean }) => {
|
|
const config = loadConfig(resolveDir(program.opts().dir));
|
|
const port = parseInt(opts.port, 10);
|
|
|
|
const server = createUIServer(config, port);
|
|
|
|
console.log(`\n ⚡ Pulse UI running at http://localhost:${port}\n`);
|
|
|
|
if (opts.open) {
|
|
try {
|
|
const { execSync } = await import('node:child_process');
|
|
const cmd =
|
|
process.platform === 'darwin'
|
|
? 'open'
|
|
: process.platform === 'win32'
|
|
? 'start'
|
|
: 'xdg-open';
|
|
execSync(`${cmd} http://localhost:${port}`, { stdio: 'ignore' });
|
|
} catch {
|
|
// Ignore — headless environments
|
|
}
|
|
}
|
|
|
|
// Keep alive
|
|
process.on('SIGINT', () => {
|
|
server.stop();
|
|
process.exit(0);
|
|
});
|
|
});
|
|
}
|