728 lines
23 KiB
TypeScript
728 lines
23 KiB
TypeScript
/**
|
|
* Enhanced Multi-Cloud Orchestration Portal
|
|
* TypeScript/Node.js Express server
|
|
*/
|
|
|
|
import express, { Request, Response, NextFunction } from 'express';
|
|
import cors from 'cors';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
import http from 'http';
|
|
import { Server as SocketIOServer } from 'socket.io';
|
|
import { ConfigManager } from './config';
|
|
import { DatabaseManager } from './database';
|
|
import { Environment, DeploymentRequest, Deployment, Alert, Cost } from './types';
|
|
import { requireAdmin, createSession, getClientIp, AuthRequest } from './middleware/auth';
|
|
import { MonitoringService } from './services/monitoring';
|
|
import { appendAudit } from './services/central-audit';
|
|
|
|
const app = express();
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
|
|
// Serve static files from client build (production) or static directory
|
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
if (isProduction) {
|
|
app.use(express.static(path.join(__dirname, '../client')));
|
|
} else {
|
|
app.use(express.static(path.join(__dirname, '../static')));
|
|
}
|
|
|
|
// Set up view engine (EJS for templates - fallback)
|
|
app.set('view engine', 'ejs');
|
|
app.set('views', path.join(__dirname, '../templates'));
|
|
|
|
// Initialize managers
|
|
const config = new ConfigManager();
|
|
const db = new DatabaseManager(config.getDbFile());
|
|
const monitoring = new MonitoringService(db);
|
|
|
|
// Helper function to group environments by provider
|
|
function groupByProvider(environments: Environment[]): Record<string, Environment[]> {
|
|
const grouped: Record<string, Environment[]> = {};
|
|
for (const env of environments) {
|
|
const provider = env.provider || 'unknown';
|
|
if (!grouped[provider]) {
|
|
grouped[provider] = [];
|
|
}
|
|
grouped[provider].push(env);
|
|
}
|
|
return grouped;
|
|
}
|
|
|
|
// ============================================
|
|
// ROUTES
|
|
// ============================================
|
|
|
|
// Main dashboard - serve Vue/React SPA in production, EJS template in development
|
|
app.get('/', (_req: Request, res: Response): void => {
|
|
if (isProduction) {
|
|
// Serve the built Vue/React app
|
|
res.sendFile(path.join(__dirname, '../client/index.html'));
|
|
} else {
|
|
// Fallback to EJS template for development
|
|
const environments = config.loadEnvironments();
|
|
const byProvider = groupByProvider(environments);
|
|
|
|
const envStatuses: Record<string, any> = {};
|
|
for (const env of environments) {
|
|
if (env.enabled) {
|
|
envStatuses[env.name] = config.getDeploymentStatus(env.name, db);
|
|
}
|
|
}
|
|
|
|
const alerts = db.getAlerts({ unacknowledged_only: true });
|
|
const recentDeployments = db.getDeployments({ limit: 10 });
|
|
|
|
const totalEnvironments = environments.length;
|
|
const enabledCount = environments.filter(e => e.enabled).length;
|
|
const totalProviders = Object.keys(byProvider).length;
|
|
|
|
res.render('dashboard', {
|
|
environments,
|
|
by_provider: byProvider,
|
|
env_statuses: envStatuses,
|
|
alerts,
|
|
recent_deployments: recentDeployments,
|
|
total_environments: totalEnvironments,
|
|
enabled_count: enabledCount,
|
|
total_providers: totalProviders
|
|
});
|
|
}
|
|
});
|
|
|
|
// API: Get all environments
|
|
app.get('/api/environments', (_req: Request, res: Response) => {
|
|
const environments = config.loadEnvironments();
|
|
res.json(environments);
|
|
});
|
|
|
|
// API: Get specific environment
|
|
app.get('/api/environments/:name', (req: Request, res: Response): void => {
|
|
const { name } = req.params;
|
|
const env = config.getEnvironmentByName(name);
|
|
|
|
if (!env) {
|
|
res.status(404).json({ error: 'Environment not found' });
|
|
return;
|
|
}
|
|
|
|
const status = config.getDeploymentStatus(name, db);
|
|
const metrics = db.getMetrics(name, 24);
|
|
const alerts = db.getAlerts({ environment: name, unacknowledged_only: true });
|
|
const costs = db.getCosts({ environment: name, days: 30 });
|
|
|
|
res.json({
|
|
config: env,
|
|
status,
|
|
metrics,
|
|
alerts,
|
|
costs
|
|
});
|
|
});
|
|
|
|
// API: Deploy to environment
|
|
app.post('/api/environments/:name/deploy', (req: Request, res: Response): void => {
|
|
const { name } = req.params;
|
|
const body: DeploymentRequest = req.body || {};
|
|
|
|
const env = config.getEnvironmentByName(name);
|
|
if (!env) {
|
|
res.status(404).json({ error: 'Environment not found' });
|
|
return;
|
|
}
|
|
|
|
if (!env.enabled) {
|
|
res.status(400).json({ error: 'Environment is disabled' });
|
|
return;
|
|
}
|
|
|
|
const strategy = body.strategy || 'blue-green';
|
|
const version = body.version || 'latest';
|
|
const now = new Date();
|
|
const deploymentId = `${name}-${now.toISOString().replace(/[-:T.]/g, '').slice(0, 14)}`;
|
|
const logFile = path.join(config.getDeploymentLogDir(), `${deploymentId}.log`);
|
|
|
|
// Log deployment request
|
|
const logContent = [
|
|
`Deployment requested for ${name} at ${now.toISOString()}`,
|
|
`Strategy: ${strategy}`,
|
|
`Version: ${version}`,
|
|
`Environment config: ${JSON.stringify(env, null, 2)}`
|
|
].join('\n');
|
|
|
|
fs.writeFileSync(logFile, logContent);
|
|
|
|
// Store in database
|
|
const deployment: Deployment = {
|
|
id: deploymentId,
|
|
environment: name,
|
|
status: 'queued',
|
|
started_at: now.toISOString(),
|
|
triggered_by: body.triggered_by || 'api',
|
|
strategy,
|
|
version,
|
|
logs_path: logFile
|
|
};
|
|
|
|
db.createDeployment(deployment);
|
|
|
|
res.json({
|
|
deployment_id: deploymentId,
|
|
status: 'queued',
|
|
environment: name,
|
|
strategy,
|
|
version,
|
|
message: 'Deployment queued successfully'
|
|
});
|
|
});
|
|
|
|
// API: Get environment status
|
|
app.get('/api/environments/:name/status', (req: Request, res: Response): void => {
|
|
const { name } = req.params;
|
|
const status = config.getDeploymentStatus(name, db);
|
|
res.json(status);
|
|
});
|
|
|
|
// API: Get environment metrics
|
|
app.get('/api/environments/:name/metrics', (req: Request, res: Response): void => {
|
|
const { name } = req.params;
|
|
const hours = parseInt(req.query.hours as string) || 24;
|
|
const metrics = db.getMetrics(name, hours);
|
|
res.json(metrics);
|
|
});
|
|
|
|
// API: Get environment alerts
|
|
app.get('/api/environments/:name/alerts', (req: Request, res: Response) => {
|
|
const { name } = req.params;
|
|
const unacknowledgedOnly = req.query.unacknowledged_only === 'true';
|
|
const alerts = db.getAlerts({ environment: name, unacknowledged_only: unacknowledgedOnly });
|
|
res.json(alerts);
|
|
});
|
|
|
|
// API: Acknowledge alert
|
|
app.post('/api/alerts/:id/acknowledge', (req: Request, res: Response) => {
|
|
const alertId = parseInt(req.params.id);
|
|
db.acknowledgeAlert(alertId);
|
|
res.json({ message: 'Alert acknowledged' });
|
|
});
|
|
|
|
// API: Get costs
|
|
app.get('/api/costs', (req: Request, res: Response): void => {
|
|
const environment = req.query.environment as string | undefined;
|
|
const days = parseInt(req.query.days as string) || 30;
|
|
const costs = db.getCosts({ environment, days });
|
|
res.json(costs);
|
|
});
|
|
|
|
// API: Get deployments
|
|
app.get('/api/deployments', (req: Request, res: Response): void => {
|
|
const environment = req.query.environment as string | undefined;
|
|
const status = req.query.status as string | undefined;
|
|
const limit = parseInt(req.query.limit as string) || 50;
|
|
const deployments = db.getDeployments({ environment, status, limit });
|
|
res.json(deployments);
|
|
});
|
|
|
|
// API: Get deployment logs
|
|
app.get('/api/deployments/:id/logs', (req: Request, res: Response): void => {
|
|
const { id } = req.params;
|
|
const logsPath = db.getDeploymentLogsPath(id);
|
|
|
|
if (!logsPath || !fs.existsSync(logsPath)) {
|
|
res.status(404).json({ error: 'Logs not found' });
|
|
return;
|
|
}
|
|
|
|
const logs = fs.readFileSync(logsPath, 'utf-8');
|
|
res.json({ logs });
|
|
});
|
|
|
|
// Environment detail page - serve SPA in production
|
|
app.get('/environment/:name', (req: Request, res: Response): void => {
|
|
if (isProduction) {
|
|
res.sendFile(path.join(__dirname, '../client/index.html'));
|
|
} else {
|
|
const { name } = req.params;
|
|
const env = config.getEnvironmentByName(name);
|
|
|
|
if (!env) {
|
|
res.status(404).send('Environment not found');
|
|
return;
|
|
}
|
|
|
|
const status = config.getDeploymentStatus(name, db);
|
|
const metrics = db.getMetrics(name, 168); // 7 days
|
|
const alerts = db.getAlerts({ environment: name });
|
|
const costs = db.getCosts({ environment: name, days: 30 });
|
|
const deployments = db.getDeployments({ environment: name, limit: 20 });
|
|
|
|
res.render('environment_detail', {
|
|
environment: env,
|
|
status,
|
|
metrics,
|
|
alerts,
|
|
costs,
|
|
deployments
|
|
});
|
|
}
|
|
});
|
|
|
|
// Health dashboard - serve SPA in production
|
|
app.get('/dashboard/health', (_req: Request, res: Response): void => {
|
|
if (isProduction) {
|
|
res.sendFile(path.join(__dirname, '../client/index.html'));
|
|
} else {
|
|
const environments = config.loadEnvironments();
|
|
const healthData: any[] = [];
|
|
|
|
for (const env of environments) {
|
|
if (env.enabled) {
|
|
const status = config.getDeploymentStatus(env.name, db);
|
|
healthData.push({
|
|
name: env.name,
|
|
provider: env.provider,
|
|
region: env.region,
|
|
status,
|
|
health: status.cluster_health
|
|
});
|
|
}
|
|
}
|
|
|
|
res.render('health_dashboard', { health_data: healthData });
|
|
}
|
|
});
|
|
|
|
// Cost dashboard - serve SPA in production
|
|
app.get('/dashboard/costs', (_req: Request, res: Response): void => {
|
|
if (isProduction) {
|
|
res.sendFile(path.join(__dirname, '../client/index.html'));
|
|
} else {
|
|
const costs = db.getCosts({ days: 90 });
|
|
|
|
// Aggregate by provider
|
|
const byProvider: Record<string, number> = {};
|
|
let totalCost = 0;
|
|
|
|
for (const cost of costs) {
|
|
const provider = cost.provider;
|
|
if (!byProvider[provider]) {
|
|
byProvider[provider] = 0;
|
|
}
|
|
byProvider[provider] += cost.cost;
|
|
totalCost += cost.cost;
|
|
}
|
|
|
|
res.render('cost_dashboard', {
|
|
costs,
|
|
by_provider: byProvider,
|
|
total_cost: totalCost
|
|
});
|
|
}
|
|
});
|
|
|
|
// Seed sample data
|
|
function seedSampleData(): void {
|
|
if (db.getMetricsCount() > 0) {
|
|
return; // Data already seeded
|
|
}
|
|
|
|
const environments = config.loadEnvironments();
|
|
const now = new Date();
|
|
|
|
for (const env of environments.slice(0, 3)) {
|
|
const envName = env.name;
|
|
|
|
// Generate sample metrics (24 hours)
|
|
for (let i = 0; i < 24; i++) {
|
|
const timestamp = new Date(now.getTime() - (24 - i) * 60 * 60 * 1000).toISOString();
|
|
db.insertMetric(envName, 'cpu_usage', Math.random() * 60 + 20, timestamp);
|
|
db.insertMetric(envName, 'memory_usage', Math.random() * 55 + 30, timestamp);
|
|
}
|
|
|
|
// Generate sample alerts (30% chance)
|
|
if (Math.random() > 0.7) {
|
|
const alert: Omit<Alert, 'id'> = {
|
|
environment: envName,
|
|
severity: Math.random() > 0.5 ? 'warning' : 'error',
|
|
message: `Sample alert for ${envName}`,
|
|
timestamp: now.toISOString()
|
|
};
|
|
db.createAlert(alert);
|
|
}
|
|
|
|
// Generate sample costs (30 days)
|
|
for (let i = 0; i < 30; i++) {
|
|
const periodStart = new Date(now.getTime() - (30 - i) * 24 * 60 * 60 * 1000).toISOString();
|
|
const periodEnd = new Date(now.getTime() - (29 - i) * 24 * 60 * 60 * 1000).toISOString();
|
|
const cost: Cost = {
|
|
environment: envName,
|
|
provider: env.provider || 'azure',
|
|
cost: Math.random() * 490 + 10,
|
|
currency: 'USD',
|
|
period_start: periodStart,
|
|
period_end: periodEnd,
|
|
resource_type: 'compute'
|
|
};
|
|
db.insertCost(cost);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// ADMIN API ROUTES
|
|
// ============================================
|
|
|
|
// Admin login (simple for now - enhance with proper auth later)
|
|
app.post('/api/admin/login', (req: Request, res: Response): void => {
|
|
const { username, password } = req.body;
|
|
|
|
// Simple hardcoded admin for now (replace with proper auth)
|
|
if (username === 'admin' && (password === process.env.ADMIN_PASSWORD || password === 'admin')) {
|
|
const token = createSession(username);
|
|
res.json({ token, username });
|
|
} else {
|
|
res.status(401).json({ error: 'Invalid credentials' });
|
|
}
|
|
});
|
|
|
|
// Get all service configurations
|
|
app.get('/api/admin/services', requireAdmin, (_req: AuthRequest, res: Response): void => {
|
|
try {
|
|
const services = db.getAllServiceConfigs();
|
|
res.json(services);
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to fetch services' });
|
|
}
|
|
});
|
|
|
|
// Get specific service configuration
|
|
app.get('/api/admin/services/:name', requireAdmin, (req: AuthRequest, res: Response): void => {
|
|
try {
|
|
const { name } = req.params;
|
|
const service = db.getServiceConfig(name);
|
|
if (!service) {
|
|
res.status(404).json({ error: 'Service not found' });
|
|
return;
|
|
}
|
|
res.json({ service_name: name, ...service });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to fetch service' });
|
|
}
|
|
});
|
|
|
|
// Update service configuration
|
|
app.put('/api/admin/services/:name', requireAdmin, (req: AuthRequest, res: Response): void => {
|
|
try {
|
|
const { name } = req.params;
|
|
const { enabled, config } = req.body;
|
|
const adminUser = req.adminUser || 'unknown';
|
|
const ipAddress = getClientIp(req);
|
|
|
|
db.setServiceConfig(name, enabled !== false, config || null, adminUser);
|
|
db.logAdminAction(adminUser, 'update_service', 'service', name, JSON.stringify({ enabled, config }), ipAddress);
|
|
appendAudit({ employeeId: adminUser, action: 'update_service', permission: 'admin:action', resourceType: 'service', resourceId: name, ipAddress, userAgent: req.headers['user-agent'] as string }).catch(() => {});
|
|
|
|
// Broadcast real-time update
|
|
broadcastAdminUpdate('service-updated', { service_name: name, enabled, updated_by: adminUser });
|
|
|
|
res.json({ success: true, message: `Service ${name} ${enabled ? 'enabled' : 'disabled'}` });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to update service' });
|
|
}
|
|
});
|
|
|
|
// Get all provider configurations
|
|
app.get('/api/admin/providers', requireAdmin, (_req: AuthRequest, res: Response): void => {
|
|
try {
|
|
const providers = db.getAllProviderConfigs();
|
|
res.json(providers);
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to fetch providers' });
|
|
}
|
|
});
|
|
|
|
// Get specific provider configuration
|
|
app.get('/api/admin/providers/:name', requireAdmin, (req: AuthRequest, res: Response): void => {
|
|
try {
|
|
const { name } = req.params;
|
|
const provider = db.getProviderConfig(name);
|
|
if (!provider) {
|
|
res.status(404).json({ error: 'Provider not found' });
|
|
return;
|
|
}
|
|
res.json({ provider_name: name, ...provider });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to fetch provider' });
|
|
}
|
|
});
|
|
|
|
// Update provider configuration
|
|
app.put('/api/admin/providers/:name', requireAdmin, (req: AuthRequest, res: Response): void => {
|
|
try {
|
|
const { name } = req.params;
|
|
const { enabled, config } = req.body;
|
|
const adminUser = req.adminUser || 'unknown';
|
|
const ipAddress = getClientIp(req);
|
|
|
|
db.setProviderConfig(name, enabled !== false, config || null, adminUser);
|
|
db.logAdminAction(adminUser, 'update_provider', 'provider', name, JSON.stringify({ enabled, config }), ipAddress);
|
|
appendAudit({ employeeId: adminUser, action: 'update_provider', permission: 'admin:action', resourceType: 'provider', resourceId: name, ipAddress, userAgent: req.headers['user-agent'] as string }).catch(() => {});
|
|
|
|
// Broadcast real-time update
|
|
broadcastAdminUpdate('provider-updated', { provider_name: name, enabled, updated_by: adminUser });
|
|
|
|
res.json({ success: true, message: `Provider ${name} ${enabled ? 'enabled' : 'disabled'}` });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to update provider' });
|
|
}
|
|
});
|
|
|
|
// Get audit logs
|
|
app.get('/api/admin/audit-logs', requireAdmin, (req: AuthRequest, res: Response): void => {
|
|
try {
|
|
const limit = parseInt(req.query.limit as string) || 100;
|
|
const logs = db.getAuditLogs(limit);
|
|
res.json(logs);
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to fetch audit logs' });
|
|
}
|
|
});
|
|
|
|
// Toggle environment enabled/disabled (updates YAML via config manager)
|
|
app.put('/api/admin/environments/:name/toggle', requireAdmin, (req: AuthRequest, res: Response): void => {
|
|
try {
|
|
const { name } = req.params;
|
|
const { enabled } = req.body;
|
|
const adminUser = req.adminUser || 'unknown';
|
|
const ipAddress = getClientIp(req);
|
|
|
|
const env = config.getEnvironmentByName(name);
|
|
if (!env) {
|
|
res.status(404).json({ error: 'Environment not found' });
|
|
return;
|
|
}
|
|
|
|
// Update environment in YAML file
|
|
config.updateEnvironmentEnabled(name, enabled !== false);
|
|
db.logAdminAction(adminUser, 'toggle_environment', 'environment', name, JSON.stringify({ enabled }), ipAddress);
|
|
appendAudit({ employeeId: adminUser, action: 'toggle_environment', permission: 'admin:action', resourceType: 'environment', resourceId: name, metadata: { enabled }, ipAddress, userAgent: req.headers['user-agent'] as string }).catch(() => {});
|
|
|
|
// Broadcast real-time update
|
|
broadcastAdminUpdate('environment-updated', { environment_name: name, enabled, updated_by: adminUser });
|
|
|
|
res.json({ success: true, message: `Environment ${name} ${enabled ? 'enabled' : 'disabled'}` });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to toggle environment' });
|
|
}
|
|
});
|
|
|
|
// ============================================
|
|
// MONITORING API ROUTES
|
|
// ============================================
|
|
|
|
// Get monitoring dashboard data
|
|
app.get('/api/monitoring/dashboard', (_req: Request, res: Response): void => {
|
|
try {
|
|
const besuHealth = db.getServiceHealthSummary('besu');
|
|
const cactiHealth = db.getServiceHealthSummary('cacti');
|
|
const fireflyHealth = db.getServiceHealthSummary('firefly');
|
|
const chainlinkHealth = db.getServiceHealthSummary('chainlink-ccip');
|
|
|
|
res.json({
|
|
besu: besuHealth,
|
|
cacti: cactiHealth,
|
|
firefly: fireflyHealth,
|
|
chainlinkCcip: chainlinkHealth,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to fetch monitoring data' });
|
|
}
|
|
});
|
|
|
|
// Get Besu metrics
|
|
app.get('/api/monitoring/besu', (req: Request, res: Response): void => {
|
|
try {
|
|
const environment = req.query.environment as string;
|
|
const serviceName = req.query.service as string || 'besu-validator-1';
|
|
|
|
if (environment) {
|
|
monitoring.collectBesuMetrics(environment, serviceName).then(metrics => {
|
|
res.json(metrics);
|
|
}).catch(() => {
|
|
res.status(500).json({ error: 'Failed to collect Besu metrics' });
|
|
});
|
|
} else {
|
|
const metrics = db.getServiceMetrics({ serviceType: 'besu', hours: 1 });
|
|
res.json(metrics);
|
|
}
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to fetch Besu metrics' });
|
|
}
|
|
});
|
|
|
|
// Get Cacti metrics
|
|
app.get('/api/monitoring/cacti', (req: Request, res: Response): void => {
|
|
try {
|
|
const environment = req.query.environment as string;
|
|
const serviceName = req.query.service as string || 'cacti-1';
|
|
|
|
if (environment) {
|
|
monitoring.collectCactiMetrics(environment, serviceName).then(metrics => {
|
|
res.json(metrics);
|
|
}).catch(() => {
|
|
res.status(500).json({ error: 'Failed to collect Cacti metrics' });
|
|
});
|
|
} else {
|
|
const metrics = db.getServiceMetrics({ serviceType: 'cacti', hours: 1 });
|
|
res.json(metrics);
|
|
}
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to fetch Cacti metrics' });
|
|
}
|
|
});
|
|
|
|
// Get Firefly metrics
|
|
app.get('/api/monitoring/firefly', (req: Request, res: Response): void => {
|
|
try {
|
|
const environment = req.query.environment as string;
|
|
const serviceName = req.query.service as string || 'firefly-1';
|
|
|
|
if (environment) {
|
|
monitoring.collectFireflyMetrics(environment, serviceName).then(metrics => {
|
|
res.json(metrics);
|
|
}).catch(() => {
|
|
res.status(500).json({ error: 'Failed to collect Firefly metrics' });
|
|
});
|
|
} else {
|
|
const metrics = db.getServiceMetrics({ serviceType: 'firefly', hours: 1 });
|
|
res.json(metrics);
|
|
}
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to fetch Firefly metrics' });
|
|
}
|
|
});
|
|
|
|
// Get Chainlink CCIP metrics
|
|
app.get('/api/monitoring/chainlink-ccip', (req: Request, res: Response): void => {
|
|
try {
|
|
const environment = req.query.environment as string;
|
|
const serviceName = req.query.service as string || 'chainlink-ccip-1';
|
|
|
|
if (environment) {
|
|
monitoring.collectChainlinkCCIPMetrics(environment, serviceName).then(metrics => {
|
|
res.json(metrics);
|
|
}).catch(() => {
|
|
res.status(500).json({ error: 'Failed to collect Chainlink CCIP metrics' });
|
|
});
|
|
} else {
|
|
const metrics = db.getServiceMetrics({ serviceType: 'chainlink-ccip', hours: 1 });
|
|
res.json(metrics);
|
|
}
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to fetch Chainlink CCIP metrics' });
|
|
}
|
|
});
|
|
|
|
// Get service status
|
|
app.get('/api/monitoring/services/:type/:name/status', (req: Request, res: Response): void => {
|
|
try {
|
|
const { type, name } = req.params;
|
|
const status = db.getServiceStatus(name, type);
|
|
|
|
if (!status) {
|
|
res.status(404).json({ error: 'Service status not found' });
|
|
return;
|
|
}
|
|
|
|
res.json(status);
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to fetch service status' });
|
|
}
|
|
});
|
|
|
|
// Collect all metrics for an environment
|
|
app.post('/api/monitoring/environments/:name/collect', async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { name } = req.params;
|
|
const results = await monitoring.collectAllMetrics(name);
|
|
|
|
// Broadcast update via WebSocket
|
|
broadcastAdminUpdate('monitoring-updated', { environment: name, services: results });
|
|
|
|
res.json({ success: true, data: results });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Failed to collect metrics' });
|
|
}
|
|
});
|
|
|
|
// Error handling middleware
|
|
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
|
|
console.error('Error:', err);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
});
|
|
|
|
// Start server
|
|
const PORT = process.env.PORT || 5000;
|
|
const HOST = process.env.HOST || '0.0.0.0';
|
|
|
|
// Create HTTP server for Socket.IO
|
|
const server = http.createServer(app);
|
|
|
|
// Initialize Socket.IO
|
|
const io = new SocketIOServer(server, {
|
|
cors: {
|
|
origin: '*',
|
|
methods: ['GET', 'POST']
|
|
}
|
|
});
|
|
|
|
// Socket.IO connection handling
|
|
io.on('connection', (socket: import('socket.io').Socket) => {
|
|
console.log('Client connected:', socket.id);
|
|
|
|
// Join admin room for real-time updates
|
|
socket.on('join-admin', () => {
|
|
socket.join('admin');
|
|
console.log('Client joined admin room:', socket.id);
|
|
});
|
|
|
|
// Handle disconnection
|
|
socket.on('disconnect', () => {
|
|
console.log('Client disconnected:', socket.id);
|
|
});
|
|
});
|
|
|
|
// Helper function to broadcast admin updates
|
|
function broadcastAdminUpdate(type: string, data: any): void {
|
|
io.to('admin').emit('admin-update', { type, data, timestamp: new Date().toISOString() });
|
|
}
|
|
|
|
// Seed sample data on startup
|
|
seedSampleData();
|
|
|
|
server.listen(PORT, () => {
|
|
console.log('🚀 Enhanced Multi-Cloud Orchestration Portal starting...');
|
|
console.log(`📊 Access dashboard at: http://${HOST}:${PORT}`);
|
|
console.log(`🔍 Health dashboard at: http://${HOST}:${PORT}/dashboard/health`);
|
|
console.log(`💰 Cost dashboard at: http://${HOST}:${PORT}/dashboard/costs`);
|
|
console.log(`🔐 Admin panel at: http://${HOST}:${PORT}/admin`);
|
|
console.log(`🔍 Monitoring dashboard at: http://${HOST}:${PORT}/monitoring`);
|
|
console.log(`🔌 WebSocket server ready on port ${PORT}`);
|
|
});
|
|
|
|
// Graceful shutdown
|
|
process.on('SIGINT', () => {
|
|
console.log('\nShutting down gracefully...');
|
|
db.close();
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('SIGTERM', () => {
|
|
console.log('\nShutting down gracefully...');
|
|
db.close();
|
|
process.exit(0);
|
|
});
|
|
|