diff --git a/server/engine-room/kernel-adapter.ts b/server/engine-room/kernel-adapter.ts index df46070..ebdcfd4 100644 --- a/server/engine-room/kernel-adapter.ts +++ b/server/engine-room/kernel-adapter.ts @@ -1,10 +1,26 @@ /** * Kernel Adapter - Ponte entre Engine Room e Arcadia Kernel - * Traduz chamadas do formato antigo para o novo Kernel (porta 5001) + * + * MODO NORMAL: Conecta ao Kernel local (porta 5001) para gerenciar serviços + * MODO DOCKER: Consulta diretamente os serviços externos via HTTP health check + * + * A detecção de modo Docker é automática via variável DOCKER_MODE */ const KERNEL_BASE_URL = 'http://localhost:5001/api/kernel'; +// Detectar modo Docker - em produção os serviços rodam em containers separados +const IS_DOCKER_MODE = process.env.DOCKER_MODE === 'true'; + +// URLs dos serviços externos em modo Docker (vindas das env vars) +const SERVICE_URLS: Record = { + "contabil": process.env.CONTABIL_PYTHON_URL || 'http://contabil:8003', + "bi-engine": process.env.BI_PYTHON_URL || 'http://bi:8004', + "automation-engine": process.env.AUTOMATION_PYTHON_URL || 'http://automation:8005', + "fisco": process.env.FISCO_PYTHON_URL || 'http://fisco:8002', + "communication": process.env.MIROFLOW_HOST ? `http://${process.env.MIROFLOW_HOST}:${process.env.MIROFLOW_PORT || 8006}` : 'http://miroflow:8006', +}; + // Mapeamento: Nome do Engine Room -> ID do Kernel // NOTA: metaset NÃO está aqui - é gerenciado externamente (Java) const ENGINE_TO_KERNEL_ID: Record = { @@ -42,6 +58,15 @@ const DISPLAY_NAME_MAP: Record = { "node-communication": "Motor Comunicacao", }; +// Configurações dos engines para health check em modo Docker +const ENGINE_CONFIG: Record = { + "contabil": { port: 8003, type: "python", healthPath: "/health" }, + "bi-engine": { port: 8004, type: "python", healthPath: "/health" }, + "automation-engine": { port: 8005, type: "python", healthPath: "/health" }, + "fisco": { port: 8002, type: "python", healthPath: "/health" }, + "communication": { port: 8006, type: "node", healthPath: "/health" }, +}; + interface KernelServiceState { config: { id: string; @@ -71,6 +96,87 @@ interface EngineStatus { error?: string; } +// Health check direto de um serviço (modo Docker) +async function checkServiceHealth(engineName: string): Promise { + const config = ENGINE_CONFIG[engineName]; + const serviceUrl = SERVICE_URLS[engineName]; + const kernelId = ENGINE_TO_KERNEL_ID[engineName]; + + if (!config || !serviceUrl) { + return { + name: engineName, + displayName: DISPLAY_NAME_MAP[kernelId || engineName] || engineName, + type: "unknown", + port: 0, + category: CATEGORY_MAP[kernelId || engineName] || "data", + description: `Serviço ${engineName}`, + status: "offline", + error: "Configuração não encontrada", + }; + } + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); + + try { + const start = Date.now(); + const response = await fetch(`${serviceUrl}${config.healthPath}`, { + signal: controller.signal, + }); + clearTimeout(timeout); + const elapsed = Date.now() - start; + + if (response.ok) { + const data = await response.json().catch(() => ({})); + return { + name: engineName, + displayName: DISPLAY_NAME_MAP[kernelId] || engineName, + type: config.type, + port: config.port, + category: CATEGORY_MAP[kernelId] || "data", + description: `Serviço ${engineName}`, + status: "online", + responseTime: elapsed, + details: { + url: serviceUrl, + health: "healthy", + ...data, + }, + }; + } else { + return { + name: engineName, + displayName: DISPLAY_NAME_MAP[kernelId] || engineName, + type: config.type, + port: config.port, + category: CATEGORY_MAP[kernelId] || "data", + description: `Serviço ${engineName}`, + status: "error", + responseTime: elapsed, + details: { + url: serviceUrl, + httpStatus: response.status, + }, + }; + } + } catch (err: any) { + clearTimeout(timeout); + return { + name: engineName, + displayName: DISPLAY_NAME_MAP[kernelId] || engineName, + type: config.type, + port: config.port, + category: CATEGORY_MAP[kernelId] || "data", + description: `Serviço ${engineName}`, + status: "offline", + error: err.name === "AbortError" ? "timeout" : err.message, + details: { + url: serviceUrl, + }, + }; + } +} + // Helper para fazer requests ao Kernel async function kernelRequest(endpoint: string, method = 'GET', body?: any): Promise { try { @@ -114,8 +220,38 @@ function mapStatus(kernelStatus: string, health?: string): "online" | "offline" return 'offline'; } -// Obter todos os serviços do Kernel formatados para Engine Room +// Obter todos os serviços formatados para Engine Room export async function getKernelEngines(): Promise { + // MODO DOCKER: Consulta diretamente os serviços externos + if (IS_DOCKER_MODE) { + console.log('[KernelAdapter] Modo Docker detectado - consultando serviços externos diretamente'); + + const engineNames = Object.keys(ENGINE_CONFIG); + const results = await Promise.allSettled( + engineNames.map(name => checkServiceHealth(name)) + ); + + return results.map((result, index) => { + if (result.status === 'fulfilled') { + return result.value; + } else { + const name = engineNames[index]; + const kernelId = ENGINE_TO_KERNEL_ID[name]; + return { + name, + displayName: DISPLAY_NAME_MAP[kernelId] || name, + type: ENGINE_CONFIG[name]?.type || "unknown", + port: ENGINE_CONFIG[name]?.port || 0, + category: CATEGORY_MAP[kernelId] || "data", + description: `Serviço ${name}`, + status: "offline" as const, + error: result.reason?.message || "Erro desconhecido", + }; + } + }); + } + + // MODO NORMAL: Conecta ao Kernel local try { const response = await kernelRequest('/services'); const services: KernelServiceState[] = response.data || []; @@ -148,6 +284,23 @@ export async function getKernelEngines(): Promise { // Obter info de um serviço específico (formato compatível com antigo) export async function getKernelServiceInfo(engineName: string): Promise { + // MODO DOCKER: Retorna info baseada no health check + if (IS_DOCKER_MODE) { + const health = await checkServiceHealth(engineName); + return { + name: engineName, + port: health.port, + status: health.status === 'online' ? 'running' : 'stopped', + startedAt: null, + restartCount: 0, + uptime: 0, + logLines: 0, + health: health.status === 'online' ? 'healthy' : 'unhealthy', + mode: 'docker-external', + }; + } + + // MODO NORMAL: Conecta ao Kernel const kernelId = ENGINE_TO_KERNEL_ID[engineName]; if (!kernelId) return null; @@ -166,7 +319,7 @@ export async function getKernelServiceInfo(engineName: string): Promise { uptime: service.startTime ? Math.round((Date.now() - new Date(service.startTime).getTime()) / 1000) : 0, - logLines: 0, // Kernel não expõe isso diretamente + logLines: 0, health: service.health, }; } catch (error) { @@ -177,6 +330,12 @@ export async function getKernelServiceInfo(engineName: string): Promise { // Obter logs de um serviço (formato compatível) export async function getKernelServiceLogs(engineName: string, lines = 50): Promise { + // MODO DOCKER: Logs não disponíveis via HTTP + if (IS_DOCKER_MODE) { + return [`[Docker Mode] Logs não disponíveis para ${engineName}. Use 'docker logs' no container.`]; + } + + // MODO NORMAL: Conecta ao Kernel const kernelId = ENGINE_TO_KERNEL_ID[engineName]; if (!kernelId) return []; @@ -197,6 +356,13 @@ export async function getKernelServiceLogs(engineName: string, lines = 50): Prom // Iniciar serviço export async function startKernelService(engineName: string): Promise { + // MODO DOCKER: Não pode iniciar container externo + if (IS_DOCKER_MODE) { + console.log(`[KernelAdapter] Modo Docker: não é possível iniciar ${engineName} (container externo)`); + return false; + } + + // MODO NORMAL: Conecta ao Kernel const kernelId = ENGINE_TO_KERNEL_ID[engineName]; if (!kernelId) return false; @@ -211,6 +377,13 @@ export async function startKernelService(engineName: string): Promise { // Parar serviço export async function stopKernelService(engineName: string): Promise { + // MODO DOCKER: Não pode parar container externo + if (IS_DOCKER_MODE) { + console.log(`[KernelAdapter] Modo Docker: não é possível parar ${engineName} (container externo)`); + return false; + } + + // MODO NORMAL: Conecta ao Kernel const kernelId = ENGINE_TO_KERNEL_ID[engineName]; if (!kernelId) return false; @@ -225,6 +398,13 @@ export async function stopKernelService(engineName: string): Promise { // Reiniciar serviço export async function restartKernelService(engineName: string): Promise { + // MODO DOCKER: Não pode reiniciar container externo + if (IS_DOCKER_MODE) { + console.log(`[KernelAdapter] Modo Docker: não é possível reiniciar ${engineName} (container externo)`); + return false; + } + + // MODO NORMAL: Conecta ao Kernel const kernelId = ENGINE_TO_KERNEL_ID[engineName]; if (!kernelId) return false; @@ -239,6 +419,14 @@ export async function restartKernelService(engineName: string): Promise // Verificar health geral do Kernel export async function getKernelHealth(): Promise { + if (IS_DOCKER_MODE) { + return { + mode: 'docker', + message: 'Modo Docker - serviços gerenciados externamente', + services: Object.keys(ENGINE_CONFIG), + }; + } + try { const response = await kernelRequest('/health'); return response.data; @@ -248,8 +436,14 @@ export async function getKernelHealth(): Promise { } } -// Verificar se Kernel está disponível +// Verificar se Kernel está disponível (ou modo Docker está ativo) export async function isKernelAvailable(): Promise { + // MODO DOCKER: Sempre "disponível" (serviços externos) + if (IS_DOCKER_MODE) { + return true; + } + + // MODO NORMAL: Verifica se Kernel responde try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 3000); @@ -265,4 +459,4 @@ export async function isKernelAvailable(): Promise { } } -console.log('[KernelAdapter] Adapter loaded - proxying to localhost:5001'); +console.log(`[KernelAdapter] Adapter loaded - Modo: ${IS_DOCKER_MODE ? 'DOCKER (serviços externos)' : 'Kernel local (porta 5001)'}`);