update
This commit is contained in:
@@ -13,6 +13,7 @@ export class OneboxHttpServer {
|
||||
private oneboxRef: Onebox;
|
||||
private server: Deno.HttpServer | null = null;
|
||||
private port = 3000;
|
||||
private wsClients: Set<WebSocket> = new Set();
|
||||
|
||||
constructor(oneboxRef: Onebox) {
|
||||
this.oneboxRef = oneboxRef;
|
||||
@@ -70,6 +71,11 @@ export class OneboxHttpServer {
|
||||
logger.debug(`${req.method} ${path}`);
|
||||
|
||||
try {
|
||||
// WebSocket upgrade
|
||||
if (path === '/api/ws' && req.headers.get('upgrade') === 'websocket') {
|
||||
return this.handleWebSocketUpgrade(req);
|
||||
}
|
||||
|
||||
// API routes
|
||||
if (path.startsWith('/api/')) {
|
||||
return await this.handleApiRequest(req, path);
|
||||
@@ -184,7 +190,7 @@ export class OneboxHttpServer {
|
||||
return await this.handleStatusRequest();
|
||||
} else if (path === '/api/settings' && method === 'GET') {
|
||||
return await this.handleGetSettingsRequest();
|
||||
} else if (path === '/api/settings' && method === 'PUT') {
|
||||
} else if (path === '/api/settings' && (method === 'PUT' || method === 'POST')) {
|
||||
return await this.handleUpdateSettingsRequest(req);
|
||||
} else if (path === '/api/services' && method === 'GET') {
|
||||
return await this.handleListServicesRequest();
|
||||
@@ -268,52 +274,117 @@ export class OneboxHttpServer {
|
||||
}
|
||||
|
||||
private async handleStatusRequest(): Promise<Response> {
|
||||
const status = await this.oneboxRef.getSystemStatus();
|
||||
return this.jsonResponse({ success: true, data: status });
|
||||
try {
|
||||
const status = await this.oneboxRef.getSystemStatus();
|
||||
return this.jsonResponse({ success: true, data: status });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get system status: ${error.message}`);
|
||||
return this.jsonResponse({ success: false, error: error.message || 'Failed to get system status' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleListServicesRequest(): Promise<Response> {
|
||||
const services = this.oneboxRef.services.listServices();
|
||||
return this.jsonResponse({ success: true, data: services });
|
||||
try {
|
||||
const services = this.oneboxRef.services.listServices();
|
||||
return this.jsonResponse({ success: true, data: services });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to list services: ${error.message}`);
|
||||
return this.jsonResponse({ success: false, error: error.message || 'Failed to list services' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDeployServiceRequest(req: Request): Promise<Response> {
|
||||
const body = await req.json();
|
||||
const service = await this.oneboxRef.services.deployService(body);
|
||||
return this.jsonResponse({ success: true, data: service });
|
||||
try {
|
||||
const body = await req.json();
|
||||
const service = await this.oneboxRef.services.deployService(body);
|
||||
|
||||
// Broadcast service created
|
||||
this.broadcastServiceUpdate(service.name, 'created', service);
|
||||
|
||||
return this.jsonResponse({ success: true, data: service });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to deploy service: ${error.message}`);
|
||||
return this.jsonResponse({ success: false, error: error.message || 'Failed to deploy service' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleGetServiceRequest(name: string): Promise<Response> {
|
||||
const service = this.oneboxRef.services.getService(name);
|
||||
if (!service) {
|
||||
return this.jsonResponse({ success: false, error: 'Service not found' }, 404);
|
||||
try {
|
||||
const service = this.oneboxRef.services.getService(name);
|
||||
if (!service) {
|
||||
return this.jsonResponse({ success: false, error: 'Service not found' }, 404);
|
||||
}
|
||||
return this.jsonResponse({ success: true, data: service });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get service ${name}: ${error.message}`);
|
||||
return this.jsonResponse({ success: false, error: error.message || 'Failed to get service' }, 500);
|
||||
}
|
||||
return this.jsonResponse({ success: true, data: service });
|
||||
}
|
||||
|
||||
private async handleDeleteServiceRequest(name: string): Promise<Response> {
|
||||
await this.oneboxRef.services.removeService(name);
|
||||
return this.jsonResponse({ success: true, message: 'Service removed' });
|
||||
try {
|
||||
await this.oneboxRef.services.removeService(name);
|
||||
|
||||
// Broadcast service deleted
|
||||
this.broadcastServiceUpdate(name, 'deleted');
|
||||
|
||||
return this.jsonResponse({ success: true, message: 'Service removed' });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to delete service ${name}: ${error.message}`);
|
||||
return this.jsonResponse({ success: false, error: error.message || 'Failed to delete service' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleStartServiceRequest(name: string): Promise<Response> {
|
||||
await this.oneboxRef.services.startService(name);
|
||||
return this.jsonResponse({ success: true, message: 'Service started' });
|
||||
try {
|
||||
await this.oneboxRef.services.startService(name);
|
||||
|
||||
// Broadcast service started
|
||||
this.broadcastServiceUpdate(name, 'started');
|
||||
|
||||
return this.jsonResponse({ success: true, message: 'Service started' });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to start service ${name}: ${error.message}`);
|
||||
return this.jsonResponse({ success: false, error: error.message || 'Failed to start service' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleStopServiceRequest(name: string): Promise<Response> {
|
||||
await this.oneboxRef.services.stopService(name);
|
||||
return this.jsonResponse({ success: true, message: 'Service stopped' });
|
||||
try {
|
||||
await this.oneboxRef.services.stopService(name);
|
||||
|
||||
// Broadcast service stopped
|
||||
this.broadcastServiceUpdate(name, 'stopped');
|
||||
|
||||
return this.jsonResponse({ success: true, message: 'Service stopped' });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to stop service ${name}: ${error.message}`);
|
||||
return this.jsonResponse({ success: false, error: error.message || 'Failed to stop service' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleRestartServiceRequest(name: string): Promise<Response> {
|
||||
await this.oneboxRef.services.restartService(name);
|
||||
return this.jsonResponse({ success: true, message: 'Service restarted' });
|
||||
try {
|
||||
await this.oneboxRef.services.restartService(name);
|
||||
|
||||
// Broadcast service updated
|
||||
this.broadcastServiceUpdate(name, 'updated');
|
||||
|
||||
return this.jsonResponse({ success: true, message: 'Service restarted' });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to restart service ${name}: ${error.message}`);
|
||||
return this.jsonResponse({ success: false, error: error.message || 'Failed to restart service' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleGetLogsRequest(name: string): Promise<Response> {
|
||||
const logs = await this.oneboxRef.services.getServiceLogs(name);
|
||||
return this.jsonResponse({ success: true, data: logs });
|
||||
try {
|
||||
const logs = await this.oneboxRef.services.getServiceLogs(name);
|
||||
return this.jsonResponse({ success: true, data: logs });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get logs for service ${name}: ${error.message}`);
|
||||
return this.jsonResponse({ success: false, error: error.message || 'Failed to get logs' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleGetSettingsRequest(): Promise<Response> {
|
||||
@@ -332,11 +403,32 @@ export class OneboxHttpServer {
|
||||
);
|
||||
}
|
||||
|
||||
// Update each setting
|
||||
for (const [key, value] of Object.entries(body)) {
|
||||
if (typeof value === 'string') {
|
||||
this.oneboxRef.database.setSetting(key, value);
|
||||
logger.info(`Setting updated: ${key}`);
|
||||
// Handle three formats:
|
||||
// 1. Single setting: { key: "settingName", value: "settingValue" }
|
||||
// 2. Array format: [{ key: "name1", value: "val1" }, ...]
|
||||
// 3. Object format: { settingName1: "value1", settingName2: "value2", ... }
|
||||
|
||||
if (Array.isArray(body)) {
|
||||
// Array format from UI
|
||||
for (const item of body) {
|
||||
if (item.key && typeof item.value === 'string') {
|
||||
this.oneboxRef.database.setSetting(item.key, item.value);
|
||||
logger.info(`Setting updated: ${item.key} = ${item.value}`);
|
||||
}
|
||||
}
|
||||
} else if (body.key && body.value !== undefined) {
|
||||
// Single setting format: { key: "name", value: "val" }
|
||||
if (typeof body.value === 'string') {
|
||||
this.oneboxRef.database.setSetting(body.key, body.value);
|
||||
logger.info(`Setting updated: ${body.key} = ${body.value}`);
|
||||
}
|
||||
} else {
|
||||
// Object format: { name1: "val1", name2: "val2", ... }
|
||||
for (const [key, value] of Object.entries(body)) {
|
||||
if (typeof value === 'string') {
|
||||
this.oneboxRef.database.setSetting(key, value);
|
||||
logger.info(`Setting updated: ${key} = ${value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,6 +442,102 @@ export class OneboxHttpServer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle WebSocket upgrade
|
||||
*/
|
||||
private handleWebSocketUpgrade(req: Request): Response {
|
||||
const { socket, response } = Deno.upgradeWebSocket(req);
|
||||
|
||||
socket.onopen = () => {
|
||||
this.wsClients.add(socket);
|
||||
logger.info(`WebSocket client connected (${this.wsClients.size} total)`);
|
||||
|
||||
// Send initial connection message
|
||||
socket.send(JSON.stringify({
|
||||
type: 'connected',
|
||||
message: 'Connected to Onebox server',
|
||||
timestamp: Date.now(),
|
||||
}));
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
this.wsClients.delete(socket);
|
||||
logger.info(`WebSocket client disconnected (${this.wsClients.size} remaining)`);
|
||||
};
|
||||
|
||||
socket.onerror = (error) => {
|
||||
logger.error(`WebSocket error: ${error}`);
|
||||
this.wsClients.delete(socket);
|
||||
};
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast message to all connected WebSocket clients
|
||||
*/
|
||||
broadcast(message: Record<string, any>): void {
|
||||
const data = JSON.stringify(message);
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
for (const client of this.wsClients) {
|
||||
try {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(data);
|
||||
successCount++;
|
||||
} else {
|
||||
this.wsClients.delete(client);
|
||||
failCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to send to WebSocket client: ${error.message}`);
|
||||
this.wsClients.delete(client);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (successCount > 0) {
|
||||
logger.debug(`Broadcast to ${successCount} clients (${failCount} failed)`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast service update
|
||||
*/
|
||||
broadcastServiceUpdate(serviceName: string, action: 'created' | 'updated' | 'deleted' | 'started' | 'stopped', data?: any): void {
|
||||
this.broadcast({
|
||||
type: 'service_update',
|
||||
action,
|
||||
serviceName,
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast service status update
|
||||
*/
|
||||
broadcastServiceStatus(serviceName: string, status: string): void {
|
||||
this.broadcast({
|
||||
type: 'service_status',
|
||||
serviceName,
|
||||
status,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast system status update
|
||||
*/
|
||||
broadcastSystemStatus(status: any): void {
|
||||
this.broadcast({
|
||||
type: 'system_status',
|
||||
data: status,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create JSON response
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user