289 lines
9.0 KiB
TypeScript
289 lines
9.0 KiB
TypeScript
/**
|
|
* Engine Room Routes - Casa de Máquinas (Simplificado)
|
|
*
|
|
* APENAS consulta o Registry do Kernel.
|
|
* Não gerencia mais presets - mostra apenas serviços descobertos.
|
|
*/
|
|
|
|
import type { Express, Request, Response } from "express";
|
|
import http from "http";
|
|
|
|
// Consulta Registry do Kernel para serviços descobertos
|
|
// Usa http.get em vez de fetch por causa de problemas com loopback no Node.js
|
|
// Implementa retry com backoff para maior resiliência
|
|
async function fetchRegistryServicesWithRetry(maxRetries = 3, delay = 500): Promise<any[] | null> {
|
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
const result = await fetchRegistryServicesOnce();
|
|
if (result !== null) {
|
|
return result;
|
|
}
|
|
if (attempt < maxRetries) {
|
|
console.log(`[Engine Room] Tentativa ${attempt} falhou, aguardando ${delay}ms...`);
|
|
await new Promise(r => setTimeout(r, delay));
|
|
delay *= 2; // Exponential backoff
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Tenta múltiplos hostnames (127.0.0.1, localhost) para conectar ao Kernel
|
|
async function fetchRegistryServicesOnce(): Promise<any[] | null> {
|
|
const hostnames = ['127.0.0.1', 'localhost'];
|
|
|
|
for (const hostname of hostnames) {
|
|
const result = await tryConnectToRegistry(hostname);
|
|
if (result !== null) {
|
|
return result;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function tryConnectToRegistry(hostname: string): Promise<any[] | null> {
|
|
return new Promise((resolve) => {
|
|
const options = {
|
|
hostname,
|
|
port: 5001,
|
|
path: '/api/registry/services',
|
|
method: 'GET',
|
|
timeout: 3000,
|
|
};
|
|
|
|
console.log(`[Engine Room] Tentando conectar em ${hostname}:5001...`);
|
|
|
|
const req = http.request(options, (res) => {
|
|
let data = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
data += chunk;
|
|
});
|
|
|
|
res.on('end', () => {
|
|
try {
|
|
const json = JSON.parse(data);
|
|
const services = json.success && Array.isArray(json.data?.services) ? json.data.services : null;
|
|
console.log(`[Engine Room] Registry retornou ${services?.length || 0} serviços via ${hostname}`);
|
|
resolve(services);
|
|
} catch (e) {
|
|
console.error(`[Engine Room] Erro ao parsear resposta de ${hostname}:`, e);
|
|
resolve(null);
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', (error) => {
|
|
console.error(`[Engine Room] Erro ao consultar Registry em ${hostname}:`, error.message);
|
|
resolve(null);
|
|
});
|
|
|
|
req.on('timeout', () => {
|
|
console.error(`[Engine Room] Timeout ao consultar Registry em ${hostname}`);
|
|
req.destroy();
|
|
resolve(null);
|
|
});
|
|
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
// Converte serviço do Registry para formato EngineStatus
|
|
function mapRegistryToEngine(service: any): any {
|
|
const id = service.id || service.name;
|
|
|
|
let status: 'online' | 'offline' | 'error' = 'offline';
|
|
if (service.healthStatus === 'healthy') status = 'online';
|
|
else if (service.healthStatus === 'unhealthy') status = 'error';
|
|
|
|
return {
|
|
name: id,
|
|
displayName: service.displayName || service.name,
|
|
type: service.type || 'unknown',
|
|
port: service.port || 0,
|
|
category: service.category || 'data',
|
|
description: service.description || `Serviço ${service.name}`,
|
|
status,
|
|
responseTime: undefined,
|
|
details: {
|
|
endpoint: service.endpoint,
|
|
health: service.healthStatus,
|
|
version: service.version,
|
|
capabilities: service.capabilities?.map((c: any) => c.name) || [],
|
|
source: 'registry',
|
|
},
|
|
};
|
|
}
|
|
|
|
export function registerEngineRoomRoutes(app: Express): void {
|
|
// Status da Casa de Máquinas - APENAS do Registry
|
|
app.get("/api/engine-room/status", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const registryServices = await fetchRegistryServicesWithRetry();
|
|
const engines: any[] = [];
|
|
|
|
if (registryServices && registryServices.length > 0) {
|
|
for (const service of registryServices) {
|
|
engines.push(mapRegistryToEngine(service));
|
|
}
|
|
}
|
|
|
|
let agentsStatus: any[] = [];
|
|
try {
|
|
const { getAgentsStatus } = await import("../blackboard/agents");
|
|
agentsStatus = getAgentsStatus();
|
|
} catch {
|
|
agentsStatus = [];
|
|
}
|
|
|
|
const online = engines.filter((r) => r.status === "online").length;
|
|
const total = engines.length;
|
|
|
|
res.json({
|
|
engines,
|
|
agents: agentsStatus,
|
|
summary: {
|
|
total_engines: total,
|
|
online_engines: online,
|
|
offline_engines: total - online,
|
|
health_pct: total > 0 ? Math.round((online / total) * 100) : 0,
|
|
total_agents: agentsStatus.length,
|
|
running_agents: agentsStatus.filter((a: any) => a.running).length,
|
|
},
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
} catch (error: any) {
|
|
console.error("[Engine Room] Status error:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Lista de engines - APENAS do Registry
|
|
app.get("/api/engine-room/engines", async (_req: Request, res: Response) => {
|
|
const registryServices = await fetchRegistryServicesWithRetry();
|
|
const engines = registryServices?.map(mapRegistryToEngine) || [];
|
|
|
|
res.json({
|
|
engines: engines.map((e) => ({
|
|
name: e.name,
|
|
displayName: e.displayName,
|
|
type: e.type,
|
|
port: e.port,
|
|
category: e.category,
|
|
description: e.description,
|
|
})),
|
|
});
|
|
});
|
|
|
|
// Agents
|
|
app.get("/api/engine-room/agents", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
let agentsStatus: any[] = [];
|
|
try {
|
|
const { getAgentsStatus } = await import("../blackboard/agents");
|
|
agentsStatus = getAgentsStatus();
|
|
} catch {
|
|
agentsStatus = [];
|
|
}
|
|
|
|
res.json({ agents: agentsStatus });
|
|
} catch (error: any) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
app.post("/api/engine-room/agents/start", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
try {
|
|
const { startAllAgents } = await import("../blackboard/agents");
|
|
startAllAgents();
|
|
res.json({ success: true, message: "Agentes iniciados" });
|
|
} catch (err: any) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
} catch (error: any) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
app.post("/api/engine-room/agents/stop", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
try {
|
|
const { stopAllAgents } = await import("../blackboard/agents");
|
|
stopAllAgents();
|
|
res.json({ success: true, message: "Agentes parados" });
|
|
} catch (err: any) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
} catch (error: any) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// AÇÕES DESABILITADAS - Não gerenciamos mais presets
|
|
// Os serviços são gerenciados pelo Docker/Coolify
|
|
|
|
app.post("/api/engine-room/engine/:name/restart", async (req: Request, res: Response) => {
|
|
res.status(501).json({
|
|
error: "Não implementado",
|
|
message: "Serviços são gerenciados pelo Docker/Coolify. Use o painel do Coolify."
|
|
});
|
|
});
|
|
|
|
app.post("/api/engine-room/engine/:name/stop", async (req: Request, res: Response) => {
|
|
res.status(501).json({
|
|
error: "Não implementado",
|
|
message: "Serviços são gerenciados pelo Docker/Coolify. Use o painel do Coolify."
|
|
});
|
|
});
|
|
|
|
app.post("/api/engine-room/engine/:name/start", async (req: Request, res: Response) => {
|
|
res.status(501).json({
|
|
error: "Não implementado",
|
|
message: "Serviços são gerenciados pelo Docker/Coolify. Use o painel do Coolify."
|
|
});
|
|
});
|
|
|
|
app.get("/api/engine-room/engine/:name/info", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const registryServices = await fetchRegistryServicesWithRetry();
|
|
const service = registryServices?.find((s: any) => s.id === req.params.name || s.name === req.params.name);
|
|
|
|
if (!service) {
|
|
return res.status(404).json({ error: "Servico nao encontrado no registry" });
|
|
}
|
|
|
|
res.json(mapRegistryToEngine(service));
|
|
} catch (error: any) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
app.get("/api/engine-room/engine/:name/logs", async (req: Request, res: Response) => {
|
|
res.status(501).json({
|
|
error: "Não implementado",
|
|
message: "Logs disponiveis via Docker: docker logs <container_name>"
|
|
});
|
|
});
|
|
|
|
console.log("[Engine Room] Rotas registradas (modo simplificado - apenas Registry)");
|
|
}
|