This commit is contained in:
RGJorge
2026-05-02 04:53:08 +00:00
parent 49c4cde4d7
commit 37a81952c6
6 changed files with 38 additions and 354 deletions
+1 -2
View File
@@ -18,13 +18,12 @@
- `nodes/` — componentes de nodos (ServiceNode, GroupNode) - `nodes/` — componentes de nodos (ServiceNode, GroupNode)
- `panels/` — paneles (DetailPanel) - `panels/` — paneles (DetailPanel)
- `hooks/` — hooks (useDocker) - `hooks/` — hooks (useDocker)
- `engine/` — layout y particulas - `engine/` — layout
- `components/` — componentes generales - `components/` — componentes generales
- `src/server/` — backend Hono - `src/server/` — backend Hono
- `index.ts` — servidor principal, WebSocket, API REST - `index.ts` — servidor principal, WebSocket, API REST
- `docker.ts` — interaccion con Docker - `docker.ts` — interaccion con Docker
- `watcher.ts` — polling de stats y eventos - `watcher.ts` — polling de stats y eventos
- `flows.ts` — flujos de particulas
- `src/shared/` — tipos compartidos - `src/shared/` — tipos compartidos
## Comandos ## Comandos
+8 -9
View File
@@ -55,7 +55,7 @@ Estado actual del proyecto: **v0.1.0** | ~1,774 lineas de codigo | 0 tests | 0 C
- [ ] **Quick start** en 4 lineas o menos - [ ] **Quick start** en 4 lineas o menos
- [ ] **Features** con iconos o emojis descriptivos - [ ] **Features** con iconos o emojis descriptivos
- [ ] **Configuration** (tabla de env vars) - [ ] **Configuration** (tabla de env vars)
- [ ] **Flow simulation** con ejemplo YAML - [ ] **Container actions** (start, stop, restart, rebuild, remove)
- [ ] **MCP integration** (esto es diferenciador, destacarlo) - [ ] **MCP integration** (esto es diferenciador, destacarlo)
- [ ] **Tech stack** (tabla limpia) - [ ] **Tech stack** (tabla limpia)
- [ ] **Contributing** link - [ ] **Contributing** link
@@ -66,8 +66,8 @@ Estado actual del proyecto: **v0.1.0** | ~1,774 lineas de codigo | 0 tests | 0 C
- [ ] Levantar el dashboard con containers de ejemplo - [ ] Levantar el dashboard con containers de ejemplo
- [ ] Grabar un GIF de ~10 segundos mostrando: - [ ] Grabar un GIF de ~10 segundos mostrando:
- Vista general con servicios conectados - Vista general con servicios conectados
- Particulas animadas fluyendo - Metricas en tiempo real (CPU/MEM)
- Metricas en tiempo real - Acciones sobre containers
- [ ] Herramientas recomendadas: `peek` (Linux), `gifski`, o `Kap` (macOS) - [ ] Herramientas recomendadas: `peek` (Linux), `gifski`, o `Kap` (macOS)
- [ ] Guardar en `docs/assets/demo.gif` y referenciar desde README - [ ] Guardar en `docs/assets/demo.gif` y referenciar desde README
- [ ] Alternativa: screenshot estatico como fallback - [ ] Alternativa: screenshot estatico como fallback
@@ -97,7 +97,7 @@ Estado actual del proyecto: **v0.1.0** | ~1,774 lineas de codigo | 0 tests | 0 C
- v0.0.2 — WebSocket, metricas en tiempo real - v0.0.2 — WebSocket, metricas en tiempo real
- v0.0.3 — Nodos visuales, layout React Flow - v0.0.3 — Nodos visuales, layout React Flow
- v0.0.4 — Filtro de proyectos, autenticacion - v0.0.4 — Filtro de proyectos, autenticacion
- v0.0.5 — Flujos animados, MCP server, logs, polish - v0.0.5 — MCP server, logs, acciones de containers, polish
- [ ] De aqui en adelante, actualizar con cada release - [ ] De aqui en adelante, actualizar con cada release
--- ---
@@ -131,12 +131,11 @@ Estado actual del proyecto: **v0.1.0** | ~1,774 lineas de codigo | 0 tests | 0 C
- [ ] Usar `bun:test` (ya viene con Bun, zero config) - [ ] Usar `bun:test` (ya viene con Bun, zero config)
- [ ] Tests prioritarios: - [ ] Tests prioritarios:
- [ ] `src/server/__tests__/docker.test.ts` — parseo de conexiones, deteccion de tipos - [ ] `src/server/__tests__/docker.test.ts` — parseo de conexiones, deteccion de tipos
- [ ] `src/server/__tests__/flows.test.ts` — parseo de flows.yaml, validacion - [ ] `src/server/__tests__/watcher.test.ts` — polling de stats, eventos Docker
- [ ] `src/shared/__tests__/types.test.ts` — validacion de tipos con Zod si aplica - [ ] `src/shared/__tests__/types.test.ts` — validacion de tipos con Zod si aplica
- [ ] `src/client/engine/__tests__/layout.test.ts` — calculo de layout basico - [ ] `src/client/engine/__tests__/layout.test.ts` — calculo de layout basico
- [ ] `src/client/engine/__tests__/particles.test.ts` — motor de particulas
- [ ] Agregar script: `"test": "bun test"` - [ ] Agregar script: `"test": "bun test"`
- [ ] Meta inicial: cubrir la logica de negocio del server (docker.ts, flows.ts) - [ ] Meta inicial: cubrir la logica de negocio del server (docker.ts, watcher.ts)
- [ ] No hace falta 100% coverage, pero si que lo critico este cubierto - [ ] No hace falta 100% coverage, pero si que lo critico este cubierto
### 3.3 Type checking estricto ### 3.3 Type checking estricto
@@ -297,10 +296,10 @@ Estado actual del proyecto: **v0.1.0** | ~1,774 lineas de codigo | 0 tests | 0 C
### 7.2 Publicar en comunidades ### 7.2 Publicar en comunidades
- [ ] **Reddit**: r/selfhosted, r/docker, r/devops, r/opensource - [ ] **Reddit**: r/selfhosted, r/docker, r/devops, r/opensource
- Titulo sugerido: "I built a real-time Docker architecture visualizer with animated data flows" - Titulo sugerido: "I built a real-time Docker architecture visualizer with live metrics"
- Incluir GIF y link al repo - Incluir GIF y link al repo
- [ ] **Hacker News**: Show HN post - [ ] **Hacker News**: Show HN post
- Titulo: "Show HN: DockerFlow Real-time Docker architecture visualization" - Titulo: "Show HN: Flowteon Real-time Docker architecture visualization"
- [ ] **Twitter/X**: Thread con GIF y features - [ ] **Twitter/X**: Thread con GIF y features
- [ ] **Dev.to**: Articulo sobre como se construyo - [ ] **Dev.to**: Articulo sobre como se construyo
- [ ] **Discord**: Servidores de Docker, Bun, React - [ ] **Discord**: Servidores de Docker, Bun, React
+27 -331
View File
@@ -1,6 +1,6 @@
# Alteonx DockerFlow # Alteonx DockerFlow
Herramienta open source para visualizar arquitecturas Docker en tiempo real con partículas animadas que muestran el flujo de datos entre servicios. Herramienta open source para visualizar arquitecturas Docker en tiempo real.
``` ```
git clone github.com/user/alteonx-dockerflow git clone github.com/user/alteonx-dockerflow
@@ -24,7 +24,7 @@ claude mcp add alteonx-dockerflow -- bun run src/mcp.ts
| **Frontend** | React 19 + @xyflow/react 12 | Librería de grafos más madura, nodos custom, edges custom, minimap | | **Frontend** | React 19 + @xyflow/react 12 | Librería de grafos más madura, nodos custom, edges custom, minimap |
| **Styling** | Tailwind v4 | Utility-first, tree-shaking agresivo, sin runtime | | **Styling** | Tailwind v4 | Utility-first, tree-shaking agresivo, sin runtime |
| **Real-time** | Bun WebSocket | Nativo en Bun, zero dependencias, más rápido que socket.io | | **Real-time** | Bun WebSocket | Nativo en Bun, zero dependencias, más rápido que socket.io |
| **Animaciones** | SVG animateMotion + CSS | Sin librerías extra, GPU-accelerated, zero overhead | | **Animaciones** | CSS transitions + keyframes | Sin librerías extra, GPU-accelerated, zero overhead |
| **Docker API** | dockerode | Estándar de facto, tipado, streams | | **Docker API** | dockerode | Estándar de facto, tipado, streams |
| **MCP** | @modelcontextprotocol/sdk | SDK oficial, stdio transport | | **MCP** | @modelcontextprotocol/sdk | SDK oficial, stdio transport |
| **Build** | Vite 6 | HMR instantáneo, tree-shaking, build optimizado | | **Build** | Vite 6 | HMR instantáneo, tree-shaking, build optimizado |
@@ -81,13 +81,13 @@ claude mcp add alteonx-dockerflow -- bun run src/mcp.ts
│ WS /ws │ │ • get_stats │ │ WS /ws │ │ • get_stats │
│ → push eventos │ │ • get_logs │ │ → push eventos │ │ • get_logs │
│ → push stats │ │ • restart_service │ │ → push stats │ │ • restart_service │
│ │ │ • list_flows │ │ │ • inspect_service
│ Docker watcher: │ │ • simulate_flow │ Docker watcher: │ │ • get_networks
│ • listContainers │ │ • create_flow │ • listContainers │ │
│ • getEvents stream │ │ │ • getEvents stream │ │ Resources:
│ • stats polling │ │ Resources: │ • stats polling │ │ • docker://services
│ │ │ • docker://services │ │ │
│ Para: humanos 👀 │ │ • docker://flows │ Para: humanos 👀 │ │
└──────────────────────┘ │ │ └──────────────────────┘ │ │
│ Para: Claude Code 🤖 │ │ Para: Claude Code 🤖 │
└──────────────────────────┘ └──────────────────────────┘
@@ -103,8 +103,7 @@ alteonx-dockerflow/
│ ├── server/ │ ├── server/
│ │ ├── index.ts # Hono server + WebSocket │ │ ├── index.ts # Hono server + WebSocket
│ │ ├── docker.ts # Docker API wrapper (dockerode) │ │ ├── docker.ts # Docker API wrapper (dockerode)
│ │ ── watcher.ts # Docker events stream + stats polling │ │ ── watcher.ts # Docker events stream + stats polling
│ │ └── flows.ts # Flow engine (partículas)
│ ├── mcp/ │ ├── mcp/
│ │ └── index.ts # MCP server (stdio) │ │ └── index.ts # MCP server (stdio)
│ ├── client/ │ ├── client/
@@ -112,22 +111,16 @@ alteonx-dockerflow/
│ │ ├── main.tsx # Entry point │ │ ├── main.tsx # Entry point
│ │ ├── nodes/ │ │ ├── nodes/
│ │ │ ├── ServiceNode.tsx # Nodo de container │ │ │ ├── ServiceNode.tsx # Nodo de container
│ │ │ └── ExternalNode.tsx# Nodo externo (APIs, users) │ │ │ └── GroupNode.tsx # Nodo grupo (compose project)
│ │ ├── edges/
│ │ │ └── ParticleEdge.tsx# Edge con partículas animadas
│ │ ├── panels/ │ │ ├── panels/
│ │ │ ── FlowPanel.tsx # Panel de control de flows │ │ │ ── DetailPanel.tsx # Panel lateral de detalles
│ │ │ ├── StatsPanel.tsx # Panel de stats CPU/MEM
│ │ │ └── LogPanel.tsx # Panel de logs
│ │ ├── hooks/ │ │ ├── hooks/
│ │ │ ├── useDocker.ts # WebSocket hook │ │ │ ├── useDocker.ts # WebSocket hook
│ │ │ └── useFlows.ts # Flow state management │ │ │ └── useStatsStore.ts# Stats por nodo (useSyncExternalStore)
│ │ └── engine/ │ │ └── engine/
│ │ ── particles.ts # Motor de partículas │ │ ── layout.ts # Auto-layout
│ │ └── layout.ts # Auto-layout con dagre
│ └── shared/ │ └── shared/
│ └── types.ts # Tipos compartidos server/client │ └── types.ts # Tipos compartidos server/client
├── flows.yaml # Config de flows (opcional)
├── package.json ├── package.json
├── tsconfig.json ├── tsconfig.json
├── vite.config.ts ├── vite.config.ts
@@ -397,7 +390,7 @@ export function watchDockerEvents(onEvent: (e: DockerEvent) => void) {
} }
``` ```
**Partículas gratis:** cada start/stop/restart se ve como un pulso animado en el nodo. Cada start/stop/restart se ve como un pulso animado (flash) en el nodo.
### 4. Stats polling ### 4. Stats polling
@@ -432,185 +425,6 @@ export async function pollStats(services: Service[]): Promise<Stats[]> {
--- ---
## Flows (simulaciones visuales)
Los flows son recorridos animados que muestran cómo viaja una request o evento por la arquitectura. Son **puramente visuales** y sirven para entender y presentar tu sistema.
### Dos modos
**Modo simulación (MVP, funciona para todos):**
- El usuario define un path: "request pasa por nginx → backend → redis → db"
- Click en "simular" → partícula recorre esa ruta con animación
- Como un diagrama de secuencia pero animado y en el grafo real
- No necesita conectar nada, solo definir el camino
**Modo eventos reales (avanzado, opcional):**
- Requiere un probe (middleware) en los servicios
- El probe emite eventos a Redis/WebSocket
- El dashboard escucha y dispara partículas automáticamente
- Para fase 2+, no MVP
### Configuración de flows — `flows.yaml`
```yaml
# flows.yaml — Define recorridos visuales en tu arquitectura
# Cada flow es un camino que una operación recorre entre servicios
flows:
# Ejemplo genérico: request HTTP
api_request:
name: "API Request"
description: "Request del usuario al backend"
color: "#3b82f6" # azul
speed: 0.8 # segundos por edge
path: [nginx, backend, redis, db, backend, nginx]
# Ejemplo: background job
background_job:
name: "Background Job"
description: "Tarea programada del worker"
color: "#ec4899" # rosa
speed: 1.0
path: [redis, worker, db, worker]
# Ejemplo: cache flow
cache_hit:
name: "Cache Hit"
description: "Request servida desde cache"
color: "#22c55e" # verde
speed: 0.5
path: [nginx, backend, redis, backend, nginx]
cache_miss:
name: "Cache Miss"
description: "Cache miss, va a DB"
color: "#f59e0b" # amarillo
speed: 0.8
path: [nginx, backend, redis, db, redis, backend, nginx]
# Los nombres de servicios deben coincidir con el nombre del container
# (com.docker.compose.service label o container name)
settings:
particle_size: 5 # radio en px
trail: true # estela detrás de la partícula
trail_opacity: 0.3
glow: true # efecto neón
max_particles: 50 # máximo simultáneas
auto_simulate: false # simular automáticamente en idle
auto_simulate_interval: 5 # segundos entre simulaciones auto
```
**Zero config posible:** sin `flows.yaml` el dashboard muestra la arquitectura estática con stats. Los flows son un addon visual opcional.
### Motor de partículas
```tsx
// src/client/engine/particles.ts
interface Particle {
id: string;
flowName: string;
color: string;
path: string[]; // lista de service names
currentStep: number; // índice actual en path
progress: number; // 0-1 progreso en el edge actual
label?: string;
}
class ParticleEngine {
particles: Particle[] = [];
private maxParticles = 50;
spawn(flow: Flow, label?: string) {
if (this.particles.length >= this.maxParticles) return;
this.particles.push({
id: crypto.randomUUID(),
flowName: flow.name,
color: flow.color,
path: flow.path,
currentStep: 0,
progress: 0,
label,
});
}
tick(deltaMs: number, speed: number) {
const step = deltaMs / (speed * 1000);
this.particles = this.particles.filter((p) => {
p.progress += step;
if (p.progress >= 1) {
p.currentStep++;
p.progress = 0;
// Llegó al final → flash en nodo destino → eliminar
if (p.currentStep >= p.path.length - 1) return false;
}
return true;
});
}
}
```
### Partícula visual (SVG sobre React Flow edge)
```tsx
// src/client/edges/ParticleEdge.tsx
import { BaseEdge, getSmoothStepPath, type EdgeProps } from "@xyflow/react";
export function ParticleEdge({ id, sourceX, sourceY, targetX, targetY, ...props }: EdgeProps) {
const [path] = getSmoothStepPath({ sourceX, sourceY, targetX, targetY });
const particles = useParticlesForEdge(id); // del engine
return (
<>
<BaseEdge id={id} path={path} style={{ stroke: "#334155", strokeWidth: 2 }} />
<svg>
<defs>
<filter id="glow">
<feGaussianBlur stdDeviation="3" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
</defs>
{particles.map((p) => (
<g key={p.id}>
{/* Estela */}
<circle r="3" fill={p.color} opacity="0.3" filter="url(#glow)">
<animateMotion dur={`${p.speed}s`} path={path} keyPoints={`${p.progress};${p.progress}`} keyTimes="0;1" fill="freeze" />
</circle>
{/* Partícula principal */}
<circle r="5" fill={p.color} filter="url(#glow)">
<animateMotion dur={`${p.speed}s`} path={path} keyPoints={`${p.progress};${p.progress}`} keyTimes="0;1" fill="freeze" />
</circle>
{/* Label */}
{p.label && (
<text fill="#e2e8f0" fontSize="9" textAnchor="middle" dy="-14">
<animateMotion dur={`${p.speed}s`} path={path} keyPoints={`${p.progress};${p.progress}`} keyTimes="0;1" fill="freeze" />
{p.label}
</text>
)}
</g>
))}
</svg>
</>
);
}
```
### Panel de control de flows
```
┌──────────────────────────────────────────────────────────────┐
│ Flows [⚙️] │
├──────────────────────────────────────────────────────────────┤
│ ● API Request 0/min [ ON ] [ ▶ Simular ] │
│ ● Cache Hit 0/min [ ON ] [ ▶ Simular ] │
│ ● Cache Miss 0/min [ ON ] [ ▶ Simular ] │
│ ● Background Job 0/min [ ON ] [ ▶ Simular ] │
├──────────────────────────────────────────────────────────────┤
│ [ ▶ Simular todos ] [ ⏸ Pausar ] [ + Nuevo flow ] │
└──────────────────────────────────────────────────────────────┘
```
--- ---
## Nodo visual (ServiceNode) ## Nodo visual (ServiceNode)
@@ -707,18 +521,8 @@ import { Hono } from "hono";
import { serveStatic } from "hono/bun"; import { serveStatic } from "hono/bun";
import { discoverServices, discoverConnections } from "./docker"; import { discoverServices, discoverConnections } from "./docker";
import { watchDockerEvents, pollStats } from "./watcher"; import { watchDockerEvents, pollStats } from "./watcher";
import { FlowEngine } from "./flows";
import { parse } from "yaml";
import { readFileSync, existsSync } from "fs";
const app = new Hono(); const app = new Hono();
const flowEngine = new FlowEngine();
// Load flows if exists
if (existsSync("flows.yaml")) {
const config = parse(readFileSync("flows.yaml", "utf-8"));
flowEngine.loadFlows(config.flows || []);
}
// Serve frontend // Serve frontend
app.use("/*", serveStatic({ root: "./dist" })); app.use("/*", serveStatic({ root: "./dist" }));
@@ -726,12 +530,6 @@ app.use("/*", serveStatic({ root: "./dist" }));
// REST endpoints (para MCP y fallback) // REST endpoints (para MCP y fallback)
app.get("/api/services", async (c) => c.json(await discoverServices())); app.get("/api/services", async (c) => c.json(await discoverServices()));
app.get("/api/connections", async (c) => c.json(await discoverConnections())); app.get("/api/connections", async (c) => c.json(await discoverConnections()));
app.get("/api/flows", (c) => c.json(flowEngine.getFlows()));
app.post("/api/flows/:name/simulate", (c) => {
flowEngine.simulate(c.req.param("name"));
return c.json({ ok: true });
});
// WebSocket (Bun native) // WebSocket (Bun native)
const clients = new Set<WebSocket>(); const clients = new Set<WebSocket>();
@@ -747,8 +545,7 @@ Bun.serve({
open(ws) { clients.add(ws); }, open(ws) { clients.add(ws); },
close(ws) { clients.delete(ws); }, close(ws) { clients.delete(ws); },
message(ws, msg) { message(ws, msg) {
const data = JSON.parse(msg.toString()); // Handle subscribe_logs, unsubscribe_logs, etc.
if (data.type === "simulate") flowEngine.simulate(data.flow);
}, },
}, },
}); });
@@ -771,12 +568,6 @@ setInterval(async () => {
broadcast("stats", stats); broadcast("stats", stats);
}, 3000); }, 3000);
// Particle engine tick (60fps)
setInterval(() => {
const particles = flowEngine.tick(16);
if (particles.length > 0) broadcast("particles", particles);
}, 16);
console.log(`Alteonx DockerFlow running on http://${HOST}:${PORT}`); console.log(`Alteonx DockerFlow running on http://${HOST}:${PORT}`);
``` ```
@@ -915,73 +706,6 @@ server.tool("get_networks",
} }
); );
// ── Flow tools ──
server.tool("list_flows",
"List configured flow simulations",
{},
async () => {
// Read flows.yaml
const { readFileSync, existsSync } = await import("fs");
const { parse } = await import("yaml");
if (!existsSync("flows.yaml")) {
return { content: [{ type: "text", text: "No flows.yaml found. Create one to define flow simulations." }] };
}
const config = parse(readFileSync("flows.yaml", "utf-8"));
const flows = Object.entries(config.flows || {}).map(([key, f]: [string, any]) => ({
id: key,
name: f.name,
color: f.color,
path: f.path.join(" → "),
description: f.description || "",
}));
return { content: [{ type: "text", text: JSON.stringify(flows, null, 2) }] };
}
);
server.tool("simulate_flow",
"Trigger a flow simulation on the dashboard",
{ flow: z.string().describe("Flow ID from flows.yaml") },
async ({ flow }) => {
// POST to dashboard API
try {
await fetch(`http://localhost:9470/api/flows/${flow}/simulate`, { method: "POST" });
return { content: [{ type: "text", text: `Simulation triggered: ${flow}` }] };
} catch {
return { content: [{ type: "text", text: "Dashboard not running. Use start_dashboard first." }] };
}
}
);
server.tool("create_flow",
"Add a new flow simulation to flows.yaml",
{
id: z.string().describe("Unique flow ID (snake_case)"),
name: z.string().describe("Display name"),
color: z.string().describe("Hex color for the particle"),
path: z.array(z.string()).describe("Ordered list of service names the flow traverses"),
description: z.string().optional(),
},
async ({ id, name, color, path, description }) => {
const { readFileSync, writeFileSync, existsSync } = await import("fs");
const { parse, stringify } = await import("yaml");
const file = "flows.yaml";
const config = existsSync(file) ? parse(readFileSync(file, "utf-8")) : { flows: {}, settings: {} };
config.flows[id] = {
name,
description: description || "",
color,
speed: 0.8,
path,
};
writeFileSync(file, stringify(config));
return { content: [{ type: "text", text: `Flow "${name}" created: ${path.join(" → ")}` }] };
}
);
// ── Resources ── // ── Resources ──
server.resource("services", "docker://services", async (uri) => ({ server.resource("services", "docker://services", async (uri) => ({
@@ -992,17 +716,6 @@ server.resource("services", "docker://services", async (uri) => ({
}], }],
})); }));
server.resource("flows", "docker://flows", async (uri) => {
const { readFileSync, existsSync } = await import("fs");
return {
contents: [{
uri: uri.href,
mimeType: "text/yaml",
text: existsSync("flows.yaml") ? readFileSync("flows.yaml", "utf-8") : "# No flows configured",
}],
};
});
// ── Start ── // ── Start ──
const transport = new StdioServerTransport(); const transport = new StdioServerTransport();
@@ -1023,8 +736,6 @@ claude mcp add alteonx-dockerflow -- bun run src/mcp/index.ts
> "arranca el dashboard" → start_dashboard > "arranca el dashboard" → start_dashboard
> "qué containers tengo?" → list_services > "qué containers tengo?" → list_services
> "cuánta RAM usa el backend?" → get_stats > "cuánta RAM usa el backend?" → get_stats
> "crea un flow para mi login endpoint" → create_flow
> "simula el login flow" → simulate_flow
> "muéstrame los logs de redis" → get_logs > "muéstrame los logs de redis" → get_logs
``` ```
@@ -1130,15 +841,12 @@ AUTH_TOKEN=mi-clave-super-segura bun run src/server/index.ts
| Tipo | Color | Uso | | Tipo | Color | Uso |
|---|---|---| |---|---|---|
| HTTP request | `#3b82f6` azul | Partículas de requests |
| Cache | `#f59e0b` amarillo | Redis hit/miss/invalidate |
| Database | `#8b5cf6` violeta | Queries a PostgreSQL/MySQL |
| Push/WebSocket | `#22c55e` verde | Notificaciones, eventos real-time |
| Email/external | `#ef4444` rojo | Envíos de email, llamadas API externas |
| Background job | `#ec4899` rosa | Celery, cron, workers |
| Docker event | `#64748b` gris | Container start/stop/restart |
| Nodo running | `#22c55e` verde | Borde + pulso animado | | Nodo running | `#22c55e` verde | Borde + pulso animado |
| Nodo stopped | `#ef4444` rojo | Borde estático | | Nodo stopped | `#ef4444` rojo | Borde estático |
| Nodo paused | `#f59e0b` amarillo | Borde estático |
| Docker event start | `#22c55e` verde | Flash en nodo |
| Docker event stop | `#ef4444` rojo | Flash en nodo |
| Docker event restart | `#f59e0b` amarillo | Flash en nodo |
--- ---
@@ -1199,8 +907,6 @@ El backend siempre envía el campo `project` en cada servicio. El frontend tiene
| `claude mcp add` | Trivial | 10s | | `claude mcp add` | Trivial | 10s |
| "arranca el dashboard" | Trivial | 5s | | "arranca el dashboard" | Trivial | 5s |
| Ver arquitectura + stats | Automático | 0s (auto-discovery) | | Ver arquitectura + stats | Automático | 0s (auto-discovery) |
| Definir flows custom | Fácil (YAML o via MCP) | 2-5 min |
| Conectar probes reales | Intermedio (middleware) | 15-30 min |
**Requisitos del usuario:** **Requisitos del usuario:**
- Docker instalado y corriendo - Docker instalado y corriendo
@@ -1223,31 +929,21 @@ El backend siempre envía el campo `project` en cada servicio. El frontend tiene
- [ ] Docker events (start/stop/restart) como pulso en nodos - [ ] Docker events (start/stop/restart) como pulso en nodos
- [ ] Dark theme, minimap, zoom, pan - [ ] Dark theme, minimap, zoom, pan
### Fase 2 — Flows (partículas) ### Fase 2 — MCP
- [ ] Flow engine con partículas SVG animadas - [ ] MCP server con tools de Docker
- [ ] flows.yaml parser
- [ ] Panel de control de flows (toggle, simulate)
- [ ] Efecto glow/neón en partículas
- [ ] Trail (estela) detrás de cada partícula
### Fase 3 — MCP
- [ ] MCP server con tools de Docker + flows
- [ ] start_dashboard, list_services, get_stats, get_logs, restart - [ ] start_dashboard, list_services, get_stats, get_logs, restart
- [ ] list_flows, simulate_flow, create_flow - [ ] inspect_service, get_networks
- [ ] Resources: docker://services, docker://flows - [ ] Resources: docker://services
- [ ] README con instrucciones de setup - [ ] README con instrucciones de setup
### Fase 4 — Polish ### Fase 3 — Polish
- [ ] Click en nodo → panel lateral con logs en vivo - [ ] Click en nodo → panel lateral con logs en vivo
- [ ] Modo "demo" / auto-simulate para presentaciones - [ ] Notificaciones y alertas
- [ ] Responsive (funcione en tablet) - [ ] Responsive (funcione en tablet)
- [ ] Export PNG/SVG del grafo actual - [ ] Export PNG/SVG del grafo actual
- [ ] Customizar posiciones de nodos (drag + guardar layout) - [ ] Customizar posiciones de nodos (drag + guardar layout)
### Fase 5 — Avanzado ### Fase 4 — Avanzado
- [ ] Probe middleware genérico (npm package para Express/Fastify/Hono)
- [ ] Probe middleware genérico (pip package para FastAPI/Django)
- [ ] Eventos reales → partículas automáticas
- [ ] Health check status en nodos (healthy/unhealthy/starting) - [ ] Health check status en nodos (healthy/unhealthy/starting)
- [ ] Métricas históricas (SQLite para mini-gráficas en cada nodo) - [ ] Métricas históricas (SQLite para mini-gráficas en cada nodo)
- [ ] Alertas cuando un container se cae (webhook/push) - [ ] Alertas cuando un container se cae (webhook/push)
+1 -1
View File
@@ -42,7 +42,7 @@
| Ejemplo | URL | Aplicacion en Flowteon | | Ejemplo | URL | Aplicacion en Flowteon |
|---------|-----|------------------------| |---------|-----|------------------------|
| Animating Edges | https://reactflow.dev/examples/edges/animating-edges | Mejorar animaciones de particulas en edges | | Animating Edges | https://reactflow.dev/examples/edges/animating-edges | Animaciones en edges para indicar actividad |
| Custom Edges | https://reactflow.dev/examples/edges/custom-edges | Ya usamos OffsetEdge custom | | Custom Edges | https://reactflow.dev/examples/edges/custom-edges | Ya usamos OffsetEdge custom |
| Edge Label Renderer | https://reactflow.dev/examples/edges/edge-label-renderer | Mostrar tipo de conexion (db, cache, proxy) como label en el edge | | Edge Label Renderer | https://reactflow.dev/examples/edges/edge-label-renderer | Mostrar tipo de conexion (db, cache, proxy) como label en el edge |
| Floating Edges | https://reactflow.dev/examples/edges/floating-edges | Handles que se mueven con el edge en vez de posicion fija | | Floating Edges | https://reactflow.dev/examples/edges/floating-edges | Handles que se mueven con el edge en vez de posicion fija |
-11
View File
@@ -436,17 +436,7 @@ function Dashboard({ token }: { token: string }) {
totalStats={totalStats} totalStats={totalStats}
/> />
{/* Loading skeleton */}
{services.length === 0 && (
<div className="flex-1 min-h-0 relative m-2 rounded-xl overflow-hidden ring-1 ring-slate-700/60 shadow-[inset_0_2px_12px_rgba(0,0,0,0.5)] flex items-center justify-center gap-8 bg-slate-900">
{[1, 2, 3].map((i) => (
<div key={i} className="w-[220px] h-[140px] rounded-xl bg-slate-800/60 animate-pulse" />
))}
</div>
)}
{/* Canvas — inset */} {/* Canvas — inset */}
{services.length > 0 && (
<div className="flex-1 min-h-0 relative m-2 rounded-xl overflow-hidden ring-1 ring-slate-700/60 shadow-[inset_0_2px_12px_rgba(0,0,0,0.5)]"> <div className="flex-1 min-h-0 relative m-2 rounded-xl overflow-hidden ring-1 ring-slate-700/60 shadow-[inset_0_2px_12px_rgba(0,0,0,0.5)]">
<ReactFlow <ReactFlow
onInit={(instance) => { reactFlowRef.current = instance; }} onInit={(instance) => { reactFlowRef.current = instance; }}
@@ -540,7 +530,6 @@ function Dashboard({ token }: { token: string }) {
/> />
)} )}
</div> </div>
)}
</div> </div>
</StatsStoreContext.Provider> </StatsStoreContext.Provider>
); );
+1
View File
@@ -18,6 +18,7 @@
overflow: hidden !important; overflow: hidden !important;
} }
.react-flow__controls { .react-flow__controls {
border: 1px solid #1e293b !important; border: 1px solid #1e293b !important;
border-radius: 8px !important; border-radius: 8px !important;