Add port 3000 mapping to docker-compose

This commit is contained in:
Erwin
2026-03-27 20:31:23 +00:00
parent a65a973310
commit 978be535d3
4 changed files with 99 additions and 60 deletions

View File

@@ -5,6 +5,8 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: mission-control-ui container_name: mission-control-ui
restart: unless-stopped restart: unless-stopped
ports:
- "3000:80"
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.mission-control-ui.rule=Host(`mission-control.danielarroyo.cl`)" - "traefik.http.routers.mission-control-ui.rule=Host(`mission-control.danielarroyo.cl`)"

View File

@@ -60,7 +60,7 @@ export function MissionControlDashboard() {
{/* Status Bar */} {/* Status Bar */}
<StatusBar <StatusBar
agents={agents} agents={agents}
health={health} health={health ?? undefined}
lastUpdated={lastUpdated} lastUpdated={lastUpdated}
loading={loading} loading={loading}
error={error} error={error}

View File

@@ -1,26 +1,36 @@
import { useState, useEffect, useCallback, useRef } from 'react'; import { useState, useEffect, useCallback, useRef } from 'react';
import { openclawClient } from '../lib/openclaw-api'; import { openclawClient } from '../lib/openclaw-api';
import type { Agent, TimelineEvent } from '../types/agent'; import type { Agent, Connection, TimelineEvent } from '../types/agent';
interface UseOpenClawOptions {
pollInterval?: number;
gatewayUrl?: string;
token?: string;
}
interface UseOpenClawReturn { interface UseOpenClawReturn {
agents: Agent[]; agents: Agent[];
connections: Connection[];
events: TimelineEvent[]; events: TimelineEvent[];
health: { status: string; uptime: number; sessions: number } | null; health: { status: string; uptime: number; sessions: number } | null;
isLoading: boolean; lastUpdated: Date | null;
loading: boolean;
error: string | null; error: string | null;
lastUpdate: Date | null; connected: boolean;
refresh: () => void; refetch: () => void;
} }
const POLLING_INTERVAL = 30000; export function useOpenClaw(options: UseOpenClawOptions = {}): UseOpenClawReturn {
const { pollInterval = 30000 } = options;
export function useOpenClaw(): UseOpenClawReturn {
const [agents, setAgents] = useState<Agent[]>([]); const [agents, setAgents] = useState<Agent[]>([]);
const [connections] = useState<Connection[]>([]);
const [events, setEvents] = useState<TimelineEvent[]>([]); const [events, setEvents] = useState<TimelineEvent[]>([]);
const [health, setHealth] = useState<{ status: string; uptime: number; sessions: number } | null>(null); const [health, setHealth] = useState<{ status: string; uptime: number; sessions: number } | null>(null);
const [isLoading, setIsLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [lastUpdate, setLastUpdate] = useState<Date | null>(null); const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
const [connected, setConnected] = useState(false);
const intervalRef = useRef<number | null>(null); const intervalRef = useRef<number | null>(null);
const isMountedRef = useRef(true); const isMountedRef = useRef(true);
@@ -37,19 +47,21 @@ export function useOpenClaw(): UseOpenClawReturn {
setAgents(agentsData); setAgents(agentsData);
setEvents(eventsData); setEvents(eventsData);
setHealth(healthData); setHealth(healthData);
setConnected(true);
setError(null); setError(null);
setLastUpdate(new Date()); setLastUpdated(new Date());
setIsLoading(false); setLoading(false);
} }
} catch (err) { } catch (err) {
if (isMountedRef.current) { if (isMountedRef.current) {
setError(err instanceof Error ? err.message : 'Failed to connect to gateway'); setError(err instanceof Error ? err.message : 'Failed to connect to gateway');
setIsLoading(false); setConnected(false);
setLoading(false);
} }
} }
}, []); }, []);
const refresh = useCallback(() => { const refetch = useCallback(() => {
fetchData(); fetchData();
}, [fetchData]); }, [fetchData]);
@@ -57,7 +69,7 @@ export function useOpenClaw(): UseOpenClawReturn {
isMountedRef.current = true; isMountedRef.current = true;
fetchData(); fetchData();
intervalRef.current = window.setInterval(fetchData, POLLING_INTERVAL); intervalRef.current = window.setInterval(fetchData, pollInterval);
return () => { return () => {
isMountedRef.current = false; isMountedRef.current = false;
@@ -66,15 +78,17 @@ export function useOpenClaw(): UseOpenClawReturn {
intervalRef.current = null; intervalRef.current = null;
} }
}; };
}, [fetchData]); }, [fetchData, pollInterval]);
return { return {
agents, agents,
connections,
events, events,
health, health,
isLoading, lastUpdated,
loading,
error, error,
lastUpdate, connected,
refresh, refetch,
}; };
} }

View File

