const App = {
currentSection: 'dashboard',
ws: null,
wsReconnectAttempts: 0,
wsReconnectTimer: null,
_initialized: false,
_lastAgentName: '',
sectionTitles: {
dashboard: 'Dashboard',
agents: 'Agentes',
tasks: 'Tarefas',
schedules: 'Agendamentos',
pipelines: 'Pipelines',
webhooks: 'Webhooks',
terminal: 'Terminal',
history: 'Histórico',
settings: 'Configurações',
},
sections: ['dashboard', 'agents', 'tasks', 'schedules', 'pipelines', 'webhooks', 'terminal', 'history', 'settings'],
init() {
if (App._initialized) return;
App._initialized = true;
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme);
App.setupNavigation();
App.setupWebSocket();
App.setupEventListeners();
App.setupKeyboardShortcuts();
const initialSection = location.hash.replace('#', '') || 'dashboard';
App.navigateTo(App.sections.includes(initialSection) ? initialSection : 'dashboard');
App.startPeriodicRefresh();
window.addEventListener('hashchange', () => {
const section = location.hash.replace('#', '') || 'dashboard';
if (App.sections.includes(section)) App.navigateTo(section);
});
const themeToggle = document.getElementById('theme-toggle');
if (themeToggle) {
themeToggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme') || 'dark';
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
Utils.refreshIcons();
});
}
if (typeof NotificationsUI !== 'undefined') NotificationsUI.init();
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission();
}
Utils.refreshIcons();
},
setupNavigation() {
document.querySelectorAll('.sidebar-nav-link[data-section]').forEach((link) => {
link.addEventListener('click', (e) => {
e.preventDefault();
App.navigateTo(link.dataset.section);
});
});
const refreshBtn = document.getElementById('refresh-activity-btn');
if (refreshBtn) {
refreshBtn.addEventListener('click', () => DashboardUI.load());
}
},
navigateTo(section) {
if (location.hash !== `#${section}`) {
history.pushState(null, '', `#${section}`);
}
document.querySelectorAll('.section').forEach((el) => {
const isActive = el.id === section;
el.classList.toggle('active', isActive);
el.hidden = !isActive;
});
document.querySelectorAll('.sidebar-nav-item').forEach((item) => {
const link = item.querySelector('.sidebar-nav-link');
item.classList.toggle('active', link && link.dataset.section === section);
});
const titleEl = document.getElementById('header-title');
if (titleEl) titleEl.textContent = App.sectionTitles[section] || section;
App.currentSection = section;
App._loadSection(section);
},
async _loadSection(section) {
try {
switch (section) {
case 'dashboard': await DashboardUI.load(); break;
case 'agents': await AgentsUI.load(); break;
case 'tasks': await TasksUI.load(); break;
case 'schedules': await SchedulesUI.load(); break;
case 'pipelines': await PipelinesUI.load(); break;
case 'webhooks': await WebhooksUI.load(); break;
case 'history': await HistoryUI.load(); break;
case 'settings': await SettingsUI.load(); break;
}
} catch (err) {
Toast.error(`Erro ao carregar seção: ${err.message}`);
}
},
setupWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const clientId = API.clientId;
const url = `${protocol}//${window.location.host}?clientId=${clientId}`;
try {
App.ws = new WebSocket(url);
App.ws.onopen = () => {
App.updateWsStatus('connected');
App.wsReconnectAttempts = 0;
if (App.wsReconnectTimer) {
clearTimeout(App.wsReconnectTimer);
App.wsReconnectTimer = null;
}
};
App.ws.onclose = () => {
App.updateWsStatus('disconnected');
App._scheduleWsReconnect();
};
App.ws.onerror = () => {
App.updateWsStatus('error');
};
App.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
App.handleWsMessage(data);
} catch {
//
}
};
} catch {
App.updateWsStatus('error');
App._scheduleWsReconnect();
}
},
_scheduleWsReconnect() {
const delay = Math.min(1000 * Math.pow(2, App.wsReconnectAttempts), 30000);
App.wsReconnectAttempts++;
App.wsReconnectTimer = setTimeout(() => {
App.setupWebSocket();
}, delay);
},
handleWsMessage(data) {
switch (data.type) {
case 'connected':
break;
case 'execution_output': {
Terminal.stopProcessing();
const evtType = data.data?.type || 'chunk';
const content = data.data?.content || '';
if (!content) break;
if (evtType === 'tool') {
Terminal.addLine(`▸ ${content}`, 'tool', data.executionId);
} else if (evtType === 'turn') {
Terminal.addLine(`── ${content} ──`, 'turn', data.executionId);
} else if (evtType === 'system') {
Terminal.addLine(content, 'system', data.executionId);
} else if (evtType === 'stderr') {
Terminal.addLine(content, 'stderr', data.executionId);
} else {
Terminal.addLine(content, 'default', data.executionId);
}
App._updateActiveBadge();
break;
}
case 'execution_complete': {
Terminal.stopProcessing();
const result = data.data?.result || '';
if (result) {
Terminal.addLine(result, 'success', data.executionId);
} else {
Terminal.addLine('Execução concluída (sem resultado textual).', 'info', data.executionId);
}
if (data.data?.stderr) {
Terminal.addLine(data.data.stderr, 'error', data.executionId);
}
const costUsd = data.data?.costUsd || 0;
const numTurns = data.data?.numTurns || 0;
if (costUsd > 0) {
Terminal.addLine(`Custo: $${costUsd.toFixed(4)} | Turnos: ${numTurns}`, 'info', data.executionId);
}
const sessionId = data.data?.sessionId || '';
if (sessionId && data.agentId) {
if (Terminal.getChatSession()?.sessionId === sessionId || !Terminal.getChatSession()) {
const agentName = App._lastAgentName || 'Agente';
Terminal.enableChat(data.agentId, agentName, sessionId);
}
if (Terminal.getChatSession()) {
Terminal.updateSessionId(sessionId);
}
}
if (typeof NotificationsUI !== 'undefined') {
NotificationsUI.loadCount();
NotificationsUI.showBrowserNotification('Execução concluída', data.agentName || 'Agente');
}
Toast.success('Execução concluída');
App.refreshCurrentSection();
App._updateActiveBadge();
break;
}
case 'execution_error':
Terminal.stopProcessing();
Terminal.addLine(data.data?.error || 'Erro na execução', 'error', data.executionId);
if (typeof NotificationsUI !== 'undefined') {
NotificationsUI.loadCount();
NotificationsUI.showBrowserNotification('Execução falhou', data.agentName || 'Agente');
}
Toast.error(`Erro na execução: ${data.data?.error || 'desconhecido'}`);
App._updateActiveBadge();
break;
case 'pipeline_step_output': {
Terminal.stopProcessing();
const stepEvtType = data.data?.type || 'chunk';
const stepContent = data.data?.content || '';
if (!stepContent) break;
if (stepEvtType === 'tool') {
Terminal.addLine(`▸ ${stepContent}`, 'tool', data.executionId);
} else if (stepEvtType === 'turn') {
Terminal.addLine(`── ${stepContent} ──`, 'turn', data.executionId);
} else if (stepEvtType === 'system') {
Terminal.addLine(stepContent, 'system', data.executionId);
} else if (stepEvtType === 'stderr') {
Terminal.addLine(stepContent, 'stderr', data.executionId);
} else {
Terminal.addLine(stepContent, 'default', data.executionId);
}
break;
}
case 'pipeline_step_start':
Terminal.stopProcessing();
Terminal.addLine(`Pipeline passo ${data.stepIndex + 1}/${data.totalSteps}: Executando agente "${data.agentName}"...`, 'system');
Terminal.startProcessing(data.agentName);
break;
case 'pipeline_step_complete':
Terminal.stopProcessing();
Terminal.addLine(`Passo ${data.stepIndex + 1} concluído.`, 'info');
Terminal.addLine(data.result || '(sem output)', 'default');
break;
case 'pipeline_complete':
Terminal.stopProcessing();
Terminal.addLine('Pipeline concluído com sucesso.', 'success');
Toast.success('Pipeline concluído');
App.refreshCurrentSection();
break;
case 'pipeline_error':
Terminal.stopProcessing();
Terminal.addLine(`Erro no passo ${data.stepIndex + 1}: ${data.error}`, 'error');
Toast.error('Erro no pipeline');
break;
case 'pipeline_approval_required':
Terminal.stopProcessing();
Terminal.addLine(`Passo ${data.stepIndex + 1} requer aprovação antes de executar.`, 'system');
if (data.previousOutput) {
Terminal.addLine(`Output do passo anterior:\n${data.previousOutput.slice(0, 1000)}`, 'info');
}
App._showApprovalNotification(data.pipelineId, data.stepIndex, data.agentName);
Toast.warning('Pipeline aguardando aprovação');
break;
case 'pipeline_rejected':
Terminal.stopProcessing();
Terminal.addLine(`Pipeline rejeitado no passo ${data.stepIndex + 1}.`, 'error');
App._hideApprovalNotification();
Toast.info('Pipeline rejeitado');
App.refreshCurrentSection();
break;
case 'pipeline_status':
break;
case 'report_generated':
if (data.reportFile) {
Terminal.addLine(`📄 Relatório gerado: ${data.reportFile}`, 'info');
App._openReport(data.reportFile);
}
break;
}
},
async _openReport(filename) {
try {
const data = await API.request('GET', `/reports/${encodeURIComponent(filename)}`);
if (!data || !data.content) return;
const modal = document.getElementById('execution-detail-modal-overlay');
const title = document.getElementById('execution-detail-title');
const content = document.getElementById('execution-detail-content');
if (!modal || !title || !content) return;
title.textContent = 'Relatório de Execução';
content.innerHTML = `
${Utils.escapeHtml(data.content)} `;
Modal.open('execution-detail-modal-overlay');
} catch (e) {}
},
_showApprovalNotification(pipelineId, stepIndex, agentName) {
const container = document.getElementById('approval-notification');
if (!container) return;
container.innerHTML = `
Aprovação necessária
Passo ${stepIndex + 1} (${Utils.escapeHtml(agentName) || 'agente'}) aguardando autorização
`;
container.hidden = false;
container.dataset.pipelineId = pipelineId;
Utils.refreshIcons(container);
document.getElementById('approval-approve-btn')?.addEventListener('click', () => {
App._handleApproval(pipelineId, true);
});
document.getElementById('approval-reject-btn')?.addEventListener('click', () => {
App._handleApproval(pipelineId, false);
});
},
_hideApprovalNotification() {
const container = document.getElementById('approval-notification');
if (container) {
container.hidden = true;
container.innerHTML = '';
}
},
async _handleApproval(pipelineId, approve) {
try {
if (approve) {
await API.pipelines.approve(pipelineId);
Terminal.addLine('Passo aprovado. Continuando pipeline...', 'success');
Toast.success('Passo aprovado');
} else {
await API.pipelines.reject(pipelineId);
Terminal.addLine('Pipeline rejeitado pelo usuário.', 'error');
Toast.info('Pipeline rejeitado');
}
App._hideApprovalNotification();
} catch (err) {
Toast.error(`Erro: ${err.message}`);
}
},
updateWsStatus(status) {
const indicator = document.getElementById('ws-indicator');
const label = document.getElementById('ws-label');
const terminalDot = document.getElementById('terminal-ws-dot');
const terminalLabel = document.getElementById('terminal-ws-label');
const wsBadge = document.getElementById('system-ws-status-badge');
const labels = {
connected: 'Conectado',
disconnected: 'Desconectado',
error: 'Erro de conexão',
};
const cssClass = {
connected: 'ws-indicator--connected',
disconnected: 'ws-indicator--disconnected',
error: 'ws-indicator--error',
};
const badgeClass = {
connected: 'badge--green',
disconnected: 'badge--red',
error: 'badge--red',
};
const displayLabel = labels[status] || status;
const dotClass = cssClass[status] || 'ws-indicator--disconnected';
[indicator, terminalDot].forEach((el) => {
if (!el) return;
el.className = `ws-indicator ${dotClass}`;
});
[label, terminalLabel].forEach((el) => {
if (el) el.textContent = displayLabel;
});
if (wsBadge) {
wsBadge.textContent = displayLabel;
wsBadge.className = `badge ${badgeClass[status] || 'badge--red'}`;
}
},
setupEventListeners() {
const on = (id, event, handler) => {
const el = document.getElementById(id);
if (el) el.addEventListener(event, handler);
};
on('new-agent-btn', 'click', () => AgentsUI.openCreateModal());
on('agents-empty-new-btn', 'click', () => AgentsUI.openCreateModal());
on('import-agent-btn', 'click', () => AgentsUI.openImportModal());
on('agent-form-submit', 'click', (e) => {
e.preventDefault();
AgentsUI.save();
});
on('agent-form', 'submit', (e) => {
e.preventDefault();
AgentsUI.save();
});
on('import-confirm-btn', 'click', () => AgentsUI.importAgent());
on('execute-form-submit', 'click', (e) => {
e.preventDefault();
App._handleExecute();
});
on('execute-form', 'submit', (e) => {
e.preventDefault();
App._handleExecute();
});
on('execute-saved-task', 'change', (e) => {
const taskId = e.target.value;
if (!taskId) return;
const task = (AgentsUI._savedTasksCache || []).find((t) => t.id === taskId);
if (!task) return;
const taskEl = document.getElementById('execute-task-desc');
if (taskEl) {
const parts = [task.name];
if (task.description) parts.push(task.description);
taskEl.value = parts.join('\n\n');
}
});
on('tasks-new-btn', 'click', () => TasksUI.openCreateModal());
on('tasks-empty-new-btn', 'click', () => TasksUI.openCreateModal());
on('schedules-new-btn', 'click', () => SchedulesUI.openCreateModal());
on('schedule-form-submit', 'click', (e) => {
e.preventDefault();
SchedulesUI.save();
});
on('schedule-form', 'submit', (e) => {
e.preventDefault();
SchedulesUI.save();
});
on('webhooks-new-btn', 'click', () => WebhooksUI.openCreateModal());
on('webhook-form-submit', 'click', (e) => {
e.preventDefault();
WebhooksUI.save();
});
on('webhook-target-type', 'change', (e) => {
WebhooksUI._updateTargetSelect(e.target.value);
});
on('pipelines-new-btn', 'click', () => PipelinesUI.openCreateModal());
on('pipeline-form-submit', 'click', (e) => {
e.preventDefault();
PipelinesUI.save();
});
on('pipeline-add-step-btn', 'click', () => PipelinesUI.addStep());
on('pipeline-execute-submit', 'click', () => PipelinesUI._executeFromModal());
on('terminal-clear-btn', 'click', () => {
Terminal.clear();
Terminal.disableChat();
});
on('terminal-send-btn', 'click', () => App._sendChatMessage());
on('terminal-input', 'keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
App._sendChatMessage();
}
});
on('export-copy-btn', 'click', () => App._copyExportJson());
on('system-status-btn', 'click', () => App.navigateTo('dashboard'));
on('terminal-execution-select', 'change', (e) => {
Terminal.setExecutionFilter(e.target.value || null);
});
on('settings-form', 'submit', (e) => {
e.preventDefault();
SettingsUI.save();
});
on('agents-search', 'input', () => {
AgentsUI.filter(
document.getElementById('agents-search')?.value,
document.getElementById('agents-filter-status')?.value
);
});
on('agents-filter-status', 'change', () => {
AgentsUI.filter(
document.getElementById('agents-search')?.value,
document.getElementById('agents-filter-status')?.value
);
});
on('tasks-search', 'input', () => {
TasksUI.filter(
document.getElementById('tasks-search')?.value,
document.getElementById('tasks-filter-category')?.value
);
});
on('tasks-filter-category', 'change', () => {
TasksUI.filter(
document.getElementById('tasks-search')?.value,
document.getElementById('tasks-filter-category')?.value
);
});
on('schedules-search', 'input', () => {
SchedulesUI.filter(
document.getElementById('schedules-search')?.value,
document.getElementById('schedules-filter-status')?.value
);
});
on('schedules-filter-status', 'change', () => {
SchedulesUI.filter(
document.getElementById('schedules-search')?.value,
document.getElementById('schedules-filter-status')?.value
);
});
on('webhooks-search', 'input', () => {
WebhooksUI.filter(document.getElementById('webhooks-search')?.value);
});
on('pipelines-search', 'input', () => {
PipelinesUI.filter(document.getElementById('pipelines-search')?.value);
});
on('history-search', 'input', () => {
HistoryUI.filter(
document.getElementById('history-search')?.value,
document.getElementById('history-filter-type')?.value,
document.getElementById('history-filter-status')?.value
);
});
on('history-filter-type', 'change', () => {
HistoryUI.filter(
document.getElementById('history-search')?.value,
document.getElementById('history-filter-type')?.value,
document.getElementById('history-filter-status')?.value
);
});
on('history-filter-status', 'change', () => {
HistoryUI.filter(
document.getElementById('history-search')?.value,
document.getElementById('history-filter-type')?.value,
document.getElementById('history-filter-status')?.value
);
});
on('history-clear-btn', 'click', () => HistoryUI.clearHistory());
document.getElementById('agents-grid')?.addEventListener('click', (e) => {
const btn = e.target.closest('[data-action]');
if (!btn) return;
const { action, id } = btn.dataset;
switch (action) {
case 'execute': AgentsUI.execute(id); break;
case 'edit': AgentsUI.openEditModal(id); break;
case 'export': AgentsUI.export(id); break;
case 'delete': AgentsUI.delete(id); break;
case 'duplicate': AgentsUI.duplicate(id); break;
}
});
document.getElementById('tasks-grid')?.addEventListener('click', (e) => {
const btn = e.target.closest('[data-action]');
if (!btn) return;
const { action, id } = btn.dataset;
switch (action) {
case 'execute-task': TasksUI.execute(id); break;
case 'edit-task': TasksUI.openEditModal(id); break;
case 'delete-task': TasksUI.delete(id); break;
}
});
document.getElementById('schedules-tbody')?.addEventListener('click', (e) => {
const btn = e.target.closest('[data-action]');
if (!btn) return;
const { action, id } = btn.dataset;
switch (action) {
case 'edit-schedule': SchedulesUI.openEditModal(id); break;
case 'delete-schedule': SchedulesUI.delete(id); break;
}
});
document.getElementById('schedules-history')?.addEventListener('click', (e) => {
const btn = e.target.closest('[data-action]');
if (!btn) return;
const { action, id } = btn.dataset;
if (action === 'view-schedule-exec') HistoryUI.viewDetail(id);
});
document.getElementById('pipelines-grid')?.addEventListener('click', (e) => {
if (e.target.closest('#pipelines-empty-new-btn')) {
PipelinesUI.openCreateModal();
return;
}
const btn = e.target.closest('[data-action]');
if (!btn) return;
const { action, id } = btn.dataset;
switch (action) {
case 'execute-pipeline': PipelinesUI.execute(id); break;
case 'edit-pipeline': PipelinesUI.openEditModal(id); break;
case 'delete-pipeline': PipelinesUI.delete(id); break;
}
});
document.getElementById('history-list')?.addEventListener('click', (e) => {
const btn = e.target.closest('[data-action]');
if (!btn) return;
const { action, id } = btn.dataset;
switch (action) {
case 'view-execution': HistoryUI.viewDetail(id); break;
case 'delete-execution': HistoryUI.deleteExecution(id); break;
case 'retry': HistoryUI.retryExecution(id); break;
}
});
document.getElementById('webhooks-list')?.addEventListener('click', (e) => {
const btn = e.target.closest('[data-action]');
if (!btn) return;
const { action, id, url } = btn.dataset;
switch (action) {
case 'toggle-webhook': WebhooksUI.toggleActive(id); break;
case 'delete-webhook': WebhooksUI.delete(id); break;
case 'copy-webhook-url': WebhooksUI.copyUrl(url); break;
case 'copy-webhook-curl': WebhooksUI.copyCurl(id); break;
case 'edit-webhook': WebhooksUI.openEditModal(id); break;
case 'test-webhook': WebhooksUI.test(id); break;
}
});
document.getElementById('pipeline-steps-container')?.addEventListener('click', (e) => {
const btn = e.target.closest('[data-step-action]');
if (!btn) return;
const stepAction = btn.dataset.stepAction;
const stepIndex = parseInt(btn.dataset.stepIndex, 10);
switch (stepAction) {
case 'move-up': PipelinesUI.moveStep(stepIndex, -1); break;
case 'move-down': PipelinesUI.moveStep(stepIndex, 1); break;
case 'remove': PipelinesUI.removeStep(stepIndex); break;
}
});
document.addEventListener('click', (e) => {
const template = e.target.closest('[data-template]');
if (template) {
const taskEl = document.getElementById('execute-task-desc');
if (taskEl) taskEl.value = template.dataset.template;
return;
}
const cronPreset = e.target.closest('[data-cron]');
if (cronPreset) {
const cronEl = document.getElementById('schedule-cron');
if (cronEl) cronEl.value = cronPreset.dataset.cron;
return;
}
});
App._setupTagsInput();
},
_setupTagsInput() {
const input = document.getElementById('agent-tags-input');
const chips = document.getElementById('agent-tags-chips');
const hidden = document.getElementById('agent-tags');
if (!input || !chips || !hidden) return;
const getTags = () => {
try { return JSON.parse(hidden.value || '[]'); } catch { return []; }
};
const setTags = (tags) => {
hidden.value = JSON.stringify(tags);
chips.innerHTML = tags.map((t) => `
${Utils.escapeHtml(t)}
`).join('');
};
input.addEventListener('keydown', (e) => {
if (e.key !== 'Enter' && e.key !== ',') return;
e.preventDefault();
const value = input.value.trim().replace(/,$/, '');
if (!value) return;
const tags = getTags();
if (!tags.includes(value)) {
tags.push(value);
setTags(tags);
}
input.value = '';
});
chips.addEventListener('click', (e) => {
const removeBtn = e.target.closest('.tag-remove');
if (!removeBtn) return;
const tag = removeBtn.dataset.tag;
const tags = getTags().filter((t) => t !== tag);
setTags(tags);
});
},
async _handleExecute() {
const agentId = document.getElementById('execute-agent-select')?.value
|| document.getElementById('execute-agent-id')?.value;
if (!agentId) {
Toast.warning('Selecione um agente para executar');
return;
}
const task = document.getElementById('execute-task-desc')?.value.trim();
if (!task) {
Toast.warning('Descreva a tarefa a ser executada');
return;
}
const instructions = document.getElementById('execute-instructions')?.value.trim() || '';
try {
const selectEl = document.getElementById('execute-agent-select');
const agentName = selectEl?.selectedOptions[0]?.text || 'Agente';
Terminal.disableChat();
App._lastAgentName = agentName;
await API.agents.execute(agentId, task, instructions);
Modal.close('execute-modal-overlay');
App.navigateTo('terminal');
Toast.info('Execução iniciada');
Terminal.startProcessing(agentName);
} catch (err) {
Toast.error(`Erro ao iniciar execução: ${err.message}`);
}
},
async _sendChatMessage() {
const session = Terminal.getChatSession();
if (!session) return;
const input = document.getElementById('terminal-input');
const message = input?.value.trim();
if (!message) return;
input.value = '';
Terminal.addLine(`❯ ${message}`, 'user-message', null);
try {
await API.agents.continue(session.agentId, session.sessionId, message);
Terminal.startProcessing(session.agentName);
} catch (err) {
Terminal.addLine(`Erro: ${err.message}`, 'error');
Toast.error(`Erro ao continuar conversa: ${err.message}`);
}
},
async _copyExportJson() {
const jsonEl = document.getElementById('export-code-content');
if (!jsonEl) return;
try {
await navigator.clipboard.writeText(jsonEl.textContent);
Toast.success('JSON copiado para a área de transferência');
} catch {
Toast.error('Não foi possível copiar o JSON');
}
},
setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
Modal.closeAll();
return;
}
const isInInput = ['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement?.tagName);
if (isInInput) return;
if (e.key === 'n' || e.key === 'N') {
if (App.currentSection === 'agents') {
AgentsUI.openCreateModal();
}
}
if (!e.ctrlKey && !e.metaKey && !e.altKey) {
const sectionKeys = {
'1': 'dashboard',
'2': 'agents',
'3': 'tasks',
'4': 'schedules',
'5': 'pipelines',
'6': 'terminal',
'7': 'history',
'8': 'webhooks',
'9': 'settings',
};
if (sectionKeys[e.key]) {
e.preventDefault();
App.navigateTo(sectionKeys[e.key]);
}
}
});
},
async refreshCurrentSection() {
await App._loadSection(App.currentSection);
},
async _updateActiveBadge() {
try {
const active = await API.system.activeExecutions();
const count = Array.isArray(active) ? active.length : 0;
const badge = document.getElementById('active-executions-badge');
const countEl = document.getElementById('active-executions-count');
if (countEl) countEl.textContent = count;
if (badge) badge.style.display = count > 0 ? 'flex' : 'none';
const terminalSelect = document.getElementById('terminal-execution-select');
if (terminalSelect && Array.isArray(active)) {
const existing = new Set(
Array.from(terminalSelect.options).map((o) => o.value).filter(Boolean)
);
active.forEach((exec) => {
const execId = exec.executionId || exec.id;
if (!existing.has(execId)) {
const option = document.createElement('option');
option.value = execId;
const agentName = (exec.agentConfig && exec.agentConfig.agent_name) || exec.agentId || 'Agente';
option.textContent = `${agentName} — ${new Date(exec.startedAt).toLocaleTimeString('pt-BR')}`;
terminalSelect.appendChild(option);
}
});
}
} catch {
//
}
},
startPeriodicRefresh() {
setInterval(async () => {
await App._updateActiveBadge();
if (App.currentSection === 'dashboard') {
await DashboardUI.load();
}
}, 30000);
},
};
document.addEventListener('DOMContentLoaded', () => App.init());
window.App = App;