Follow-ups on the Session Cleanup admin page after first hands-on review:
an entry point for restoring already-completed cascades (Sessions row gone,
manifest still in the 33-day TTL window), a proper outcome panel that surfaces
what the backend already returned, and a sidebar fix.
== New Restore Browser tab ==
- Backend: GET /api/admin/tenants/{tenantId}/deletion-manifests enumerates
every persisted snapshot blob under <tenantId>/, parses (sessionId,
manifestId, sizeBytes, lastModifiedUtc) from the blob name, groups by
sessionId and sorts both levels by recency. GA-only + TenantScoping.RouteParam
in EndpointAccessPolicyCatalog (pinned by EndpointPolicyCatalogCompletenessTests).
- BlobStorageService.EnumerateDeletionManifestsByTenantAsync — sibling to
the existing maintenance-sweep enumerator but tenant-prefixed and yielding
size + timestamp so the UI can rank without an extra HEAD per blob.
- Web: new tab "Restore Browser" between Stranded and Activity. Tenant
dropdown (sourced from AdminConfigContext.tenants) → collapsible
session/manifest tree on the left, manifest summary on the right with a
Restore… button that opens the dialog with IDs pre-filled. Useful when
the cascade completed cleanly and the row no longer appears on the
In-Flight / Poisoned / Stranded tabs.
== Reusable summary view ==
DeletionManifestSummaryView extracted from the modal so the same fetch +
field block + worker-progress + preflight-counts + last-failure panel renders
both inside the modal (Preview button on the other tabs) and inline on the
Restore Browser's right-hand panel. Modal becomes a thin wrapper.
== Dry-run / real-run side-by-side ==
RestoreConfirmDialog now keeps both outcomes visible:
- Two slots (dryRunResult, realRunResult), each rendered as a full result
section with predicted/actual rows-by-table, inventory re-increments,
duration, backend message, current state, pending manifest id.
- "Start over" resets both slots so the operator can re-run cleanly.
- Submit buttons gate the workflow: pre-submit shows "Run dry-run" + an
explicit "Skip dry-run · Restore now"; after dry-run the second button
becomes "Apply restore" (clear-intent); after real-run all submit
buttons disappear so accidental double-applies aren't possible.
Previously the dialog discarded everything but Outcome + Mode + dryRun
flag, hiding the actual point of dry-run (the wouldRestoreByTable map).
== Codex review follow-ups (this round) ==
- P2: Restore Browser cleared the displayed sessions only after the new
fetch returned, so a fast A→B switch (or out-of-order responses) could
leave the operator looking at tenant A's manifests with the dropdown
saying B. Fix: clear sessions/expandedSessions/selection synchronously
on tenant change, abort the in-flight request via AbortController,
pair the response with a loadedTenantId state, and gate rendering on
loadedTenantId === selectedTenantId. Three-layer protection so a late
response that sneaks past the abort can't paint stale data.
- P3: PoisonedTab's onRestored callback closed the dialog immediately
after a real restore, defeating the new dry-run vs real-run side-by-side
display. Fix: drop setRestoring(null) from the callback so the dialog
stays open until the operator dismisses it via Close / Start over.
Background refresh of the Poisoned list still happens via setRefreshKey.
Now consistent with the Restore Browser tab.
== Sidebar highlight fix ==
GlobalSidebar marked BOTH ga-ops sub-items active when on the longer
nested route, because each sub used pathname.startsWith(sub.href + "/")
independently. Visible bug: navigating to /admin/ops/session-cleanup lit
up both "Maintenance" (/admin/ops) and "Session Cleanup" in the sidebar.
Fix: compute the longest-matching sub.href once per expanded group, mark
only that one active.
Tests: backend xUnit 1538/1538 pass (one new endpoint registered in the
catalog, picked up by the existing completeness test). Web vitest 347/347
pass. TypeScript strict + dotnet build clean.
Deliberately deferred:
- xUnit for GetTenantDeletionManifestsFunction (same HttpRequestData
mock-harness gap as the other read endpoints).
- vitest for RestoreBrowserTab + the new dialog flow (defer until a
real field issue).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Autopilot Monitor
Advanced monitoring and troubleshooting solution for Windows Autopilot deployments. Gain full visibility into every enrollment session with a detailed event timeline, fleet health dashboards, and session reporting. Define custom analysis rules to automatically detect issues and gather rules to collect targeted evidence. Retrieve diagnostics packages on demand, configure agent settings like auto-reboot behavior and automatic timezone adjustment — all managed centrally from the web dashboard.
Private Preview
Autopilot Monitor is currently running as a Private Preview. Visit autopilotmonitor.com to request access and learn more.
Overview
Autopilot Monitor provides real-time tracking, intelligent diagnostics, and automated troubleshooting for Windows Autopilot enrollment processes. It consists of:
- Bootstrap Script — PowerShell script deployed via Intune that starts monitoring early in the enrollment process
- Monitoring Agent — Lightweight .NET application that collects telemetry and evidence during enrollment
- Backend API — Azure Functions-based ingestion and processing pipeline
- Web Dashboard — Next.js application for real-time monitoring and fleet analytics
Architecture
For detailed information about the system architecture, components, and data flow, see Architecture Documentation.
Documentation
Full documentation is available at autopilotmonitor.com/docs
License
This project uses a split licensing model:
- MIT License — Agent (
src/Agent/) and Shared library (src/Shared/) — unrestricted use on end-user devices - AGPL-3.0 — Backend (
src/Backend/), Web Dashboard (src/Web/), and MCP Server (src/McpServer/) — server-side components remain open source
See LICENSE for full details.



