Commit Graph

236 Commits

Author SHA1 Message Date
Oliver Kieselbach 865410fc21 fix: remove timer-based redirect delay in ProtectedRoute
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>
2026-02-27 15:42:49 +01:00
Oliver Kieselbach 97ca48f56a fix: stop MSAL-triggered navigation to dashboard while on session detail page
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>
2026-02-27 15:40:32 +01:00
Oliver Kieselbach 24862ca1de fix: allow retry when fetchSessionDetails fails due to token not ready
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>
2026-02-27 15:29:24 +01:00
Oliver Kieselbach a5e83f066b download progress sanity check if miscalculation occurs 2026-02-27 15:12:11 +01:00
Oliver Kieselbach 1368678f48 fix: prevent ProtectedRoute from flashing children on transient MSAL auth-state changes
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>
2026-02-27 15:11:17 +01:00
Oliver Kieselbach 0f6a0cc21d added private preview changelog entry for first try pre-provisioning support 2026-02-27 14:40:46 +01:00
Oliver Kieselbach b78b872f68 implement WhiteGlove (Pre-Provisioning) support end-to-end
- 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>
2026-02-27 14:36:24 +01:00
Oliver Kieselbach 03ba8ab905 added WhiteGlove example event as comment for later WhiteGlove detection implementation 2026-02-26 23:53:45 +01:00
Oliver Kieselbach 81c4806083 Hardening: re-architecture to use mTLS for complete agent communication (agent endpoints consolidated) 2026-02-26 23:02:50 +01:00
Oliver Kieselbach 64d631376e fix footer on mobile 2026-02-26 21:25:52 +01:00
Oliver Kieselbach ca9c084d23 fix Docs table of content rendering on mobile 2026-02-26 21:00:45 +01:00
Oliver Kieselbach c09c74f7aa added Docs link to public site navbar 2026-02-26 20:45:11 +01:00
Oliver Kieselbach fc7f73b301 add tenant existence and suspension gate to all agent endpoints
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>
2026-02-26 19:07:57 +01:00
Oliver Kieselbach 2bdce7e2cd HelloDetector fix and Changelog/Known-Issues Updates 2026-02-26 18:39:46 +01:00
Oliver Kieselbach 646eb68d7b Added Modal Dialog for unsaved changes in Settings 2026-02-26 14:58:15 +01:00
Oliver Kieselbach 1275f8faed add dark mode toggle to authenticated navbar and replace planned feature cards
- 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>
2026-02-26 13:38:57 +01:00
Oliver Kieselbach 4b59a1f63c add totalSignedUpTenants and totalEventsProcessed to platform stats and improve dashboard welcome screen
- 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>
2026-02-26 13:08:15 +01:00
Oliver Kieselbach 2d5efc9b73 add excessive data sender detection to nightly maintenance
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>
2026-02-26 12:44:03 +01:00
Oliver Kieselbach 13f2ead02b roadmap update 2026-02-26 11:24:01 +01:00
Oliver Kieselbach 3fa6741f33 fix wording and links 2026-02-26 11:16:10 +01:00
Oliver Kieselbach 6501af09aa add Galactic Admin mode banner to Fleet Health page
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>
2026-02-26 11:15:47 +01:00
Oliver Kieselbach c2bf4b4e2c fix nullable reference warnings in BlockedDeviceService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 10:49:18 +01:00
Oliver Kieselbach 58b41a26b1 add temporary device blocking for rogue devices sending excessive data
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>
2026-02-26 10:42:17 +01:00
Oliver Kieselbach f0df9926f7 improve dashboard Private Preview banner with LinkedIn link
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 10:38:12 +01:00
Oliver Kieselbach 50e06cec4e fix missing X-Tenant-Id header in agent IngestEvents request
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>
2026-02-26 10:14:54 +01:00
Oliver Kieselbach 326557c4cd add max collector duration policy, SignalR signal refactor, session export tool, changelog page, security-first ingest ordering
- 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>
2026-02-26 09:52:22 +01:00
Oliver Kieselbach ad5c5ff878 add alternate GitHub download link for bootstrapper script in docs
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>
2026-02-25 13:22:55 +01:00
Oliver Kieselbach 775108ea0f remove fake tenantId placeholder, fix all race conditions, add error notifications
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>
2026-02-25 13:09:39 +01:00
Oliver Kieselbach 074013b2e1 fix race condition causing backend call failures on dashboard and session detail
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>
2026-02-25 12:19:09 +01:00
Oliver Kieselbach ef02ec5696 fixed bug on login site 2026-02-25 12:13:34 +01:00
Oliver Kieselbach 9c05f376ea fix robots sitemap url 2026-02-25 01:46:37 +01:00
Oliver Kieselbach f0983837ec fix url and google verification 2026-02-25 01:35:40 +01:00
Oliver Kieselbach 44e5663682 changed sitemap 2026-02-25 01:28:40 +01:00
Oliver Kieselbach 294d79a731 fixed a compiler warning 2026-02-24 23:40:05 +01:00
Oliver Kieselbach bbf815f739 remove dead UploadBundle code and fix Lenovo model detection
- Remove UploadBundleRequest, UploadBundleResponse classes (ApiModels.cs)
- Remove UploadBundle API endpoint constant (Constants.cs)
- Remove GetBundleUploadUrlAsync method (BackendApiClient.cs) — no callers anywhere
- Fix HardwareInfo: Lenovo devices report the marketing model name in
  Win32_ComputerSystemProduct.Version, not Win32_ComputerSystem.Model;
  add Lenovo-specific WMI fallback for correct model string

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 23:28:53 +01:00
Oliver Kieselbach 1b066ba634 agent: WMI query fix for lenovo model 2026-02-24 23:12:32 +01:00
Oliver Kieselbach c1c19c4f7a fix diagnostics upload 401 and enrich upload event data
- Add /api/diagnostics/upload-url to anonymous routes in AuthenticationMiddleware so the
  new device-cert-authenticated endpoint is not blocked by the JWT middleware
