mirror of
https://github.com/RGJorge/ContainerFlow.git
synced 2026-08-03 07:21:42 +02:00
v0.0.33
This commit is contained in:
@@ -99,6 +99,8 @@ bun run start
|
||||
- **Ejecutar comandos** — terminal inline (`docker exec`) desde el DetailPanel con output, sin abrir SSH ni terminal externa
|
||||
- **Toast de errores** — cuando una accion falla (rebuild que rompe, exec con exit code != 0, etc.) aparece un toast top-right con el error completo, copiable al clipboard
|
||||
- **Control de acceso por path** — variable `ALLOWED_PATHS` permite restringir acciones a containers cuyo compose file este bajo rutas especificas. Ideal para servidores compartidos: ves todo, solo tocas lo tuyo. Los containers fuera de las rutas aparecen con candado
|
||||
- **Recomendaciones de configuracion Docker** — banners de aviso en el DetailPanel cuando un container tiene config sub-optima: sin limite de memoria, sin limite de CPU, sin restart policy (`unless-stopped` recomendado). Ayuda al usuario a adoptar mejores practicas de Docker sin tener que recordarlas
|
||||
- **Volumenes y mounts** — DetailPanel lista cada mount del container: tipo (bind / volume / tmpfs), source en el host, destination en el container, modo rw/ro. Util para debugging ("donde estan mis datos?", "es read-only?", "es persistente?")
|
||||
- **Filtro de proyectos** — dropdown para mostrar/ocultar proyectos, persiste entre sesiones
|
||||
- **Autenticacion** — pantalla de login con AUTH_TOKEN para acceso remoto seguro
|
||||
- **Leyenda de conexiones** — colores por tipo: Database (azul), Cache (rojo), Broker (naranja), Proxy (verde)
|
||||
@@ -109,6 +111,54 @@ bun run start
|
||||
- **Umbrales por contenedor** — overrides personalizados de CPU/MEM (con fallback a umbrales globales) y toggle de notificaciones por servicio
|
||||
- **Pagina de settings** — configuracion de la aplicacion (auth, Discord, hosts Docker)
|
||||
|
||||
## Mejores prácticas
|
||||
|
||||
ContainerFlow no solo monitorea: detecta configuración sub-óptima de Docker y la marca con un banner ámbar en el DetailPanel del container afectado. La idea es ayudarte a adoptar buenas prácticas sin tener que recordarlas tú.
|
||||
|
||||
### Recomendaciones activas (warnings automáticos)
|
||||
|
||||
| Detección | Por qué importa | Cómo se ve en ContainerFlow |
|
||||
|---|---|---|
|
||||
| **Sin `memory_limit`** | Un container sin tope de RAM puede acaparar toda la memoria del host y tumbar a los demás (incluido el daemon). El kernel hace OOM kill aleatorio bajo presión. | Banner: "Sin límite de memoria configurado en Docker" |
|
||||
| **Sin `cpu_quota`** | Similar al de memoria — un container puede saturar todos los núcleos. En multi-tenant esto es crítico, en single-tenant degrada la responsividad del host. | Banner: "Sin límite de CPU configurado en Docker" |
|
||||
| **`restart: no` o vacío** | Si el proceso muere, el container queda muerto. En producción casi siempre quieres `unless-stopped` (reinicia si crashea, **NO** si lo paraste manualmente). | Banner: "Restart policy: none — el contenedor no se reiniciará automáticamente si se detiene" |
|
||||
|
||||
### Configuración recomendada (template)
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml — buenas prácticas
|
||||
services:
|
||||
mi-app:
|
||||
image: mi-app:latest
|
||||
restart: unless-stopped # ← reinicia tras crashes, respeta stops manuales
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5" # ← máximo medio núcleo
|
||||
memory: 256M # ← tope absoluto, evita OOM del host
|
||||
healthcheck: # ← detecta apps "vivas pero rotas"
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
```
|
||||
|
||||
### Por qué ContainerFlow hace esto
|
||||
|
||||
La mayoría de tutoriales de Docker no mencionan estas configuraciones porque "funciona sin ellas". Pero en producción son la diferencia entre:
|
||||
|
||||
- **Sin límites**: un memory leak en un servicio tumba a TODO el servidor
|
||||
- **Con límites**: el container se mata a sí mismo, el resto sigue vivo, las restart policies lo reviven
|
||||
|
||||
ContainerFlow te lo recuerda visualmente cada vez que abres el DetailPanel — no es spam, es contexto educativo solo donde aplica.
|
||||
|
||||
### En roadmap
|
||||
|
||||
- **Healthcheck recommendations**: detectar containers sin `HEALTHCHECK` y sugerir uno contextual según la imagen (postgres → `pg_isready`, redis → `redis-cli ping`, http app → `curl /health`, etc.)
|
||||
- **Mounts no persistentes**: warning cuando una DB usa `tmpfs` o bind a directorio efímero
|
||||
- **Versión latest**: warning cuando un container usa `image:latest` (no reproducible)
|
||||
|
||||
## Monitoreo e historial
|
||||
|
||||
ContainerFlow guarda un historial de métricas y notifica eventos importantes a Discord.
|
||||
|
||||
+86
-25
@@ -1,34 +1,46 @@
|
||||
# ContainerFlow — Roadmap de Monitoreo
|
||||
|
||||
## Fase 1: Acciones básicas
|
||||
- [ ] Stop / Start / Restart desde el DetailPanel
|
||||
- [ ] Confirmación antes de ejecutar acciones destructivas (stop/restart)
|
||||
- [ ] Feedback visual del estado de la acción (loading, success, error)
|
||||
- [ ] Rebuild (docker compose up --build) por servicio
|
||||
> Actualizado tras integración de eventos persistentes, notificaciones in-app,
|
||||
> control de acceso por path y corrección de cálculo de memoria real.
|
||||
|
||||
## Fase 2: Notificaciones
|
||||
- [ ] Webhooks configurables (Discord, Slack)
|
||||
- [ ] PWA — Service Worker + manifest
|
||||
- [ ] Push notifications cuando un contenedor cae o health check falla
|
||||
- [ ] Panel de configuración de notificaciones en la UI
|
||||
## Fase 1: Acciones básicas ✅ COMPLETA
|
||||
- [x] Stop / Start / Restart desde el DetailPanel
|
||||
- [x] Confirmación antes de ejecutar acciones destructivas (stop/restart/remove/rebuild/recreate)
|
||||
- [x] Feedback visual del estado de la acción (loading, success, error) — toast top-right con copy/expand
|
||||
- [x] Rebuild (docker compose up --build) por servicio
|
||||
- [x] **Bonus**: Recreate (docker compose up --force-recreate) para aplicar cambios de compose sin rebuild
|
||||
|
||||
## Fase 3: Historial y métricas
|
||||
- [ ] SQLite para persistir stats (CPU, RAM, network I/O)
|
||||
- [ ] Gráficas temporales de CPU/RAM por servicio (últimas 1h, 6h, 24h, 7d)
|
||||
- [ ] Dashboard de métricas agregadas
|
||||
- [ ] Retención configurable (auto-limpiar datos viejos)
|
||||
## Fase 2: Notificaciones ✅ MAYORMENTE COMPLETA
|
||||
- [x] Webhooks configurables — **Discord implementado**, Slack pendiente (mismo patrón aplicaría)
|
||||
- [ ] PWA — Service Worker + manifest (no implementado, depende de habilitar PWA web notifications)
|
||||
- [x] Push notifications cuando un contenedor cae o health check falla — vía Discord + in-app
|
||||
- [x] Panel de configuración de notificaciones en la UI (SettingsPage → Discord section)
|
||||
- [x] **Bonus**: Notificaciones in-app (SQLite persistente, bell del header con read/unread, tab dedicado en Monitoring)
|
||||
- [x] **Bonus**: Debounce de 15s para detectar restart vs stop+start
|
||||
- [x] **Bonus**: Cooldown configurable + "Still Down" reminder
|
||||
|
||||
## Fase 4: Alertas
|
||||
- [ ] Reglas de alertas (ej: CPU > 80% por 5 min)
|
||||
- [ ] Historial de alertas disparadas
|
||||
- [ ] Integración con notificaciones (Fase 2)
|
||||
- [ ] Alertas por health check fallido
|
||||
## Fase 3: Historial y métricas ✅ COMPLETA
|
||||
- [x] SQLite para persistir stats — CPU/RAM (network I/O pendiente)
|
||||
- [x] Gráficas temporales de CPU/RAM por servicio (1h, 6h, 24h, 7d) con buckets agregados
|
||||
- [x] Dashboard de métricas agregadas — MonitoringTotalsCard (CPU/MEM total por filtro)
|
||||
- [x] Retención configurable — 7d default, auto-limpieza horaria + VACUUM
|
||||
- [x] **Bonus**: Filtro per-servicio en monitoring (override del range global)
|
||||
- [x] **Bonus**: Filtros por proyecto/servicio con botón reset
|
||||
- [x] **Bonus**: Cálculo de memoria real (resta page cache active+inactive, no solo inactive como `docker stats`)
|
||||
|
||||
## Fase 4: Alertas ✅ MAYORMENTE COMPLETA
|
||||
- [ ] Reglas de alertas con duración (ej: CPU > 80% **por 5 min**) — parcialmente: alertas inmediatas al cruzar threshold, no soporte de duración sostenida
|
||||
- [x] Historial de alertas disparadas — notifications_log en SQLite, tab Notificaciones
|
||||
- [x] Integración con notificaciones (Fase 2) — Discord + in-app
|
||||
- [x] Alertas por health check fallido — vía `health_status` event
|
||||
- [x] **Bonus**: Thresholds por contenedor con override del global (CPU% y MEM%)
|
||||
- [x] **Bonus**: Color amber en progress bars cuando supera threshold (dashboard ServiceNode + DetailPanel)
|
||||
|
||||
## Fase 5: Info avanzada
|
||||
- [ ] Volumes/Mounts por contenedor
|
||||
- [ ] Terminal interactiva (docker exec) desde la UI
|
||||
- [ ] Network inspector (tráfico entre servicios)
|
||||
- [ ] Image layers y tamaño
|
||||
- [ ] Volumes/Mounts por contenedor en DetailPanel (no implementado)
|
||||
- [x] Terminal interactiva (docker exec) desde la UI — botón Exec en DetailPanel
|
||||
- [ ] Network inspector (tráfico entre servicios) — parcial: conexiones visualizadas en grafo, no hay info de tráfico
|
||||
- [ ] Image layers y tamaño (no implementado)
|
||||
|
||||
## Fase 6: Deployment (futuro)
|
||||
- [ ] GitHub integration (webhook + clone + build)
|
||||
@@ -36,4 +48,53 @@
|
||||
- [ ] Deploy management (docker-compose dinámico)
|
||||
- [ ] Domain routing automático (Traefik/Caddy)
|
||||
- [ ] Rollbacks (mantener imágenes anteriores)
|
||||
- [ ] Env var editing + rebuild desde la UI
|
||||
- [ ] Env var editing + rebuild desde la UI — parcial: env vars visibles en DetailPanel, edición pendiente
|
||||
|
||||
---
|
||||
|
||||
## Features adicionales no contempladas en el roadmap original
|
||||
|
||||
### Seguridad / Multi-usuario
|
||||
- [x] `ALLOWED_PATHS` env var para limitar acciones a paths específicos
|
||||
- [x] `ALLOW_NON_COMPOSE` para containers `docker run` directo
|
||||
- [x] Lock icon visual en nodos sin acceso de acción
|
||||
- [x] Sección "Seguridad" expandida en README (privilegios del container, multi-user, ALLOWED_PATHS)
|
||||
|
||||
### UX / DX
|
||||
- [x] Tooltips descriptivos en acciones (DetailPanel + NodeContextMenu)
|
||||
- [x] Confirmación dialogs con (?) que muestra detalle del comando
|
||||
- [x] Acción labels siempre en inglés (Restart/Stop/Recreate — match docker commands)
|
||||
- [x] Hot stats al cargar página (`/api/init` devuelve último snapshot cacheado)
|
||||
- [x] Polling paralelo (Promise.all) — primera ronda en ~3s vs ~90s antes
|
||||
- [x] Tabs en Monitoring (Historial / Eventos / Notificaciones)
|
||||
- [x] Click en notificación/evento → abre DetailPanel del container en tab Stats
|
||||
- [x] DATA_DIR auto-creado en `./data/` (en vez de raíz del proyecto)
|
||||
|
||||
### Documentación
|
||||
- [x] `docker-containerflow.md` — guía Docker explicada para usuarios
|
||||
- [x] `.env.example` con secciones documentadas
|
||||
- [x] README con sección Monitoreo e historial + Seguridad expandida
|
||||
|
||||
---
|
||||
|
||||
## Pendientes prioritarios (próxima iteración sugerida)
|
||||
|
||||
1. **Alertas con duración** ("CPU > 80% por 5 min") — Fase 4 pendiente
|
||||
- Requiere ventana deslizante de samples + state machine por threshold
|
||||
2. ~~Volumes/Mounts en DetailPanel~~ — ✅ HECHO
|
||||
3. **Recomendaciones de healthcheck** — Nueva fase, "value add" para usuarios
|
||||
- Detecta containers sin healthcheck (`service.health_status === ""`)
|
||||
- Sugiere healthcheck contextual según la imagen (postgres → pg_isready, redis → redis-cli ping, etc.)
|
||||
- Banner/widget en dashboard: "N containers sin healthcheck"
|
||||
- Tooltip en DetailPanel con ejemplo copiable
|
||||
- Opción de notificación diaria de resumen ("Tienes X containers sin healthcheck")
|
||||
4. **Network I/O en stats history** — extiende `pollStats` con `networks.eth0.rx_bytes/tx_bytes`
|
||||
5. **PWA + Service Worker** — para web push notifications cuando la pestaña no está abierta
|
||||
6. **Image layers y tamaño** — vía `docker image inspect` + UI en DetailPanel
|
||||
|
||||
## Cosas que cambiaron sustancialmente desde el roadmap original
|
||||
|
||||
- **"Notificaciones"** evolucionó de "solo Discord" a "Discord + in-app + persistente + read/unread"
|
||||
- **"Historial"** evolucionó de "CPU/RAM por servicio" a "+ totales agregados + per-service override + reset filtros"
|
||||
- **Acciones** evolucionó de "stop/start/restart/rebuild" a "+ recreate + exec + remove con confirmación"
|
||||
- **Seguridad** se volvió un eje propio (ALLOWED_PATHS) que no estaba en el roadmap
|
||||
|
||||
@@ -24,6 +24,7 @@ function makeSvc(overrides: Partial<Service> = {}): Service {
|
||||
exit_code: 0,
|
||||
restart_count: 0,
|
||||
oom_killed: false,
|
||||
mounts: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -141,6 +141,7 @@ const en = {
|
||||
"detail.memory": "Memory",
|
||||
"detail.noStats": "No stats available",
|
||||
"detail.cpuHistory": "Usage History",
|
||||
"detail.mounts": "Volumes & Mounts",
|
||||
"detail.memoryHistory": "Memory History",
|
||||
"detail.noHistory": "No historical data available",
|
||||
"detail.loadingHistory": "Loading history...",
|
||||
@@ -399,6 +400,7 @@ const es: Record<TranslationKey, string> = {
|
||||
"detail.memory": "Memoria",
|
||||
"detail.noStats": "No hay estad\u00edsticas disponibles",
|
||||
"detail.cpuHistory": "Historial de Consumo",
|
||||
"detail.mounts": "Volúmenes y Mounts",
|
||||
"detail.memoryHistory": "Historial de Memoria",
|
||||
"detail.noHistory": "No hay datos hist\u00f3ricos disponibles",
|
||||
"detail.loadingHistory": "Cargando historial...",
|
||||
|
||||
@@ -723,6 +723,39 @@ export function DetailPanel({ service, stats, logLines, token, closing, locked,
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Volumes / Mounts */}
|
||||
{service.mounts && service.mounts.length > 0 && (
|
||||
<div>
|
||||
<span className="text-[11px] uppercase tracking-wider text-slate-500 block mb-1">{t("detail.mounts")}</span>
|
||||
<div className="space-y-1.5">
|
||||
{service.mounts.map((m, i) => {
|
||||
const typeColor = m.type === "volume" ? "text-emerald-400 bg-emerald-500/10"
|
||||
: m.type === "bind" ? "text-cyan-400 bg-cyan-500/10"
|
||||
: "text-amber-400 bg-amber-500/10";
|
||||
return (
|
||||
<div key={i} className="bg-slate-800/60 border border-slate-700/40 rounded px-2.5 py-1.5">
|
||||
<div className="flex items-center gap-1.5 mb-0.5">
|
||||
<span className={`text-[10px] uppercase font-mono px-1.5 py-0.5 rounded ${typeColor}`}>{m.type}</span>
|
||||
<span className="text-xs font-mono text-slate-200 truncate flex-1" title={m.destination}>{m.destination}</span>
|
||||
<span className={`text-[10px] font-mono px-1.5 py-0.5 rounded ${m.rw ? "text-slate-400 bg-slate-700/60" : "text-amber-300 bg-amber-500/15"}`}>
|
||||
{m.rw ? "rw" : "ro"}
|
||||
</span>
|
||||
</div>
|
||||
{m.name && (
|
||||
<div className="text-[10px] text-slate-400 font-mono pl-1 truncate" title={m.name}>
|
||||
<span className="text-slate-600">name:</span> {m.name}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-[10px] text-slate-500 font-mono pl-1 truncate" title={m.source}>
|
||||
<span className="text-slate-600">←</span> {m.source}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Connected services */}
|
||||
{connectedSvcs.length > 0 && (
|
||||
<div>
|
||||
|
||||
@@ -24,6 +24,7 @@ function makeSvc(overrides: Partial<Service> = {}): Service {
|
||||
exit_code: 0,
|
||||
restart_count: 0,
|
||||
oom_killed: false,
|
||||
mounts: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,6 +82,15 @@ export async function discoverServices(all: boolean, projects: string[]): Promis
|
||||
exit_code: exitCode,
|
||||
restart_count: restartCount,
|
||||
oom_killed: oomKilled,
|
||||
mounts: (info?.Mounts || []).map((m: any) => ({
|
||||
type: m.Type || "bind",
|
||||
source: m.Source || "",
|
||||
destination: m.Destination || "",
|
||||
name: m.Name || undefined,
|
||||
driver: m.Driver || undefined,
|
||||
mode: m.Mode || "",
|
||||
rw: m.RW !== false,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
export interface ContainerMount {
|
||||
/** "bind" = host directory · "volume" = named docker volume · "tmpfs" = in-memory */
|
||||
type: "bind" | "volume" | "tmpfs" | string;
|
||||
/** Path on the host (or volume backend path) */
|
||||
source: string;
|
||||
/** Path inside the container */
|
||||
destination: string;
|
||||
/** Volume name (only for type=volume) */
|
||||
name?: string;
|
||||
/** Volume driver (only for type=volume) */
|
||||
driver?: string;
|
||||
/** Mode string, e.g. "rw", "ro", "rprivate" */
|
||||
mode: string;
|
||||
/** true = read-write, false = read-only */
|
||||
rw: boolean;
|
||||
}
|
||||
|
||||
export interface Service {
|
||||
id: string;
|
||||
uid: string;
|
||||
@@ -19,6 +36,7 @@ export interface Service {
|
||||
exit_code: number;
|
||||
restart_count: number;
|
||||
oom_killed: boolean;
|
||||
mounts: ContainerMount[];
|
||||
}
|
||||
|
||||
export interface Connection {
|
||||
|
||||
Reference in New Issue
Block a user