From d0f62a6aaf8f8cb0d55b23a60d7843dc3b7034e4 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Mon, 12 Oct 2026 00:27:22 +0200 Subject: [PATCH] web(geo): sign-in before the loading card on Geographic Performance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both geo pages returned their CalculatingCard before rendering their own ProtectedRoute. MSAL keeps the account in sessionStorage, so a new tab, bookmark or typed URL starts signed out: the tenant scope never settled, the fetch never started, and ProtectedRoute never mounted to fire the login redirect. The page spun on "Calculating geographic metrics… 0s elapsed" forever with no API call. ProtectedRoute now wraps each page's default export, so the content only mounts once signed in. Verified in a signed-out headless Edge: production stays on the card, the fix redirects to login.microsoftonline.com. Co-Authored-By: Claude Opus 5.5 --- .../app/geographic-performance/page.tsx | 812 +++++++++--------- .../geographic-performance/sessions/page.tsx | 438 +++++----- 2 files changed, 627 insertions(+), 623 deletions(-) diff --git a/src/Web/autopilot-monitor-web/app/geographic-performance/page.tsx b/src/Web/autopilot-monitor-web/app/geographic-performance/page.tsx index e57631af..ef507a42 100644 --- a/src/Web/autopilot-monitor-web/app/geographic-performance/page.tsx +++ b/src/Web/autopilot-monitor-web/app/geographic-performance/page.tsx @@ -59,10 +59,14 @@ const formatBytes = (bytes: number) => { export default function GeographicPerformancePage() { // useSearchParams (the ?days= window) requires a Suspense boundary under the static export. + // ProtectedRoute stays outermost: the content's loading card waits for a tenant scope that a + // signed-out tab never gets, so rendered outside it the login redirect would never fire. return ( - }> - - + + }> + + + ); } @@ -168,429 +172,427 @@ function GeographicPerformanceContent() { } return ( - -
- -
-
-
-
-
- - - -

Geographic Performance

-
+
+ +
+
+
+
+
+ + + +

Geographic Performance

