MSP · CMD Suite KB // Microsoft Intune SOC Triage Console
Knowledge Base v1.0 · 2026-03
MSP · CMD Suite — Stack Intelligence Layer
Microsoft Intune SOC Triage Console
stack-intune-triage.html — Microsoft Graph API v1.0
A fullscreen, multi-tab endpoint management and security triage dashboard for MSP engineers and SOC analysts managing Microsoft Intune-enrolled device fleets. Surfaces device compliance status, policy violations, active alerts, app deployment health, and per-device remediation actions through Microsoft Graph API. Features an integrated AI-powered triage summary (Claude Sonnet via Anthropic API) and auto-refreshes every 30 seconds.
Microsoft Graph API v1.0 Microsoft Intune Entra ID / MSAL.js Multi-Platform MDM AI Triage (Claude) Chart.js Mock-First / Proxy-Ready
DEMO MODE — DEMO = true MSAL.js AUTH NOT CONFIGURED UI FULLY FUNCTIONAL
📋
01 / OVERVIEW
What Is This Tool
16
Graph API Endpoints
8
Remote Action Endpoints
1
Beta-Only Endpoint
🎯Purpose

The Intune SOC Triage Console replaces manual portal navigation for device compliance triage. It combines device health, compliance policy states, app deployment status, and active alerts into a single always-on interface. Engineers can search, filter, sort, and drill into any device to view policy failures and trigger remote remediation actions without leaving the console.

The console uses four tabs: Devices (main triage list with detail panel), Alerts (critical and warning alert feed), Compliance (policy-level rollup with 6-month trend chart), and Apps (deployment status with per-app device breakdown). An AI Summary button in the device detail panel sends device context to Claude Sonnet for an automated SOC triage write-up.

