Implementação completa de funcionalidades pendentes
- Settings persistentes (modelo padrão, workdir, max concurrent) - Import/export de agentes via JSON - Agendamentos persistentes com restore no startup - Edição de agendamentos e tarefas existentes - Filtros e busca em todas as seções - Isolamento de WebSocket por clientId - Autenticação via AUTH_TOKEN e CORS configurável - Graceful shutdown com cancelamento de execuções - Correção: --max-tokens removido (flag inválida do CLI) - Correção: pipeline agora verifica exit code e propaga erros - Correção: streaming de output em pipelines via WebSocket - Permission mode bypassPermissions como padrão - Página de configurações do sistema - Contagem diária de execuções no dashboard - Histórico de execuções recentes
This commit is contained in:
66
server.js
66
server.js
@@ -3,55 +3,100 @@ import { createServer } from 'http';
|
||||
import { WebSocketServer } from 'ws';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import apiRouter, { setWsBroadcast } from './src/routes/api.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import apiRouter, { setWsBroadcast, setWsBroadcastTo } from './src/routes/api.js';
|
||||
import * as manager from './src/agents/manager.js';
|
||||
import { cancelAllExecutions } from './src/agents/executor.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const AUTH_TOKEN = process.env.AUTH_TOKEN || '';
|
||||
const ALLOWED_ORIGIN = process.env.ALLOWED_ORIGIN || '';
|
||||
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
const wss = new WebSocketServer({ server: httpServer });
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const origin = ALLOWED_ORIGIN || req.headers.origin || '*';
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Client-Id');
|
||||
if (req.method === 'OPTIONS') return res.sendStatus(204);
|
||||
next();
|
||||
});
|
||||
|
||||
if (AUTH_TOKEN) {
|
||||
app.use('/api', (req, res, next) => {
|
||||
const header = req.headers.authorization || '';
|
||||
const token = header.startsWith('Bearer ') ? header.slice(7) : req.query.token;
|
||||
if (token !== AUTH_TOKEN) {
|
||||
return res.status(401).json({ error: 'Token de autenticação inválido' });
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.static(join(__dirname, 'public')));
|
||||
app.use('/api', apiRouter);
|
||||
|
||||
const connectedClients = new Set();
|
||||
const connectedClients = new Map();
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
connectedClients.add(ws);
|
||||
wss.on('connection', (ws, req) => {
|
||||
const clientId = new URL(req.url, 'http://localhost').searchParams.get('clientId') || uuidv4();
|
||||
|
||||
if (AUTH_TOKEN) {
|
||||
const token = new URL(req.url, 'http://localhost').searchParams.get('token');
|
||||
if (token !== AUTH_TOKEN) {
|
||||
ws.close(4001, 'Token inválido');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ws.clientId = clientId;
|
||||
connectedClients.set(clientId, ws);
|
||||
|
||||
ws.on('close', () => {
|
||||
connectedClients.delete(ws);
|
||||
connectedClients.delete(clientId);
|
||||
});
|
||||
|
||||
ws.on('error', () => {
|
||||
connectedClients.delete(ws);
|
||||
connectedClients.delete(clientId);
|
||||
});
|
||||
|
||||
ws.send(JSON.stringify({ type: 'connected', clientId }));
|
||||
});
|
||||
|
||||
function broadcast(message) {
|
||||
const payload = JSON.stringify(message);
|
||||
for (const client of connectedClients) {
|
||||
for (const [, client] of connectedClients) {
|
||||
if (client.readyState === 1) {
|
||||
client.send(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function broadcastTo(clientId, message) {
|
||||
const payload = JSON.stringify(message);
|
||||
const client = connectedClients.get(clientId);
|
||||
|
||||
if (client && client.readyState === 1) {
|
||||
client.send(payload);
|
||||
} else {
|
||||
broadcast(message);
|
||||
}
|
||||
}
|
||||
|
||||
setWsBroadcast(broadcast);
|
||||
setWsBroadcastTo(broadcastTo);
|
||||
|
||||
function gracefulShutdown(signal) {
|
||||
console.log(`\nSinal ${signal} recebido. Encerrando servidor...`);
|
||||
|
||||
cancelAllExecutions();
|
||||
console.log('Execuções ativas canceladas.');
|
||||
|
||||
httpServer.close(() => {
|
||||
console.log('Servidor HTTP encerrado.');
|
||||
process.exit(0);
|
||||
@@ -66,7 +111,10 @@ function gracefulShutdown(signal) {
|
||||
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
||||
|
||||
manager.restoreSchedules(broadcast);
|
||||
|
||||
httpServer.listen(PORT, () => {
|
||||
console.log(`Painel administrativo disponível em http://localhost:${PORT}`);
|
||||
console.log(`WebSocket server ativo na mesma porta.`);
|
||||
if (AUTH_TOKEN) console.log('Autenticação por token ativada.');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user