The problemO problema
The homelab grew one service at a time, each justified on its own. Collectively they became a bookmarks folder I had to work through every morning to find out whether anything needed me.
O homelab cresceu um serviço por vez, cada um justificado por si só. Juntos, viraram uma pasta de favoritos que eu precisava percorrer toda manhã para saber se alguma coisa precisava de mim.
Aggregate, don't duplicate
Agregar, não duplicar
The rule the whole design follows: the panel reads from the systems that own the data and stores almost nothing itself. The moment it starts keeping its own copy, there are two answers to every question and a sync job to get wrong.
A regra que guia todo o design: o painel lê dos sistemas donos dos dados e quase não guarda nada. No momento em que ele começa a manter a própria cópia, passa a haver duas respostas para cada pergunta e um job de sincronização para dar errado.
Each section is a folder — queries, its own route, its own front-end module — so a new one drops in without touching the others.
Cada seção é uma pasta — queries, rota própria, módulo de front-end próprio —, então uma nova entra sem mexer nas outras.
Where the numbers come from
De onde vêm os números
The metric SQL wasn't rewritten from scratch. It was lifted out of the Metabase dashboard I already used and trusted, and each query carries the source card's ID in a comment next to it. So when a number looks wrong there's a path back to the original definition instead of two plausible queries and no tiebreaker.
O SQL das métricas não foi reescrito do zero. Saiu do dashboard do Metabase que eu já usava e em que confiava, e cada query leva num comentário o ID do card de origem. Então, quando um número parece errado, existe um caminho de volta à definição original, em vez de duas queries plausíveis e nenhum critério de desempate.
A parity test asserts the panel and Metabase agree. If they diverge, the adaptation is wrong — and two adaptations were genuinely needed and are documented at the top of the file, because one card used Metabase's optional-filter syntax that isn't valid SQL outside it, and another read a view rather than the raw table.
Um teste de paridade garante que o painel e o Metabase batem. Se divergirem, a adaptação está errada — e duas adaptações foram de fato necessárias e estão documentadas no topo do arquivo: um card usava a sintaxe de filtro opcional do Metabase, que não é SQL válido fora dele, e outro lia uma view em vez da tabela bruta.
No build step, on purpose
Sem build step, de propósito
One HTML file and an ES module per section. Charts are hand-written SVG rather than a charting library, because the home page has to open on the LAN with the internet down, and every CDN dependency is a way for that to fail. It costs a few hours of writing axis code once and buys a page that will still work in five years.
Um arquivo HTML e um ES module por seção. Os gráficos são SVG escrito à mão, e não uma biblioteca de gráficos, porque a página inicial precisa abrir na rede local com a internet fora do ar, e cada dependência de CDN é uma forma de isso falhar. Custa algumas horas escrevendo código de eixo uma vez só e compra uma página que ainda vai funcionar daqui a cinco anos.