📄File Details
Filenamestack-intune-triage.html
SuiteMSP · CMD Design Suite — Stack Intelligence Layer
External DependenciesChart.js 4.4.1 (CDN), Google Fonts
Mode at DeliveryDemoDEMO = true in JS; mock data via graphFetch()
Live ActivationConfigure MSAL.js, set DEMO = false, uncomment graphFetch() production block
Refresh Interval30 seconds via startAutoRefresh(30000)
API VendorMicrosoft Graph API v1.0 — graph.microsoft.com
Auth MethodEntra ID via MSAL.js (PKCE / popup flow); Bearer token injected per request
AI FeatureAnthropic API — claude-sonnet-4-20250514 — streaming response
State PersistencelocalStorage key intune_triage_v2 — filter, sort, theme, notes
Mock-First Architecture. All device, policy, app, and alert data is served from static JS constants (DEVICES, POLICIES, APPS, ALERTS). The graphFetch() wrapper returns mock data when DEMO = true. The production fetch() block is written and commented out directly above the mock fallback in every fetch call site.
🔌
02 / INTEGRATION
Integration Status
Current State: Full demo mode. All data rendered from static JS constants. No live Graph API calls are made. MSAL.js Sign-In modal is a stub. AI Summary calls the real Anthropic API when a network key is available and gracefully falls back to an offline summary when not. See Proxy Activation section to go live.
📡Data Source Status
Data SourceStatusLive Graph Endpoint
Device listDemoGET /v1.0/deviceManagement/managedDevices
Device detailDemoGET /v1.0/deviceManagement/managedDevices/{id}
Policy compliance per deviceDemoGET /v1.0/deviceManagement/managedDevices/{id}/deviceCompliancePolicyStates
Compliance policies listDemoGET /v1.0/deviceManagement/compliancePolicies
App catalogDemoGET /v1.0/deviceAppManagement/mobileApps
App install statusDemoGET /v1.0/deviceAppManagement/mobileApps/{id}/deviceStatuses
Alert feedBeta onlyGET /beta/deviceManagement/alerts — no v1.0 equivalent
Remote syncProxy NeededPOST /v1.0/deviceManagement/managedDevices/{id}/syncDevice
Remote wipeProxy NeededPOST /v1.0/deviceManagement/managedDevices/{id}/wipe
Remote rebootProxy NeededPOST /v1.0/deviceManagement/managedDevices/{id}/rebootNow
Retire deviceProxy NeededPOST /v1.0/deviceManagement/managedDevices/{id}/retire
Remote lockProxy NeededPOST /v1.0/deviceManagement/managedDevices/{id}/remoteLock
Rotate BitLocker keyProxy NeededPOST /v1.0/deviceManagement/managedDevices/{id}/rotateBitLockerKeys
Collect diagnosticsProxy NeededPOST /v1.0/deviceManagement/managedDevices/{id}/collectDiagnostics
Reset passcodeProxy NeededPOST /v1.0/deviceManagement/managedDevices/{id}/resetPasscode
Compliance trend reportDemoGET /v1.0/reports/getDeviceNonComplianceReport
Auth / user identityMSAL StubGET /v1.0/me — requires MSAL.js configured app registration
AI SummaryLive (if key available)Anthropic API — claude-sonnet-4-20250514 streaming
🏗
03 / ARCHITECTURE
Architecture
┌────────────────────────────────────────────────────────────────┐ │ INTUNE SOC TRIAGE CONSOLE — DATA FLOW │ └────────────────────────────────────────────────────────────────┘ Browser (stack-intune-triage.html) │ └── DOMContentLoaded │ setTimeout 300ms ├── initTheme() │ restore saved theme or detect OS pref ├── runLoadingSequence() │ animated loading modal │ └── on close: loadDashboard() │ suite entry point │ │ └─ renderGauges() │ │ └─ renderDeviceList() │ │ └─ renderAlerts() │ │ └─ renderCompliance() │ │ └─ renderApps() │ │ └─ renderTicker() └── startAutoRefresh(30000) │ 30s countdown ► refreshAll() ┌────────────────────────────────────────────────────────────────┐ │ graphFetch(endpoint) — fetch wrapper │ └────────────────────────────────────────────────────────────────┘ DEMO = true ► mock data from DEVICES / POLICIES / APPS / ALERTS constants DEMO = false ► real fetch() with MSAL Bearer token ► Microsoft Graph API ┌────────────────────────────────────────────────────────────────┐ │ AI Summary — separate call path │ └────────────────────────────────────────────────────────────────┘ Button click ► runAiSummary(device, policies) ├─ Live: fetch('https://api.anthropic.com/v1/messages') streaming SSE └─ Offline fallback: buildOfflineSummary() — structured text, no API needed
Key State Variables
VariableTypePurpose
DEMOBooleanMaster mock/live switch. true = serve mock data; false = call Graph API via MSAL token
allDevicesArrayWorking copy of device list; always the full DEVICES set (filters are visual-only)
activeFilterString/nullGauge filter currently applied (compliant/noncompliant/alerts/windows/ios/android)
selectedDeviceString/nullID of currently selected device row; drives detail panel render
dismissedAlertsSetAlert IDs dismissed this session (not persisted)
bulkSelectedSetDevice IDs checked for bulk actions
currentUserObject/nullSimulated authenticated user from MSAL.js stub; null in demo mode
AUDIT_LOGArrayIn-memory array of all actions taken (logged to console)
deviceNotesObjectIn-memory cache of per-device tech notes; persisted to localStorage
🔗Suite Entry Points
FunctionPurpose
loadDashboard()Suite entry point. Called on modal close. Calls all six render functions and updates sync badge.
refreshAll()DEMO/live branch controller. Called by startAutoRefresh and the Sync button. In demo mode re-renders from static data; in live mode re-fetches from Graph API via graphFetch().
startAutoRefresh(30000)Starts 30-second countdown. Fires refreshAll() on expiry. Updates sync badge with countdown warning in final 10 seconds. Self-restarting.
graphFetch(endpoint)Unified fetch wrapper. In demo mode returns mock data via setTimeout. Production block (commented out) acquires MSAL token and calls Graph API with Bearer auth.
runLoadingSequence()Animates the boot loading modal. Steps through device list, populates ring counter and progress bar. Calls loadDashboard() via closeModal() on completion.
runRemoteAction(action, name)Fires the action modal with step-by-step progress display. Each step logs the correct Graph API endpoint for the action. In production, each step should call the real POST endpoint and resolve on 204 response.
runAiSummary(device, policies)Calls Anthropic API with device context and streams response. Falls back to buildOfflineSummary() on network error.
📋
04A / PANELS
Loading Modal

An animated boot sequence modal that appears on every page load. Iterates through the DEVICES array animating a ring counter and progress bar while cycling through 7 phase labels. Displays a live count of compliant, issue, and critical devices as they are “synced.” The modal-api-note block inside it shows the four Graph endpoints that will be called in production.

📡Loading Modal API Note BlockInformational
Displayed EndpointValidNotes
GET /v1.0/deviceManagement/managedDevicesCorrect — device list
GET /v1.0/deviceManagement/compliancePoliciesCorrect — policy list
GET /v1.0/deviceAppManagement/mobileAppsCorrect — app catalog
GET /beta/deviceManagement/alerts⚠ BetaNo v1.0 equivalent — beta endpoint only. Audited and corrected from original.
🔥
04B / PANELS
Header & Navigation

The fixed 56px header contains: Microsoft Intune logo mark, Demo Mode badge, tab navigation (Devices / Alerts / Compliance / Apps), theme switcher (5 themes), MSAL.js user badge with Sign In/Out, last sync badge, and the primary Sync button.

🌟Theme System

