Desenvolvimento
O DumbMonit é um binário Rust (crates/server, que incorpora a build do
SvelteKit) mais o VictoriaMetrics para séries temporais, iniciado pelo servidor
a partir do binário fornecido na imagem, e uma base de dados SQLite incorporada
para configuração e estado. Um contentor. O código Rust e os comentários estão
em francês; a interface web está em inglês.
Estrutura do projeto
Seção intitulada “Estrutura do projeto”crates/proto shared types: Sample, Target, Credential, trait Collector (+ ProbeError)crates/server the binary api/ axum routes (mod.rs is the route table); spa.rs serves web/build auth/ single instance password, HttpOnly session cookie, rate limit collectors/ snmp (profiles/*.yaml loaded at startup), proxmox, pbs, synology, agent, uptime scheduler.rs runs every enabled target on its interval through the collector registry tsdb/ VictoriaMetrics writer (batched flush) + query proxy; embedded.rs supervises the child VictoriaMetrics db/ SQLite + numbered migrations in db/migrations/ alerting/ rules, state machine (Phase/EffectivePhase), suppression by parent, silences, seasonal baseline notify/ 22 notification channels, described to the UI by notify/catalog.rs crypto.rs AES-256-GCM for credentials/tokens, key derived from /data/secret.key or DUMBMONIT_SECRETcrates/agent Linux/macOS/FreeBSD/Windows agent; install/ holds install.sh/.ps1 served by the serverweb/ SvelteKit (Svelte 5 runes, Tailwind 4, uPlot), adapter-static SPA fallback, ssr=falseprofiles/ SNMP collection profiles, auto-applied by sysObjectIDdocs/ this documentation (MkDocs, published on Read the Docs)Compilar sem toolchain Rust
Seção intitulada “Compilar sem toolchain Rust”A compilação faz-se no Docker; não é preciso instalar nada de Rust no anfitrião.
# Full stack (builds the image: about 10 min cold) — UI on https://localhost:8080docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
# Rust, inside the builder stage (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 # one integration test filedocker run --rm -v "$PWD:/build" -w /build dumbmonit-devenv cargo test -p dumbmonit-server test_name # one test by namedocker 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 --checkCom o Rust instalado localmente, cargo test e cargo clippy -- -D warnings
funcionam diretamente. Edition 2024, MSRV 1.95; o rustfmt.toml define
max_width = 100 e use_small_heuristics = "Max".
A build Docker é feita por camadas: build web → dependências Rust sobre fontes
fictícias → fontes reais (com um touch antes do segundo cargo build, caso
contrário o cargo reutiliza os artefactos fictícios) → agente compilado em
cruzado com cargo-zigbuild para x86_64-unknown-linux-musl,
aarch64-unknown-linux-musl e x86_64-pc-windows-gnu → FROM scratch.
web/build tem de existir antes do cargo build, ou o servidor é entregue sem
interface.
Interface web
Seção intitulada “Interface web”O Node 22 chega no anfitrião.
cd web && npm run dev # Vite on :5173, proxies /api to localhost:8080 (the Docker server)cd web && npm run check # svelte-check (CI runs this)cd web && npm run build # static build into web/build, embedded by rust_embed at cargo buildConvenções: apenas runes do Svelte 5 ($props, $state, $derived,
$effect, snippets, onclick). Todo o acesso à rede passa por src/lib/api/;
os componentes nunca fazem fetch. Os tipos em api/types.ts espelham
exatamente as structs Rust e têm de ser mantidos em sincronia com
crates/server/src/api/*.rs. O sistema de design vive em src/app.css
(tokens) e src/lib/ui/ (primitivas). Nunca execute dois vite build ao mesmo
tempo: apagam .svelte-kit/output.
Traduções
Seção intitulada “Traduções”A interface web é traduzida por pull request, como qualquer outra contribuição. A língua de origem é o inglês (web/messages/en.json: chaves planas, marcadores {param}); cada outra língua é um ficheiro, web/messages/<locale>.json, compilado pelo Paraglide JS em funções de mensagem com tree-shaking em web/src/lib/paraglide/ (gerado, não versionado).
- Edite
web/messages/<locale>.json(fr,de,es,it,pt,pt-BR,ru,zh-Hans), ou copieen.jsonpara adicionar uma nova língua. - Mantenha todas as chaves e todas as
{variables}deen.json; traduza apenas os valores. Sem marcação HTML nas mensagens. - Os plurais usam a sintaxe de mensagens, não cadeias montadas à mão: mantenha a estrutura
match/pluralda mensagem inglesa e traduza cada ramo. - Execute
cd web && npm run i18n:check: falha se a uma língua faltar uma chave ou mudar as suas{variables}. - Abra um pull request.
As novas cadeias da interface são primeiro adicionadas a web/messages/en.json (o único ficheiro de que um pull request de funcionalidade precisa; npm run i18n:merge junta fragmentos temporários). Uma língua aparece no seletor (Definições, Aparência) assim que o seu ficheiro existe. A convenção de nomes de chaves está em CONTRIBUTING.md.
web/project.inlang/settings.json aponta o seu único plugin (message-format)
para ./node_modules/@inlang/plugin-message-format/dist/index.js em vez do URL
jsdelivr que as próprias ferramentas do inlang usam por predefinição: um
caminho relativo, não http, é lido diretamente do disco, pelo que
npm run build nunca vai a uma CDN — importante porque a build da imagem não
garante uma cache npm quente que ofereça esse URL, e uma ferramenta de homelab
que precisa da internet para se reconstruir não fica bem.
Testes e CI
Seção intitulada “Testes e CI”O .github/workflows/ci.yml executa, em cada push e pull request:
cargo fmt --all --check,cargo clippy --all-targets --all-features -- -D warnings,cargo test --all-features;cargo deny check licenses bans— só licenças aprovadas pela OSI são permitidas pelodeny.toml; uma dependência copyleft ou de código disponível falha a build;npm run checkenpm run build;- a imagem (
linux/amd64), apenas build.
O .github/workflows/release.yml publica a mesma imagem em
ghcr.io/laupernoe/dumbmonit: :edge em cada push para main, :X.Y.Z,
:X.Y, :X e :latest em cada tag v*.
Os testes de integração vivem em crates/server/tests/; os módulos puros
(conversão de métricas, análise de opções, máquina de estados de alertas,
silêncios) são testados no próprio local, sem rede.
Adicionar um coletor
Seção intitulada “Adicionar um coletor”- Implemente
Collectordecrates/proto/src/collector.rs:kind(),probe(&Target) -> Result<Vec<Sample>, ProbeError>e opcionalmentediscover()para deteção de perfil. Devolva as variantes deProbeErrorcom verdade:means_down()decide se uma falha conta como «monitor em baixo» (alertada) ou como erro de configuração (mostrado no monitor, não notificado). - Leia as suas opções de
Target::tagsnumoptions.rs, com predefinições. - Registe-o em
crates/server/src/main.rscomregistry.register(...). O agendador e a API nunca conhecem tipos concretos. - Descreva-o em
crates/server/src/api/collectors.rs: rótulo, resumo, exemplos, tipos de credencial, dica de endereço, aviso de configuração e a lista deoptions(chave, rótulo, ajuda, placeholder, predefinição, entrada, escolhas). A interface constrói o seu formulário a partir disto; um teste aí verifica que todo o tipo registado tem um aviso e que as chaves das opções correspondem ao que o coletor lê. - Nomeie as métricas
<what>_<unit>sem o prefixodumbmonit_(o escritor acrescenta-o) e acrescente qualquer métrica nova que uma regra incorporada vise à lista nos testes dealerting/rules.rs. - Adicione uma página em
docs/devices/.
As amostras são Sample::new(name, value, MetricKind::Gauge | Counter, timestamp_ms) com .with_label(k, v); as etiquetas de identidade (target,
host, tag_*) são acrescentadas pelo registo. Não as emita você próprio:
nomeie uma etiqueta por objeto segundo o objeto (proxy_host, esxi,
ceph_host). Uma etiqueta em colisão é mantida como exported_<name>, e as
opções listadas para o tipo em api/collectors.rs não são copiadas como
tag_*.
Adicionar um canal de notificação
Seção intitulada “Adicionar um canal de notificação”- Implemente
Notifier(crates/server/src/notify/mod.rs):kind()easync fn send(&Message) -> Result<(), NotifyError>. Os canais web partilham o cliente HTTP e os auxiliares emnotify/http.rs; vejaservices.rs,chat.rs,push.rs,oncall.rsesmtp.rspara irmãos. - Adicione o tipo a
CHANNEL_KINDSe aomatchdebuildemnotify/mod.rs, que converte umChannelConfigguardado (definições + segredos decifrados) numNotifier. - Descreva-o em
notify/catalog.rs: rótulo, resumo, âncoradoc_url, e os campossettingsesecrets(chave, rótulo, obrigatório, entrada, ajuda, placeholder, opções, forma, predefinição). A interface renderiza o formulário a partir desta descrição, pelo que não é preciso nenhum lançamento da interface. - Documente-o em
docs/notifications.mdem Canais suportados, com um título cujo slug corresponda à âncoradoc_url: a interface liga diretamente a essa âncora no Read the Docs.
Capturas de ecrã sem interface
Seção intitulada “Capturas de ecrã sem interface”O web/tools/README.md descreve como renderizar todas as páginas num Chromium
sem interface a correr no Docker (zenika/alpine-chrome:with-puppeteer, sem
precisar de navegador local): inicie sessão com curl para obter o cookie
dumbmonit_session, depois execute web/tools/screenshot.js contra o servidor
de pré-visualização ou diretamente contra :8080. Escreve
web/shots/<page>-<theme>-<viewport>.png para ambos os temas e ambas as
janelas de visualização, que é como a interface é verificada após uma alteração
e como foram feitas as capturas de ecrã desta documentação.
Documentação
Seção intitulada “Documentação”Este site é construído com MkDocs e Material for MkDocs a partir de docs/
(mkdocs.yml na raiz, .readthedocs.yaml para o Read the Docs). Para
pré-visualizar:
pip install -r docs/requirements.txtmkdocs servemkdocs build --strict # what Read the Docs runs; must pass with no warnings