Model name now renders fully above the bar (no truncation, break-words).
Bar height reduced from h-4 to h-2. Percentage and device count shown
inline right-aligned next to the name row.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tenant ID column in galactic admin mode now shows only the first two
GUID segments (e.g. b54dc1af-5320). Full ID shown as tooltip. Click
copies the full tenant ID to clipboard; copy icon appears on hover.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Path and description stack vertically in their own row (break-all for long paths)
- Replaced rounded-full chips with rounded-lg full-width entries in both settings and admin-config
- Removed 'Core collectors Hello Detector...' info box from settings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The fetchUserInfo call to GET /api/auth/me had no timeout. When Azure
Functions are cold (scale-to-zero), this could block for 20-60 seconds,
holding isLoading=true and showing only the spinner on the landing page.
Add an AbortController with an 8-second deadline. On timeout the abort
signal causes the fetch to throw, which is caught by the existing catch
block that falls back to MSAL token claims — the user can still log in
normally. isGalacticAdmin/isTenantAdmin will be false until the backend
is warm and the next auth refresh succeeds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Form/JSON toggle to Gather Rules editor (create + edit), matching Analyze Rules pattern
- Add missing parameter UI for eventlog (source/provider), file (readContent), and logparser (pattern, trackPosition, maxLines) collector types
- Add two disabled built-in gather rules: dsregcmd /status on DevicePreparation phase change, and last startup event (Event ID 12) from System eventlog
- Fix file collector to read last 4000 chars instead of first (more useful for log files)
- Docs: expand registry allowlist to all 21 prefixes, update file read description
- Docs: add comprehensive Analyze Rules section with confidence scoring, condition types, trigger types, and examples
- Add Documentation link in Analyze Rules page header
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Agent:
- Rewrite EventLog collector from System.Diagnostics.EventLog to
EventLogReader — supports operational/analytic logs (e.g.,
Microsoft-Windows-Shell-Core/Operational), not just classic logs
- New parameters: eventId (XPath filter), messageFilter (*contains*),
source (provider name), maxEntries (1-50)
- Event entries now include Event ID in output: [ID:62407]
UI (gather-rules):
- Dynamic target placeholder and hint text per collector type
- EventLog parameter fields: Event ID, Max Entries, Message Filter
- buildParameters() serializes params per collector type (create+edit)
- on_event placeholder updated to show enrollment_complete/failed
- Documentation link in header → /docs#gather-rules
Docs:
- New "Gather Rules" section with all 6 collector types documented
- Each collector: target format, parameters, allowlists, examples
- Trigger types documented (startup, interval, phase_change, on_event)
- Hash-based deep linking from gather-rules page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CSV now sorted by Timestamp+Sequence (matches Azure Table order exactly)
- Added RowKey as first CSV column (format: yyyyMMddHHmmssfff_Sequence)
- Backend: map entity.RowKey in MapToEnrollmentEvent, expose via EnrollmentEvent
- Tenant ID field replaced with dropdown (domainName + tenantId) + free-text input
- cleanId() helper strips leading/trailing quotes and whitespace from ID inputs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The event sequence counter reset to 0 on every agent restart, causing three
bugs in WhiteGlove (pre-provisioning) sessions: spool sort order mixed boot
events, backend status regressed from Pending to InProgress, and UI timeline
split broke. Additionally, EventSpool.RemoveEvents used a glob pattern that
could delete files from both boots sharing the same sequence number.
Changes:
- SessionPersistence: persist sequence in session.seq alongside session.id
- EventSpool: RemoveEvents uses exact filename instead of glob, new
GetMaxSequence for crash recovery
- MonitoringService: init sequence from persistence + spool ceiling, periodic
save every 50 events + on critical events, save before WhiteGlove exit
- TableStorageService: document WhiteGlove guard as promotion rule
- EnrollmentEvent: clarify Timestamp is agent-side, not server-side
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause found via WhiteGlove session log (session-46ec9146):
On the user's second boot the esp_exiting event triggered the 30s Hello-Wait
timer. The Hello wizard had not yet appeared within those 30s, so
OnHelloWaitTimeout() emitted hello_wait_timeout and immediately fired
HelloCompleted → enrollment_complete. This was wrong: Hello policy was
explicitly detected as enabled (hello_policy_detected: helloEnabled=true).
The Hello wizard launched shortly after (hello_provisioning_willlaunch at seq
27), but the agent had already sent enrollment_complete.
Fix: track _isHelloPolicyEnabled in CheckHelloPolicy(). In OnHelloWaitTimeout,
if Hello policy is known to be enabled, do not fire HelloCompleted — instead
emit the hello_wait_timeout event with action=extended_wait and start the long
HelloCompletion timer (1500s). This gives the wizard up to 25 minutes to appear.
The enrollment_complete path via timeout is now only taken when Hello policy is
disabled or not yet detected.
The original session (46ec9146) was also affected by the spool-drain bug
(whiteglove_complete was seq 126+, never uploaded before exit), so the session
never reached Pending. Both bugs contributed to the incorrect Succeeded result.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace ad-hoc Task.Run block with dedicated HandleWhiteGloveComplete()
method mirroring HandleEnrollmentComplete() quality:
- StopEventCollectors() + _spool.StopWatching() before draining
- Full spool drain loop (same fix as previous commit, now in proper place)
- Task.Delay(2000) for final network transmission
- Diagnostics package upload with "preprov" suffix before exit
- Structured logging matching the enrollment complete sequence
- DiagnosticsPackageService.CreateAndUploadAsync() gains optional
fileNameSuffix parameter: "preprov" yields
AgentDiagnostics-{sessionId}-{ts}-preprov.zip, distinguishable
from the later full-enrollment package uploaded after Part 2
- UploadDiagnosticsWithTimelineEvents() passes suffix through to service
WhiteGlove devices now have a preprov diagnostics package in Pending
state, and a full package after user enrollment completes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UploadEventsAsync fetches at most MaxBatchSize (100) events per call.
If whiteglove_complete was beyond event #100 in the spool (e.g. Seq 126),
it was never sent before Environment.Exit(0) — leaving the session stuck
on InProgress instead of transitioning to Pending.
Fix: loop until spool is empty (with a safety cap of 20 iterations) so
all events including whiteglove_complete are guaranteed to reach the backend.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the fragile setTimeout(500ms) with a deterministic guard:
wasAuthenticated.current already prevents spurious redirects during
MSAL's double handleRedirectPromise cycle. The real fix was setting
navigateToLoginRequestUrl: false in msalConfig. No timer needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: MSAL fires handleRedirectPromise twice on page load, causing
isAuthenticated to briefly flip false between the two settlements. This
triggered ProtectedRoute to redirect to '/', which then re-authenticated
and redirected to '/dashboard', causing the dashboard page to mount and
unmount periodically while the user was viewing a session.
Two fixes:
1. navigateToLoginRequestUrl: false — prevents MSAL from navigating to
the stored login request URL after redirect settlement, eliminating
the spurious navigation that caused the dashboard to mount.
2. ProtectedRoute unauthenticated redirect now waits 500ms before
executing, giving MSAL time to complete its double-settlement cycle
before we conclude the user is truly unauthenticated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When galacticAdminMode is true, the initial data fetch effect fires
immediately (before MSAL settles). If getAccessToken() returns null,
hasInitialFetch was left as true, permanently blocking any retry.
The session page would load empty and never recover.
Fix: reset hasInitialFetch on token/network failures so the effect
can retry. Added `user` to dependencies so the effect re-fires once
MSAL settles and user info is available.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When MSAL's handleRedirectPromise re-settles or token renewal changes
inProgress, isAuthenticated/isLoading can briefly flip, causing
ProtectedRoute to unmount all children (showing loading spinner or null).
This made the session detail page go blank for 1-3s periodically.
Fix: track wasAuthenticated via useRef. Once authenticated, keep children
mounted through transient auth-state flips. Loading spinner and redirect
to landing only apply on the very first load (never been authenticated).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename HelloDetector → EspAndHelloTracker (class tracks both ESP and Hello)
- Detect CommercialOOBE_ESPProgress_WhiteGlove_Success (EventID 62407) and emit
whiteglove_complete event; WhiteGlove check placed before generic OOBE_ESP.*Exiting
regex to avoid false match; idempotency guard prevents duplicate events
- Detect CommercialOOBE_ESPProgress_Failure (EventID 62407) and emit esp_failure
event with Error severity for timeline visibility
- Agent exits gracefully after WhiteGlove (Environment.Exit, no DeleteSessionId,
no enrollment-complete marker); scheduled task restarts on next boot
- EnrollmentTracker subscribes to WhiteGloveCompleted event, stops summary timer,
emits whiteglove_complete without writing completion marker or deleting IME state
- Backend: whiteglove_complete transitions session to SessionStatus.Pending;
SetSessionPreProvisionedAsync marks session for split-timeline rendering
- Backend: Pending → InProgress auto-resume on agent re-registration (WhiteGlove Part 2)
- Backend: GetExcessiveDataSendersAsync excludes IsPreProvisioned sessions to prevent
false blocking of devices resuming after weeks in storage
- Shared: SessionStatus.Pending added; IsPreProvisioned added to SessionSummary;
WhiteGloveComplete event type constant added
- UI: Pending status badge (amber) in dashboard and session detail
- UI: Split timeline for WhiteGlove sessions — Pre-Provisioning Part + User Enrollment
Part; Awaiting User Enrollment info box when Part 2 has no events yet
- UI: Mark as Failed button enabled for Pending sessions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unknown tenants and suspended tenants are now rejected as the very
first step in SecurityValidator before any certificate, rate-limit,
or hardware check is performed. Adds TryGetConfigurationAsync to
TenantConfigurationService (no auto-create, cache-aware) and inserts
the gate in ValidateRequestAsync — covering RegisterSession,
IngestEvents, GetAgentConfig, and GetDiagnosticsUploadUrl.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ThemeContext: default to system preference (prefers-color-scheme) when no
manual preference is stored in localStorage; listen for OS-level changes
when user has not overridden; toggleTheme now explicitly persists to localStorage
- Navbar: add standalone sun/moon icon button to Tier 1 right-side icon row
(left of notification bell); remove dark mode entry from overflow menu
- Landing page: replace MSP Support (Planned) with Fleet Health, replace
Intelligent Alerts with Diagnostics Collection — both existing shipped features
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- publish totalSignedUpTenants in platform-stats blob JSON (was computed but not serialized)
- add totalEventsProcessed and lastUpdated timestamp to landing page stats bar
- replace plain welcome card with structured onboarding block: gradient accent, numbered steps with doc deep-links (/docs#setup, /docs#agent-setup), and polished quick-link cards with hover states
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Devices that continuously send data beyond a configurable window (default
24h) are now automatically blocked by the daily maintenance function,
regardless of session status. Introduces LastEventAt tracking on session
entities (written on every event batch ingest) to enable efficient
per-tenant queries without scanning the Events table.
- Add LastEventAt field to SessionSummary and session entity writes
- Add GetExcessiveDataSendersAsync query (StartedAt < cutoff AND LastEventAt > cutoff)
- Add BlockExcessiveDataSendersAsync maintenance step with idempotency check
- Add MaxSessionWindowHours and MaintenanceBlockDurationHours to AdminConfiguration
- Expose both settings in Device Block Management section of Admin Config UI
- Include HelloDetector agent changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows a prominent purple banner when galacticAdminMode is active,
clearly indicating that data is aggregated across all tenants.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Galactic Admins can block a device by serial number from the admin-configuration
page. The block is enforced at ingest time (after security validation, before
body decompression) and is configurable in duration (default 12h, max 30 days).
The agent receives a DeviceBlocked signal in the IngestEventsResponse and stops
all uploads for the session. Blocks persist across backend restarts via a new
BlockedDevices table in Azure Table Storage, with an in-memory cache for fast
ingest lookups.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The backend was refactored to run security checks before parsing the
request body, using X-Tenant-Id from the HTTP header. The agent was
not sending this header, causing every ingest call to fail immediately
with 400 BadRequest before any events were processed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Agent/Backend: configurable MaxCollectorDurationHours (default 4h) stops interval-based collectors on stuck sessions; setting exposed in global admin config
- Backend: IngestEventsFunction now runs all security checks (cert, rate limit, hardware whitelist, Autopilot validation) before touching the request body, eliminating unnecessary gzip decompression and JSON parsing on rejected requests; added TenantId header/body cross-check
- Backend/Frontend: SignalR eventStream refactored from full event payload to signal-only (newEventCount); frontend fetches canonical event state from Table Storage on each signal, eliminating gap issues and phase-name race conditions
- Frontend: fetchEvents replaces (not merges) event list; phaseName computed at render time in eventsByPhase useMemo; 30s catch-up fetch added as safety net
- Frontend: session event export tool in admin-configuration (teal block) — fetch by sessionId+tenantId, download timeline export (.txt with UI phase grouping) and raw CSV export
- Frontend: changelog page (/changelog) with Private Preview context and first architecture entry; dashboard feedback banner updated to link changelog; Navbar overflow menu gains Preview Changelog link
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The primary download button uses /api/download/bootstrapper (Azure Function).
A secondary "Alternate download via GitHub" button now links directly to the
raw script in the repository, so users can always get the script even if the
Function API is unavailable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TenantContext no longer uses the legacy 'deadbeef' fake placeholder ID — it now
initializes to empty string and cleans up any stale value from localStorage.
This lets every page guard with the simple !tenantId check (consistent with
settings.tsx) instead of comparing against a magic string.
All 7 affected pages now wait for a real tenantId before making API calls, and
tenantId is included in useEffect deps so fetches retry automatically once auth
completes. The previously patched dashboard and sessions/[sessionId] pages are
cleaned up to use the same pattern.
All pages now use addNotification() for API errors instead of silent console.error
or local error state, giving users visible feedback when backend calls fail.
Fullscreen error pages in audit and usage-metrics are replaced by notifications,
keeping the page content visible so users can retry from the notification area.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TenantContext starts with a 'deadbeef' placeholder ID and updates asynchronously
once AuthContext finishes loading. Dashboard and session detail pages were firing
their initial API fetches before the real tenantId was available, causing backend
calls with the wrong tenant ID and resulting in failures.
Added guard in both pages to skip the fetch until tenantId is no longer the
default placeholder. Also added tenantId and galacticAdminMode to the session
detail useEffect dependencies so the fetch retries automatically once the real
ID arrives.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>