-
- - {/* Time Range Toggle */} - - {/* Group By Toggle */} - setGroupBy(v as GroupBy)} - /> - +
+
+ + {/* Time Range Toggle */} + + {/* Group By Toggle */} + setGroupBy(v as GroupBy)} + /> + +
+
+
+
+ +
+ {/* Geo Disabled Warning */} + {geoMetrics && !geoMetrics.geoLocationEnabled && !isAggregatedGlobalView && ( +
+ + + +
+
Geo-Location collection is disabled
+
+ New sessions will not collect location data. Enable geo-location in{" "} + Configuration{" "} + to start collecting geographic data for future enrollments.
-
+ )} -
- {/* Geo Disabled Warning */} - {geoMetrics && !geoMetrics.geoLocationEnabled && !isAggregatedGlobalView && ( -
- - - + {/* Coverage Info */} + {geoMetrics && ( +
+ {geoMetrics.locationsWithData} of {geoMetrics.totalSessions} sessions have location data + {geoMetrics.locationsWithData < geoMetrics.totalSessions && ( + (older sessions may lack geo data) + )} +
+ )} + + {/* Summary Cards */} + {geoMetrics && stats && ( +
+
+
Locations Tracked
+
{geoMetrics.locations.length}
+
{geoMetrics.locationsWithData} sessions with geo
+
+
+
Outliers Detected
+
0 ? "text-red-600" : "text-green-600"}`}> + {stats.outliers} +
+
>2 std deviations from mean
+
+
+
Fastest Location
+ +
+ {stats.fastest ? `${Math.round(stats.fastest.avgDurationMinutes)} min avg` : "No data"} +
+
+
+
Slowest Location
+ +
+ {stats.slowest ? `${Math.round(stats.slowest.avgDurationMinutes)} min avg` : "No data"} +
+
+
+
DO Peer Efficiency
+
30 ? "text-green-600" : + geoMetrics.globalAverages.avgDoPercentPeerCaching > 0 ? "text-yellow-600" : "text-gray-400" + }`}> + {geoMetrics.globalAverages.avgDoPercentPeerCaching > 0 + ? `${geoMetrics.globalAverages.avgDoPercentPeerCaching}%` + : "—"} +
+
+ {geoMetrics.globalAverages.totalDoBytesFromPeers > 0 + ? `${formatBytes(geoMetrics.globalAverages.totalDoBytesFromPeers)} from peers / ${formatBytes(geoMetrics.globalAverages.totalDoBytesFromPeers + geoMetrics.globalAverages.totalDoBytesFromHttp)} total` + : "No DO data yet"} +
+
+
+ )} + + {/* Map */} + {geoMetrics && geoMetrics.locations.length > 0 && ( +
+
+

Performance Map

+ +
+
+ +
+
+ +
+
+ )} + + {/* Global Averages Banner */} + {geoMetrics && ( +
+
Global Averages (Benchmark)
+
+ Computed over the sessions on this page, not over other customers — every vs Global figure compares against this line. +
+
+
+ Avg Duration:{" "} + {geoMetrics.globalAverages.avgDurationMinutes} min +
+
+ Median:{" "} + {geoMetrics.globalAverages.medianDurationMinutes} min +
-
Geo-Location collection is disabled
-
- New sessions will not collect location data. Enable geo-location in{" "} - Configuration{" "} - to start collecting geographic data for future enrollments. -
+ Avg Min/App:{" "} + {geoMetrics.globalAverages.avgMinutesPerApp} min
-
- )} - - {/* Coverage Info */} - {geoMetrics && ( -
- {geoMetrics.locationsWithData} of {geoMetrics.totalSessions} sessions have location data - {geoMetrics.locationsWithData < geoMetrics.totalSessions && ( - (older sessions may lack geo data) - )} -
- )} - - {/* Summary Cards */} - {geoMetrics && stats && ( -
-
-
Locations Tracked
-
{geoMetrics.locations.length}
-
{geoMetrics.locationsWithData} sessions with geo
+
+ Avg Throughput:{" "} + {formatThroughput(geoMetrics.globalAverages.avgThroughputBytesPerSec)}
-
-
Outliers Detected
-
0 ? "text-red-600" : "text-green-600"}`}> - {stats.outliers} -
-
>2 std deviations from mean
-
-
-
Fastest Location
- -
- {stats.fastest ? `${Math.round(stats.fastest.avgDurationMinutes)} min avg` : "No data"} -
-
-
-
Slowest Location
- -
- {stats.slowest ? `${Math.round(stats.slowest.avgDurationMinutes)} min avg` : "No data"} -
-
-
-
DO Peer Efficiency
-
30 ? "text-green-600" : - geoMetrics.globalAverages.avgDoPercentPeerCaching > 0 ? "text-yellow-600" : "text-gray-400" - }`}> +
+ DO P2P:{" "} + {geoMetrics.globalAverages.avgDoPercentPeerCaching > 0 - ? `${geoMetrics.globalAverages.avgDoPercentPeerCaching}%` - : "—"} -
-
- {geoMetrics.globalAverages.totalDoBytesFromPeers > 0 - ? `${formatBytes(geoMetrics.globalAverages.totalDoBytesFromPeers)} from peers / ${formatBytes(geoMetrics.globalAverages.totalDoBytesFromPeers + geoMetrics.globalAverages.totalDoBytesFromHttp)} total` - : "No DO data yet"} -
+ ? `${geoMetrics.globalAverages.avgDoPercentPeerCaching}% peers` + : "No data"} + +
+
+ API Latency:{" "} + + {geoMetrics.globalAverages.medianApiLatencyMs > 0 + ? `${Math.round(geoMetrics.globalAverages.medianApiLatencyMs)} ms` + : "No data"} +
- )} +
+ )} - {/* Map */} - {geoMetrics && geoMetrics.locations.length > 0 && ( -
-
-

Performance Map

- -
-
- -
-
- -
+ {/* Heatmap Table */} + {geoMetrics && ( +
+
+

Location Performance

- )} - - {/* Global Averages Banner */} - {geoMetrics && ( -
-
Global Averages (Benchmark)
-
- Computed over the sessions on this page, not over other customers — every vs Global figure compares against this line. + {sortedLocations.length === 0 ? ( +
+ No geographic data available for the selected time range.
-
-
- Avg Duration:{" "} - {geoMetrics.globalAverages.avgDurationMinutes} min -
-
- Median:{" "} - {geoMetrics.globalAverages.medianDurationMinutes} min -
-
- Avg Min/App:{" "} - {geoMetrics.globalAverages.avgMinutesPerApp} min -
-
- Avg Throughput:{" "} - {formatThroughput(geoMetrics.globalAverages.avgThroughputBytesPerSec)} -
-
- DO P2P:{" "} - - {geoMetrics.globalAverages.avgDoPercentPeerCaching > 0 - ? `${geoMetrics.globalAverages.avgDoPercentPeerCaching}% peers` - : "No data"} - -
-
- API Latency:{" "} - - {geoMetrics.globalAverages.medianApiLatencyMs > 0 - ? `${Math.round(geoMetrics.globalAverages.medianApiLatencyMs)} ms` - : "No data"} - -
-
-
- )} - - {/* Heatmap Table */} - {geoMetrics && ( -
-
-

Location Performance

-
- {sortedLocations.length === 0 ? ( -
- No geographic data available for the selected time range. -
- ) : ( -
- - - - - - - - - - - - - - - - - {sortedLocations.map((loc) => ( - { - // Carry the selected tenant into the drill-in so a cross-tenant caller (GA override - // or delegated/MSP) scopes the session list to it. Empty = GA all-tenants aggregate. - const tenantParam = selectedTenantId ? `&tenantId=${selectedTenantId}` : ""; - router.push(`/geographic-performance/sessions?locationKey=${encodeURIComponent(loc.locationKey)}&days=${days}&groupBy=${groupBy}${tenantParam}`); - }} - onMouseEnter={() => setSelectedLocation(loc.locationKey)} - > - {/* Outlier accent on the cell, not the row: the tbody's divide color overrides a row's border-color. */} - + + + + ))} + +
- Location - handleSort("sessionCount")} - > - Sessions - - Success - handleSort("avgDurationMinutes")} - title="Average enrollment duration of the succeeded sessions at this location in the selected range" - > - Avg Duration - - P95 - handleSort("appLoadScore")} - > - App-Load-Score - handleSort("avgThroughputBytesPerSec")} - > - Throughput - handleSort("medianApiLatencyMs")} - title="Agent→backend HTTP round-trip measured on the devices (median per location) — high values indicate network distance to the backend region" - > - API Latency - handleSort("avgDoPercentPeerCaching")} - > - P2P % - - vs Global -
-
- {loc.locationKey} - {loc.isOutlier && ( - - {loc.outlierDirection === "slow" ? "Slow" : "Fast"} - - )} + ) : ( +
+ + + + + + + + + + + + + + + + + {sortedLocations.map((loc) => ( + { + // Carry the selected tenant into the drill-in so a cross-tenant caller (GA override + // or delegated/MSP) scopes the session list to it. Empty = GA all-tenants aggregate. + const tenantParam = selectedTenantId ? `&tenantId=${selectedTenantId}` : ""; + router.push(`/geographic-performance/sessions?locationKey=${encodeURIComponent(loc.locationKey)}&days=${days}&groupBy=${groupBy}${tenantParam}`); + }} + onMouseEnter={() => setSelectedLocation(loc.locationKey)} + > + {/* Outlier accent on the cell, not the row: the tbody's divide color overrides a row's border-color. */} + - - - + + - - + + + + + - - - - - - ))} - -
+ Location + handleSort("sessionCount")} + > + Sessions + + Success + handleSort("avgDurationMinutes")} + title="Average enrollment duration of the succeeded sessions at this location in the selected range" + > + Avg Duration + + P95 + handleSort("appLoadScore")} + > + App-Load-Score + handleSort("avgThroughputBytesPerSec")} + > + Throughput + handleSort("medianApiLatencyMs")} + title="Agent→backend HTTP round-trip measured on the devices (median per location) — high values indicate network distance to the backend region" + > + API Latency + handleSort("avgDoPercentPeerCaching")} + > + P2P % + + vs Global +
+
+ {loc.locationKey} + {loc.isOutlier && ( + + {loc.outlierDirection === "slow" ? "Slow" : "Fast"} + + )} +
+ {loc.avgAppCount > 0 && ( +
+ ~{loc.avgAppCount} apps/session · {formatBytes(loc.totalDownloadBytes)} total
- {loc.avgAppCount > 0 && ( -
- ~{loc.avgAppCount} apps/session · {formatBytes(loc.totalDownloadBytes)} total -
- )} -
{loc.sessionCount} - {/* Rate is over finished enrollments (succeeded + failed) only; a - location where everything is still in flight has no rate yet. */} - {loc.succeeded + loc.failed > 0 ? ( - - {loc.successRate}% - - ) : ( - - — - - )} - + )} + {loc.sessionCount} + {/* Rate is over finished enrollments (succeeded + failed) only; a + location where everything is still in flight has no rate yet. */} + {loc.succeeded + loc.failed > 0 ? ( 0 ? undefined : "No succeeded enrollments yet"} + className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${MAP_COLOR_MODE_BY_ID.success.resolve(loc, geoMetrics.globalAverages).className}`} > - {loc.avgDurationMinutes > 0 ? `${Math.round(loc.avgDurationMinutes)} min` : "—"} + {loc.successRate}% - - {loc.p95DurationMinutes > 0 ? `${Math.round(loc.p95DurationMinutes)} min` : "—"} - - - {loc.appLoadScore > 0 ? Math.round(loc.appLoadScore) : "—"} + ) : ( + + — - {loc.minutesPerApp > 0 && ( - - ({loc.minutesPerApp.toFixed(1)} min/app) + )} + + 0 ? undefined : "No succeeded enrollments yet"} + > + {loc.avgDurationMinutes > 0 ? `${Math.round(loc.avgDurationMinutes)} min` : "—"} + + + {loc.p95DurationMinutes > 0 ? `${Math.round(loc.p95DurationMinutes)} min` : "—"} + + + {loc.appLoadScore > 0 ? Math.round(loc.appLoadScore) : "—"} + + {loc.minutesPerApp > 0 && ( + + ({loc.minutesPerApp.toFixed(1)} min/app) + + )} + + {formatThroughput(loc.avgThroughputBytesPerSec)} + + {loc.medianApiLatencyMs > 0 ? ( +
0 ? "+" : ""}${loc.apiLatencyVsGlobalPct.toFixed(0)}% vs global` : ""}`}> + + {Math.round(loc.medianApiLatencyMs)} ms - )} -
- {formatThroughput(loc.avgThroughputBytesPerSec)} - - {loc.medianApiLatencyMs > 0 ? ( -
0 ? "+" : ""}${loc.apiLatencyVsGlobalPct.toFixed(0)}% vs global` : ""}`}> - - {Math.round(loc.medianApiLatencyMs)} ms - - ({loc.apiLatencySessionCount}) -
- ) : ( - — - )} -
- {loc.doSessionCount > 0 ? ( -
0 - ? `LAN: ${formatBytes(loc.totalDoBytesFromLanPeers)} | Group: ${formatBytes(loc.totalDoBytesFromGroupPeers)} | Internet: ${formatBytes(loc.totalDoBytesFromInternetPeers)}` - : `${loc.doSessionCount} session(s) with DO data` - }> - - {loc.avgDoPercentPeerCaching > 0 ? `${loc.avgDoPercentPeerCaching}%` : "0%"} - - - ({loc.doSessionCount}) - -
- ) : ( - — - )} -
-
- {loc.avgDurationMinutes > 0 && loc.durationVsGlobalPct !== 0 ? ( - 0 - ? "bg-red-50 text-red-700" - : "bg-green-50 text-green-700" - }`} - > - {loc.durationVsGlobalPct > 0 ? "+" : ""} - {loc.durationVsGlobalPct.toFixed(0)}% - - ) : ( - — - )} - - - + ({loc.apiLatencySessionCount})
-
-
- )} - {sortedLocations.length > 0 && } -
- )} + ) : ( + — + )} +
+ {loc.doSessionCount > 0 ? ( +
0 + ? `LAN: ${formatBytes(loc.totalDoBytesFromLanPeers)} | Group: ${formatBytes(loc.totalDoBytesFromGroupPeers)} | Internet: ${formatBytes(loc.totalDoBytesFromInternetPeers)}` + : `${loc.doSessionCount} session(s) with DO data` + }> + + {loc.avgDoPercentPeerCaching > 0 ? `${loc.avgDoPercentPeerCaching}%` : "0%"} + + + ({loc.doSessionCount}) + +
+ ) : ( + — + )} +
+
+ {loc.avgDurationMinutes > 0 && loc.durationVsGlobalPct !== 0 ? ( + 0 + ? "bg-red-50 text-red-700" + : "bg-green-50 text-green-700" + }`} + > + {loc.durationVsGlobalPct > 0 ? "+" : ""} + {loc.durationVsGlobalPct.toFixed(0)}% + + ) : ( + — + )} + + + +
+
+
+ )} + {sortedLocations.length > 0 && } +
+ )} - {/* No data state */} - {!geoMetrics && !loading && ( -
- Failed to load geographic metrics. Please try again later. -
- )} -
-
-
+ {/* No data state */} + {!geoMetrics && !loading && ( +
+ Failed to load geographic metrics. Please try again later. +
+ )} + + ); } diff --git a/src/Web/autopilot-monitor-web/app/geographic-performance/sessions/page.tsx b/src/Web/autopilot-monitor-web/app/geographic-performance/sessions/page.tsx index 0250f4ed..5da17c72 100644 --- a/src/Web/autopilot-monitor-web/app/geographic-performance/sessions/page.tsx +++ b/src/Web/autopilot-monitor-web/app/geographic-performance/sessions/page.tsx @@ -57,14 +57,18 @@ function formatDate(dateStr: string): string { } export default function LocationSessionsPage() { + // ProtectedRoute stays outermost: the content's loading card must never render for a signed-out + // tab, or the login redirect would never fire. return ( - -
Loading...
- - }> - -
+ + +
Loading...
+ + }> + +
+
); } @@ -180,222 +184,220 @@ function LocationSessionsContent() { } return ( - -
- {/* Delegated ("MSP") admin: blue cross-tenant banner (GA gets the purple one below). */} - - {globalAdminMode && ( -
- - +
+ {/* Delegated ("MSP") admin: blue cross-tenant banner (GA gets the purple one below). */} + + {globalAdminMode && ( +
+ + + + Global Admin View + — aggregating data across all tenants +
+ )} +
+
+ +
+ + + - Global Admin View - — aggregating data across all tenants +
+

{locationKey}

+

+ {timeLabel} · grouped by {groupBy} +

+
+
+
+
+ +
+ {/* Summary Stats */} + {stats && ( +
+
+
Sessions
+
{stats.total}
+
+
+
API Latency
+
0 ? "text-gray-900" : "text-gray-400"}`}> + {stats.medianApiLatency > 0 ? `${stats.medianApiLatency} ms` : "—"} +
+
+ {stats.latencySessions > 0 ? `${stats.latencySessions} session(s) with data` : "requires latency-reporting agent"} +
+
+
+
Succeeded
+
{stats.succeeded}
+
+ {stats.total > 0 ? `${Math.round((stats.succeeded / stats.total) * 100)}%` : "—"} +
+
+
+
Failed
+
0 ? "text-red-600" : "text-gray-400"}`}> + {stats.failed} +
+
+ {stats.total > 0 && stats.failed > 0 ? `${Math.round((stats.failed / stats.total) * 100)}%` : "—"} +
+
+
+
Avg Duration
+
+ {stats.avgDuration > 0 ? `${stats.avgDuration} min` : "—"} +
+
succeeded sessions
+
+
+
DO Peers
+
= 50 ? "text-green-600" : + stats.avgDoPercent >= 10 ? "text-yellow-600" : + "text-gray-600" + }`}> + {stats.doSessions > 0 ? `${stats.avgDoPercent}%` : "—"} +
+
+ {stats.doSessions > 0 ? `${stats.doSessions} of ${stats.total} sessions` : "no DO telemetry"} +
+
)} -
-
- -
- - - - -
-