Five themes switchable at runtime: Blue (Intune default), Storm (cyan), Ember (orange), Slate (violet), Ghost (light mode). Theme persisted in localStorage via saveState(). System dark mode preference auto-selects on first load.

🔑Auth Badge (MSAL Stub)

Sign-In button opens a modal showing required Entra ID scopes and PKCE flow details. Simulate Sign-In sets currentUser in memory only — no real MSAL token acquired. Production requires a registered Entra ID app with delegated DeviceManagementManagedDevices.Read.All permission.

Tab Navigation
TabElement IDGrid Layout
📱 Devices#tabDevices340px 1fr — list + detail
🔔 Alerts#tabAlerts1fr 1fr — critical + warnings
📋 Compliance#tabCompliance1fr 1fr — policies + trend chart
📦 Apps#tabApps1fr 1fr — app list + detail
📰
04C / PANELS
Ticker Bar

A 28px scrolling marquee bar below the header. Displays 15 live-computed statistics from allDevices including compliance rate, encryption off count, firewall off count, AV disabled count, stale sync count, and current timestamp. Items are duplicated for seamless looping. Scroll pauses on hover. Updated every 30 seconds via startAutoRefresh.

Ticker Data Points
LabelSource ExpressionColor Rule
Total Devicessrc.lengthCyan always
Compliant / Non-CompliantFilter by compliance fieldGreen / Red
Compliance Rate %compliant/total*100Green ≥80%, Warn <80%
Critical RiskFilter by risk === 'critical'Red if >0, Green if 0
Active Alertsreduce sum of d.alertsWarn if >0
Encryption OffFilter !d.encryptedRed if >0, Green if 0
Firewall OffFilter !d.firewallRed if >0, Green if 0
AV DisabledFilter d.av === falseRed if >0, Green if 0
Stale SyncFilter lastSync contains “day”, “6 hrs”, “12 hrs”Warn if >0
Windows / iOS Non-CompliantPlatform + compliance filterRed / Warn if >0
Last Syncnew Date().toLocaleTimeString()Cyan
Policies ActivePOLICIES.lengthGreen
Apps DeployedAPPS.lengthGreen
📈
04D / PANELS
Gauge Strip

Seven donut gauge cards in a collapsible strip below the ticker. Each card is clickable and applies a filter to the device list. The active filter highlights with a colored border glow. A filter chip appears below the strip showing the active filter name with an × to clear. Keyboard shortcuts 1–7 trigger each gauge filter. The strip collapses/expands via the “Gauges” toggle button and remembers state in localStorage.

🏊Gauge Definitions
GaugeFilter ValueSourceDonut Color
DevicesallallDevices.lengthCyan (--accent)
CompliantcompliantFilter compliance === 'compliant'Green (--ok)
Non-CompliantnoncompliantFilter compliance === 'noncompliant'Red (--danger)
Alertsalertsreduce sum d.alerts — max scale 20Amber (--warn)
WindowswindowsFilter platform === 'windows'Cyan
iOS / macOSiosFilter platform === 'ios'Info (--info)
AndroidandroidFilter platform === 'android'Purple (--onboard)
📱
04E / PANELS
Devices Tab

The primary triage view. A 340px left panel lists all devices; the right panel shows selected device detail. The list supports search (by name/user/OS), sort (status/name/user/lastSync/OS), bulk selection, and gauge-based filtering. Devices are sorted by risk severity by default (critical first).

📱Device Row Fields
FieldSourceVisual Treatment
Status dotd.risk + d.complianceRed = critical/high, Amber = medium, Green = compliant, Grey = unknown
OS icond.platform🏟 Windows, 🍎 iOS/macOS, 🤖 Android
Device named.nameBold, truncated with ellipsis
User + last syncd.user, d.lastSyncLast sync colored: green <24h, amber 24–48h, red >48h
Risk badged.riskCritical (red), High (red), Medium (amber), Low (green)
Alert badged.alertsAmber badge with count; hidden if 0
CheckboxBulk selectionEnables bulk toolbar on any selection
🔧Bulk Actions

Selecting one or more checkboxes shows the bulk toolbar with three actions that require confirmation: Sync All (syncDevice), Reboot All (rebootNow), and Collect Diag (collectDiagnostics). A confirm modal shows device names before executing. In live mode, each selected device would receive a separate POST request.

📄Export

The Export button opens a dropdown with CSV and JSON options. Exports the currently visible (filtered) device list. Fields exported: name, user, OS, version, model, serial, compliance, lastSync, risk, alerts, encrypted, firewall, AV, patches, enrolled. No API call required — purely client-side from allDevices in memory.

🔬
04F / PANELS
Device Detail Panel

Clicking a device row renders a full detail panel in the right column. The panel header shows the Graph API endpoint for the selected device (GET /managedDevices/{id}). Four sub-tabs provide drill-down: Policies, Apps, Remote Actions, and Ticket.

