Files
AutopilotMonitor/src/Backend/AutopilotMonitor.Functions
Oliver Kieselbach 8dd45b20fb feat(delegated): scoped-global reader — dashboard + analytics + fleet overview
Make a delegated ("MSP") admin a "scoped Global Reader": the same pages a Global
Admin sees, but every tenant switcher/filter is bounded to the managed allow-list.
Reader path only (readWrite deferred). MCP stays read-only (Phase 6 already covers
delegated reads).

Backend (bounded aggregate — reuses the GA per-tenant fan-out):
- global/sessions + global/stats/sessions: GlobalReadOrAdmin -> GlobalReadOrDelegatedSubset
  (keep QueryParam). A delegated caller WITHOUT ?tenantId= is admitted as a bounded
  aggregate; the handler restricts the cross-tenant fan-out to RequestContext.AllowedTenantIds.
  With ?tenantId=X it drills into one managed tenant (middleware validates X in scope).
- TableStorageService: GetAllSessions{,Page,Stats}Async gain an optional allowedTenantIds
  set; FetchAllSessionsPageInternalAsync swaps its Step-1 tenant source to that subset
  (everything downstream — per-tenant query, inverted-tick merge, cursor — is reused).
- IsDelegatedReadTier += GlobalReadOrDelegatedSubset so the single-tenant drill rescue fires.
- SignalR AddToGroup/RemoveFromGroup: admit a delegated caller to a managed tenant-{id}
  group (in scope); global-admins stays platform-scope-only.

Web:
- useAggregatedAdminScope / useGlobalAdminScope: cross-tenant mode is now on for a delegated
  admin too (isGlobalAdmin = crossTenant; new isDelegatedScope). Tenant list bounded to the
  managed subset; always a concrete tenant (no "All tenants" aggregate on analytics pages).
- TenantScopeSelector hides the aggregate option for delegated; GlobalAdminBanner gains a
  blue "Delegated Admin View" variant (wired on all analytics pages + dashboard).
- Nav: Fleet group exposes Dashboard + Fleet Health + Software + Geographic + SLA + Usage;
  showDashboard now includes delegated.
- Dashboard delegated-ready: crossTenant drives the /global endpoints + filter UI; empty
  filter = bounded aggregate over managed tenants, a selected tenant = drill-in; global-admins
  SignalR join stays real-GA-only; read-only (mutations already GA-gated). `?tenant=` deep-link
  seeds the filter.
- Fleet is now a pure stats card grid; the per-tenant session browser /fleet/[tenantId] is
  removed and cards link to /dashboard?tenant=<id>.

Deferred follow-ups: Audit Log + Geographic session drill-in for delegated (both diverge from
the scope-hook pattern / need tenantId threading).

Tests: backend 2484 green (catalog/middleware contract updated for the new subset tier),
web 481 green, MCP 292 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:06:25 +02:00
..