Building an Operator-Focused Kubernetes Dashboard
How FlexDeck combines Kubernetes, Flux, CI, observability, and model state without hiding freshness, access boundaries, or the limits of a homelab control surface.
- Published
- Materially updated
- Reading time
- 6 min read
- Context
- flexinfer.ai homelab
- Delivery window
- Ongoing development
Evidence at a glance
Context markers for the implementation. Measurement windows, caveats, and remaining gaps are documented in the study.
- Operator Surface
- K8s + GitOps
- workloads, Flux, observability, CI, and model lifecycle in one workflow
- Application Shape
- Go + SolidJS
- API and integration layer with a fine-grained reactive frontend
- Public Boundary
- Read-only
- sanitized endpoints run separately from private operator capabilities
Implementation stack
Backend
- Go
- Server-Sent Events
Frontend
- SolidJS
Infrastructure
- Kubernetes
DevOps
- Flux
Monitoring
- Prometheus
- Loki
The useful question during an incident is rarely “is Kubernetes up?” It is “what changed, which source says so, and what can I safely do next?”
I built FlexDeck around that question. It brings cluster state, Flux reconciliation, CI pipelines, observability, and AI model lifecycle into one operator surface. The hard part was not drawing more charts. It was preserving provenance and freshness while several systems update at different speeds.
TL;DR
- FlexDeck is an operator console, not a replacement for Kubernetes, Flux, Prometheus, Loki, or GitLab. It composes their state around troubleshooting workflows.
- The current transport is deliberately hybrid: server-sent events (SSE) for suitable streams, WebSocket bridges for interactive or upstream streaming paths, and coordinated polling where the source is snapshot-based.
- A value without freshness or provenance is decoration. Live, cached, and stale states need different treatment.
- Public status and private operations use different server modes. The public deployment exposes a sanitized, read-only contract instead of relying on hidden buttons for security.
- I removed the old sub-100 ms latency, 99.9% uptime, three-cluster, bundle-size, and API-load claims because the current repository does not preserve their benchmark inputs and time windows.
Context and evidence
FlexDeck is the Go and SolidJS dashboard for the flexinfer.ai homelab. The current repository covers Kubernetes workloads, Flux GitOps, FlexInfer, observability, stack exploration, project state, and the Loom control plane.
I refreshed this study on August 30, 2026 against:
services/flexdeck/README.mdandROADMAP.mdfor current scope and known partial areas;services/flexdeck/internal/api/andinternal/k8s/for server boundaries and watch behavior;services/flexdeck/web/src/stores/andweb/src/lib/polling.tsfor frontend freshness behavior; and- the FlexDeck deployment under
platform/gitops/for GitOps ownership.
This is implementation evidence from a single homelab environment. It does not establish a general uptime or latency service-level objective (SLO).
The challenge
The cluster has several valid views of reality:
- Kubernetes knows current workload and custom-resource status.
- Flux knows whether declared state has reconciled.
- Prometheus and Loki know recent behavior, not desired state.
- GitLab knows pipeline and artifact history.
- FlexInfer knows the model lifecycle and scheduling decisions.
Putting those sources on one screen creates a new risk: the dashboard can make stale or partial information look authoritative. The goal therefore became more specific than “single pane of glass”:
Reduce context switching without collapsing distinct sources of truth into one unexplained health score.
Constraints
| Constraint | Design consequence |
|---|---|
| Sources update at different rates | Each surface needs an explicit refresh strategy and stale-state behavior. |
| Some actions mutate production | Authentication and authorization must live at the server boundary. |
| Mobile checks are common | Dense tables need deliberate narrow-screen and keyboard behavior. |
| Streams disconnect | SSE paths need reconnect and polling fallback instead of freezing silently. |
| The dashboard depends on upstream APIs | Empty, error, permission, and partial states must remain distinguishable. |
| Public demos share some data | Public routes need a separate least-privilege process mode. |
Decisions and implementation
Compose sources; do not impersonate them
FlexDeck keeps source-specific concepts visible: Flux resources retain reconciliation status, Kubernetes workloads retain conditions and events, and observability panels retain their metric or log context. Cross-links make it possible to move between those views without inventing a synthetic certainty they do not share.
Use a hybrid freshness model
The original article said “no polling.” That is no longer accurate, and it was not a useful design goal.
The current code uses Kubernetes watches and SSE for model and workload events. The browser falls back to polling when the stream is unavailable. Other integrations use a shared polling scheduler with intervals chosen for their source and cost. Loki tailing bridges an upstream WebSocket into browser-friendly SSE.
This is less romantic than “everything is real time,” but more resilient. A stream is appropriate when the source supports ordered events. Polling is appropriate for bounded snapshots and APIs without a durable event contract.
Preserve identity across refreshes
Polling used to replace object trees and cause rows, hover state, and embedded timelines to flicker. The current frontend stabilizes list identity and shares poll registrations across consumers. SolidJS then updates the affected signals without requiring the entire surface to remount.
The important outcome is continuity, not a framework-size comparison. I removed the old React-versus-Solid bundle figures because they were uncited and irrelevant to the operational contract.
Split public and private capabilities
PUBLIC_API_ONLY=true starts a constrained mode that serves health and sanitized GET /api/public/* routes while omitting the private application, metrics endpoint, private clients, and persistent workers. The deployment and its role-based access control (RBAC) are tested in the FlexDeck repository.
That is a concrete trust boundary. Rendering a read-only page in the same privileged process would not be.
Make state quality visible
The roadmap records work to disclose stale Loom snapshots, preserve deep-link integrity, expose workload failure reasons, and make tables and the command palette keyboard-accessible. These are not decorative polish items. They reduce the chance that an operator acts on old data or loses context while navigating.
Outcomes and measurement context
One workflow across related systems
The current source includes workload and job views, Flux reconciliation, CI pipeline state, Prometheus and Loki integrations, FlexInfer model operations, stack drift, and project risk. That coverage is verified from routes and components, not from a user-study claim about time saved.
Honest degraded behavior
Streaming Kubernetes state falls back to coordinated polling when disconnected. Snapshot surfaces preserve the last usable data while identifying staleness rather than flashing empty. API-backed views distinguish loading, empty, permission, and upstream failure states.
A testable public boundary
The repository tests that the public deployment enables PUBLIC_API_ONLY and uses the intended RBAC shape. This is a more useful reliability statement than the old six-month uptime figure: the access invariant is executable and repeatable.
Delivery through Flux
FlexDeck is deployed through the workspace GitOps repository. The dashboard can inspect Flux state, but it does not bypass Flux ownership for its own release. That keeps the operational tool inside the same review and rollback model it presents.
Limits and open work
- This is a focused homelab console, not a validated general-purpose multi-tenant dashboard.
- The FlexDeck roadmap still marks RBAC, audit, and multicluster capabilities as partial or feature-gated.
- Several integration handlers need stronger fake-upstream coverage. A broad UI does not imply uniform test depth behind every route.
- The public surface is intentionally smaller than the private operator application.
- “Current” still depends on source behavior. A disconnected watch, slow upstream, or stale snapshot must remain visible to the operator.
Takeaways
- Optimize for the troubleshooting question. Organize state around “what changed?” rather than around a catalog of APIs.
- Choose freshness per source. Streams, snapshots, and polling each have legitimate uses.
- Show provenance and age. A stale value with a timestamp is safer than a fresh-looking value with no context.
- Protect capabilities at the process boundary. Public read-only mode should not initialize private clients or routes.
- Preserve interface continuity. Stable row identity and explicit degraded states matter more than decorative real-time effects.
You can explore the public, sanitized data surfaces in the FlexDeck demos. The private operator console remains private by design.
Working through a similar constraint?
I am happy to compare implementation notes, tradeoffs, and the evidence you would want before shipping.