📊Compliance Banner + Stats Block
ElementSource
Banner color/icond.compliance + d.risk — OK green / warn amber / critical red
OS, Model, Last Sync, Riskd.os, d.osVer, d.model, d.lastSync, d.risk
Encrypted / Firewall / AV / Patchesd.encrypted, d.firewall, d.av, d.os_patch
14-day compliance chartSimulated per-device trend — no Graph Reporting API equivalent per-device
📋Policies Tab

Shows a policy compliance table from DEVICE_POLICIES[d.id] (or falls back to derived fields). Labeled with the corrected Graph endpoint: GET /managedDevices/{id}/deviceCompliancePolicyStates. In live mode this should be fetched per-device on detail expand.

📦Apps Tab

Lists apps installed on the selected device from DEVICE_APPS[d.id]. Labeled with GET /deviceAppManagement/mobileApps?deviceId={id}. The correct live endpoint to achieve this is GET /v1.0/deviceAppManagement/mobileApps/{appId}/deviceStatuses filtered by device ID — there is no single Graph call that returns all apps for one device directly.

🄋Ticket Tab

Auto-generates a plain-text triage ticket note from device fields and policy failures. Includes the correct Graph API calls used. A Tech Notes textarea auto-saves to localStorage keyed by device ID with a 600ms debounce. Copy Full Ticket combines the ticket and notes to clipboard.

04G / PANELS
Remote Actions

Eight remote action buttons appear in the Remote Actions sub-tab of the device detail panel. Each fires runRemoteAction(action, deviceName) which shows an animated step-by-step modal displaying the correct Graph API endpoint for that action. All actions are POST with 204 No Content response in production.

🔒Action → Correct Graph Endpoint Mapping
ButtonAction KeyGraph API EndpointNotes
Sync DevicesyncPOST /v1.0/deviceManagement/managedDevices/{id}/syncDeviceNo request body required
Remote WipewipePOST /v1.0/deviceManagement/managedDevices/{id}/wipeOptional body: keepEnrollmentData, keepUserData
Remote RebootrebootPOST /v1.0/deviceManagement/managedDevices/{id}/rebootNowWindows 10+ only
Retire DeviceretirePOST /v1.0/deviceManagement/managedDevices/{id}/retireRemoves corporate data, preserves personal data
Remote LocklockPOST /v1.0/deviceManagement/managedDevices/{id}/remoteLockiOS/macOS/Android; Windows requires PIN policy
Rotate BitLocker KeybitlockerPOST /v1.0/deviceManagement/managedDevices/{id}/rotateBitLockerKeysWindows only; requires BitLocker enabled
Collect DiagnosticscollectPOST /v1.0/deviceManagement/managedDevices/{id}/collectDiagnosticsReturns 202 Accepted; bundle appears in Intune portal
Reset PasscoderesetPOST /v1.0/deviceManagement/managedDevices/{id}/resetPasscodeiOS / Android only
🚫
Wipe is irreversible. In production mode, wipe will factory-reset the device on next check-in. The action modal displays a warning step but does not currently require a typed confirmation. Consider adding a device-name confirmation input before enabling this in live mode.
🔔
04H / PANELS
Alerts Tab

Two-column layout: Critical Alerts (left) and Warnings & Info (right). Alerts are sourced from the ALERTS constant (8 items in demo). Each alert has a Dismiss button which adds the alert ID to dismissedAlerts (session-only, not persisted) and decrements the header badge count. The tab header label correctly references /beta/deviceManagement/alerts.

Beta endpoint. GET /beta/deviceManagement/alerts is the only available Graph API endpoint for Intune alerts. There is no equivalent in v1.0. Production code should handle the possibility that this beta endpoint behavior or schema may change without notice.
📋
04I / PANELS
Compliance Tab

Two-column layout: Compliance Policies list (left) and 6-month compliance trend chart (right). Each policy card is expandable and shows compliant vs. non-compliant device counts with a progress bar. The trend chart uses Chart.js with three datasets: Compliant %, Non-Compliant %, and Critical Risk %. An API reference block in the right column correctly shows all three Graph endpoints.

📊Compliance Trend Chart
Chart typeLine chart, Chart.js 4.4.1
Labels6 months hardcoded: Nov, Dec, Jan, Feb, Mar, Apr
Dataset 1Compliant % — green fill
Dataset 2Non-Compliant % — red fill
Dataset 3Critical Risk % — amber dashed line
Live data sourceGET /v1.0/reports/getDeviceNonComplianceReport — returns aggregated snapshots
LimitationGraph does not provide per-day/per-month compliance rate time-series natively; requires custom aggregation or Intune Analytics exports
📦
04J / PANELS
Apps Tab

