Azure / Azure/unbounded

Proposal: Unbounded management dashboard

Open
#264 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
28
Forks
11
Avg merge
1d 8h
Merged PRs (30d)
55

Description

# Proposal: Unbounded Management Dashboard

## Problem

Unbounded is becoming a collection of related but distinct components: networking/CNI, storage, node lifecycle operations, image distribution, inventory, and future operational systems. Each component has its own status, resources, actions, and troubleshooting workflows.

A single monolithic frontend owned by one component will not scale well. It would either become tightly coupled to every subsystem or force each component to build its own unrelated UI. Both outcomes make the project harder to operate and harder to extend.

We need a management dashboard that presents Unbounded as one coherent product while allowing individual components to expose their own operational surfaces.

## Relationship to the Existing unbounded-net Dashboard

This effort is the successor to the dashboard currently embedded in `unbounded-net-controller`. That dashboard is a client-side React/Vite SPA served by the controller, backed by a status API (`/status/json`, `/status/node/{name}`, `/status/ws`) and an existing SubjectAccessReview-based auth stack.

We treat that dashboard as a predecessor whose functionality will be **merged into** the new dashboard rather than replaced from scratch. Specifically:

- **Auth is reused.** The new dashboard adopts the existing SAR-based authorization, HMAC viewer tokens, and kube-aggregator front-proxy integration (`cmd/unbounded-net-controller/dashboard_auth.go`, `token_endpoint.go`, `internal/net/authn`) rather than reinventing it.
- **`status/json` becomes the net module's data source.** The existing `ClusterStatusResponse` / `ClusterSummary` model (`cmd/unbounded-net-controller/status_types.go`, `internal/net/status/v1alpha1`) is the foundation the `net` module renders from.
- **The React UI stays running during the transition** and is removed long-term once the new dashboard reaches parity (including topology and connectivity views).

## Goals

- Provide a unified Unbounded dashboard with consistent navigation, theme, layout, and identity.
- Allow each component to contribute status, resources, details, and actions without owning separate frontend technology.
- Prefer server-rendered HTML for simplicity and maintainability, while allowing modules with genuinely live/real-time data to stream updates.
- Reuse the existing unbounded-net auth and status surfaces rather than rebuilding them.
- Expose JSON APIs alongside HTML pages so users can build custom dashboards or automation.
- Keep the first version practical for backend-heavy contributors.
- Avoid a frontend technology soup or microfrontend/plugin-bundle architecture.
- Preserve Kubernetes RBAC as the source of truth for dashboard access and actions.

## Non-Goals

- Build a React/Vue/Svelte single-page application as the long-term direction.
- Allow components to ship arbitrary JavaScript UI plugins.
- Design a fully dynamic marketplace-style extension system in the first version.
- Replace observability systems such as Prometheus or Grafana.
- Build every component dashboard page in the initial milestone.
- Reach full feature parity with the existing React topology UI in v1.

## Proposed Solution

Introduce a new, standalone server-side rendered Unbounded dashboard as its own component: **`cmd/dashboard`**.

The dashboard server owns:

- Application shell
- Navigation
- Authentication and authorization checks
- Shared layout and visual style
- HTML rendering
- JSON API routing
- Component/module discovery
- Common UI primitives such as cards, tables, badges, forms, and detail sections

Individual Unbounded components expose structured dashboard data and actions. The dashboard renders that data using shared templates so the UI remains consistent.

### Component Topology

`cmd/dashboard` is a separate binary and deployment, not embedded in any single controller. Because modules are owned by different components (net, storage, gantry, machina, inventory), the dashboard communicates with each component over the network (HTTP or another RPC mechanism, to be settled in the design doc).

The static v1 module registry (below) defines, in-repo, how to reach each component's dashboard data endpoint. This keeps a clean process boundary from day one and means the later runtime-discovery model is an evolution of the same contract rather than a rewrite.

Open question for the design doc: transport (plain HTTP+JSON vs. a typed RPC), and whether the dashboard calls component endpoints directly or a component exposes a dedicated "dashboard data" surface.

## Recommended V1 Stack

- Go HTTP server
- Server-side HTML templates
- Bootstrap 5 for the first visual baseline
- htmx for partial page updates and form-driven interactivity
- htmx SSE/WebSocket extensions for modules that expose live data
- Minimal custom CSS for Unbounded identity and status colors
- JSON endpoints for all dashboard data

Bootstrap should be treated as implementation detail, not the public extension contract. Modules should not return arbitrary Bootstrap markup. They should return structured data that the dashboard renders through shared Unbounded templates.

## Static vs. Live Data

Most operational surfaces (overview, resource lists, detail pages) are a good fit for server-rendered HTML refreshed on navigation or via htmx polling.

Some components have dynamic / real-time data that is a poor fit for pure SSR. The existing net dashboard, for example, streams live cluster status deltas over WebSocket. The module contract must therefore account for two data modes:

- **Snapshot data** rendered server-side (default).
- **Live data** streamed to the browser via SSE or WebSocket, for modules that declare a streaming capability.

