mirror of
https://github.com/RGJorge/ContainerFlow.git
synced 2026-08-03 07:21:42 +02:00
v0.0.28
This commit is contained in:
@@ -38,6 +38,7 @@ Variables disponibles:
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `PORT` | `9470` | Puerto del servidor |
|
| `PORT` | `9470` | Puerto del servidor |
|
||||||
| `AUTH_TOKEN` | _(vacio)_ | Token de autenticacion. Vacio = sin auth, solo localhost. Con valor = auth activado, acceso remoto |
|
| `AUTH_TOKEN` | _(vacio)_ | Token de autenticacion. Vacio = sin auth, solo localhost. Con valor = auth activado, acceso remoto |
|
||||||
|
| `DATA_DIR` | _(cwd)_ | Directorio para persistencia: SQLite de historial (`.dockerflow-stats.db`), config Discord (`.dockerflow-discord.json`) y overrides por contenedor (`.dockerflow-container-settings.json`) |
|
||||||
|
|
||||||
## Uso
|
## Uso
|
||||||
|
|
||||||
@@ -93,8 +94,56 @@ bun run start
|
|||||||
- **Leyenda de conexiones** — colores por tipo: Database (azul), Cache (rojo), Broker (naranja), Proxy (verde)
|
- **Leyenda de conexiones** — colores por tipo: Database (azul), Cache (rojo), Broker (naranja), Proxy (verde)
|
||||||
- **Grupos visuales** — recuadros por proyecto/compose con titulo, archivo compose y conteo de containers
|
- **Grupos visuales** — recuadros por proyecto/compose con titulo, archivo compose y conteo de containers
|
||||||
- **Menu contextual** — click derecho en un nodo para acciones rapidas
|
- **Menu contextual** — click derecho en un nodo para acciones rapidas
|
||||||
- **Pagina de monitoring** — vista de eventos Docker recientes
|
- **Pagina de monitoring** — historial de CPU/RAM por servicio (1h, 6h, 24h, 7d) persistido en SQLite, gráficas con sparkline, expand por contenedor, filtros por proyecto/servicio y feed de eventos Docker
|
||||||
- **Pagina de settings** — configuracion de la aplicacion
|
- **Notificaciones Discord** — webhook configurable que avisa cambios de estado, alertas de recursos, acciones manuales y errores
|
||||||
|
- **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)
|
||||||
|
|
||||||
|
## Monitoreo e historial
|
||||||
|
|
||||||
|
ContainerFlow guarda un historial de métricas y notifica eventos importantes a Discord.
|
||||||
|
|
||||||
|
### Historial de métricas
|
||||||
|
|
||||||
|
- **Persistencia** — stats de CPU y memoria se almacenan en SQLite (`.dockerflow-stats.db`) cada vez que se hace polling de Docker (~3s)
|
||||||
|
- **Rangos** — `1h`, `6h`, `24h`, `7d` con buckets agregados (30s / 60s / 5min / 30min) para rendimiento
|
||||||
|
- **Retención** — auto-limpieza horaria descarta datos con más de 7 días y compacta la base con `VACUUM`
|
||||||
|
- **API** —
|
||||||
|
- `GET /api/stats/history?range=1h` — historial de todos los servicios
|
||||||
|
- `GET /api/stats/history/:uid?range=1h` — historial de un servicio específico
|
||||||
|
- **UI** — la página de monitoring (`MonitoringPage.tsx`) muestra una tarjeta por servicio con sparkline de CPU y MEM, valor actual, promedio y línea de umbral. Cada tarjeta puede expandirse para ver una gráfica más grande, y se filtra por proyecto y/o servicio (los filtros son acumulativos).
|
||||||
|
|
||||||
|
### Notificaciones Discord
|
||||||
|
|
||||||
|
Configurables desde **Settings → Discord Notifications**. Requiere un webhook URL que empiece por `https://discord.com/api/webhooks/`.
|
||||||
|
|
||||||
|
Eventos soportados (cada uno se puede activar/desactivar):
|
||||||
|
|
||||||
|
| Evento | Cuándo dispara |
|
||||||
|
|---|---|
|
||||||
|
| **Container State Changes** | `start`, `stop`, `die` (crash), `restart`, `health_status`. Los eventos `stop`/`die` se debouncean 15s para detectar reinicios y enviar un solo mensaje "Container Restarted" en lugar de stop+start separados |
|
||||||
|
| **Resource Alerts** | CPU o memoria de un contenedor supera el umbral (global o por-container) |
|
||||||
|
| **UI Actions** | Acción manual disparada desde el panel: start/stop/restart/rebuild/remove |
|
||||||
|
| **Action Errors** | Falló una acción ejecutada desde la UI (incluye el mensaje de error) |
|
||||||
|
|
||||||
|
Mecanismos anti-spam:
|
||||||
|
|
||||||
|
- **Cooldown global** — minutos mínimos entre alertas del mismo tipo+servicio (default `5 min`, configurable `1-60`)
|
||||||
|
- **Down reminder** — si un contenedor sigue caído, reenvía un recordatorio "Container Still Down" cada N minutos (default `5 min`)
|
||||||
|
- **Cola con rate limit** — 500ms mínimo entre webhooks; si Discord responde `429`, respeta el `Retry-After` y reintenta
|
||||||
|
- **Debounce de stop/die** — buffer de 15s para colapsar restart/redeploy en una sola notificación
|
||||||
|
|
||||||
|
Umbrales:
|
||||||
|
|
||||||
|
- **Globales** — CPU% y MEM% en Settings (default 50% / 60%)
|
||||||
|
- **Por contenedor** — desde la página de monitoring, click en el ícono ⚙️ de un servicio para abrir el panel inline. Permite:
|
||||||
|
- Activar/desactivar notificaciones para ese contenedor
|
||||||
|
- Override del umbral de CPU (drag del slider)
|
||||||
|
- Override del umbral de memoria
|
||||||
|
- Reset al valor global (X)
|
||||||
|
- Los overrides se persisten en `.dockerflow-container-settings.json` y se auto-guardan con debounce de 400ms
|
||||||
|
|
||||||
|
Botón **Test** en Settings envía un embed de prueba al webhook para verificar que funciona antes de habilitarlo.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
@@ -151,9 +200,12 @@ Ver `.github/workflows/ci.yml`.
|
|||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
server/
|
server/
|
||||||
index.ts — servidor Hono + WebSocket + CLI args
|
index.ts — servidor Hono + WebSocket + CLI args + REST API
|
||||||
docker.ts — descubrimiento de servicios y conexiones
|
docker.ts — descubrimiento de servicios y conexiones
|
||||||
watcher.ts — polling de stats + stream de eventos Docker
|
watcher.ts — polling de stats + stream de eventos Docker
|
||||||
|
stats-db.ts — SQLite de historial de stats (insert, query por rango, cleanup 7d)
|
||||||
|
discord.ts — webhooks Discord (state changes, resource alerts, cooldown, debounce, queue)
|
||||||
|
container-settings.ts — overrides por contenedor (umbrales y toggle de notificaciones)
|
||||||
client/
|
client/
|
||||||
App.tsx — dashboard principal + login screen
|
App.tsx — dashboard principal + login screen
|
||||||
main.tsx — entry point React
|
main.tsx — entry point React
|
||||||
@@ -162,8 +214,10 @@ src/
|
|||||||
ServiceNode.tsx — nodo visual por container
|
ServiceNode.tsx — nodo visual por container
|
||||||
GroupNode.tsx — header de grupo (proyecto/compose)
|
GroupNode.tsx — header de grupo (proyecto/compose)
|
||||||
hooks/
|
hooks/
|
||||||
useDocker.ts — hook WebSocket para datos en tiempo real
|
useDocker.ts — hook WebSocket para datos en tiempo real
|
||||||
processing.ts — logica pura de estados processing
|
useStatsHistory.ts — fetch del historial de stats por rango (1h/6h/24h/7d)
|
||||||
|
useStatsStore.ts — store en memoria para stats live
|
||||||
|
processing.ts — logica pura de estados processing
|
||||||
engine/
|
engine/
|
||||||
layout.ts — layout de grupos + grid + edges
|
layout.ts — layout de grupos + grid + edges
|
||||||
components/
|
components/
|
||||||
@@ -172,12 +226,15 @@ src/
|
|||||||
LoginScreen.tsx — pantalla de autenticacion
|
LoginScreen.tsx — pantalla de autenticacion
|
||||||
NodeContextMenu.tsx — menu contextual de nodos
|
NodeContextMenu.tsx — menu contextual de nodos
|
||||||
OffsetEdge.tsx — edge custom con offset para evitar superposicion
|
OffsetEdge.tsx — edge custom con offset para evitar superposicion
|
||||||
|
Sparkline.tsx — gráfica de línea ligera para historial de stats
|
||||||
|
StatsCard.tsx — tarjeta de métrica con sparkline, hover, promedio y umbral
|
||||||
|
ThresholdBar.tsx — slider de umbral por contenedor con override/reset
|
||||||
panels/
|
panels/
|
||||||
DetailPanel.tsx — panel lateral con info, stats, env, config y logs
|
DetailPanel.tsx — panel lateral con info, stats, env, config y logs
|
||||||
LogPanel.tsx — panel de logs por container
|
LogPanel.tsx — panel de logs por container
|
||||||
pages/
|
pages/
|
||||||
MonitoringPage.tsx — vista de eventos Docker
|
MonitoringPage.tsx — historial de CPU/RAM, eventos Docker y umbrales por contenedor
|
||||||
SettingsPage.tsx — configuracion de la aplicacion
|
SettingsPage.tsx — configuracion (auth, Discord webhook, eventos, umbrales globales)
|
||||||
shared/
|
shared/
|
||||||
types.ts — tipos compartidos server/client
|
types.ts — tipos compartidos server/client
|
||||||
```
|
```
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 208 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 291 KiB |
@@ -96,7 +96,7 @@ export function MonitoringPage({ events, token, services }: MonitoringPageProps)
|
|||||||
const serviceRef = useRef<HTMLDivElement>(null);
|
const serviceRef = useRef<HTMLDivElement>(null);
|
||||||
const { data: allHistory, loading: historyLoading } = useAllStatsHistory(statsRange, token);
|
const { data: allHistory, loading: historyLoading } = useAllStatsHistory(statsRange, token);
|
||||||
const [containerSettings, setContainerSettings] = useState<Record<string, ContainerSettings>>({});
|
const [containerSettings, setContainerSettings] = useState<Record<string, ContainerSettings>>({});
|
||||||
const [globalThresholds, setGlobalThresholds] = useState<{ cpu: number; mem: number }>({ cpu: 80, mem: 90 });
|
const [globalThresholds, setGlobalThresholds] = useState<{ cpu: number; mem: number }>({ cpu: 50, mem: 60 });
|
||||||
const [discordEnabled, setDiscordEnabled] = useState(false);
|
const [discordEnabled, setDiscordEnabled] = useState(false);
|
||||||
|
|
||||||
// Load thresholds
|
// Load thresholds
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ const DEFAULT_CONFIG: DiscordConfig = {
|
|||||||
actionErrors: true,
|
actionErrors: true,
|
||||||
},
|
},
|
||||||
thresholds: {
|
thresholds: {
|
||||||
cpuPercent: 80,
|
cpuPercent: 50,
|
||||||
memPercent: 90,
|
memPercent: 60,
|
||||||
},
|
},
|
||||||
cooldownMinutes: 5,
|
cooldownMinutes: 5,
|
||||||
downReminderMinutes: 5,
|
downReminderMinutes: 5,
|
||||||
@@ -261,7 +261,7 @@ export function SettingsPage({ projects, servicesCount, token }: SettingsPagePro
|
|||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min={50}
|
min={10}
|
||||||
max={100}
|
max={100}
|
||||||
value={config.thresholds.cpuPercent}
|
value={config.thresholds.cpuPercent}
|
||||||
onChange={(e) => updateThresholds("cpuPercent", parseInt(e.target.value))}
|
onChange={(e) => updateThresholds("cpuPercent", parseInt(e.target.value))}
|
||||||
@@ -275,7 +275,7 @@ export function SettingsPage({ projects, servicesCount, token }: SettingsPagePro
|
|||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min={50}
|
min={10}
|
||||||
max={100}
|
max={100}
|
||||||
value={config.thresholds.memPercent}
|
value={config.thresholds.memPercent}
|
||||||
onChange={(e) => updateThresholds("memPercent", parseInt(e.target.value))}
|
onChange={(e) => updateThresholds("memPercent", parseInt(e.target.value))}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function DetailPanel({ service, stats, logLines, token, closing, onClose,
|
|||||||
const [csLoaded, setCsLoaded] = useState(false);
|
const [csLoaded, setCsLoaded] = useState(false);
|
||||||
const [csSaving, setCsSaving] = useState(false);
|
const [csSaving, setCsSaving] = useState(false);
|
||||||
const [csSaved, setCsSaved] = useState(false);
|
const [csSaved, setCsSaved] = useState(false);
|
||||||
const [globalThresholds, setGlobalThresholds] = useState<{ cpu: number; mem: number }>({ cpu: 80, mem: 90 });
|
const [globalThresholds, setGlobalThresholds] = useState<{ cpu: number; mem: number }>({ cpu: 50, mem: 60 });
|
||||||
const [discordEnabled, setDiscordEnabled] = useState(false);
|
const [discordEnabled, setDiscordEnabled] = useState(false);
|
||||||
const [statsRange, setStatsRange] = useState<StatsRange>("1h");
|
const [statsRange, setStatsRange] = useState<StatsRange>("1h");
|
||||||
const { data: historyData, loading: historyLoading } = useStatsHistory(service.uid, statsRange, token);
|
const { data: historyData, loading: historyLoading } = useStatsHistory(service.uid, statsRange, token);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ const DEFAULT_CONFIG: DiscordConfig = {
|
|||||||
actionErrors: true,
|
actionErrors: true,
|
||||||
},
|
},
|
||||||
thresholds: {
|
thresholds: {
|
||||||
cpuPercent: 80,
|
cpuPercent: 50,
|
||||||
memPercent: 90,
|
memPercent: 60,
|
||||||
},
|
},
|
||||||
cooldownMinutes: 5,
|
cooldownMinutes: 5,
|
||||||
downReminderMinutes: 5,
|
downReminderMinutes: 5,
|
||||||
|
|||||||
Reference in New Issue
Block a user