From 6804bcfd4e5369dfacb0a9f4dd0e5c764ae6668f Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Thu, 10 Sep 2026 03:27:19 +0200 Subject: [PATCH] =?UTF-8?q?fix(web):=20Reporting=20=E2=80=94=20Global=20Ad?= =?UTF-8?q?min=20banner=20and=20tenant=20selector=20in=20the=20page=20head?= =?UTF-8?q?er,=20section=20control=20row=20back=20to=20its=20old=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reporting layout (ReportingSidebar) now owns the override-only scope, renders the banner above the header and the selector next to the title like Fleet Health, and hands the scope to the sections through ReportingScopeContext (one hook instance). Delegated callers get neither. Fixes the five-line wrap of the section control row on phones; internal gitlink. Co-Authored-By: Claude Fable 5.1 --- internal | 2 +- .../reporting/ReportingScopeContext.tsx | 17 +++++++++++++ .../settings/reporting/ReportingSidebar.tsx | 25 ++++++++++++++++--- .../reporting/sections/SectionMcpUsage.tsx | 14 +++++------ 4 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 src/Web/autopilot-monitor-web/app/settings/reporting/ReportingScopeContext.tsx diff --git a/internal b/internal index cc2d79d3..94e48036 160000 --- a/internal +++ b/internal @@ -1 +1 @@ -Subproject commit cc2d79d3a5fd36e6c61911c8c75fdb6a9e444c3e +Subproject commit 94e4803674730350c944e2024593dea117ee247c diff --git a/src/Web/autopilot-monitor-web/app/settings/reporting/ReportingScopeContext.tsx b/src/Web/autopilot-monitor-web/app/settings/reporting/ReportingScopeContext.tsx new file mode 100644 index 00000000..34ffe47d --- /dev/null +++ b/src/Web/autopilot-monitor-web/app/settings/reporting/ReportingScopeContext.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { createContext, useContext } from "react"; +import type { GlobalAdminScope } from "@/hooks"; + +// One scope instance for the reporting subtree: the header (ReportingSidebar) owns the banner and the +// tenant selector, the sections read the selection. A second useGlobalAdminScope() call would hold its +// own selection state and never see the header's changes. +const ReportingScopeContext = createContext(null); + +export const ReportingScopeProvider = ReportingScopeContext.Provider; + +export function useReportingScope(): GlobalAdminScope { + const scope = useContext(ReportingScopeContext); + if (!scope) throw new Error("useReportingScope must be used inside the reporting layout"); + return scope; +} diff --git a/src/Web/autopilot-monitor-web/app/settings/reporting/ReportingSidebar.tsx b/src/Web/autopilot-monitor-web/app/settings/reporting/ReportingSidebar.tsx index afdf4b22..b722da10 100644 --- a/src/Web/autopilot-monitor-web/app/settings/reporting/ReportingSidebar.tsx +++ b/src/Web/autopilot-monitor-web/app/settings/reporting/ReportingSidebar.tsx @@ -1,17 +1,34 @@ "use client"; +import { useGlobalAdminScope } from "@/hooks"; +import { GlobalAdminBanner, globalAdminSubtitle } from "@/components/GlobalAdminBanner"; +import { TenantScopeSelector } from "@/components/TenantScopeSelector"; +import { ReportingScopeProvider } from "./ReportingScopeContext"; + export function ReportingSidebar({ children }: { children: React.ReactNode }) { + // Override-only scope (always a concrete tenant, defaulting to the caller's own). A delegated (MSP) + // caller gets neither banner nor selector here: the organization route never lists a managed + // tenant's accounts, so the reporting sections stay on their own tenant for them. + const scope = useGlobalAdminScope(); + const crossTenant = scope.isGlobalAdmin && !scope.isDelegatedScope; + return ( - <> + +
-

Reporting

-

AI agent access and usage statistics

+
+
+

Reporting

+

AI agent access and usage statistics

+
+ {crossTenant && } +
{children}
- +
); } diff --git a/src/Web/autopilot-monitor-web/app/settings/reporting/sections/SectionMcpUsage.tsx b/src/Web/autopilot-monitor-web/app/settings/reporting/sections/SectionMcpUsage.tsx index e827b60c..8154d4d0 100644 --- a/src/Web/autopilot-monitor-web/app/settings/reporting/sections/SectionMcpUsage.tsx +++ b/src/Web/autopilot-monitor-web/app/settings/reporting/sections/SectionMcpUsage.tsx @@ -6,8 +6,7 @@ import { useAuth } from "../../../../contexts/AuthContext"; import { ApiError, apiErrorText, fetchJson } from "@/lib/apiClient"; import { api } from "@/lib/api"; import { scopedApi } from "@/lib/scopedApi"; -import { useGlobalAdminScope } from "@/hooks"; -import { TenantScopeSelector } from "@/components/TenantScopeSelector"; +import { useReportingScope } from "../ReportingScopeContext"; import { DocsLink } from "@/components/DocsLink"; import { DOCS_PATHS } from "@/lib/docsPaths"; import type { @@ -88,11 +87,11 @@ export function SectionMcpUsage() { const { getAccessToken, user } = useAuth(); const canSeeOrganization = !!(user?.isTenantAdmin || user?.isGlobalAdmin); - // Global-admin tenant scope (override-only: always a concrete tenant, defaulting to the caller's own). - // Only the organization cards follow the selection — the caller's own quota and request history never - // do. A delegated (MSP) caller keeps the member path: the organization route never lists a managed - // tenant's accounts, so the selector stays hidden for them. - const scope = useGlobalAdminScope(); + // Global-admin tenant scope from the reporting header (override-only: always a concrete tenant, + // defaulting to the caller's own). Only the organization cards follow the selection — the caller's own + // quota and request history never do. A delegated (MSP) caller keeps the member path: the organization + // route never lists a managed tenant's accounts (the header shows them no selector). + const scope = useReportingScope(); const crossTenant = scope.routeGlobal && !scope.isDelegatedScope; const { effectiveTenantId, isGlobalOverride } = scope; @@ -212,7 +211,6 @@ export function SectionMcpUsage() { {upn &&

{upn}

}
- {!scope.isDelegatedScope && } {/* Plan Badge */} {usagePlan && (