Two-column layout: app list (left) with install rate progress bars, and app detail panel (right) when an app is selected. The detail panel shows install statistics by platform (Windows/iOS/Android) with simulated per-platform percentages, and displays the three relevant Graph endpoints for that app.

Per-platform install % is simulated. The Graph API returns per-device install state via GET /mobileApps/{id}/deviceStatuses. Grouping by platform requires client-side aggregation of those results. The random percentages shown are placeholders for this aggregation logic.
04K / PANELS
AI Summary (Claude)

The ✦ AI Summary button appears in the device detail compliance banner. It calls the Anthropic API directly from the browser using claude-sonnet-4-20250514 with streaming SSE. The prompt includes device name, user, OS, compliance state, risk level, active policy failures, and tech notes. The response streams character-by-character into the AI panel body.

🌟AI Call Behavior
APIhttps://api.anthropic.com/v1/messages
Modelclaude-sonnet-4-20250514
Max tokens500
Streamtrue — SSE, reads content_block_delta events
Prompt inputsDevice name, user, OS, model, compliance, risk, last sync, encryption, firewall, AV, patches, alerts, policy failures, policy warnings, tech notes
Offline fallbackbuildOfflineSummary() — generates structured text from device fields with simulated stream animation; no API call required
Error handlingAny network or API error triggers the offline fallback with a warning note in the AI panel footer
Copy buttonCopies textContent of AI panel to clipboard
CORS note. The Anthropic API is called directly from the browser. This requires the browser environment to permit the request origin. In a locked-down intranet deployment, route AI calls through a server-side proxy to avoid CORS issues and to keep the Anthropic API key server-side. The current implementation has no API key protection — this is acceptable for demo use but should be proxied in production.
🌐
05 / API
Exact API Endpoints
All endpoints verified against Microsoft Graph API v1.0 documentation at learn.microsoft.com/graph/api/resources/intune-graph-overview. Base URL: https://graph.microsoft.com.
GET/v1.0/deviceManagement/managedDevices
Returns all managed devices enrolled in Intune. Paginated. Supports $filter, $select, $orderby. Used to populate the device list, gauge counts, and ticker.
✓ Confirmed valid — Graph API docs
GET/v1.0/deviceManagement/managedDevices/{managedDeviceId}
Single device detail. Shown in the device detail panel header and ticket note. Returns full device properties including compliance state, OS version, serial, enrolled date.
✓ Confirmed valid
GET/v1.0/deviceManagement/managedDevices/{id}/deviceCompliancePolicyStates
Returns compliance state for each policy applied to a specific device. Used in the Policies tab of the device detail panel. Audit fix: the original dashboard incorrectly used GET /compliancePolicies?deviceId= which is not a valid Graph parameter.
✓ Confirmed valid — corrected from original
GET/v1.0/deviceManagement/compliancePolicies
Returns all Intune compliance policies in the tenant. Used in the Compliance tab policy list. Each policy object includes platform, assigned devices count, and compliance settings.
✓ Confirmed valid
GET/v1.0/deviceAppManagement/mobileApps
Returns all apps in the Intune app catalog. Used in the Apps tab list. Supports filtering by $filter=isAssigned eq true to return only deployed apps.
✓ Confirmed valid
GET/v1.0/deviceAppManagement/mobileApps/{id}
Single app detail. Shown in the app detail panel header. Returns publisher, version, platform, required/available status.
✓ Confirmed valid
GET/v1.0/deviceAppManagement/mobileApps/{id}/deviceStatuses
Returns per-device install state for a specific app. Used to compute install rate and failure count. Platform breakdown requires client-side aggregation of these results.
✓ Confirmed valid
GET/v1.0/reports/getDeviceNonComplianceReport
OData action-based report endpoint. Returns a snapshot of non-compliant devices. Used as the data source for the 6-month compliance trend chart. Returns aggregate data; per-month time-series requires custom aggregation.
✓ Confirmed valid
GET/v1.0/me
Returns the authenticated user profile. Used in the Sign-In modal stub to establish the current user context. Requires delegated permission and a valid MSAL.js token.
✓ Confirmed valid
BETA/beta/deviceManagement/alerts
Returns Intune alert records. This is the only available Graph endpoint for Intune alerts — there is no v1.0 equivalent. The original dashboard incorrectly referenced /v1.0/deviceManagement/alerts which does not exist. Both the loading modal and alerts tab header have been corrected.
⚠ Beta only — no v1.0 equivalent exists — corrected from original
POST/v1.0/deviceManagement/managedDevices/{id}/syncDevice
Triggers an MDM sync on the device. No request body. Returns 204 No Content. Device will sync on next check-in.
✓ Confirmed valid — corrected from original (was missing /syncDevice suffix)
POST/v1.0/deviceManagement/managedDevices/{id}/wipe
Factory-resets the device. Optional body: {"keepEnrollmentData": true, "keepUserData": false}. Irreversible. Returns 204. Device wipes on next check-in.
✓ Confirmed valid — corrected from original (was missing device ID in path)
POST/v1.0/deviceManagement/managedDevices/{id}/rebootNow
Remotely reboots the device. Windows 10+ only. No body required. Returns 204. Device reboots on next check-in.
✓ Confirmed valid — corrected from original (was missing device ID in path)
POST/v1.0/deviceManagement/managedDevices/{id}/retire
Removes corporate data from the device while preserving personal data. Suitable for BYOD offboarding. Returns 204.
✓ Confirmed valid — added (was falling through to generic default step)
POST/v1.0/deviceManagement/managedDevices/{id}/remoteLock
Locks the device screen. Supported on iOS, macOS, Android. Windows requires a PIN policy to be set. Returns 204.
✓ Confirmed valid — added (was falling through to generic default step)
POST/v1.0/deviceManagement/managedDevices/{id}/rotateBitLockerKeys
Rotates the BitLocker recovery key for the device. Windows only. New key is escrowed to Entra ID. Returns 204.
✓ Confirmed valid — added (was falling through to generic default step)
POST/v1.0/deviceManagement/managedDevices/{id}/collectDiagnostics
Initiates a diagnostic data collection job. Returns 202 Accepted. The resulting bundle appears in the Intune portal under the device diagnostics section.
✓ Confirmed valid — corrected from original (was missing device ID in path)
POST/v1.0/deviceManagement/managedDevices/{id}/resetPasscode
Removes the passcode from an iOS or Android device. Returns 204. For iOS, the device must be supervised. Not applicable to Windows or macOS.
✓ Confirmed valid — added (was falling through to generic default step)
POST/v1.0/deviceAppManagement/mobileApps/{id}/assign
Assigns an app to device groups. Requires a body with assignment targets and intent (required / available). Shown in the app detail endpoint reference block.
✓ Confirmed valid
🔑
06 / API
Auth & Proxy
🔐Microsoft Graph Authentication