- Add DiagnosticsUploadResult value object to DiagnosticsPackageService carrying BlobName,
  SasUrlPrefix (truncated, safe to log), and ErrorCode
- Change CreateAndUploadAsync return type to DiagnosticsUploadResult for richer error context
- Add BuildSasUrlPrefix helper to produce a safe truncated URL for event data
- Change UploadToBlobStorageAsync to return (bool success, string errorCode) tuple
- Update MonitoringService to populate sasUrl and errorCode in diagnostics_upload_failed
  event data dict for visible timeline diagnostics

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 22:18:21 +01:00
Oliver Kieselbach 8b17b82b78 route diagnostics SAS expiry warnings to notification bell
Instead of inline banners on the settings page, expired or near-expiry
SAS URLs now fire a persistent warning/error notification to the bell icon.
The notification includes a direct "View" link to /settings#diagnostics.

NotificationContext: added optional href field to Notification; addNotification
signature extended with href parameter for both keyed and unkeyed paths.

Navbar: notifications with href render a "View" link that marks the
notification as read and navigates to the target anchor.

Settings: removed inline SAS warning banners; addNotification called on
config load when expiry <= 7 days (warning) or expired (error); diagnostics
section gets id="diagnostics" anchor for deep-linking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 21:34:56 +01:00
Oliver Kieselbach 6aafaa5ff8 fetch diagnostics SAS URL on-demand to reduce device exposure window
Instead of delivering the long-lived SAS URL in the agent config response
(where it was cached on disk for the full enrollment period), the agent now
requests a short-lived URL via POST /api/diagnostics/upload-url just before
uploading — the URL is held in memory only and never written to config.

Backend: new GetDiagnosticsUploadUrlFunction with same cert/hardware
security validation as all other agent endpoints; GetAgentConfigFunction
now sends DiagnosticsUploadEnabled (bool) instead of the SAS URL;
ConfigVersion bumped to 6.