{locationKey}

-

- {timeLabel} · grouped by {groupBy} -

-
-
-
-
-
- {/* Summary Stats */} - {stats && ( -
-
-
Sessions
-
{stats.total}
-
-
-
API Latency
-
0 ? "text-gray-900" : "text-gray-400"}`}> - {stats.medianApiLatency > 0 ? `${stats.medianApiLatency} ms` : "—"} -
-
- {stats.latencySessions > 0 ? `${stats.latencySessions} session(s) with data` : "requires latency-reporting agent"} -
-
-
-
Succeeded
-
{stats.succeeded}
-
- {stats.total > 0 ? `${Math.round((stats.succeeded / stats.total) * 100)}%` : "—"} -
-
-
-
Failed
-
0 ? "text-red-600" : "text-gray-400"}`}> - {stats.failed} -
-
- {stats.total > 0 && stats.failed > 0 ? `${Math.round((stats.failed / stats.total) * 100)}%` : "—"} -
-
-
-
Avg Duration
-
- {stats.avgDuration > 0 ? `${stats.avgDuration} min` : "—"} -
-
succeeded sessions
-
-
-
DO Peers
-
= 50 ? "text-green-600" : - stats.avgDoPercent >= 10 ? "text-yellow-600" : - "text-gray-600" - }`}> - {stats.doSessions > 0 ? `${stats.avgDoPercent}%` : "—"} -
-
- {stats.doSessions > 0 ? `${stats.doSessions} of ${stats.total} sessions` : "no DO telemetry"} -
-
+ {/* Session Table */} +
+
+

+ Sessions ({data?.totalCount ?? 0}) +

+
+ {!data?.sessions?.length ? ( +
+ No sessions found for this location. +
+ ) : ( +
+ + + + + + + + + + + + + + + {data.sessions.map((session) => ( + router.push(sessionUrl(session.sessionId))} + className="hover:bg-gray-50 cursor-pointer transition-colors" + > + + + + + + + + + + ))} + +
+ Device Name + + Serial Number + + Model + + Status + + Duration + + API Latency + + DO Peers + + Started At +
+ {session.deviceName || "—"} + + {session.serialNumber || "—"} + + {session.model || "—"} + + + + {formatDuration(session.durationSeconds)} + + {(session.avgApiLatencyMs ?? 0) > 0 ? ( + + {Math.round(session.avgApiLatencyMs!)} ms + + ) : ( + — + )} + + {session.hasDoTelemetry ? ( +
+ = 50 + ? "bg-green-100 text-green-800" + : (session.doPercentPeerCaching ?? 0) >= 10 + ? "bg-yellow-100 text-yellow-800" + : "bg-gray-100 text-gray-600" + }`} + > + {(session.doPercentPeerCaching ?? 0).toFixed(1)}% + + + ({session.doAppCount}/{session.totalAppCount}) + +
+ ) : ( + 0 + ? `${session.totalAppCount} app(s) installed, no DO telemetry` + : "no app install data" + } + > + — + + )} +
+ {formatDate(session.startedAt)} +
)} - - {/* Session Table */} -
-
-