Graph API calls require a Bearer token issued by Entra ID (Azure AD). The recommended approach for this browser-based console is MSAL.js with PKCE flow. The Sign-In modal shows the required setup details.

// MSAL.js production setup (pseudocode) const msalConfig = { auth: { clientId: 'YOUR_APP_CLIENT_ID', authority: 'https://login.microsoftonline.com/YOUR_TENANT_ID', redirectUri: window.location.origin, } }; const msalInstance = new msal.PublicClientApplication(msalConfig); // Acquire token (popup or silent) async function acquireMSALToken() { const scopes = ['DeviceManagementManagedDevices.Read.All']; try { const result = await msalInstance.acquireTokenSilent({ scopes }); return result.accessToken; } catch (e) { const result = await msalInstance.acquireTokenPopup({ scopes }); return result.accessToken; } }
PropertyValue
Auth typeOAuth 2.0 PKCE via MSAL.js 3.x
Token typeBearer (Entra ID JWT)
Read scopeDeviceManagementManagedDevices.Read.All
Write scopeDeviceManagementManagedDevices.ReadWrite.All (for remote actions)
App typePublic client (SPA) — no client secret required
Graph base URLhttps://graph.microsoft.com
Token headerAuthorization: Bearer {access_token}
🔀graphFetch() Wrapper

The graphFetch(endpoint) function is the single fetch entry point for all Graph calls. The production block (commented out) calls acquireMSALToken() and passes the Bearer token. Uncomment this block and comment out the mock fallback to go live.

