Desarrollo
DumbMonit es un único binario Rust (crates/server, que incluye la
compilación de SvelteKit) más VictoriaMetrics para series temporales,
iniciado por el servidor desde el binario incluido en la imagen, y una
base de datos SQLite integrada para configuración y estado. Un
contenedor. El código Rust y los comentarios están en francés; la
interfaz web está en inglés.
Estructura del proyecto
Sección titulada «Estructura del proyecto»crates/proto tipos compartidos: Sample, Target, Credential, trait Collector (+ ProbeError)crates/server el binario api/ rutas axum (mod.rs es la tabla de rutas); spa.rs sirve web/build auth/ contraseña de instancia única, cookie de sesión HttpOnly, limitación de tasa collectors/ snmp (profiles/*.yaml cargados al arrancar), proxmox, pbs, synology, agent, uptime scheduler.rs ejecuta cada target activado en su intervalo a través del registro de recolectores tsdb/ escritor de VictoriaMetrics (flush agrupado) + proxy de consultas; embedded.rs supervisa el VictoriaMetrics hijo db/ SQLite + migraciones numeradas en db/migrations/ alerting/ reglas, máquina de estados (Phase/EffectivePhase), supresión por padre, silencios, referencia estacional notify/ 22 canales de notificación, descritos a la UI por notify/catalog.rs crypto.rs AES-256-GCM para credenciales/tokens, clave derivada de /data/secret.key o DUMBMONIT_SECRETcrates/agent agente Linux/macOS/FreeBSD/Windows; install/ contiene install.sh/.ps1 servidos por el servidorweb/ SvelteKit (Svelte 5 runes, Tailwind 4, uPlot), fallback SPA adapter-static, ssr=falseprofiles/ perfiles de recolección SNMP, aplicados automáticamente por sysObjectIDdocs/ esta documentación (MkDocs, publicada en Read the Docs)Compilar sin un toolchain de Rust
Sección titulada «Compilar sin un toolchain de Rust»La compilación ocurre en Docker; nada de Rust necesita instalarse en el host.
# Stack completo (construye la imagen: unos 10 min en frío) — UI en https://localhost:8080docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
# Rust, dentro de la etapa builder (musl/alpine)docker build -t dumbmonit-devenv --target builder .docker run --rm -v "$PWD:/build" -w /build dumbmonit-devenv cargo testdocker run --rm -v "$PWD:/build" -w /build dumbmonit-devenv cargo test -p dumbmonit-server --test alerts_api # un archivo de test de integracióndocker run --rm -v "$PWD:/build" -w /build dumbmonit-devenv cargo test -p dumbmonit-server test_name # un test por nombredocker run --rm -v "$PWD:/build" -w /build dumbmonit-devenv cargo clippy --all-targets --all-features -- -D warningsdocker run --rm -v "$PWD:/build" -w /build dumbmonit-devenv cargo fmt --all --checkCon Rust instalado localmente, cargo test y cargo clippy -- -D warnings funcionan directamente. Edition 2024, MSRV 1.95;
rustfmt.toml define max_width = 100 y use_small_heuristics = "Max".
La compilación Docker está en capas: compilación web → dependencias Rust
sobre fuentes ficticias → fuentes reales (con un touch antes del
segundo cargo build, de lo contrario cargo reutiliza los artefactos
ficticios) → agente compilado de forma cruzada con cargo-zigbuild para
x86_64-unknown-linux-musl, aarch64-unknown-linux-musl y
x86_64-pc-windows-gnu → FROM scratch. web/build debe existir antes
de cargo build, o el servidor se distribuye sin interfaz.
Interfaz web
Sección titulada «Interfaz web»Node 22 basta en el host.
cd web && npm run dev # Vite en :5173, redirige /api a localhost:8080 (el servidor Docker)cd web && npm run check # svelte-check (CI lo ejecuta)cd web && npm run build # compilación estática en web/build, incluida por rust_embed en cargo buildConvenciones: solo Svelte 5 runes ($props, $state, $derived,
$effect, snippets, onclick). Todo el acceso a red pasa por
src/lib/api/; los componentes nunca hacen fetch. Los tipos en
api/types.ts reflejan exactamente las estructuras Rust y deben
mantenerse sincronizados con crates/server/src/api/*.rs. El sistema de
diseño vive en src/app.css (tokens) y src/lib/ui/ (primitivas).
Nunca ejecutes dos vite build a la vez: borran .svelte-kit/output.
Traducciones
Sección titulada «Traducciones»La interfaz web se traduce mediante pull request, como cualquier otra contribución. El idioma de origen es el inglés (web/messages/en.json: claves planas, marcadores {param}); cada otro idioma es un archivo, web/messages/<locale>.json, que Paraglide JS compila en funciones de mensaje con tree-shaking bajo web/src/lib/paraglide/ (generado, sin seguimiento).
- Edita
web/messages/<locale>.json(fr,de,es,it,pt,pt-BR,ru,zh-Hans), o copiaen.jsonpara añadir un idioma nuevo. - Conserva todas las claves y todas las
{variables}deen.json; traduce solo los valores. Sin marcado HTML en los mensajes. - Los plurales usan la sintaxis de mensajes, no cadenas armadas a mano: conserva la estructura
match/pluraldel mensaje en inglés y traduce cada rama. - Ejecuta
cd web && npm run i18n:check: falla si a un idioma le falta una clave o cambia sus{variables}. - Abre un pull request.
Las cadenas nuevas de la interfaz se añaden primero a web/messages/en.json (el único archivo que necesita un pull request de funcionalidad; npm run i18n:merge fusiona fragmentos temporales). Un idioma aparece en el selector (Ajustes, Apariencia) en cuanto existe su archivo. La convención de nombres de claves está en CONTRIBUTING.md.
web/project.inlang/settings.json apunta su único plugin
(message-format) a ./node_modules/@inlang/plugin-message-format/dist/index.js
en lugar de la URL de jsdelivr a la que apunta por defecto el propio
tooling de inlang: una ruta relativa, no http, se lee directamente del
disco, así que npm run build nunca contacta a un CDN — importante ya
que la compilación de la imagen no tiene ninguna garantía de una caché
npm caliente que ofrezca esa URL, y una herramienta de homelab que
necesita internet para reconstruirse es una mala señal.
Tests y CI
Sección titulada «Tests y CI».github/workflows/ci.yml ejecuta, en cada push y pull request:
cargo fmt --all --check,cargo clippy --all-targets --all-features -- -D warnings,cargo test --all-features;cargo deny check licenses bans— solo se permiten licencias aprobadas por la OSI segúndeny.toml; una dependencia copyleft o source-available hace fallar la compilación;npm run checkynpm run build;- la imagen (
linux/amd64), solo compilación.
.github/workflows/release.yml publica la misma imagen en
ghcr.io/laupernoe/dumbmonit: :edge en cada push a main, :X.Y.Z,
:X.Y, :X y :latest en cada etiqueta v*.
Los tests de integración viven en crates/server/tests/; los módulos
puros (conversión de métricas, análisis de opciones, máquina de estados
de alertas, silencios) se prueban en el sitio sin red.
Añadir un recolector
Sección titulada «Añadir un recolector»- Implementa
Collectordesdecrates/proto/src/collector.rs:kind(),probe(&Target) -> Result<Vec<Sample>, ProbeError>y opcionalmentediscover()para la detección de perfil. Devuelve las variantes deProbeErrorcon veracidad:means_down()decide si un fallo cuenta como «monitor caído» (alertado) o como un error de configuración (mostrado en el monitor, no notificado). - Lee tus opciones de
Target::tagsen unoptions.rs, con valores por defecto. - Regístralo en
crates/server/src/main.rsconregistry.register(...). El scheduler y la API nunca conocen tipos concretos. - Descríbelo en
crates/server/src/api/collectors.rs: etiqueta, resumen, ejemplos, tipos de credencial, pista de dirección, aviso de configuración y la lista deoptions(clave, etiqueta, ayuda, placeholder, por defecto, input, opciones). La UI construye su formulario a partir de esto; un test ahí comprueba que cada tipo registrado tiene un aviso y que las claves de opción coinciden con lo que lee el recolector. - Nombra las métricas
<qué>_<unidad>sin el prefijodumbmonit_(el escritor lo añade) y añade cualquier métrica nueva que una regla integrada apunte a la lista en los tests dealerting/rules.rs. - Añade una página bajo
docs/devices/.
Las muestras son Sample::new(name, value, MetricKind::Gauge | Counter, timestamp_ms) con .with_label(k, v); las etiquetas de identidad
(target, host, tag_*) las añade el registro. No las emitas tú: nombra una
etiqueta por objeto con el nombre del objeto (proxy_host, esxi, ceph_host).
Una etiqueta que colisiona se conserva como exported_<name>, y las opciones
listadas para el tipo en api/collectors.rs no se copian como tag_*.
Añadir un canal de notificación
Sección titulada «Añadir un canal de notificación»- Implementa
Notifier(crates/server/src/notify/mod.rs):kind()yasync fn send(&Message) -> Result<(), NotifyError>. Los canales web comparten el cliente HTTP y los helpers denotify/http.rs; miraservices.rs,chat.rs,push.rs,oncall.rsysmtp.rscomo referencia. - Añade el tipo a
CHANNEL_KINDSy al matchbuildennotify/mod.rs, que convierte unChannelConfigguardado (ajustes + secretos descifrados) en unNotifier. - Descríbelo en
notify/catalog.rs: etiqueta, resumen, ancladoc_url, y los campossettingsysecrets(clave, etiqueta, obligatorio, input, ayuda, placeholder, opciones, forma, por defecto). La UI renderiza el formulario a partir de esta descripción, así que no se necesita ninguna versión de UI. - Documéntalo en
docs/notifications.mdbajo Supported channels, con un encabezado cuyo slug coincida con el ancladoc_url: la UI enlaza directamente a ese ancla en Read the Docs.
Capturas sin interfaz
Sección titulada «Capturas sin interfaz»web/tools/README.md describe cómo renderizar cada página en un
Chromium sin interfaz ejecutándose en Docker
(zenika/alpine-chrome:with-puppeteer, sin necesidad de navegador
local): inicia sesión con curl para obtener la cookie
dumbmonit_session, y luego ejecuta web/tools/screenshot.js contra el
servidor de vista previa o directamente contra :8080. Escribe
web/shots/<page>-<theme>-<viewport>.png para ambos temas y ambos
viewports, que es cómo se comprueba la UI tras un cambio y cómo se
hicieron las capturas de esta documentación.
Documentación
Sección titulada «Documentación»Este sitio se construye con MkDocs y Material for MkDocs desde docs/
(mkdocs.yml en la raíz, .readthedocs.yaml para Read the Docs). Para
previsualizar:
pip install -r docs/requirements.txtmkdocs servemkdocs build --strict # lo que ejecuta Read the Docs; debe pasar sin ningún aviso