feat(opsserver): introduce OpsServer (TypedRequest API) and new lightweight web UI; replace legacy Angular UI and add typed interfaces

This commit is contained in:
2026-02-24 18:15:44 +00:00
parent 84c47cd7f5
commit ba05cc84fe
143 changed files with 46631 additions and 20632 deletions

View File

@@ -131,9 +131,9 @@ export class OneboxDaemon {
// Start monitoring loop
this.startMonitoring();
// Start HTTP server
// Start OpsServer (serves new UI + TypedRequest API)
const httpPort = parseInt(this.oneboxRef.database.getSetting('httpPort') || '3000', 10);
await this.oneboxRef.httpServer.start(httpPort);
await this.oneboxRef.opsServer.start(httpPort);
logger.success('Onebox daemon started');
logger.info(`Web UI available at http://localhost:${httpPort}`);
@@ -163,8 +163,8 @@ export class OneboxDaemon {
// Stop monitoring
this.stopMonitoring();
// Stop HTTP server
await this.oneboxRef.httpServer.stop();
// Stop OpsServer
await this.oneboxRef.opsServer.stop();
// Remove PID file
await this.removePidFile();
@@ -280,31 +280,12 @@ export class OneboxDaemon {
}
/**
* Broadcast stats to WebSocket clients (real-time updates)
* Broadcast stats (placeholder for future WebSocket integration via OpsServer)
*/
private async broadcastStats(): Promise<void> {
try {
const services = this.oneboxRef.services.listServices();
const runningServices = services.filter(s => s.status === 'running' && s.containerID);
logger.info(`Broadcasting stats for ${runningServices.length} running services`);
for (const service of runningServices) {
try {
const stats = await this.oneboxRef.docker.getContainerStats(service.containerID!);
if (stats) {
logger.info(`Broadcasting stats for ${service.name}: CPU=${stats.cpuPercent.toFixed(1)}%, Mem=${Math.round(stats.memoryUsed / 1024 / 1024)}MB`);
this.oneboxRef.httpServer.broadcastStatsUpdate(service.name, stats);
} else {
logger.warn(`No stats returned for ${service.name} (containerID: ${service.containerID})`);
}
} catch (error) {
logger.warn(`Stats collection failed for ${service.name}: ${getErrorMessage(error)}`);
}
}
} catch (error) {
logger.error(`Broadcast stats error: ${getErrorMessage(error)}`);
}
// Stats broadcasting via WebSocket is not yet implemented in OpsServer.
// Metrics are still collected and stored in the DB by collectMetrics().
// The new UI fetches stats via TypedRequests on demand.
}
/**

View File

@@ -22,6 +22,7 @@ import { PlatformServicesManager } from './platform-services/index.ts';
import { CaddyLogReceiver } from './caddy-log-receiver.ts';
import { BackupManager } from './backup-manager.ts';
import { BackupScheduler } from './backup-scheduler.ts';
import { OpsServer } from '../opsserver/index.ts';
export class Onebox {
public database: OneboxDatabase;
@@ -40,6 +41,7 @@ export class Onebox {
public caddyLogReceiver: CaddyLogReceiver;
public backupManager: BackupManager;
public backupScheduler: BackupScheduler;
public opsServer: OpsServer;
private initialized = false;
@@ -77,6 +79,9 @@ export class Onebox {
// Initialize Backup scheduler
this.backupScheduler = new BackupScheduler(this);
// Initialize OpsServer (TypedRequest-based server)
this.opsServer = new OpsServer(this);
}
/**
@@ -330,17 +335,17 @@ export class Onebox {
}
/**
* Start HTTP server
* Start OpsServer (TypedRequest-based, serves new UI)
*/
async startHttpServer(port?: number): Promise<void> {
await this.httpServer.start(port);
await this.opsServer.start(port || 3000);
}
/**
* Stop HTTP server
* Stop OpsServer
*/
async stopHttpServer(): Promise<void> {
await this.httpServer.stop();
await this.opsServer.stop();
}
/**
@@ -356,8 +361,8 @@ export class Onebox {
// Stop daemon if running
await this.daemon.stop();
// Stop HTTP server if running
await this.httpServer.stop();
// Stop OpsServer if running
await this.opsServer.stop();
// Stop reverse proxy if running
await this.reverseProxy.stop();