/** * UI Server * * HTTP server for the management UI on port 3006 */ import type { EcoDaemon } from '../daemon/index.ts'; import { VERSION } from '../version.ts'; export class UIServer { private port: number; private daemon: EcoDaemon; private clients: Set = new Set(); constructor(port: number, daemon: EcoDaemon) { this.port = port; this.daemon = daemon; } async start(): Promise { Deno.serve({ port: this.port, hostname: '0.0.0.0' }, (req) => this.handleRequest(req) ); console.log(`Management UI running on http://0.0.0.0:${this.port}`); } private async handleRequest(req: Request): Promise { const url = new URL(req.url); const path = url.pathname; // Handle WebSocket upgrade if (path === '/ws') { return this.handleWebSocket(req); } // API routes if (path.startsWith('/api/')) { return this.handleApi(req, path); } // Static files / UI if (path === '/' || path === '/index.html') { return this.serveHtml(); } return new Response('Not Found', { status: 404 }); } private handleWebSocket(req: Request): Response { const { socket, response } = Deno.upgradeWebSocket(req); socket.onopen = () => { this.clients.add(socket); console.log('WebSocket client connected'); }; socket.onclose = () => { this.clients.delete(socket); console.log('WebSocket client disconnected'); }; socket.onerror = (e) => { console.error('WebSocket error:', e); this.clients.delete(socket); }; return response; } broadcast(data: unknown): void { const message = JSON.stringify(data); for (const client of this.clients) { try { client.send(message); } catch { this.clients.delete(client); } } } private async handleApi(req: Request, path: string): Promise { const headers = { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }; if (path === '/api/status') { const status = await this.daemon.getStatus(); return new Response(JSON.stringify(status), { headers }); } if (path === '/api/logs') { const logs = this.daemon.getLogs(); return new Response(JSON.stringify({ logs }), { headers }); } if (path === '/api/reboot' && req.method === 'POST') { const result = await this.daemon.rebootSystem(); return new Response(JSON.stringify(result), { headers }); } if (path === '/api/restart-chromium' && req.method === 'POST') { const result = await this.daemon.restartChromium(); return new Response(JSON.stringify(result), { headers }); } if (path === '/api/updates') { const updates = await this.daemon.getUpdateInfo(); return new Response(JSON.stringify(updates), { headers }); } if (path === '/api/updates/check' && req.method === 'POST') { await this.daemon.checkForUpdates(); const updates = await this.daemon.getUpdateInfo(); return new Response(JSON.stringify(updates), { headers }); } if (path === '/api/upgrade' && req.method === 'POST') { try { const body = await req.json(); const version = body.version; if (!version) { return new Response(JSON.stringify({ success: false, message: 'Version required' }), { headers }); } const result = await this.daemon.upgradeToVersion(version); return new Response(JSON.stringify(result), { headers }); } catch (error) { return new Response(JSON.stringify({ success: false, message: String(error) }), { headers }); } } if (path === '/api/displays') { const displays = await this.daemon.getDisplays(); return new Response(JSON.stringify({ displays }), { headers }); } // Display control endpoints: /api/displays/{name}/{action} const displayMatch = path.match(/^\/api\/displays\/([^/]+)\/(enable|disable|primary)$/); if (displayMatch && req.method === 'POST') { const name = decodeURIComponent(displayMatch[1]); const action = displayMatch[2]; let result; if (action === 'enable') { result = await this.daemon.setDisplayEnabled(name, true); } else if (action === 'disable') { result = await this.daemon.setDisplayEnabled(name, false); } else if (action === 'primary') { result = await this.daemon.setKioskDisplay(name); } return new Response(JSON.stringify(result), { headers }); } return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers, }); } private serveHtml(): Response { const html = ` EcoOS Management

EcoOS Management v${VERSION}

Services

Sway Compositor
Chromium Browser

CPU

Model
-
Cores
-
Usage
-

Memory

Used / Total
-

Network

Disks

System

Hostname
-
Uptime
-
GPU
-

Controls

Updates

Current Version
-

Displays

Input Devices

Speakers

Microphones

Daemon Logs
System Logs
`; return new Response(html, { headers: { 'Content-Type': 'text/html; charset=utf-8' }, }); } }