For v1 the streaming path may be minimal (or deferred for the net module), but the module contract should reserve a capability for it so we do not have to reshape the contract later. Concretely, a module manifest should be able to declare a `stream` capability and a streaming endpoint, and the shell should provide a primitive for live-updating a card/table region.

## Dashboard Module Model

Each component should be represented as a dashboard module.

A module should expose:

- Manifest: id, title, description, navigation, capabilities, permissions
- Summary: overall health, key metrics, warnings, errors
- Resources: tables or lists of component-owned resources
- Details: structured information for one resource
- Actions: supported operations, forms, required permissions
- Stream (optional): live updates for summary/resources via SSE or WebSocket
- Raw JSON: fallback/debug view for all surfaces

Example module manifest:

```json
{
"id": "net",
"title": "Networking",
"description": "Sites, gateway pools, routes, tunnels, and CNI health",
"capabilities": ["summary", "resources", "details", "actions", "stream"],
"requiredPermissions": [
{
"apiGroup": "status.net.unbounded-cloud.io",
"resource": "status",
"verb": "get"
}
]
}
```

## UI Primitives

The dashboard should provide a small set of reusable rendering primitives:

- Page
- Card
- Metric card
- Status badge
- Alert
- Data table
- Detail list
- Action form
- Tabs
- Empty state
- Live region (SSE/WS-backed)
- Raw JSON view

These primitives should be implemented once and reused by all modules.

## Initial Pages

The first milestone should provide:

- `/` global overview
- `/modules` installed modules
- `/modules/net` networking overview
- `/modules/net/sites`
- `/modules/net/nodes`
- `/modules/net/nodes/{name}`
- `/api/dashboard/v1/modules`
- `/api/dashboard/v1/modules/{id}/summary`
- `/api/dashboard/v1/modules/{id}/resources`
- `/api/dashboard/v1/modules/{id}/resources/{kind}/{name}`

## Initial Module

The first module is `net`, backed by the existing unbounded-net status data. The `net` module is an adapter over the controller's existing `status/json` surface and status model, not a reimplementation of net status collection.

The current React-based topology dashboard remains in place during the transition. The new dashboard initially focuses on simple operational pages: overview, sites, nodes, and node details.

### v1 Scope Tradeoff (explicit)

The new dashboard's v1 net pages will initially provide **less** than the existing React UI: no WebGL topology graph and no connectivity heatmap, and possibly snapshot-only data instead of live WebSocket updates. This is an accepted tradeoff to establish the shell and module contract first. The React UI continues to serve the richer topology/heatmap experience until the new dashboard reaches parity, at which point React/Vite is removed.

## Auth and RBAC

The dashboard uses Kubernetes authorization as the source of truth, reusing the existing unbounded-net auth stack rather than building a new one.

The browser authenticates once to the dashboard. The dashboard server performs SubjectAccessReview checks before rendering module pages, returning JSON, or executing actions.

Actions are explicit server-side POSTs with clear RBAC requirements.

Component APIs may still enforce their own authorization when directly reachable, but the normal browser path goes through the dashboard server.

Two areas need design work beyond the existing implementation:

- **Finer-grained, per-action/per-resource permissions.** The existing net SAR check is coarse (a single `get` on `status.net.unbounded-cloud.io/status`). The module `requiredPermissions` contract implies per-capability checks, which is new work to design.
- **Aggregated apiserver path.** The existing dashboard is reachable both directly and via kube-aggregator. How a standalone `cmd/dashboard` fits that topology (and whether it sits behind the aggregator at all) is an open question to resolve in the design doc.

## Extensibility Path

V1 uses a static in-repo module registry for core Unbounded components, including the network address/endpoint for each component's dashboard data surface.

Later versions can add runtime discovery through a Kubernetes resource or annotated Service, for example:

```yaml
apiVersion: dashboard.unbounded-cloud.io/v1alpha1
kind: DashboardModule
metadata:
name: net
spec:
title: Networking
serviceRef:
namespace: unbounded-system
name: unbounded-net-controller
basePath: /dashboard/v1
```

Runtime discovery should be added only after the static module contract is proven.

## Candidate Future Modules

- Networking/CNI
- Node operations
- Machina machines and machine configurations
- Machine operations
- Storage
- Gantry image distribution
- Inventory

## Acceptance Criteria

- A design document exists describing the dashboard architecture, component topology, transport, and module contract. (This proposal is intentionally low-detail; the design document is the next step after the team accepts the proposal.)
- A minimal server-rendered dashboard shell exists as a new `cmd/dashboard` component.
- The shell uses Bootstrap 5 and shared Unbounded templates.
- The shell exposes both HTML pages and JSON endpoints.
- The shell reuses the existing unbounded-net SAR-based auth stack.
- A static `net` module is registered, adapting the existing `status/json` data source.
- The `net` module renders basic overview, sites, nodes, and node detail pages.
- The module contract reserves a streaming (SSE/WebSocket) capability for live data, even if minimally exercised in v1.
- Dashboard access is protected by Kubernetes RBAC checks.
- The existing net dashboard is not broken during the transition.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.