docs: add orchestration, registry, dashy, cron, and content pipeline write-ups
Completes the orchestration/registry/content batch - Kestra's check-pause-approve-apply pattern, the self-hosted build pipeline, the SOC dashboard's 14-source aggregation, the cron heatmap, and the in-progress content pipeline, all in the diagram+callout+table style.
This commit is contained in:
5 files changed
+248
No files matched your search
@@ -0,0 +1,75 @@
|
||||
# 🖥️ Dashy SOC Board
|
||||
|
||||
> [!NOTE]
|
||||
> The homelab's "one pane of glass" — a single dashboard page backed by one large custom function, aggregating roughly a dozen distinct data sources that used to be scattered across separate pages into one glanceable board.
|
||||
|
||||
## Fourteen data sources, one board
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Sources["Data sources"]
|
||||
direction LR
|
||||
Wazuh["SOC indexer<br/>+ compliance API"]
|
||||
Loki["Log aggregator<br/>24h error counts"]
|
||||
Prom["Metrics<br/>CPU/RAM/disk"]
|
||||
Slack["Alert channels<br/>per app + per host"]
|
||||
Orch["Orchestration APIs<br/>execution health"]
|
||||
Audit["Fleet config-drift<br/>audit feed"]
|
||||
Hyper["Hypervisor API<br/>VM/LXC tree"]
|
||||
Vault["Secrets platform<br/>seal state, lease counts"]
|
||||
SSO["SSO provider<br/>login/failure stats"]
|
||||
DNS["DNS server<br/>query stats, blocklist"]
|
||||
Backup["Backup server<br/>job age/status"]
|
||||
FW["Edge firewall<br/>state table, top rules"]
|
||||
end
|
||||
|
||||
Sources --> Function["One aggregation function"]
|
||||
Function --> Board["SOC Board<br/>client-rendered"]
|
||||
```
|
||||
|
||||
## Layout
|
||||
|
||||
A top row shows a 6-tier infrastructure dependency ladder alongside fleet-wide 24-hour KPIs. Below that, an "Active Issues" strip surfaces the worst findings fleet-wide, followed by a grid of per-host tiles — worst-severity badge, error sparkline, sorted consistently. Clicking a tile expands a full detail panel: compliance and resource gauges, that host's folded-in specialty widget where relevant, and findings grouped by category.
|
||||
|
||||
> [!TIP]
|
||||
> **Deliberately scoped**: security/error/warning signal only. Per-app uptime is handled separately by the dashboard's own native status checks on individual tiles — the only up/down signal inside this board is *infrastructure-tier* reachability, not per-app availability. Keeping the scope narrow is what keeps the board actually scannable.
|
||||
|
||||
## Acknowledge mechanism: client-side, no backend
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Finding["Finding<br/>stable content-derived key"] --> Ack["User acknowledges"]
|
||||
Ack --> Storage["Browser localStorage<br/>flat map, per-browser"]
|
||||
Storage --> Recompute["Live re-derive:<br/>badges, issue strip, gauges"]
|
||||
Recompute -.never touches.-> Upstream["Wazuh / Slack / any<br/>upstream source"]
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Acknowledging a finding is a **display filter only** — it never writes back to any upstream source, is scoped per-browser rather than fleet-wide, and a full data set for every host is always kept in memory so the recompute has everything it needs regardless of which panel happens to be open. A reload in a different browser shows the finding again — a deliberate tradeoff favoring simplicity (no backend state to manage) over persistence.
|
||||
|
||||
## Real integration gotchas hit building this
|
||||
|
||||
> [!WARNING]
|
||||
> A few non-obvious lessons from actually building this, in case they save someone else the debugging time:
|
||||
> - Embedding via `fetch` + `innerHTML` means injected `<script>` tags never execute — each one has to be explicitly re-created on every refresh to keep interactive elements working.
|
||||
> - A background poll that could clobber an open detail panel needs to check "is a panel open" **both** before *and* after the fetch, since the aggregation endpoint itself can take tens of seconds to respond.
|
||||
> - One backing API hard-caps results per page regardless of a higher requested limit — real pagination has to be implemented, not assumed away.
|
||||
> - Templating engines scoped to the wrong object silently produce an empty string instead of erroring — an easy, quiet failure mode.
|
||||
> - Replacing a bind-mounted config file's content doesn't get picked up by a plain container restart — the container keeps watching the old inode; a full recreate is required.
|
||||
|
||||
## Widget design: shape follows data, not one template everywhere
|
||||
|
||||
Every widget's visual style is chosen to match the *shape* of the data it shows, not a single card/badge component reused everywhere:
|
||||
|
||||
| Data shape | Visual treatment |
|
||||
|---|---|
|
||||
| Proportion of a whole (disk %, capacity) | Gauge or bar, not a badge |
|
||||
| Hierarchy (host containing guests) | Real visual nesting — indentation, a tree — not a flat grid |
|
||||
| Time-ordered audit/status log | A dense, sortable list ranked worst-first, recency as the dominant signal |
|
||||
| Named media/content items | A poster/cover-art grid, never a text list |
|
||||
|
||||
> [!TIP]
|
||||
> The media-poster widgets route through one shared image-proxy endpoint rather than each hitting its own upstream API directly — since these are private, authenticated homelab APIs, not public image CDNs. One exception: a public poster CDN gets hit directly, since no auth or proxying is needed there.
|
||||
|
||||
---
|
||||
Related stacks: [`stacks/platform/dashy/`](../stacks/platform/dashy/), [`stacks/platform/node-red/`](../stacks/platform/node-red/)
|
||||
Reference in new issue
Block a user