Add port 3000 mapping to docker-compose
This commit is contained in:
@@ -5,6 +5,8 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
container_name: mission-control-ui
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:80"
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.mission-control-ui.rule=Host(`mission-control.danielarroyo.cl`)"
|
||||
|
||||
@@ -60,7 +60,7 @@ export function MissionControlDashboard() {
|
||||
{/* Status Bar */}
|
||||
<StatusBar
|
||||
agents={agents}
|
||||
health={health}
|
||||
health={health ?? undefined}
|
||||
lastUpdated={lastUpdated}
|
||||
loading={loading}
|
||||
error={error}
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
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 {
|
||||
agents: Agent[];
|
||||
connections: Connection[];
|
||||
events: TimelineEvent[];
|
||||
health: { status: string; uptime: number; sessions: number } | null;
|
||||
isLoading: boolean;
|
||||
lastUpdated: Date | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
lastUpdate: Date | null;
|
||||
refresh: () => void;
|
||||
connected: boolean;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
const POLLING_INTERVAL = 30000;
|
||||
|
||||
export function useOpenClaw(): UseOpenClawReturn {
|
||||
export function useOpenClaw(options: UseOpenClawOptions = {}): UseOpenClawReturn {
|
||||
const { pollInterval = 30000 } = options;
|
||||
|
||||
const [agents, setAgents] = useState<Agent[]>([]);
|
||||
const [connections] = useState<Connection[]>([]);
|
||||
const [events, setEvents] = useState<TimelineEvent[]>([]);
|
||||
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 [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 isMountedRef = useRef(true);
|
||||
@@ -37,19 +47,21 @@ export function useOpenClaw(): UseOpenClawReturn {
|
||||
setAgents(agentsData);
|
||||
setEvents(eventsData);
|
||||
setHealth(healthData);
|
||||
setConnected(true);
|
||||
setError(null);
|
||||
setLastUpdate(new Date());
|
||||
setIsLoading(false);
|
||||
setLastUpdated(new Date());
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
if (isMountedRef.current) {
|
||||
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]);
|
||||
|
||||
@@ -57,7 +69,7 @@ export function useOpenClaw(): UseOpenClawReturn {
|
||||
isMountedRef.current = true;
|
||||
fetchData();
|
||||
|
||||
intervalRef.current = window.setInterval(fetchData, POLLING_INTERVAL);
|
||||
intervalRef.current = window.setInterval(fetchData, pollInterval);
|
||||
|
||||
return () => {
|
||||
isMountedRef.current = false;
|
||||
@@ -66,15 +78,17 @@ export function useOpenClaw(): UseOpenClawReturn {
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [fetchData]);
|
||||
}, [fetchData, pollInterval]);
|
||||
|
||||
return {
|
||||
agents,
|
||||
connections,
|
||||
events,
|
||||
health,
|
||||
isLoading,
|
||||
lastUpdated,
|
||||
loading,
|
||||
error,
|
||||
lastUpdate,
|
||||
refresh,
|
||||
connected,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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_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 {
|
||||
private baseUrl: string;
|
||||
private token: string;
|
||||
@@ -53,16 +82,25 @@ export class OpenClawGatewayClient {
|
||||
async fetchAgents(): Promise<Agent[]> {
|
||||
try {
|
||||
const data = await this.fetch<GatewayAgentsResponse>('/api/agents');
|
||||
return data.agents.map((agent, index) => ({
|
||||
id: agent.id,
|
||||
name: agent.name,
|
||||
role: this.getRoleFromAgentId(agent.id),
|
||||
status: agent.status === 'active' ? 'active' : agent.status === 'busy' ? 'busy' : 'idle',
|
||||
queue: 0,
|
||||
icon: this.getIconFromIndex(index),
|
||||
x: this.getAgentX(index),
|
||||
y: this.getAgentY(index),
|
||||
}));
|
||||
return data.agents.map((agent) => {
|
||||
const pos = agentPositions.find(p => p.id === agent.id) || { x: 50, y: 50 };
|
||||
const role = roleMap[agent.id] || 'assistant';
|
||||
const status = statusMap[agent.status] || 'idle';
|
||||
|
||||
return {
|
||||
id: agent.id,
|
||||
name: agent.name,
|
||||
role,
|
||||
status,
|
||||
position: { x: pos.x, y: pos.y },
|
||||
connections: this.getDefaultConnections(agent.id),
|
||||
stats: {
|
||||
tasksCompleted: 0,
|
||||
tasksInProgress: 0,
|
||||
tasksBlocked: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
} catch {
|
||||
return this.getDefaultAgents();
|
||||
}
|
||||
@@ -77,41 +115,26 @@ export class OpenClawGatewayClient {
|
||||
}
|
||||
}
|
||||
|
||||
private getRoleFromAgentId(id: string): string {
|
||||
const roles: Record<string, string> = {
|
||||
orchestrator: 'Coordina flujo y delegación',
|
||||
architect: 'Define solución y tareas',
|
||||
coder: 'Implementa cambios',
|
||||
reviewer: 'Revisa calidad y riesgos',
|
||||
docs: 'Documenta decisiones y cambios',
|
||||
assistant: 'Asistente general',
|
||||
private getDefaultConnections(agentId: string): string[] {
|
||||
const connectionsMap: Record<string, string[]> = {
|
||||
erwin: ['bulma', 'rocket', 'sherlock', 'claudia'],
|
||||
bulma: ['rocket', 'sherlock'],
|
||||
rocket: ['sherlock', 'claudia'],
|
||||
sherlock: ['claudia', 'erwin'],
|
||||
hiro: ['erwin'],
|
||||
claudia: ['erwin'],
|
||||
};
|
||||
return roles[id] || 'Agente';
|
||||
}
|
||||
|
||||
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;
|
||||
return connectionsMap[agentId] || [];
|
||||
}
|
||||
|
||||
private getDefaultAgents(): Agent[] {
|
||||
return [
|
||||
{ id: 'erwin', name: 'Erwin', role: 'Orquestador', status: 'active', queue: 0, icon: 'Workflow', x: 10, y: 38 },
|
||||
{ id: 'bulma', name: 'Bulma', role: 'Arquitecto', status: 'active', queue: 0, icon: 'Layers3', x: 36, y: 12 },
|
||||
{ id: 'rocket', name: 'Rocket', role: 'Desarrollador', status: 'busy', queue: 0, icon: 'Bot', x: 36, y: 64 },
|
||||
{ id: 'sherlock', name: 'Sherlock', role: 'Revisor', status: 'idle', queue: 0, icon: 'ShieldCheck', x: 66, y: 24 },
|
||||
{ id: 'hiro', name: 'Hiro', role: 'Diseñador', status: 'idle', queue: 0, icon: 'FileText', x: 66, y: 64 },
|
||||
{ id: 'claudia', name: 'Claudia', role: 'Asistente', status: 'idle', queue: 0, icon: 'Activity', x: 66, y: 50 },
|
||||
{ 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: 'architect', status: 'active', position: { x: 36, y: 12 }, connections: ['rocket'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } },
|
||||
{ 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: 'reviewer', status: 'idle', position: { x: 66, y: 24 }, connections: ['claudia'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } },
|
||||
{ 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: 'assistant', status: 'idle', position: { x: 66, y: 50 }, connections: ['erwin'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user