When the console cannot reach a live backend or agent — for example on the public site console.kubestellar.io — it switches to demo mode. In demo mode every dashboard card displays synthetic data so visitors can explore the UI without a running Kubernetes cluster.
Cards that are showing demo data are marked with a Demo badge and a yellow outline.
Demo mode is controlled by a three-level priority system
(source: web/src/lib/demoMode.ts):
| Priority | Condition | Can the user toggle it off? |
|---|---|---|
| 1 — Forced | Running on a Netlify deployment (console.kubestellar.io, deploy previews) | No |
| 2 — User toggle | localStorage['kc-demo-mode'] === 'true' | Yes |
| 3 — Token fallback | No auth token or token equals demo-token, and the user has not explicitly disabled demo mode | Yes |
On the public site (Priority 1) demo mode is always forced on because there is no backend or agent to serve real data.
When running a local development server with ./start-dev.sh or
./startup-oauth.sh, demo mode is off by default and live cluster data
is used instead. Users can still enable demo mode manually through the UI
toggle, which sets the localStorage key.
Demo data is generated in two places:
web/src/lib/unified/demo/demoDataRegistry.ts is a central registry that
stores generator functions keyed by data type. Generator functions are
registered at startup from
web/src/lib/unified/demo/generators/registerDemoGenerators.ts.
The registry produces data for clusters, pod issues, deployment issues, events, security findings, GPU nodes, Helm releases, operators, and services. For example, the demo cluster list contains a set of realistic clusters (EKS, GKE, AKS, OKE, OpenShift, etc.) with plausible configurations.
Each useCached* hook in web/src/hooks/useCachedData.ts defines an
inline demo-data generator that is used as a last-resort fallback when:
These hook-level generators are intentionally small and self-contained so that every card always has some data to display.
A few cards ship their own demoData.ts alongside the component (for
example web/src/components/cards/flatcar_status/demoData.ts). These are
used when the card’s data shape is too specialised for the shared
generators.
The decision path for each dashboard card is:
┌─────────────────────────────┐
│ Card calls useCached*() │
│ hook for its data type │
└──────────┬──────────────────┘
│
┌──────────▼──────────────────┐
│ Is demo mode active? │──Yes──▶ Return demo data
│ (isDemoMode()) │ isDemoFallback = true
└──────────┬──────────────────┘
│ No
┌──────────▼──────────────────┐
│ Fetch from API / agent │
└──────────┬──────────────────┘
│
┌──────────▼──────────────────┐
│ Fetch succeeded? │──Yes──▶ Return live data
└──────────┬──────────────────┘ isDemoFallback = false
│ No (3+ failures)
▼
Return demo data as fallback
isDemoFallback = true
The card component then reports its state up to CardWrapper via the
useCardLoadingState or useReportCardDataState hook. Inside the hooks
the flag is called isDemoFallback; the card passes it to CardWrapper
as the isDemoData prop. When isDemoData is true, CardWrapper
renders the yellow outline and Demo badge.
On first visit the card shows a loading skeleton while the API call is in flight. Demo data is never shown during this initial loading phase — the skeleton appears instead. On subsequent visits the card instantly renders cached data from IndexedDB while a background refresh runs (stale-while-revalidate pattern).
| Scenario | What the user sees |
|---|---|
| First visit, backend reachable | Loading skeleton → live data |
| Return visit, backend reachable | Cached data instantly → refresh spinner → updated data |
| No backend / demo mode | Demo data with Demo badge and yellow outline |
| Backend reachable, fetch fails | Loading skeleton → demo fallback after 3 failures |
Toggling demo mode in browser tab is picked up by all other open tabs
through the storage event on localStorage plus a custom
kc-demo-mode-change window event. When the mode changes, all registered
caches are cleared so that every card transitions through a skeleton state
before loading the appropriate data source.
Because demo data is generated independently by multiple sources (the unified registry, per-hook fallbacks, and per-card files), the synthetic values shown by different cards are not guaranteed to be mutually consistent. Two cards that each derive a summary from their own demo dataset may display different totals for the same logical metric. This is expected: demo mode is designed to showcase the UI layout and card interactions, not to present a coherent simulated cluster state.