Skip to content

Add the AVD Dashboards application - #1

Merged
BG-Red merged 4 commits into
mainfrom
feat/avd-dashboards-app
Sep 18, 2026
Merged

BG-Red merged 4 commits into
mainfrom
feat/avd-dashboards-app

Conversation

@BG-Red

@BG-Red BG-Red commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Self-hosted Azure Virtual Desktop dashboards: a Bun container behind Azure Container Apps authentication, backed by Postgres, reading AVD data from Azure directly or through Nerdio Manager.

What this adds

Package What it does
packages/core Metric catalog (25 metrics, typed dimensions), 7 dashboard templates, RBAC, zod schemas
packages/db Drizzle schema (23 tables) and migrations
packages/connectors One test / discover / sync interface; Azure direct, Lighthouse, Partner Center (GDAP), Nerdio, demo
packages/demo Synthetic data, so the app is usable without an Azure tenant
apps/server Hono API, EasyAuth middleware, secret store, query engine, Postgres job queue and worker
apps/web React SPA: guided dashboard builder, connection wizard, customers, sync health, users
infra Bicep for Container Apps, Key Vault and an Entra-auth Postgres, plus a deploy script

Multi-tenancy is a first-class concern rather than a later addition: connections can be a service principal or managed identity, an Azure Lighthouse managing tenant, a Partner Center partner tenant, or a Nerdio Manager instance — and they all feed the same dashboards. Azure and Nerdio records about the same host pool merge on the ARM resource id.

Two decisions worth reviewing

Tenant scope is enforced where the SQL is built, not in the UI. Widgets reference metric IDs from a catalog that lives in code, and every value a user can influence is a bound parameter. A viewer scoped to one customer who asks for another gets zero rows rather than falling back to everything. apps/server/src/query/engine.test.ts asserts that, and that a filter value like Windows 11'; drop table connection_facts; -- is treated as a literal.

The repo has to stay free of anything identifying a real environment. gitleaks runs as a pre-commit hook and in CI, with rules that fail on tenant/subscription GUIDs, onmicrosoft.com domains and Nerdio instance URLs. Sample data uses Microsoft's fictional companies and placeholder GUIDs.

Verified

  • Migrations applied against a real Postgres; the demo connection synced 4 tenants over 30 days (~35k session snapshots, ~21k connections, cost and perf rows)
  • Every visualization kind returned correct data through the API; built and saved a dashboard through the real UI in a browser
  • 57 tests pass, including the SQL-level scope and injection tests above
  • Lint, both typechecks, web build and az bicep build are clean; gitleaks reports no leaks across the full history
  • Chart palette validated in light and dark mode; every chart widget carries a table view, which the light-mode contrast result requires

Not verified

  1. Nerdio REST paths are best-effort. Rather than guess silently, the connection test fetches the instance's own Swagger, compares the defaults and reports which paths need overriding. Needs one run against a real instance.
  2. No live Azure tenant. ARM, Log Analytics, Cost Management, the GDAP consent flow and Lighthouse discovery are written against the documented APIs and unit-tested with stubbed fetches only.
  3. Container image not built locally (no Docker daemon available); CI builds it on every PR.
  4. No Playwright e2e — the UI was driven manually in a browser instead.
  5. Fact tables use retention-based deletes rather than monthly partitions.

Before merging

  • The release workflow publishes to ghcr.io/BG-Red/AVD_Dashboards, which GHCR lowercases; the README references avd-dashboards. Pin images: in .github/workflows/release.yml or rename the repo.
  • SECURITY.md points at GitHub private vulnerability reporting — that needs enabling in the repo's security settings.

🤖 Generated with Claude Code

BG-Red and others added 4 commits September 17, 2026 14:54
Self-hosted Azure Virtual Desktop dashboards: a Bun container behind Azure
Container Apps authentication, backed by Postgres, that reads AVD data from
Azure directly or through Nerdio Manager.

Multi-tenancy is a first-class concern rather than a later addition — a
connection is an Azure service principal or managed identity, an Azure
Lighthouse managing tenant, a Partner Center (GDAP) partner tenant, or a
Nerdio Manager instance, and they can all feed the same dashboards. Azure and
Nerdio records about one host pool merge on the ARM resource id.

Onboarding is the part that usually stalls, so the connection wizard generates
the exact `az` commands for the roles it needs, then proves the access with a
permission checklist before anything syncs. Dashboards are built the same way:
pick a question, preview the template against real data, adjust, save.