async function graphFetch(endpoint) { /* ── PRODUCTION — uncomment when MSAL is configured ───────── const token = await acquireMSALToken(); const res = await fetch('https://graph.microsoft.com' + endpoint, { headers: { 'Authorization': 'Bearer ' + token, 'Accept': 'application/json' } }); if (!res.ok) throw new Error('Graph ' + res.status + ' on ' + endpoint); return res.json(); ──────────────────────────────────────────────────────────── */ // DEMO fallback return new Promise(resolve => setTimeout(() => { if (endpoint.includes('managedDevices')) resolve({ value: DEVICES }); else if (endpoint.includes('compliancePolicies')) resolve({ value: POLICIES }); else if (endpoint.includes('mobileApps')) resolve({ value: APPS }); else if (endpoint.includes('alerts')) resolve({ value: ALERTS }); else resolve({ value: [] }); }, Math.random() * 200 + 80)); }
07 / API
Limitations & Gaps
🚫
No v1.0 Alerts Endpoint. GET /v1.0/deviceManagement/alerts does not exist in Microsoft Graph. Alerts are available only at GET /beta/deviceManagement/alerts. The original dashboard incorrectly showed this as a v1.0 endpoint in both the loading modal and the alerts tab. Both have been corrected to reference the beta endpoint with an explicit warning note.
🚫
No Per-Device Compliance History Time-Series. Graph API does not provide a per-device compliance score over time. The 14-day compliance chart in the device detail panel is entirely simulated using Math.random(). The closest real data source is GET /v1.0/reports/getDeviceNonComplianceReport which returns a current snapshot, not historical data. Historical compliance requires Intune Analytics (Power BI / Azure Monitor integration).
🚫
No Single Endpoint for All Apps on One Device. Graph API does not support GET /mobileApps?deviceId=. The correct approach requires fetching GET /mobileApps/{appId}/deviceStatuses per app and filtering for the device. The Apps tab in device detail uses a static DEVICE_APPS lookup; in live mode this requires one API call per tracked app.
Risk Score is Console-Computed. The risk levels (low/medium/high/critical) shown in the device list are computed by the console from compliance state, encryption, AV, and patch level. Microsoft Graph does not provide a risk score field on managed devices directly. Intune does have a deviceActionResults field but no aggregate risk rating. The d.risk field in mock data is console-generated logic that must be implemented in the proxy when going live.
Per-Platform App Install % is Simulated. The app detail panel shows install percentages per platform (Windows/iOS/Android) using Math.random() on every render. In live mode, this requires fetching GET /mobileApps/{id}/deviceStatuses and grouping results by device platform. The random values change on every app click in demo mode.
Alerts Not Persisted Across Refreshes. The dismissedAlerts Set is session-only (not in localStorage). Dismissing an alert and refreshing the page restores it. In live mode, alert dismissal state should be stored server-side or in localStorage keyed by alert ID.
MSAL.js Not Loaded. The current file does not load the MSAL.js library. The Sign-In modal is a visual stub only. To enable real authentication, add the MSAL.js CDN script tag, register the app in Entra ID, and implement acquireMSALToken(). The graphFetch() wrapper already calls this function and the full implementation is shown in the Auth section above.
08 / OPS
Proxy Activation Checklist
Current Status: Demo mode. All items below must be completed before setting DEMO = false.
🔓Entra ID App Registration
  • Register a new application in the Entra ID portal (App registrations → New registration)
  • Set platform: Single-page application (SPA) — add the serving origin as redirect URI
  • Add delegated API permissions: DeviceManagementManagedDevices.Read.All (minimum)
  • Add DeviceManagementManagedDevices.ReadWrite.All if remote actions are needed
  • Add DeviceManagementApps.Read.All for app catalog
  • Grant admin consent for the organization on all added permissions
  • Note the Application (client) ID and Directory (tenant) ID for MSAL config
💻MSAL.js Integration
  • Add MSAL.js CDN script tag to the file head: <script src="https://alcdn.msauth.net/browser/3.x.x/js/msal-browser.min.js"></script>
  • Implement acquireMSALToken() using PublicClientApplication with acquireTokenSilentacquireTokenPopup fallback
  • Fill in clientId and authority in the MSAL config object
  • Wire the real Sign-In button to call msalInstance.loginPopup()
🔄Frontend Activation
  • Open stack-intune-triage.html and locate the DEMO constant near the top of the script block
  • Set const DEMO = false;
  • In graphFetch(), uncomment the 8-line production block (fetch with Bearer token)
  • Comment out (or delete) the mock return new Promise(resolve => setTimeout(...)) fallback block
  • Reload — Sign In prompt should appear; authenticate with a user who has Intune read access
  • Verify device list populates with real device names (not CORP-WIN-JSMITH01)
  • Verify gauge counts match the Intune portal total device count
  • Test sync action on a non-critical test device; confirm 204 response in the action modal log
  • Verify AI Summary works (Anthropic API key available in browser environment)
💡Features That Activate When Live
FeatureChange
Device listReal enrolled devices from the tenant; real names, users, compliance states
Gauge countsReal compliant/non-compliant/alert totals from Graph API responses
Alert feedReal Intune alert records from /beta/deviceManagement/alerts
Policy compliance per deviceActual policy states from /deviceCompliancePolicyStates
Remote actionsAll POST actions execute against real devices — verify on test devices first
App install statusReal install state from /mobileApps/{id}/deviceStatuses
Auto-refreshRe-fetches all data from Graph API every 30 seconds
Auth badgeShows real user UPN and role from Entra ID
🔩
09 / OPS
Config Fields
All Configurable Values in stack-intune-triage.html
Field / LocationDefaultPurpose
DEMO constanttrueMaster mock/live switch. Set false with MSAL token to go live.
startAutoRefresh(30000)30000 msRefresh interval. Increase to 60000 for large tenants to avoid Graph throttling.
STORAGE_KEY'intune_triage_v2'localStorage key for persistent state. Change version suffix to reset all saved state.
NOTES_KEY_PREFIX'intune_note_'Prefix for per-device note keys in localStorage.
AI modelclaude-sonnet-4-20250514In runAiSummary() fetch body. Update to newer model when available.
AI max_tokens500In runAiSummary() fetch body. Increase for longer summaries.
Stale sync threshold48h = stale, 24h = warnIn syncClass()parseSyncHours(). Adjust to policy SLA requirements.
Ticker scroll speed55sCSS .ticker-track animation duration. Increase to slow scroll.
Loading interval130ms per deviceIn runLoadingSequence() setInterval. Reduce for faster boot animation.
Gauge max (alerts)20In renderGauges()setGauge('alerts', alertCount, 20). The denominator for the donut fill; tune to expected alert volume.
Main layout columns340px 1frCSS main grid-template-columns. Increase 340px to widen the device list panel.
Compliance history days14In renderDeviceDetail() Array.from({length:14}. Adjust label count if connecting to real telemetry.
Default sortstatusSort select default value in HTML. Changes initial list order.
Theme defaultblue (or OS pref)In initTheme(). Change the fallback theme string.
🔧
10 / OPS
Troubleshooting
Gauges Show Dashes or Zero After Load

Cause: renderGauges() was called before allDevices was populated, or the loading modal was dismissed before closeModal() ran loadDashboard().

Fix: Check browser console for JS errors. In demo mode this should not occur. Confirm closeModal() calls loadDashboard() and that allDevices = [...DEVICES] runs before loadDashboard(). If the modal close button is clicked before the animation completes, runLoadingSequence interval may still be running — verify clearInterval is called on close.

Device List Empty / No Results

Cause: An active gauge filter combined with search text returns no matches.

Fix: Click Show All in the list header or press the filter chip ×. Check if a gauge filter is active (highlighted border). Call clearAllFilters() from the console to reset everything. In live mode, confirm graphFetch('/v1.0/deviceManagement/managedDevices') returns a non-empty value array.

Device Detail Panel Shows Empty State After Click

Cause: selectDevice(id) called DEVICES.find(x => x.id === id) but the ID did not match (possible if allDevices was replaced with a different object reference).

Fix: Confirm allDevices is always initialized to [...DEVICES]. Check that row data-id attributes match the d.id values in the DEVICES constant. In live mode, ensure the device IDs returned by Graph API are stored directly as d.id in the normalized device objects.

Remote Action Modal Stalls at 0%

Cause: runRemoteAction(action, deviceName) called with an action key not in the steps object; falls through to the default branch which should still work. Or the action modal element is missing from the DOM.

Fix: Check browser console for errors. Verify document.getElementById('actionModal') returns the element. Confirm the action key is one of: sync, wipe, reboot, retire, lock, bitlocker, collect, reset. All are now explicitly defined in the steps object after the audit fix — none fall to the generic default anymore.

AI Summary Shows “Offline mode” Warning

Cause: The fetch call to https://api.anthropic.com/v1/messages failed. Common reasons: no network access to Anthropic from the current environment, CORS policy rejection, or the Anthropic API key is not configured (the code currently does not send an API key — this requires proxy-side key injection).

Fix: Route AI calls through a server-side proxy that injects the x-api-key header. The offline fallback (buildOfflineSummary()) is fully functional and accurate — it reads the same device fields and produces a useful triage summary without any network call. Consider this sufficient for demo environments.

Auto-Refresh Not Firing / Countdown Not Updating

Cause: startAutoRefresh(30000) was not called, or the intervals were cleared without being restarted.

Fix: Check _refreshInterval and _countdownInterval are non-null after boot (log them from the console). Confirm the BOOT section at the bottom of the script calls startAutoRefresh(30000). Manually call refreshAll() from the console to confirm the data cycle works independently of the timer.

Live Mode: 401 Unauthorized from Graph API

Cause: MSAL token is expired, not acquired, or the scope does not match the endpoint.

Fix: Verify the token is acquired before the graphFetch() call. Check that the Entra ID app registration has admin consent granted for all required scopes. Test the token manually: curl -H "Authorization: Bearer {token}" https://graph.microsoft.com/v1.0/me. For remote actions (POST endpoints), confirm the ReadWrite.All scope is present — Read.All alone will return 403 on action endpoints.

Chart.js Not Rendering

Cause: Chart.js CDN script failed to load, or destroyChart() was not called before re-initializing a chart on the same canvas element.

Fix: Check browser console for Chart is not defined error. Verify the CDN URL is accessible from the deployment environment (cdnjs.cloudflare.com). If deploying in an air-gapped environment, bundle Chart.js locally. The activeCharts object and destroyChart() function prevent canvas reuse errors — confirm they are both present and called before every chart initialization.