feat: Implement real-time stats and metrics for platform services with WebSocket integration
This commit is contained in:
@@ -8,7 +8,7 @@ import * as plugins from '../plugins.ts';
|
||||
import { logger } from '../logging.ts';
|
||||
import { getErrorMessage } from '../utils/error.ts';
|
||||
import type { Onebox } from './onebox.ts';
|
||||
import type { IApiResponse, ICreateRegistryTokenRequest, IRegistryTokenView, TPlatformServiceType } from '../types.ts';
|
||||
import type { IApiResponse, ICreateRegistryTokenRequest, IRegistryTokenView, TPlatformServiceType, IContainerStats } from '../types.ts';
|
||||
|
||||
export class OneboxHttpServer {
|
||||
private oneboxRef: Onebox;
|
||||
@@ -245,6 +245,13 @@ export class OneboxHttpServer {
|
||||
} else if (path.match(/^\/api\/services\/[^/]+\/logs$/) && method === 'GET') {
|
||||
const name = path.split('/')[3];
|
||||
return await this.handleGetLogsRequest(name);
|
||||
} else if (path.match(/^\/api\/services\/[^/]+\/stats$/) && method === 'GET') {
|
||||
const name = path.split('/')[3];
|
||||
return await this.handleGetServiceStatsRequest(name);
|
||||
} else if (path.match(/^\/api\/services\/[^/]+\/metrics$/) && method === 'GET') {
|
||||
const name = path.split('/')[3];
|
||||
const limit = new URL(req.url).searchParams.get('limit');
|
||||
return await this.handleGetServiceMetricsRequest(name, limit ? parseInt(limit, 10) : 60);
|
||||
} else if (path === '/api/ssl/obtain' && method === 'POST') {
|
||||
return await this.handleObtainCertificateRequest(req);
|
||||
} else if (path === '/api/ssl/list' && method === 'GET') {
|
||||
@@ -293,6 +300,9 @@ export class OneboxHttpServer {
|
||||
} else if (path.match(/^\/api\/platform-services\/(mongodb|minio|redis|postgresql|rabbitmq|caddy)\/stop$/) && method === 'POST') {
|
||||
const type = path.split('/')[3] as TPlatformServiceType;
|
||||
return await this.handleStopPlatformServiceRequest(type);
|
||||
} else if (path.match(/^\/api\/platform-services\/(mongodb|minio|redis|postgresql|rabbitmq|caddy)\/stats$/) && method === 'GET') {
|
||||
const type = path.split('/')[3] as TPlatformServiceType;
|
||||
return await this.handleGetPlatformServiceStatsRequest(type);
|
||||
} else if (path.match(/^\/api\/services\/[^/]+\/platform-resources$/) && method === 'GET') {
|
||||
const serviceName = path.split('/')[3];
|
||||
return await this.handleGetServicePlatformResourcesRequest(serviceName);
|
||||
@@ -506,6 +516,51 @@ export class OneboxHttpServer {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleGetServiceStatsRequest(name: string): Promise<Response> {
|
||||
try {
|
||||
const service = this.oneboxRef.services.getService(name);
|
||||
if (!service) {
|
||||
return this.jsonResponse({ success: false, error: 'Service not found' }, 404);
|
||||
}
|
||||
|
||||
if (!service.containerID) {
|
||||
return this.jsonResponse({ success: false, error: 'Service has no container' }, 400);
|
||||
}
|
||||
|
||||
// Get live container stats
|
||||
const stats = await this.oneboxRef.docker.getContainerStats(service.containerID);
|
||||
if (!stats) {
|
||||
return this.jsonResponse({ success: false, error: 'Could not retrieve container stats' }, 500);
|
||||
}
|
||||
|
||||
return this.jsonResponse({ success: true, data: stats });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get stats for service ${name}: ${getErrorMessage(error)}`);
|
||||
return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to get stats' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleGetServiceMetricsRequest(name: string, limit: number): Promise<Response> {
|
||||
try {
|
||||
const service = this.oneboxRef.services.getService(name);
|
||||
if (!service) {
|
||||
return this.jsonResponse({ success: false, error: 'Service not found' }, 404);
|
||||
}
|
||||
|
||||
if (!service.id) {
|
||||
return this.jsonResponse({ success: false, error: 'Service has no ID' }, 400);
|
||||
}
|
||||
|
||||
// Get historical metrics from database
|
||||
const metrics = this.oneboxRef.database.getMetrics(service.id, limit);
|
||||
|
||||
return this.jsonResponse({ success: true, data: metrics });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get metrics for service ${name}: ${getErrorMessage(error)}`);
|
||||
return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to get metrics' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleGetSettingsRequest(): Promise<Response> {
|
||||
const settings = this.oneboxRef.database.getAllSettings();
|
||||
return this.jsonResponse({ success: true, data: settings });
|
||||
@@ -1251,6 +1306,18 @@ export class OneboxHttpServer {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast stats update for a service
|
||||
*/
|
||||
broadcastStatsUpdate(serviceName: string, stats: IContainerStats): void {
|
||||
this.broadcast({
|
||||
type: 'stats_update',
|
||||
serviceName,
|
||||
stats,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
// ============ Platform Services Endpoints ============
|
||||
|
||||
private async handleListPlatformServicesRequest(): Promise<Response> {
|
||||
@@ -1263,11 +1330,19 @@ export class OneboxHttpServer {
|
||||
const service = platformServices.find((s) => s.type === provider.type);
|
||||
// Check if provider has isCore property (like CaddyProvider)
|
||||
const isCore = 'isCore' in provider && (provider as any).isCore === true;
|
||||
|
||||
// For Caddy, check actual runtime status since it starts without a DB record
|
||||
let status = service?.status || 'not-deployed';
|
||||
if (provider.type === 'caddy') {
|
||||
const proxyStatus = this.oneboxRef.reverseProxy.getStatus();
|
||||
status = proxyStatus.http.running ? 'running' : 'stopped';
|
||||
}
|
||||
|
||||
return {
|
||||
type: provider.type,
|
||||
displayName: provider.displayName,
|
||||
resourceTypes: provider.resourceTypes,
|
||||
status: service?.status || 'not-deployed',
|
||||
status,
|
||||
containerId: service?.containerId,
|
||||
isCore,
|
||||
createdAt: service?.createdAt,
|
||||
@@ -1302,13 +1377,20 @@ export class OneboxHttpServer {
|
||||
? this.oneboxRef.database.getPlatformResourcesByPlatformService(service.id)
|
||||
: [];
|
||||
|
||||
// For Caddy, check actual runtime status since it starts without a DB record
|
||||
let status = service?.status || 'not-deployed';
|
||||
if (type === 'caddy') {
|
||||
const proxyStatus = this.oneboxRef.reverseProxy.getStatus();
|
||||
status = proxyStatus.http.running ? 'running' : 'stopped';
|
||||
}
|
||||
|
||||
return this.jsonResponse({
|
||||
success: true,
|
||||
data: {
|
||||
type: provider.type,
|
||||
displayName: provider.displayName,
|
||||
resourceTypes: provider.resourceTypes,
|
||||
status: service?.status || 'not-deployed',
|
||||
status,
|
||||
containerId: service?.containerId,
|
||||
config: provider.getDefaultConfig(),
|
||||
resourceCount: allResources.length,
|
||||
@@ -1391,6 +1473,52 @@ export class OneboxHttpServer {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleGetPlatformServiceStatsRequest(type: TPlatformServiceType): Promise<Response> {
|
||||
try {
|
||||
const provider = this.oneboxRef.platformServices.getProvider(type);
|
||||
if (!provider) {
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: `Unknown platform service type: ${type}`,
|
||||
}, 404);
|
||||
}
|
||||
|
||||
// For Caddy, return proxy stats instead of container stats
|
||||
if (type === 'caddy') {
|
||||
const proxyStatus = this.oneboxRef.reverseProxy.getStatus();
|
||||
return this.jsonResponse({
|
||||
success: true,
|
||||
data: {
|
||||
type: 'caddy',
|
||||
running: proxyStatus.http.running,
|
||||
httpPort: proxyStatus.http.port,
|
||||
httpsPort: proxyStatus.https.port,
|
||||
routes: proxyStatus.routes,
|
||||
certificates: proxyStatus.https.certificates,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const service = this.oneboxRef.database.getPlatformServiceByType(type);
|
||||
if (!service || !service.containerId) {
|
||||
return this.jsonResponse({ success: false, error: 'Platform service has no container' }, 400);
|
||||
}
|
||||
|
||||
const stats = await this.oneboxRef.docker.getContainerStats(service.containerId);
|
||||
if (!stats) {
|
||||
return this.jsonResponse({ success: false, error: 'Could not retrieve container stats' }, 500);
|
||||
}
|
||||
|
||||
return this.jsonResponse({ success: true, data: stats });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get stats for platform service ${type}: ${getErrorMessage(error)}`);
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: getErrorMessage(error) || 'Failed to get platform service stats',
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleGetServicePlatformResourcesRequest(serviceName: string): Promise<Response> {
|
||||
try {
|
||||
const service = this.oneboxRef.services.getService(serviceName);
|
||||
|
||||
Reference in New Issue
Block a user