Structure:
  packages/core        metric catalog, dashboard templates, RBAC, schemas
  packages/db          Drizzle schema and migrations
  packages/connectors  one test/discover/sync interface, five implementations
  packages/demo        synthetic data so the app is usable with no tenant
  apps/server          API, EasyAuth, query engine, Postgres job queue, worker
  apps/web             React SPA with the guided builder
  infra                Bicep for Container Apps, Key Vault, Entra-auth Postgres

Two things shape the design more than anything else:

Tenant scope is enforced where the SQL is built, not in the UI. Widgets
reference metric ids from a catalog that lives in code; every value a user can
influence is a bound parameter. A viewer scoped to one customer who asks for
another gets no rows rather than falling back to everything, and there are
tests that assert exactly that.

The repository has to stay free of anything identifying a real environment, so
gitleaks runs as a pre-commit hook and in CI with rules that fail on tenant and
subscription GUIDs, onmicrosoft.com domains and Nerdio instance URLs. Sample
data uses Microsoft's fictional companies and placeholder GUIDs throughout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GHCR lowercases image paths, so ${{ github.repository }} on a repository with
capitals publishes somewhere the documentation does not mention. Pin the name
and make every reference — README, deploy docs, deploy script, parameters —
agree with what the release workflow actually pushes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three changes that turn a working app into a product.

**The name.** Repository, image path, package scopes and UI all become DashFlow,
with a mark and wordmark drawn as SVG so the identity lives in the repo rather
than in a design tool. The local database and Azure resource prefix follow.

**The design system.** Dark is now the designed default — this is a console
people keep open all day — with light designed second rather than inverted.
Real elevation ladder, self-hosted Inter with tabular numerals, and a component
layer (drawer, modal, popover, toasts, multi-select, sortable paginated table)
built in-house so there is no third-party theme to fight. A command palette on
⌘K reaches any dashboard, page or host pool.

The validated series palette is untouched: it clears the colour-vision and
contrast gates in both modes and should only change by re-running that check.
Series colours now follow the entity rather than its array position, so
filtering a chart no longer repaints the series that remain — a wrong answer
that looked like a styling detail.

**The depth.** Widgets can be dragged and resized on a 12-column grid, with the
layout saved per widget and dashboards that predate it falling back to flowing
by size. Clicking any point, bar or row opens the rows behind it, filtered by
whatever was clicked and scoped exactly like the chart query. Widgets take
per-dimension filters fed by a new values endpoint. Dashboards can be
favourited, duplicated, searched and auto-refreshed.

New screens: Explore for ad-hoc questions (URL-encoded, saveable onto a
dashboard), host pool and session host pages, an error catalogue with the
busiest users beside it, and Settings — which reports the real security
posture rather than advice.

Finally, `autoscale.actions` was a metric nothing could populate. Rather than
leave a dead end, the worker now derives start, stop and drain actions from
consecutive host health snapshots, which is true whether the scaling came from
an Azure scaling plan, from Nerdio, or from a person.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
**A demo anyone can click.** The UI now builds a second way, against a mock API backed
by a synthetic estate generated in the browser — no server, no database. That is what
ships to GitHub Pages on every merge, and it is the honest kind of demo: the same
React app, the same metric catalog, the same dashboards.

Keeping it honest needed a second implementation of the catalog in JavaScript, which
is a liability unless something checks it. So `packages/demo/src/parity.test.ts` loads
one generated dataset into real Postgres and runs identical questions through both the
SQL engine and the evaluator — scalars, split series bucket by bucket, breakdown
ordering, heatmap cells and a dimension filter. Percentiles interpolate the way
`percentile_cont` does, because that is what the test demanded.

**Browser tests.** Eight Playwright specs against that demo build, so CI needs no
database to prove the app loads, a dashboard renders, drilling opens the records,
Explore answers a question, the palette responds to ⌘K, and the theme toggles.

**The public face.** A README with the live demo, real screenshots (captured from the
demo build, so they can never leak a tenant), an honest comparison against AVD
Insights, Workbooks and Nerdio's reporting, and the two claims worth making: tenant
scope is enforced where the SQL is built, and the demo cannot drift from the product.
Plus a roadmap that says what is not planned, a code of conduct, support and
contribution guides, issue and PR templates, a changelog, and a Deploy to Azure button
backed by the compiled template.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@BG-Red
BG-Red merged commit ad0416e into main Sep 18, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant