- Complete Deno-based architecture following nupst/spark patterns - SQLite database with full schema - Docker container management - Service orchestration (Docker + Nginx + DNS + SSL) - Registry authentication - Nginx reverse proxy configuration - Cloudflare DNS integration - Let's Encrypt SSL automation - Background daemon with metrics collection - HTTP API server - Comprehensive CLI - Cross-platform compilation setup - NPM distribution wrapper - Shell installer script Core features: - Deploy containers with single command - Automatic domain configuration - Automatic SSL certificates - Multi-registry support - Metrics and logging - Systemd integration Ready for Angular UI implementation and testing.
194 lines
6.4 KiB
TypeScript
194 lines
6.4 KiB
TypeScript
/**
|
|
* HTTP Server for Onebox
|
|
*
|
|
* Serves REST API and Angular UI
|
|
*/
|
|
|
|
import * as plugins from './onebox.plugins.ts';
|
|
import { logger } from './onebox.logging.ts';
|
|
import type { Onebox } from './onebox.classes.onebox.ts';
|
|
import type { IApiResponse } from './onebox.types.ts';
|
|
|
|
export class OneboxHttpServer {
|
|
private oneboxRef: Onebox;
|
|
private server: Deno.HttpServer | null = null;
|
|
private port = 3000;
|
|
|
|
constructor(oneboxRef: Onebox) {
|
|
this.oneboxRef = oneboxRef;
|
|
}
|
|
|
|
/**
|
|
* Start HTTP server
|
|
*/
|
|
async start(port?: number): Promise<void> {
|
|
try {
|
|
if (this.server) {
|
|
logger.warn('HTTP server already running');
|
|
return;
|
|
}
|
|
|
|
this.port = port || 3000;
|
|
|
|
logger.info(`Starting HTTP server on port ${this.port}...`);
|
|
|
|
this.server = Deno.serve({ port: this.port }, (req) => this.handleRequest(req));
|
|
|
|
logger.success(`HTTP server started on http://localhost:${this.port}`);
|
|
} catch (error) {
|
|
logger.error(`Failed to start HTTP server: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop HTTP server
|
|
*/
|
|
async stop(): Promise<void> {
|
|
try {
|
|
if (!this.server) {
|
|
return;
|
|
}
|
|
|
|
logger.info('Stopping HTTP server...');
|
|
await this.server.shutdown();
|
|
this.server = null;
|
|
logger.success('HTTP server stopped');
|
|
} catch (error) {
|
|
logger.error(`Failed to stop HTTP server: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle HTTP request
|
|
*/
|
|
private async handleRequest(req: Request): Promise<Response> {
|
|
const url = new URL(req.url);
|
|
const path = url.pathname;
|
|
|
|
logger.debug(`${req.method} ${path}`);
|
|
|
|
try {
|
|
// API routes
|
|
if (path.startsWith('/api/')) {
|
|
return await this.handleApiRequest(req, path);
|
|
}
|
|
|
|
// Serve Angular UI (TODO: implement static file serving)
|
|
return new Response('Onebox API - UI coming soon', {
|
|
headers: { 'Content-Type': 'text/plain' },
|
|
});
|
|
} catch (error) {
|
|
logger.error(`Request error: ${error.message}`);
|
|
return this.jsonResponse({ success: false, error: error.message }, 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle API requests
|
|
*/
|
|
private async handleApiRequest(req: Request, path: string): Promise<Response> {
|
|
const method = req.method;
|
|
|
|
// Auth check (simplified - should use proper JWT middleware)
|
|
// Skip auth for login endpoint
|
|
if (path !== '/api/auth/login') {
|
|
const authHeader = req.headers.get('Authorization');
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
return this.jsonResponse({ success: false, error: 'Unauthorized' }, 401);
|
|
}
|
|
}
|
|
|
|
// Route to appropriate handler
|
|
if (path === '/api/status' && method === 'GET') {
|
|
return await this.handleStatusRequest();
|
|
} else if (path === '/api/services' && method === 'GET') {
|
|
return await this.handleListServicesRequest();
|
|
} else if (path === '/api/services' && method === 'POST') {
|
|
return await this.handleDeployServiceRequest(req);
|
|
} else if (path.match(/^\/api\/services\/[^/]+$/) && method === 'GET') {
|
|
const name = path.split('/').pop()!;
|
|
return await this.handleGetServiceRequest(name);
|
|
} else if (path.match(/^\/api\/services\/[^/]+$/) && method === 'DELETE') {
|
|
const name = path.split('/').pop()!;
|
|
return await this.handleDeleteServiceRequest(name);
|
|
} else if (path.match(/^\/api\/services\/[^/]+\/start$/) && method === 'POST') {
|
|
const name = path.split('/')[3];
|
|
return await this.handleStartServiceRequest(name);
|
|
} else if (path.match(/^\/api\/services\/[^/]+\/stop$/) && method === 'POST') {
|
|
const name = path.split('/')[3];
|
|
return await this.handleStopServiceRequest(name);
|
|
} else if (path.match(/^\/api\/services\/[^/]+\/restart$/) && method === 'POST') {
|
|
const name = path.split('/')[3];
|
|
return await this.handleRestartServiceRequest(name);
|
|
} else if (path.match(/^\/api\/services\/[^/]+\/logs$/) && method === 'GET') {
|
|
const name = path.split('/')[3];
|
|
return await this.handleGetLogsRequest(name);
|
|
} else {
|
|
return this.jsonResponse({ success: false, error: 'Not found' }, 404);
|
|
}
|
|
}
|
|
|
|
// API Handlers
|
|
|
|
private async handleStatusRequest(): Promise<Response> {
|
|
const status = await this.oneboxRef.getSystemStatus();
|
|
return this.jsonResponse({ success: true, data: status });
|
|
}
|
|
|
|
private async handleListServicesRequest(): Promise<Response> {
|
|
const services = this.oneboxRef.services.listServices();
|
|
return this.jsonResponse({ success: true, data: services });
|
|
}
|
|
|
|
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 });
|
|
}
|
|
|
|
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);
|
|
}
|
|
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' });
|
|
}
|
|
|
|
private async handleStartServiceRequest(name: string): Promise<Response> {
|
|
await this.oneboxRef.services.startService(name);
|
|
return this.jsonResponse({ success: true, message: 'Service started' });
|
|
}
|
|
|
|
private async handleStopServiceRequest(name: string): Promise<Response> {
|
|
await this.oneboxRef.services.stopService(name);
|
|
return this.jsonResponse({ success: true, message: 'Service stopped' });
|
|
}
|
|
|
|
private async handleRestartServiceRequest(name: string): Promise<Response> {
|
|
await this.oneboxRef.services.restartService(name);
|
|
return this.jsonResponse({ success: true, message: 'Service restarted' });
|
|
}
|
|
|
|
private async handleGetLogsRequest(name: string): Promise<Response> {
|
|
const logs = await this.oneboxRef.services.getServiceLogs(name);
|
|
return this.jsonResponse({ success: true, data: logs });
|
|
}
|
|
|
|
/**
|
|
* Helper to create JSON response
|
|
*/
|
|
private jsonResponse(data: IApiResponse, status = 200): Response {
|
|
return new Response(JSON.stringify(data), {
|
|
status,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
}
|