- Sessions ({data?.totalCount ?? 0}) -

-
- {!data?.sessions?.length ? ( -
- No sessions found for this location. -
- ) : ( -
- - - - - - - - - - - - - - - {data.sessions.map((session) => ( - router.push(sessionUrl(session.sessionId))} - className="hover:bg-gray-50 cursor-pointer transition-colors" - > - - - - - - - - - - ))} - -
- Device Name - - Serial Number - - Model - - Status - - Duration - - API Latency - - DO Peers - - Started At -
- {session.deviceName || "—"} - - {session.serialNumber || "—"} - - {session.model || "—"} - - - - {formatDuration(session.durationSeconds)} - - {(session.avgApiLatencyMs ?? 0) > 0 ? ( - - {Math.round(session.avgApiLatencyMs!)} ms - - ) : ( - — - )} - - {session.hasDoTelemetry ? ( -
- = 50 - ? "bg-green-100 text-green-800" - : (session.doPercentPeerCaching ?? 0) >= 10 - ? "bg-yellow-100 text-yellow-800" - : "bg-gray-100 text-gray-600" - }`} - > - {(session.doPercentPeerCaching ?? 0).toFixed(1)}% - - - ({session.doAppCount}/{session.totalAppCount}) - -
- ) : ( - 0 - ? `${session.totalAppCount} app(s) installed, no DO telemetry` - : "no app install data" - } - > - — - - )} -
- {formatDate(session.startedAt)} -
-
- )} -
-
-
- +
+ +
); }