From a2de05fe33c1223efd2d9d3774e0a07797c8b2c1 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Sun, 1 Mar 2026 16:47:55 +0100 Subject: [PATCH] refactor: reorganize metrics/stats API routes and fix session-reports 404 handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename app-metrics → metrics/app (tenant + galactic) - Rename usage-metrics → metrics/usage (tenant) - Rename platform/usage-metrics → galactic/metrics/usage - Rename platform-stats → stats/platform (anon whitelist updated) - SessionReportService: gracefully handle missing table (404) with empty list - SessionReportsSection: treat 404 as no reports yet (suppress error) Co-Authored-By: Claude Opus 4.6 --- .../Functions/GetAppMetricsFunction.cs | 2 +- .../GetGalacticAppMetricsFunction.cs | 2 +- .../Functions/GetPlatformStatsFunction.cs | 2 +- .../Functions/PlatformUsageMetricsFunction.cs | 4 +-- .../Functions/UsageMetricsFunction.cs | 4 +-- .../Middleware/AuthenticationMiddleware.cs | 2 +- .../Services/SessionReportService.cs | 31 ++++++++++++------- .../components/SessionReportsSection.tsx | 8 +++-- .../app/admin-configuration/page.tsx | 12 +++---- .../app/fleet-health/page.tsx | 4 +-- .../app/platform-usage-metrics/page.tsx | 2 +- .../app/usage-metrics/page.tsx | 2 +- 12 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/GetAppMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/GetAppMetricsFunction.cs index 4d723f5a..d44e502e 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/GetAppMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/GetAppMetricsFunction.cs @@ -20,7 +20,7 @@ namespace AutopilotMonitor.Functions.Functions [Function("GetAppMetrics")] public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "app-metrics")] HttpRequestData req) + [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "metrics/app")] HttpRequestData req) { try { diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/GetGalacticAppMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/GetGalacticAppMetricsFunction.cs index 112ef53f..bb4bff00 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/GetGalacticAppMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/GetGalacticAppMetricsFunction.cs @@ -25,7 +25,7 @@ namespace AutopilotMonitor.Functions.Functions [Function("GetGalacticAppMetrics")] public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "galactic/app-metrics")] HttpRequestData req) + [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "galactic/metrics/app")] HttpRequestData req) { try { diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/GetPlatformStatsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/GetPlatformStatsFunction.cs index 22098611..ef5171a4 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/GetPlatformStatsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/GetPlatformStatsFunction.cs @@ -23,7 +23,7 @@ namespace AutopilotMonitor.Functions.Functions [Function("GetPlatformStats")] public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "platform-stats")] HttpRequestData req) + [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "stats/platform")] HttpRequestData req) { try { diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/PlatformUsageMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/PlatformUsageMetricsFunction.cs index 3d3b7a1f..a2e7b02d 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/PlatformUsageMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/PlatformUsageMetricsFunction.cs @@ -27,12 +27,12 @@ namespace AutopilotMonitor.Functions.Functions } /// - /// GET /api/platform/usage-metrics - Compute and return platform usage metrics + /// GET /api/galactic/metrics/usage - Compute and return platform usage metrics /// On-demand computation with 5-minute cache (Galactic Admin only) /// [Function("GetPlatformUsageMetrics")] public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "platform/usage-metrics")] + [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "galactic/metrics/usage")] HttpRequestData req) { _logger.LogInformation("Platform usage metrics requested"); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/UsageMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/UsageMetricsFunction.cs index 641cff49..5c1cfac4 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/UsageMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/UsageMetricsFunction.cs @@ -24,12 +24,12 @@ namespace AutopilotMonitor.Functions.Functions } /// - /// GET /api/usage-metrics?tenantId={tenantId} - Compute and return tenant-specific usage metrics + /// GET /api/metrics/usage?tenantId={tenantId} - Compute and return tenant-specific usage metrics /// On-demand computation with 5-minute cache (Tenant Admin) /// [Function("GetTenantUsageMetrics")] public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "usage-metrics")] + [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "metrics/usage")] HttpRequestData req) { _logger.LogInformation("Tenant usage metrics requested"); diff --git a/src/Backend/AutopilotMonitor.Functions/Middleware/AuthenticationMiddleware.cs b/src/Backend/AutopilotMonitor.Functions/Middleware/AuthenticationMiddleware.cs index 7af20805..32a1b595 100644 --- a/src/Backend/AutopilotMonitor.Functions/Middleware/AuthenticationMiddleware.cs +++ b/src/Backend/AutopilotMonitor.Functions/Middleware/AuthenticationMiddleware.cs @@ -41,7 +41,7 @@ public class AuthenticationMiddleware : IFunctionsWorkerMiddleware private static readonly string[] _anonymousRoutes = { "/api/health", - "/api/platform-stats", + "/api/stats/platform", "/api/agent/register-session", "/api/agent/ingest", "/api/agent/config", diff --git a/src/Backend/AutopilotMonitor.Functions/Services/SessionReportService.cs b/src/Backend/AutopilotMonitor.Functions/Services/SessionReportService.cs index 15e117f2..950e9ad5 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/SessionReportService.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/SessionReportService.cs @@ -4,6 +4,7 @@ using System.IO; using System.IO.Compression; using System.Text; using System.Threading.Tasks; +using Azure; using Azure.Data.Tables; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; @@ -172,19 +173,27 @@ namespace AutopilotMonitor.Functions.Services var tableClient = _storageService.GetTableServiceClient().GetTableClient(Constants.TableNames.SessionReports); var results = new List(); - await foreach (var entity in tableClient.QueryAsync(e => e.PartitionKey == "reports")) + try { - results.Add(new SessionReportMetadata + await foreach (var entity in tableClient.QueryAsync(e => e.PartitionKey == "reports")) { - ReportId = entity.GetString("ReportId") ?? string.Empty, - TenantId = entity.GetString("TenantId") ?? string.Empty, - SessionId = entity.GetString("SessionId") ?? string.Empty, - Comment = entity.GetString("Comment") ?? string.Empty, - Email = entity.GetString("Email") ?? string.Empty, - BlobName = entity.GetString("BlobName") ?? string.Empty, - SubmittedBy = entity.GetString("SubmittedBy") ?? string.Empty, - SubmittedAt = entity.GetDateTimeOffset("SubmittedAt")?.UtcDateTime ?? DateTime.MinValue - }); + results.Add(new SessionReportMetadata + { + ReportId = entity.GetString("ReportId") ?? string.Empty, + TenantId = entity.GetString("TenantId") ?? string.Empty, + SessionId = entity.GetString("SessionId") ?? string.Empty, + Comment = entity.GetString("Comment") ?? string.Empty, + Email = entity.GetString("Email") ?? string.Empty, + BlobName = entity.GetString("BlobName") ?? string.Empty, + SubmittedBy = entity.GetString("SubmittedBy") ?? string.Empty, + SubmittedAt = entity.GetDateTimeOffset("SubmittedAt")?.UtcDateTime ?? DateTime.MinValue + }); + } + } + catch (RequestFailedException ex) when (ex.Status == 404) + { + // Table doesn't exist yet — no reports have been submitted + _logger.LogDebug("SessionReports table does not exist yet, returning empty list"); } return results; diff --git a/src/Web/autopilot-monitor-web/app/admin-configuration/components/SessionReportsSection.tsx b/src/Web/autopilot-monitor-web/app/admin-configuration/components/SessionReportsSection.tsx index add4be7e..31aa48b6 100644 --- a/src/Web/autopilot-monitor-web/app/admin-configuration/components/SessionReportsSection.tsx +++ b/src/Web/autopilot-monitor-web/app/admin-configuration/components/SessionReportsSection.tsx @@ -42,6 +42,11 @@ export function SessionReportsSection({ headers: { Authorization: `Bearer ${token}` } }); + if (res.status === 404) { + // Table/container doesn't exist yet — no reports submitted so far + setReports([]); + return; + } if (!res.ok) throw new Error(`Failed to load reports: ${res.statusText}`); const data = await res.json(); setReports(data.reports ?? []); @@ -63,9 +68,6 @@ export function SessionReportsSection({

Session Reports

- - Private Preview -

Sessions reported by Tenant Admins for analysis diff --git a/src/Web/autopilot-monitor-web/app/admin-configuration/page.tsx b/src/Web/autopilot-monitor-web/app/admin-configuration/page.tsx index 4f63a362..0505b6dd 100644 --- a/src/Web/autopilot-monitor-web/app/admin-configuration/page.tsx +++ b/src/Web/autopilot-monitor-web/app/admin-configuration/page.tsx @@ -322,6 +322,12 @@ export default function AdminConfigurationPage() { setSuccessMessage={setSuccessMessage} /> + {/* Session Reports */} + + {/* Session Event Export */} - - {/* Session Reports (Private Preview) */} -

diff --git a/src/Web/autopilot-monitor-web/app/fleet-health/page.tsx b/src/Web/autopilot-monitor-web/app/fleet-health/page.tsx index e961d9f3..98b9541d 100644 --- a/src/Web/autopilot-monitor-web/app/fleet-health/page.tsx +++ b/src/Web/autopilot-monitor-web/app/fleet-health/page.tsx @@ -169,8 +169,8 @@ export default function FleetHealthPage() { if (!token) return; const days = range === "7d" ? 7 : range === "30d" ? 30 : 90; const endpoint = galacticAdminMode - ? `${API_BASE_URL}/api/galactic/app-metrics?days=${days}` - : `${API_BASE_URL}/api/app-metrics?tenantId=${tenantId}&days=${days}`; + ? `${API_BASE_URL}/api/galactic/metrics/app?days=${days}` + : `${API_BASE_URL}/api/metrics/app?tenantId=${tenantId}&days=${days}`; const response = await fetch( endpoint, { headers: { Authorization: `Bearer ${token}` } } diff --git a/src/Web/autopilot-monitor-web/app/platform-usage-metrics/page.tsx b/src/Web/autopilot-monitor-web/app/platform-usage-metrics/page.tsx index de3f9889..21764f51 100644 --- a/src/Web/autopilot-monitor-web/app/platform-usage-metrics/page.tsx +++ b/src/Web/autopilot-monitor-web/app/platform-usage-metrics/page.tsx @@ -96,7 +96,7 @@ export default function PlatformUsageMetricsPage() { throw new Error('Failed to get access token'); } - const response = await fetch(`${API_BASE_URL}/api/platform/usage-metrics`, { + const response = await fetch(`${API_BASE_URL}/api/galactic/metrics/usage`, { headers: { 'Authorization': `Bearer ${token}` } diff --git a/src/Web/autopilot-monitor-web/app/usage-metrics/page.tsx b/src/Web/autopilot-monitor-web/app/usage-metrics/page.tsx index 82f68bcf..af2aa021 100644 --- a/src/Web/autopilot-monitor-web/app/usage-metrics/page.tsx +++ b/src/Web/autopilot-monitor-web/app/usage-metrics/page.tsx @@ -85,7 +85,7 @@ export default function UsageMetricsPage() { return; } - const response = await fetch(`${API_BASE_URL}/api/usage-metrics?tenantId=${tenantId}`, { + const response = await fetch(`${API_BASE_URL}/api/metrics/usage?tenantId=${tenantId}`, { headers: { 'Authorization': `Bearer ${token}` }