This commit is contained in:
RGJorge
2026-04-09 21:50:42 +00:00
parent 8001bbf582
commit 7bd9ed207f
3 changed files with 618 additions and 1 deletions
+234
View File
@@ -0,0 +1,234 @@
# Plan: Monitoreo Multi-Host Docker
## Objetivo
Permitir que una sola instancia de DockerFlow se conecte a múltiples Docker daemons (local + remotos) y visualice todos los contenedores en un solo dashboard, agrupados por host.
## Enfoque
Usar la API TCP de Docker con TLS. No se instala nada adicional en los servidores remotos — solo se configura el Docker daemon para aceptar conexiones TCP.
---
## Fase 1: Configuración de hosts
### 1.1 Variable de entorno `DOCKER_HOSTS`
```bash
# Formato: nombre=tipo://dirección, separados por coma
# El host local usa socket, los remotos usan tcp+tls
DOCKER_HOSTS="local=unix:///var/run/docker.sock,server-a=tcp://192.168.1.10:2376,server-b=tcp://192.168.1.20:2376"
```
Si `DOCKER_HOSTS` no está definido, comportamiento actual (solo socket local). Retrocompatible.
### 1.2 Certificados TLS
```bash
# Directorio de certs por host
DOCKER_CERTS_DIR=./certs
# Estructura:
# certs/
# server-a/
# ca.pem
# cert.pem
# key.pem
# server-b/
# ca.pem
# cert.pem
# key.pem
```
---
## Fase 2: Cambios en Backend
### 2.1 `src/server/docker.ts` — Multi-host connections
**Actual:** Una sola instancia de `dockerode` hardcodeada.
```ts
const docker = new Docker({ socketPath: "/var/run/docker.sock" });
```
**Nuevo:** Map de instancias `dockerode` por host.
```ts
interface DockerHost {
name: string;
client: Docker;
}
function createHosts(): DockerHost[] {
const hostsEnv = process.env.DOCKER_HOSTS;
if (!hostsEnv) {
return [{ name: "local", client: new Docker({ socketPath: "/var/run/docker.sock" }) }];
}
const certsDir = process.env.DOCKER_CERTS_DIR || "./certs";
return hostsEnv.split(",").map((entry) => {
const [name, url] = entry.split("=");
if (url.startsWith("unix://")) {
return { name, client: new Docker({ socketPath: url.replace("unix://", "") }) };
}
// tcp://host:port
const { hostname, port } = new URL(url.replace("tcp://", "https://"));
return {
name,
client: new Docker({
host: hostname,
port: parseInt(port),
ca: fs.readFileSync(`${certsDir}/${name}/ca.pem`),
cert: fs.readFileSync(`${certsDir}/${name}/cert.pem`),
key: fs.readFileSync(`${certsDir}/${name}/key.pem`),
}),
};
});
}
export const dockerHosts = createHosts();
```
### 2.2 `src/shared/types.ts` — Agregar campo `host`
```ts
export interface Service {
// ... campos existentes ...
host: string; // nombre del host (ej: "local", "server-a")
}
```
### 2.3 `discoverServices()` — Iterar sobre todos los hosts
```ts
export async function discoverServices(all, projects): Promise<Service[]> {
const allServices: Service[] = [];
for (const { name, client } of dockerHosts) {
const containers = await client.listContainers({ all: true });
const services = containers.map((c) => ({
// ... mapeo actual ...
host: name,
uid: `${name}/${project}/${serviceName}`, // incluir host en uid
}));
allServices.push(...services);
}
// filtrar por projects...
return allServices;
}
```
### 2.4 `discoverConnections()` — Conexiones solo dentro del mismo host
Las conexiones por red compartida solo aplican entre contenedores del mismo host. Agregar filtro:
```ts
// Solo conectar servicios del mismo host
if (app.host !== infra.host) continue;
```
### 2.5 `getContainerLogs()` y `streamContainerLogs()` — Resolver host
Actualmente usan `docker.getContainer(id)`. Cambiar para recibir el host y usar el client correcto:
```ts
export async function getContainerLogs(hostName: string, id: string, tail = 200) {
const host = dockerHosts.find(h => h.name === hostName);
const container = host.client.getContainer(id);
// ... resto igual ...
}
```
### 2.6 `src/server/watcher.ts` — Stats y eventos multi-host
`pollStats` y `watchDockerEvents` deben iterar sobre todos los hosts. Cada host tiene su propio stream de eventos.
---
## Fase 3: Cambios en Frontend
### 3.1 Agrupación visual por host
- Usar un **borde/fondo coloreado** alrededor de los nodos de cada host
- Mostrar label del host sobre cada grupo
- Colores distintos por host (auto-asignados)
### 3.2 Sidebar/filtro por host
- Agregar selector de host en la UI para filtrar la vista
- Opción "Todos" para ver todo junto
### 3.3 Panel de stats
- Mostrar en qué host está cada contenedor
- Badge con el nombre del host en cada nodo del grafo
---
## Fase 4: Documentación de setup remoto
### Guía para configurar un Docker daemon remoto
En el servidor remoto:
```bash
# 1. Generar certificados (una vez)
# Usar el script que incluiremos en tools/generate-certs.sh
# 2. Editar /etc/docker/daemon.json
{
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"],
"tls": true,
"tlscacert": "/etc/docker/ssl/ca.pem",
"tlscert": "/etc/docker/ssl/server-cert.pem",
"tlskey": "/etc/docker/ssl/server-key.pem",
"tlsverify": true
}
# 3. Reiniciar Docker
sudo systemctl restart docker
```
### Script de generación de certs
Incluir `tools/generate-certs.sh` que genere CA + server cert + client cert.
---
## Archivos a modificar
| Archivo | Cambio |
|---------|--------|
| `src/server/docker.ts` | Multi-host connections, refactor todas las funciones |
| `src/server/watcher.ts` | Stats y eventos por host |
| `src/server/index.ts` | Pasar host en log subscribe/unsubscribe |
| `src/shared/types.ts` | Campo `host` en Service, WSMessage updates |
| `src/client/App.tsx` | Agrupación visual, filtros, badges |
| `src/client/components/*` | Nodos con indicador de host |
## Archivos nuevos
| Archivo | Descripción |
|---------|-------------|
| `tools/generate-certs.sh` | Script para generar certificados TLS |
---
## Orden de implementación
1. Types (`host` field) — 5 min
2. `docker.ts` multi-host — core del cambio
3. `watcher.ts` multi-host
4. `index.ts` ajustes WebSocket
5. Frontend: badges y agrupación
6. Script de certs + docs
7. Testing con host local (simular con socket duplicado)
## Consideraciones
- **Retrocompatible**: sin `DOCKER_HOSTS`, funciona exactamente igual que ahora
- **Seguridad**: nunca TCP sin TLS, los certs son obligatorios para hosts remotos
- **Performance**: cada host se consulta en paralelo con `Promise.all`
- **Errores**: si un host remoto no responde, mostrar el host como "offline" sin afectar los demás
+351
View File
@@ -0,0 +1,351 @@
# Roadmap para publicar DockerFlow como Open Source
Estado actual del proyecto: **v0.1.0** | ~1,774 lineas de codigo | 0 tests | 0 CI/CD | Sin licencia formal
---
## Fase 1 — Fundamentos legales y limpieza
> Sin esto, nadie puede usar tu codigo legalmente ni contribuir con confianza.
### 1.1 Crear archivo LICENSE
- [ ] Crear `LICENSE` en la raiz con el texto completo de MIT
- [ ] El README ya dice "MIT" al final, pero sin el archivo no tiene validez legal
- [ ] Opciones alternativas si cambias de opinion:
- **MIT** — maxima adopcion, cualquiera puede hacer lo que quiera
- **Apache 2.0** — como MIT pero protege contra demandas de patentes
- **GPL v3** — obliga a que los forks tambien sean open source
### 1.2 Auditar secretos en el historial de git
- [ ] Verificar que `.env` nunca fue commiteado (HECHO: confirmado limpio)
- [ ] Verificar que no hay tokens, passwords o claves hardcodeados en el codigo
- [ ] Buscar en el historial: `git log -p --all -S 'AUTH_TOKEN' -- '*.ts'`
- [ ] Buscar en el historial: `git log -p --all -S 'password' -- '*.ts'`
- [ ] Si se encuentra algo comprometido, considerar `git filter-branch` o `bfg` para limpiar
### 1.3 Eliminar archivos internos del repo publico
- [ ] Eliminar `tareas/completadas/` — son notas internas de desarrollo, no aportan al usuario final
- [ ] Eliminar `docker-project.md` — documento de planificacion interna
- [ ] Decidir sobre `PLAN-MULTI-HOST.md` — puede quedarse como roadmap publico o moverse a GitHub Issues/Projects
- [ ] Eliminar `.claude/settings.local.json` si contiene paths locales
- [ ] Actualizar `.gitignore` para excluir `tareas/` y documentos internos futuros
### 1.4 Limpiar configuracion local
- [ ] Verificar que `.dockerflow-positions.json` esta en `.gitignore` (esta)
- [ ] Verificar que `.env` esta en `.gitignore` (esta)
- [ ] Agregar a `.gitignore`: `PUBLICAR.md`, `tareas/`, `docker-project.md`
---
## Fase 2 — Documentacion para la comunidad
> La documentacion es la primera impresion. Un proyecto sin docs claras no recibe contribuciones.
### 2.1 README en ingles (idioma principal)
- [ ] Crear `README.md` en ingles como version principal
- [ ] Mover el README actual a `README.es.md` y linkear desde el principal
- [ ] Incluir en el README:
- [ ] **Hero section**: nombre, descripcion de una linea, badges (license, version, bun)
- [ ] **Screenshot/GIF** del dashboard funcionando (esto es CRITICO para adopcion)
- [ ] **Quick start** en 4 lineas o menos
- [ ] **Features** con iconos o emojis descriptivos
- [ ] **Configuration** (tabla de env vars)
- [ ] **Flow simulation** con ejemplo YAML
- [ ] **MCP integration** (esto es diferenciador, destacarlo)
- [ ] **Tech stack** (tabla limpia)
- [ ] **Contributing** link
- [ ] **License** badge + link
### 2.2 Captura de pantalla / GIF del dashboard
- [ ] Levantar el dashboard con containers de ejemplo
- [ ] Grabar un GIF de ~10 segundos mostrando:
- Vista general con servicios conectados
- Particulas animadas fluyendo
- Metricas en tiempo real
- [ ] Herramientas recomendadas: `peek` (Linux), `gifski`, o `Kap` (macOS)
- [ ] Guardar en `docs/assets/demo.gif` y referenciar desde README
- [ ] Alternativa: screenshot estatico como fallback
### 2.3 CONTRIBUTING.md
- [ ] Crear `CONTRIBUTING.md` con:
- [ ] Requisitos: Bun >= 1.0, Docker corriendo
- [ ] Setup del entorno de desarrollo (`bun install && bun run dev`)
- [ ] Estructura del proyecto (breve, linkear a README)
- [ ] Convenciones de codigo (TypeScript estricto, sin `any`, imports absolutos)
- [ ] Proceso de PRs: fork → branch → PR con descripcion
- [ ] Issues: como reportar bugs, como proponer features
- [ ] Commits: formato convencional (`feat:`, `fix:`, `docs:`)
### 2.4 CODE_OF_CONDUCT.md
- [ ] Adoptar Contributor Covenant v2.1 (estandar de la industria)
- [ ] Copiar de https://www.contributor-covenant.org/
- [ ] Personalizar email de contacto
### 2.5 CHANGELOG.md
- [ ] Crear `CHANGELOG.md` siguiendo formato Keep a Changelog
- [ ] Documentar retroactivamente las versiones existentes:
- v0.0.1 — Setup inicial, descubrimiento Docker
- v0.0.2 — WebSocket, metricas en tiempo real
- v0.0.3 — Nodos visuales, layout React Flow
- v0.0.4 — Filtro de proyectos, autenticacion
- v0.0.5 — Flujos animados, MCP server, logs, polish
- [ ] De aqui en adelante, actualizar con cada release
---
## Fase 3 — Calidad de codigo
> Da confianza a los contribuidores y previene regresiones.
### 3.1 Configurar linter + formatter
- [ ] Instalar Biome (rapido, todo-en-uno, compatible con Bun):
```bash
bun add -d @biomejs/biome
bunx biome init
```
- [ ] Configurar reglas en `biome.json`:
- Formatter: tabs/spaces, ancho de linea
- Linter: reglas recomendadas de TypeScript + React
- Organizar imports automaticamente
- [ ] Agregar scripts a `package.json`:
```json
"lint": "biome check src/",
"lint:fix": "biome check --write src/",
"format": "biome format --write src/"
```
- [ ] Ejecutar `bun run lint:fix` una vez para normalizar todo el codigo
- [ ] Commit con mensaje: `chore: configure biome linter and format codebase`
### 3.2 Agregar tests minimos
- [ ] Usar `bun:test` (ya viene con Bun, zero config)
- [ ] Tests prioritarios:
- [ ] `src/server/__tests__/docker.test.ts` — parseo de conexiones, deteccion de tipos
- [ ] `src/server/__tests__/flows.test.ts` — parseo de flows.yaml, validacion
- [ ] `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__/particles.test.ts` — motor de particulas
- [ ] Agregar script: `"test": "bun test"`
- [ ] Meta inicial: cubrir la logica de negocio del server (docker.ts, flows.ts)
- [ ] No hace falta 100% coverage, pero si que lo critico este cubierto
### 3.3 Type checking estricto
- [ ] Verificar que `bun run build` no genera errores de TypeScript
- [ ] Agregar script: `"typecheck": "tsc --noEmit"`
- [ ] Corregir cualquier error que aparezca
---
## Fase 4 — CI/CD con GitHub Actions
> Automatiza la verificacion. Cada PR debe pasar lint + tests + build.
### 4.1 Workflow de CI basico
- [ ] Crear `.github/workflows/ci.yml`:
```yaml
name: CI
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install
- run: bun run lint
- run: bun run typecheck
- run: bun run test
- run: bun run build
```
- [ ] Verificar que pasa en la primera ejecucion
- [ ] Agregar badge de CI al README
### 4.2 (Opcional) Release automatizado
- [ ] Configurar workflow de release al pushear tags:
```yaml
on:
push:
tags: ['v*']
```
- [ ] Generar GitHub Release con changelog automatico
- [ ] Considerar `changesets` o `release-please` para automatizar versiones
---
## Fase 5 — Distribucion y Docker
> Facilitar que la gente lo pruebe sin clonar el repo.
### 5.1 Dockerfile
- [ ] Crear `Dockerfile` multi-stage:
```dockerfile
# Build
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lock* ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
# Run
FROM oven/bun:1-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/src/server ./src/server
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json .
EXPOSE 9470
CMD ["bun", "run", "start"]
```
- [ ] Crear `.dockerignore` (node_modules, .git, tareas, etc.)
- [ ] Testear localmente: `docker build -t dockerflow . && docker run -v /var/run/docker.sock:/var/run/docker.sock -p 9470:9470 dockerflow`
### 5.2 docker-compose.yml de ejemplo
- [ ] Crear `docker-compose.yml` para que los usuarios levanten con un comando:
```yaml
services:
dockerflow:
image: ghcr.io/rgjorge/dockerflow:latest
ports:
- "9470:9470"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- AUTH_TOKEN=${AUTH_TOKEN:-}
```
### 5.3 Publicar imagen en GitHub Container Registry
- [ ] Crear workflow `.github/workflows/docker.yml` para build + push automatico
- [ ] Publicar en `ghcr.io/rgjorge/dockerflow`
- [ ] Tags: `latest`, `v0.1.0`, `v0.1`, `v0`
- [ ] Documentar en README el one-liner de Docker
---
## Fase 6 — Preparacion del repositorio
> Detalles finales antes de hacer el repo publico.
### 6.1 GitHub repo settings
- [ ] Descripcion del repo: "Real-time Docker architecture visualization dashboard"
- [ ] Topics: `docker`, `monitoring`, `dashboard`, `visualization`, `devtools`, `bun`, `react`, `mcp`
- [ ] Website: URL del repo o demo si la hay
- [ ] Habilitar Issues
- [ ] Habilitar Discussions (opcional, bueno para comunidad)
- [ ] Configurar branch protection en `main`:
- Require PR reviews
- Require status checks (CI)
- No force push
### 6.2 Issue templates
- [ ] Crear `.github/ISSUE_TEMPLATE/bug_report.md`
- [ ] Crear `.github/ISSUE_TEMPLATE/feature_request.md`
- [ ] Crear `.github/PULL_REQUEST_TEMPLATE.md`
### 6.3 Issues iniciales como roadmap publico
- [ ] Crear issues con label `good first issue` para atraer contribuidores:
- "Add dark/light theme toggle"
- "Support podman as alternative to Docker"
- "Add container restart/stop actions from UI"
- "Export dashboard as PNG/SVG"
- [ ] Crear issues con label `enhancement` del roadmap:
- "Multi-host Docker monitoring (TCP/TLS)"
- "Container health check visualization"
- "Custom node colors/icons per service type"
- [ ] Convertir `PLAN-MULTI-HOST.md` en un issue detallado
### 6.4 Crear GitHub Release v0.1.0
- [ ] Tag: `v0.1.0`
- [ ] Titulo: "DockerFlow v0.1.0 — Initial Public Release"
- [ ] Body: features principales, screenshot, instrucciones de instalacion
- [ ] Esto reemplaza los tags internos v0.0.x
---
## Fase 7 — Lanzamiento y difusion
> El codigo listo no sirve si nadie lo ve.
### 7.1 Preparar assets de lanzamiento
- [ ] GIF/screenshot de alta calidad del dashboard
- [ ] Descripcion corta (1 parrafo) para copiar/pegar en redes
- [ ] Lista de features destacadas (3-5 bullet points)
### 7.2 Publicar en comunidades
- [ ] **Reddit**: r/selfhosted, r/docker, r/devops, r/opensource
- Titulo sugerido: "I built a real-time Docker architecture visualizer with animated data flows"
- Incluir GIF y link al repo
- [ ] **Hacker News**: Show HN post
- Titulo: "Show HN: DockerFlow Real-time Docker architecture visualization"
- [ ] **Twitter/X**: Thread con GIF y features
- [ ] **Dev.to**: Articulo sobre como se construyo
- [ ] **Discord**: Servidores de Docker, Bun, React
- [ ] **Product Hunt**: Si quieres traccion con publico mas amplio
### 7.3 Post-lanzamiento
- [ ] Monitorear issues y PRs las primeras 48-72 horas
- [ ] Responder rapidamente a las primeras contribuciones (esto define la cultura)
- [ ] Agregar un "Star History" badge al README despues de ganar traccion
- [ ] Considerar crear un sitio web/landing page si hay interes
---
## Orden de ejecucion recomendado
| Prioridad | Tarea | Esfuerzo | Impacto |
|-----------|-------|----------|---------|
| 1 | Licencia MIT | 5 min | Critico |
| 2 | Limpiar archivos internos | 10 min | Alto |
| 3 | Screenshot/GIF del dashboard | 20 min | Critico |
| 4 | README en ingles | 1-2 hrs | Critico |
| 5 | CONTRIBUTING + CODE_OF_CONDUCT | 30 min | Alto |
| 6 | Biome linter + format | 30 min | Medio |
| 7 | Tests minimos (bun:test) | 2-3 hrs | Alto |
| 8 | GitHub Actions CI | 30 min | Alto |
| 9 | Dockerfile + compose | 1 hr | Alto |
| 10 | CHANGELOG retroactivo | 30 min | Medio |
| 11 | Issue templates + good first issues | 30 min | Medio |
| 12 | GitHub Release v0.1.0 | 15 min | Alto |
| 13 | Difusion en comunidades | 1-2 hrs | Critico |
**Tiempo total estimado: 8-12 horas de trabajo**
---
## Checklist final antes de hacer publico
- [ ] `LICENSE` existe y es MIT
- [ ] No hay secretos en el codigo ni en el historial de git
- [ ] No hay archivos internos/personales en el repo
- [ ] README en ingles con screenshot/GIF
- [ ] CONTRIBUTING.md existe
- [ ] Al menos 1 test pasa
- [ ] `bun run build` funciona sin errores
- [ ] CI pasa en verde
- [ ] GitHub Release creada
- [ ] Listo para compartir el link
+33 -1
View File
@@ -13,7 +13,7 @@ import {
type NodeChange,
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";
import { Wifi, WifiOff, ChevronDown, Check, Lock, LogOut, Eye, EyeOff, Terminal, Database, Zap, Radio, Globe } from "lucide-react";
import { Wifi, WifiOff, ChevronDown, Check, Lock, LogOut, Eye, EyeOff, Terminal, Database, Zap, Radio, Globe, Cpu, MemoryStick } from "lucide-react";
import { ServiceNode } from "./nodes/ServiceNode";
import { GroupNode } from "./nodes/GroupNode";
@@ -600,6 +600,21 @@ function Dashboard({ token }: { token: string }) {
const runningCount = filteredServices.filter((s) => s.state === "running").length;
// Total resource consumption
const totalStats = useMemo(() => {
let cpu = 0;
let mem = 0;
for (const svc of filteredServices) {
const s = stats.get(svc.uid);
if (s) {
cpu += s.cpu;
mem += s.mem_mb;
}
}
return { cpu, mem };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filteredServices, statsVersion]);
// Highlight edges connected to selected node, dim the rest
const connectedNodeIds = useMemo(() => {
if (!selectedNode) return null;
@@ -662,6 +677,23 @@ function Dashboard({ token }: { token: string }) {
<span className="text-xs text-slate-600 font-mono bg-slate-800 px-2 py-0.5 rounded">
v0.1
</span>
{/* Total resource usage */}
{totalStats.cpu > 0 && (
<div className="flex items-center gap-3 ml-2 text-xs font-mono bg-slate-800/80 border border-slate-700/50 px-3 py-1 rounded-md">
<div className="flex items-center gap-1.5">
<Cpu size={12} className="text-cyan-500" />
<span className="text-cyan-400">{totalStats.cpu.toFixed(1)}%</span>
</div>
<div className="w-px h-3 bg-slate-700" />
<div className="flex items-center gap-1.5">
<MemoryStick size={12} className="text-violet-500" />
<span className="text-violet-400">
{totalStats.mem >= 1024 ? `${(totalStats.mem / 1024).toFixed(1)} GB` : `${totalStats.mem.toFixed(0)} MB`}
</span>
</div>
</div>
)}
</div>
<div className="flex items-center gap-5">