# ๐Ÿ–ฅ๏ธ Dashy SOC Board > [!NOTE] > The homelab's "one pane of glass" โ€” a single Dashy page backed by one large custom Node-RED function, aggregating fourteen distinct data sources that used to be scattered across separate pages into one glanceable board. This is where every other doc in this repo actually converges visually โ€” if a system produces a signal worth watching, it shows up here. ## Fourteen data sources, one board ```mermaid flowchart TD subgraph Sources["Data sources"] direction LR Wazuh["Wazuh indexer
+ compliance API"] Loki["Loki
24h error counts"] Prom["Prometheus
CPU/RAM/disk"] Slack["Slack channels
per app + per host"] Orch["Kestra / n8n APIs
execution health"] Audit["Fleet config-drift
audit feed"] Hyper["Proxmox API
VM/LXC tree"] Vault["Vault
seal state, lease counts"] SSO["Authentik
login/failure stats"] DNS["Technitium
query stats, blocklist"] Backup["Backup server
job age/status"] FW["pfSense
state table, top rules"] end Sources --> Function["One Node-RED function
~104KB source"] Function --> Board["SOC Board
client-rendered"] ``` ## Why one function instead of native Dashy widgets Dashy's own built-in widget types cover simple cases well, but none of them can join data across sources the way this board needs to โ€” a per-host tile that combines Wazuh findings, Prometheus resource usage, and Slack alert history into one expandable panel isn't expressible as a single native widget config. Building it as one Node-RED function that does the aggregation server-side, then hands the dashboard pre-joined data, was the tradeoff made: a large, custom function to maintain, in exchange for a board that actually reflects cross-system state instead of fourteen disconnected tiles a human has to mentally correlate themselves. ## Layout A top row shows a 6-tier infrastructure dependency ladder (mirroring the trust-boundary structure in [`docs/architecture-overview.md`](architecture-overview.md)) 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 (a Vault seal-state panel on the secrets host, a Wazuh compliance ring on any host, and so on), and findings grouped by category. > [!TIP] > **Deliberately scoped**: security/error/warning signal only. Per-app uptime is handled separately by Dashy's own native status checks on individual tiles โ€” the only up/down signal inside this board is *infrastructure-tier* reachability from Prometheus, not per-app availability. Keeping the scope narrow โ€” resisting the temptation to also make this the uptime dashboard โ€” is what keeps the board actually scannable instead of turning into a second copy of Grafana. ## Acknowledge mechanism: client-side, no backend ```mermaid flowchart LR Finding["Finding
stable content-derived key"] --> Ack["User acknowledges"] Ack --> Storage["Browser localStorage
flat map, per-browser"] Storage --> Recompute["Live re-derive:
badges, issue strip, gauges"] Recompute -.never touches.-> Upstream["Wazuh / Slack / any
upstream source"] ``` > [!IMPORTANT] > Acknowledging a finding is a **display filter only** โ€” it never writes back to Wazuh, Slack, or any upstream source described elsewhere in this repo, 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. This was a deliberate tradeoff favoring simplicity โ€” no backend state to build, secure, or back up โ€” over persistence, made because the board's job is triage visibility, not an audit trail of who acknowledged what (that's what Wazuh's own alert log in [`docs/security-monitoring.md`](security-monitoring.md) is for). ## 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 `