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 && (