@@ -1,4 +1,4 @@
import type { Agent, Connection, TimelineEvent } from '../types/agent'; import type { Agent, TimelineEvent, AgentRole, AgentStatus } from '../types/agent';
const GATEWAY_URL = import.meta.env.VITE_OPENCLAW_GATEWAY_URL || 'http://127.0.0.1:18789'; const GATEWAY_URL = import.meta.env.VITE_OPENCLAW_GATEWAY_URL || 'http://127.0.0.1:18789';
const GATEWAY_TOKEN = import.meta.env.VITE_OPENCLAW_GATEWAY_TOKEN || ''; const GATEWAY_TOKEN = import.meta.env.VITE_OPENCLAW_GATEWAY_TOKEN || '';
@@ -19,6 +19,35 @@ interface GatewayAgentsResponse {
}>; }>;
} }
const roleMap: Record<string, AgentRole> = {
orchestrator: 'orchestrator',
architect: 'architect',
coder: 'developer',
developer: 'developer',
designer: 'designer',
reviewer: 'reviewer',
assistant: 'assistant',
docs: 'assistant',
};
const statusMap: Record<string, AgentStatus> = {
active: 'active',
busy: 'thinking',
thinking: 'thinking',
blocked: 'blocked',
idle: 'idle',
completed: 'completed',
};
const agentPositions = [
{ id: 'erwin', x: 10, y: 38 },
{ id: 'bulma', x: 36, y: 12 },
{ id: 'rocket', x: 36, y: 64 },
{ id: 'sherlock', x: 66, y: 24 },
{ id: 'hiro', x: 66, y: 64 },
{ id: 'claudia', x: 66, y: 50 },
];
export class OpenClawGatewayClient { export class OpenClawGatewayClient {
private baseUrl: string; private baseUrl: string;
private token: string; private token: string;
@@ -53,16 +82,25 @@ export class OpenClawGatewayClient {
async fetchAgents(): Promise<Agent[]> { async fetchAgents(): Promise<Agent[]> {
try { try {
const data = await this.fetch<GatewayAgentsResponse>('/api/agents'); const data = await this.fetch<GatewayAgentsResponse>('/api/agents');
return data.agents.map((agent, index) => ({ return data.agents.map((agent) => {
id: agent.id, const pos = agentPositions.find(p => p.id === agent.id) || { x: 50, y: 50 };
name: agent.name, const role = roleMap[agent.id] || 'assistant';
role: this.getRoleFromAgentId(agent.id), const status = statusMap[agent.status] || 'idle';
status: agent.status === 'active' ? 'active' : agent.status === 'busy' ? 'busy' : 'idle',
queue: 0, return {
icon: this.getIconFromIndex(index), id: agent.id,
x: this.getAgentX(index), name: agent.name,
y: this.getAgentY(index), role,
})); status,
position: { x: pos.x, y: pos.y },
connections: this.getDefaultConnections(agent.id),
stats: {
tasksCompleted: 0,
tasksInProgress: 0,
tasksBlocked: 0,
},
};
});
} catch { } catch {
return this.getDefaultAgents(); return this.getDefaultAgents();
} }
@@ -77,41 +115,26 @@ export class OpenClawGatewayClient {
} }
} }
private getRoleFromAgentId(id: string): string { private getDefaultConnections(agentId: string): string[] {
const roles: Record<string, string> = { const connectionsMap: Record<string, string[]> = {
orchestrator: 'Coordina flujo y delegación', erwin: ['bulma', 'rocket', 'sherlock', 'claudia'],
architect: 'Define solución y tareas', bulma: ['rocket', 'sherlock'],
coder: 'Implementa cambios', rocket: ['sherlock', 'claudia'],
reviewer: 'Revisa calidad y riesgos', sherlock: ['claudia', 'erwin'],
docs: 'Documenta decisiones y cambios', hiro: ['erwin'],
assistant: 'Asistente general', claudia: ['erwin'],
}; };
return roles[id] || 'Agente'; return connectionsMap[agentId] || [];
}
private getIconFromIndex(index: number) {
const icons = ['Workflow', 'Layers3', 'Bot', 'ShieldCheck', 'FileText', 'Activity'];
return icons[index % icons.length];
}
private getAgentX(index: number): number {
const positions = [10, 36, 36, 66, 66, 66];
return positions[index] || 50;
}
private getAgentY(index: number): number {
const positions = [38, 12, 64, 24, 64, 50];
return positions[index] || 50;
} }
private getDefaultAgents(): Agent[] { private getDefaultAgents(): Agent[] {
return [ return [
{ id: 'erwin', name: 'Erwin', role: 'Orquestador', status: 'active', queue: 0, icon: 'Workflow', x: 10, y: 38 }, { id: 'erwin', name: 'Erwin', role: 'orchestrator', status: 'active', position: { x: 10, y: 38 }, connections: ['bulma', 'rocket'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } },
{ id: 'bulma', name: 'Bulma', role: 'Arquitecto', status: 'active', queue: 0, icon: 'Layers3', x: 36, y: 12 }, { id: 'bulma', name: 'Bulma', role: 'architect', status: 'active', position: { x: 36, y: 12 }, connections: ['rocket'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } },
{ id: 'rocket', name: 'Rocket', role: 'Desarrollador', status: 'busy', queue: 0, icon: 'Bot', x: 36, y: 64 }, { id: 'rocket', name: 'Rocket', role: 'developer', status: 'thinking', position: { x: 36, y: 64 }, connections: ['sherlock'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } },
{ id: 'sherlock', name: 'Sherlock', role: 'Revisor', status: 'idle', queue: 0, icon: 'ShieldCheck', x: 66, y: 24 }, { id: 'sherlock', name: 'Sherlock', role: 'reviewer', status: 'idle', position: { x: 66, y: 24 }, connections: ['claudia'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } },
{ id: 'hiro', name: 'Hiro', role: 'Diseñador', status: 'idle', queue: 0, icon: 'FileText', x: 66, y: 64 }, { id: 'hiro', name: 'Hiro', role: 'designer', status: 'idle', position: { x: 66, y: 64 }, connections: [], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } },
{ id: 'claudia', name: 'Claudia', role: 'Asistente', status: 'idle', queue: 0, icon: 'Activity', x: 66, y: 50 }, { id: 'claudia', name: 'Claudia', role: 'assistant', status: 'idle', position: { x: 66, y: 50 }, connections: ['erwin'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } },
]; ];
} }
} }