/** * Lego 09 / Lego 11 — Surveillance HUD Service * * Opens a persistent Socket.io connection to NestJS /monitor namespace. * Emits monitor:subscribe on connect and keeps the tunnel alive indefinitely. * Exposes signals for the PerformanceHUD and chatbot status indicators. * * Exposes nestStatus (ONLINE/OFFLINE) derived from socket connection state. */ import { Injectable, signal, computed, OnDestroy } from '@angular/core'; import { io, Socket } from 'socket.io-client'; import { environment } from '../../environments/environment'; export interface MonitorPayload { service: string; pid: number; cpu: number; memory: number; // bytes timestamp: string; } @Injectable({ providedIn: 'root' }) export class SurveillanceService implements OnDestroy { /** Live metrics — HUD reads this signal directly */ readonly metrics = signal([]); readonly connected = signal(false); /** Derived status string — consumed by Scanner and HUD */ readonly nestStatus = computed<'ONLINE' | 'OFFLINE'>(() => this.connected() ? 'ONLINE' : 'OFFLINE' ); private socket: Socket; constructor() { // Persistent tunnel — opened on service construction (app boot) this.socket = io(`${environment.nestWsUrl}/monitor`, { transports: ['websocket'], reconnection: true, reconnectionDelay: 1000, secure: true, rejectUnauthorized: false, }); this.socket.on('connect', () => { this.connected.set(true); this.socket.emit('monitor:subscribe'); }); this.socket.on('disconnect', () => { this.connected.set(false); }); this.socket.on('monitor:data', (payload: MonitorPayload[]) => { this.metrics.set(payload); }); } ngOnDestroy() { this.socket.disconnect(); } formatBytes(bytes: number): string { if (bytes === 0) return '0 B'; const mb = bytes / (1024 * 1024); return `${mb.toFixed(1)} MB`; } }