Add port 3000 mapping to docker-compose
This commit is contained in:
@@ -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`)"
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 } },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user