Agent: BackendApiClient.GetDiagnosticsUploadUrlAsync added;
DiagnosticsPackageService fetches URL on-demand via the API client;
AgentConfiguration.DiagnosticsBlobSasUrl replaced by DiagnosticsUploadEnabled.

UI: settings page parses SAS URL expiry (se= param) and shows expiry date
inline with colour coding; expired or near-expiry URLs trigger
error/warning notifications at the top of the page on load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 21:15:50 +01:00
Oliver Kieselbach 9aa4d0a30d added tenaant management refresh button 2026-02-24 18:11:34 +01:00
Oliver Kieselbach ef7d7febed platform stats fix and english wording 2026-02-24 18:05:40 +01:00
Oliver Kieselbach 79a4407831 enhanced platform stats and better tenant management 2026-02-24 17:54:39 +01:00
Oliver Kieselbach 525cf0a0aa wording updates 2026-02-24 17:42:37 +01:00
Oliver Kieselbach 0cf6a700d8 wording, fix tenant management, preview badge 2026-02-24 17:29:11 +01:00
Oliver Kieselbach 146d53982f better admin tenant management 2026-02-24 17:14:26 +01:00
Oliver Kieselbach a63fdba9e6 fix: dark mode for docs Settings section
- Add missing globals.css overrides: text-purple-900, text-sky-900,
  text-emerald-900, text-rose-900, bg-rose-50, border-rose-200,
  divide-gray-100 (row dividers)
- Refactor SettingsGroup: split color prop into color + borderColor so
  the outer border renders with an explicit dark-aware color class
- Add bg-white to SettingsRow and Danger Zone body so they pick up the
  global .dark .bg-white override correctly
- Change default text in SettingsRow to text-gray-500 (already has dark
  override) instead of text-gray-400

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 15:12:36 +01:00
Oliver Kieselbach de2baad1d0 chore: add temporary banner about Analyze Rules refinement phase
Informs users that rules are being actively validated and changed,
occasional redeploys and inconsistent analysis results are expected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:51:56 +01:00
Oliver Kieselbach d807f7de7e fix: clean up built-in analyze rules
- Remove ANALYZE-ESP-002 (Security Policy Application Failure): too many
  false positives, the pattern matched unrelated EnforcementState events
- Disable ANALYZE-CORR-001 (Slow Network) by default: download_progress
  events don't reliably indicate slow network, needs rethinking
- Disable ANALYZE-APP-009 (App Download Stalled) by default: relies on
  download_progress.status=failed which doesn't reflect actual stall
- Rename ANALYZE-APP-002 from "MSI Installation Error" to "App Installation
  Error": broadens scope, removes CCM/SCCM log reference (Intune-only),
  points to IME logs instead
- Also include Enabled in auto-update comparison so Enabled=false changes
  propagate to existing tenants on next seed check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:49:50 +01:00
Oliver Kieselbach 07a66f2952 fix: support cross-tenant analysis for Galactic Admins
GetRuleResultsFunction was ignoring the tenantId query parameter and
always using the tenant from the JWT token. As a result, a Galactic
Admin viewing another tenant's session would get 'No events found'
because the data was stored under the session's tenant, not the admin's.

Now follows the same pattern as GetSessionEventsFunction:
- Read tenantId from query parameter (sent by the frontend as effectiveTenantId)
- Fall back to JWT tenant if not provided
- Allow cross-tenant access only for Galactic Admins (403 otherwise)
- Use the effective tenant ID for all storage calls and rule engine runs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:31:08 +01:00
Oliver Kieselbach 8f9aec3e05 fix: make Analyze Now button actually re-run all rules
Previously, AnalyzeSessionAsync always loaded existing results and
skipped rules already evaluated (deduplication check). The reanalyze
flag from the API endpoint was never passed through, so clicking
"Analyze Now" only returned cached results.

Fix:
- Add `reanalyze` parameter to `AnalyzeSessionAsync`; when true, skip
  deduplication so all active rules evaluate against the full event stream
- In GetRuleResultsFunction, pass reanalyze=true and delete stale stored
  results before writing the fresh evaluation output

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:17:07 +01:00