The Global Operations Health Console is a single-file HTML dashboard that provides a high-level operational health view across an MSP's full technology stack. It is designed as an executive-facing or NOC wall display that aggregates platform status, client risk, SLA performance, and alert volume into a 4-column grid layout refreshed every 45 seconds.
Unlike the engineer-facing triage consoles, this dashboard is observational. It answers one question at a glance: how healthy is the environment right now? Every panel is clickable and opens a drill-down detail drawer with deeper data, but there are no action workflows β no ticket creation, no alert acknowledgement routing, no engineer assignment. It is the command center view that precedes those actions.
fetchData() and apiFetch() already integrated. It attempts to call three proxy endpoints on every refresh cycle. If any endpoint is unreachable or returns an error, that call silently falls back to makeData() and generates realistic demo data. The dashboard always renders regardless of API connectivity state.
The console is a self-contained single HTML file. Chart.js 4.4.1 loads from the Cloudflare CDN. All rendering is handled by dedicated render*() functions that accept a DATA object β the source of that object (proxy or fallback generator) is transparent to every render function.
The 6-second timeout prevents a slow or unreachable proxy from blocking the UI. If the fetch times out, fails, or returns a non-200 status, the fallback generator runs immediately. When DEMO_MODE = true (default) the fallback is silent β no console warnings. Set DEMO_MODE = false in production to surface fetch errors in browser DevTools.
critCount > 5.The sticky topbar contains six animated KPI values that update on every refresh using the anim() function β a cubic ease animation from the previous value to the new one over 600ms. An SVG arc gauge also reflects the environment health score visually.
d.critCount. Demo range: 3β12.d.warnCount. Demo range: 8β25.d.openTix. Demo range: 60β140.d.slaOv. Demo range: 88β99%.clientRisks.filter(c => c.risk !== 'ok').length.The full-width top panel shows a tile for each of the 9 monitored platforms. Tile border color reflects platform status. Clicking any tile opens the platform detail drawer showing a 24-hour activity chart, health score, alert counts, uptime bar, last seen time, and category.
| ID | PLATFORM | CATEGORY | PRIMARY LIVE DATA SOURCE |
|---|---|---|---|
ninja | π₯ Ninja RMM | Endpoint Monitoring | NinjaOne REST API β agent status, patch compliance, device counts, alert feed |
auvik | π Auvik | Network Monitoring | Auvik API β device health, interface status, network alerts |
forti | π‘ FortiGate | Firewall / Security | FortiOS REST API β policy hits, VPN tunnels, interface status |
s1 | β SentinelOne | Endpoint Security | S1 Management API β threat detections, agent health, isolation status |
huntress | π Huntress | Threat Detection | Huntress API β incident reports, persistent threats, footholds |
backup | πΎ Backup Systems | Data Protection | Veeam / Cove / Datto API β job results, failure counts, restore points |
cw | π« ConnectWise PSA | Service Desk | CW REST API β tickets, SLA, engineer assignments, CSAT |
email | π§ Email Security | Email Protection | Mimecast / Defender API β threat blocks, quarantine, delivery rates |
azure | β Azure / M365 | Cloud Infrastructure | Microsoft Graph API β tenant health, service alerts, license status |
makeData() enforces at least one platform is always crit status. If random generation produces no critical platforms, one is forcibly set. Remove this enforcement when wiring live data β your real environment may genuinely have all platforms healthy.
The right-side panel (spans rows 2β3) shows the global alert breakdown and a 24-hour trend chart. The three large numbers show Critical, Warning, and Info alert counts. Below them is a scrollable list of up to 18 alert detail rows. The panel gains a red pulse animation (pulsecrit class) when critCount > 5.
Clicking the panel or the panel header opens the alert drawer. Individual alert rows in the drawer are clickable β clicking an alert row dims it and fires a toast acknowledging it. In production, this onclick should be wired to your PSA's acknowledge endpoint (PATCH /api/proxy/psa/alerts/{id}/ack).
ALT-NNNNN format, starting from ALT-09000 in demo. In production this should be the native ID from whichever platform generated the alert.'crit', 'warn', 'info' β lowercase strings. The drawer filter uses these exact values. Case-sensitive.'Unassigned'. The filter drawer shows this in the alert row meta line.The Environment Health panel (row 2, col 1) shows an SVG ring gauge with the aggregate environment score and a grade letter, plus a set of horizontal bars showing each platform's individual score contribution. Clicking opens the Environment Health drawer, which lists all platform scores as clickable bars β clicking a platform bar opens that platform's detail drawer.
score fields: Math.round(platforms.reduce((s,p) => s+p.score, 0) / platforms.length). Range 0β100.The Operational Snapshot panel (row 2, col 2) shows four PSA-derived KPI tiles. All four come from d fields that in production map directly to ConnectWise PSA API responses.
d.openTix. Live: GET /service/tickets?conditions=status/name="Open"&pageSize=1&fields=id β use the X-Total-Count response header for the count without fetching all records.d.closedToday. Live: CW tickets closed since midnight today. Use dateResolved >= [today-00:00] filter.d.avgResp (float, hours). Live: average time-to-first-response across tickets opened this week from CW time entries or SLA report endpoint.d.clientsAtRisk. Derived from the client risk array β count of clients where risk !== 'ok'. Same value as in the topbar.The SLA Compliance panel (row 2, col 3) shows an SVG ring gauge for overall SLA percentage and three horizontal bar gauges for P1 response, P2 response, and overall compliance. The ring color is green β₯95%, orange β₯85%, red below 85%. Clicking opens the SLA detail drawer.
Math.round((slaP1 + slaP2) / 2). You can also supply this directly from the PSA's aggregate SLA endpoint.The Client Risk Leaderboard (row 3, cols 1β2) shows up to 10 clients sorted by risk score descending β highest-risk first. Each row shows a color-coded risk badge, client name, risk score, open alert count, backup failure count, security incident count, and patch failure percentage. Clicking a row or the panel opens the client detail drawer.
score > 70 β 'crit' (red) Β· score > 45 β 'warn' (orange) Β· otherwise β 'ok' (green). Your proxy should compute the score from the client's actual alert and compliance data.100 - openAlerts*4 - backupFails*8 - secInc*15 - patchFail/4 clamped to 5β95.name field must match a string in the CLIENTS array or be added to it. The drawer lookups use name-string matching against DATA.clientRisks.Stacked area chart rendered by renderAlertTrend() inside the Global Alert Summary panel. Three layers: critical (red), warning (orange), info (blue). Shows alert volume per hour over the past 24 hours. Clicking the panel opens the trends drawer which also shows this chart at larger scale.
Data shape: array of 24 objects β [{ h:0, crit:2, warn:5, info:8 }, ...] β one per hour, h is the hour (0β23). The proxy endpoint GET /api/ops/trend24 returns this array directly.
Stacked bar chart rendered by render7Day() in the bottom-right panel. Shows the first 5 platforms (by PLATFORMS array order) with 7 daily values each. Clicking opens the trend drawer.
Data shape: array of platform objects β [{ name:'Ninja', vals:[12,8,15,6,20,4,9] }, ...] β name is a short display label, vals is MonβSun alert counts. The proxy endpoint GET /api/ops/trend7d returns this array directly.
cdnjs.cloudflare.com. If this file is deployed in an environment without reliable internet access, host Chart.js locally and update the <script> src. If the CDN request fails, both chart canvases will be blank β all other panels still function normally.
The detail drawer is a 380px slide-in panel from the right side of the screen. It is opened by clicking any major panel and closed by clicking the overlay, pressing Escape, or clicking the close button. It renders different content based on the type argument passed to openDrawer().
| TRIGGER | TYPE PASSED | DRAWER CONTENT |
|---|---|---|
| Platform tile click | openPlatformDrawer(p) | Platform health score, crit/warn counts, uptime bar, last seen, category, 24h activity bar chart. Drawer message invites API connection. |
| Alert panel / critical count | 'alerts-crit' | Critical alert count stats + full list of critical alert detail rows. Each row is clickable to acknowledge. |
| Alert panel / warning count | 'alerts-warn' | Warning alert stats + warning alert list. |
| Alert panel / info count | 'alerts-info' | Info event stats + generated info event list. |
| Environment Health panel | 'env' | Overall env score, platform breakdown counts, per-platform score bars (clickable β platform drawer). |
| SLA panel | 'sla' | P1/P2/overall SLA gauges, distribution bar, top violating clients. |
| Ops Snapshot (tickets) | 'tickets' | Open/closed/overdue ticket counts, priority distribution, recent ticket list. |
| Client Risk row / panel | openClientDrawer(c) | Client risk score, open alerts, backup fails, security incidents, patch fail rate, risk breakdown bars. |
| 7-Day trend panel | 'trends' | Expanded 7-day chart at larger scale, platform alert volume table, week-over-week delta. |
e.preventDefault() suppresses the browser reload. Calls refreshAll() β fetchData() β renderAll().refreshAll() path. Button dims during the fetch and restores on completion.setInterval fires fetchData() every 45 seconds if DATA is not null (i.e. initial load has completed). Does not run until first render is done.The proxy must return objects matching these shapes. All render functions consume these shapes directly β field names are hardcoded throughout. The /api/ops/summary response carries most of the data; trend endpoints return simpler arrays.
| FIELD | GREEN | ORANGE | RED |
|---|---|---|---|
| Platform status | ok β score 88β100 | warn β score 70β87 Β· deg β 60β75 | crit β score 30β60 |
| Env / platform score | β₯ 80 | β₯ 60 | < 60 |
| Environment grade | A β₯90 Β· B β₯80 | C β₯70 Β· D β₯60 | F < 60 |
| SLA ring gauge | β₯ 95% | β₯ 85% | < 85% |
| Client risk | ok β score β€ 45 | warn β score 45β70 | crit β score > 70 |
| Alert panel pulse | critCount β€ 5 | β | critCount > 5 β pulsecrit CSS |
These are the real API endpoints your proxy will call to populate the platform health objects. Each platform's health score should be computed from the response β map error rates, alert counts, and uptime data to the 0β100 score range.
| PLATFORM | API BASE / DOCS | KEY DATA POINTS | AUTH |
|---|---|---|---|
| NinjaRMM | app.ninjarmm.com/v2 | GET /devices (device count, agent status), GET /alerts (active alerts by severity) | OAuth2 client credentials |
| Auvik | auvikapi.us1.my.auvik.com/v1 | GET /inventory/network (device status), GET /statistics/network/detail (latency, errors) | Basic auth (user:apiKey) |
| FortiGate | FortiOS REST API on device | GET /api/v2/monitor/system/status, GET /api/v2/monitor/vpn/ssl (VPN tunnels) | API key header Authorization: Bearer |
| SentinelOne | usea1.sentinelone.net/web/api/v2.1 | GET /threats (active threats), GET /agents (agent health + count) | API token header Authorization: ApiToken |
| Huntress | api.huntress.io/v1 | GET /incident_reports (active incidents), GET /accounts/{id}/summary | Basic auth (apiKey:apiKey) |
| Backup | Veeam / Cove / Datto β varies | Job results, failure counts, last backup timestamps per protected workload | Varies by vendor |
| ConnectWise PSA | na.myconnectwise.net/v4_6_release/apis/3.0 | GET /service/tickets (open count, SLA), GET /time/entries (response time) | Basic auth (clientId+publicKey:privateKey) |
| Email Security | Mimecast / Defender for 365 | Threat blocks, quarantine counts, delivery rates β vendor-specific endpoints | Varies by vendor |
| Azure / M365 | graph.microsoft.com/v1.0 | GET /admin/serviceAnnouncement/healthOverviews (M365 service health), tenant license status | OAuth2 with Entra ID app registration |
makeData() automatically via the fallback.PROXY_BASE to the Function App URL if it is on a different origin from the HTML.cdnjs.cloudflare.com. If not, host Chart.js locally and update the <script> src.Two variables at the top of the script block control the integration behaviour. No other code changes are needed to switch from demo to live mode.
PROXY_BASE as an empty string. All three proxy calls will resolve relative to the page's own origin. No CORS configuration is needed in same-origin mode.
The /api/ops/summary endpoint is the most work-intensive. It must aggregate data from all platforms you want to show live and return a single JSON object. Build this incrementally β start with one or two platforms and let the rest fall back to demo data.
- 1Platform health arrayFor each platform, call its health API, compute a 0β100 score, set status based on score thresholds, and collect alert counts, uptime, and device count. Return all 9 platform objects β use the exact
idstrings from thePLATFORMSarray (ninja,auvik,forti, etc.). Platforms not yet wired can be populated with default/neutral values. - 2Alert details arrayCollect active critical and warning alerts from the platforms that are wired. Normalize each to the alert detail shape:
{ id, priority, title, platform, client, age, assigned }. Sort criticals first. Limit to 18 items. - 3Client risk arrayFor each client, aggregate their open alerts (cross-platform), backup failures, security incidents, and patch compliance. Compute a risk score and set the
riskfield. Sort by score descending. Limit to 10β12 clients. - 4PSA metricsCall ConnectWise PSA for
openTix,closedToday,avgResp,slaP1,slaP2. These are the most straightforward fields β CW has native endpoints for each. - 5CountsEither supply
critCount,warnCount,infoCountdirectly, or omit them βfetchData()will derive them from thealertDetailsarray if absent.
Return an array of 24 objects, one per hour (hour 0 to hour 23). Each object contains the alert counts for that hour from all wired platforms. Your proxy aggregates alert timestamps from across platforms, bins them by hour, and returns the array.
Return 5 platform objects, each with 7 daily alert counts (MonβSun). The name field is a short display label. Use the first word of the platform name (p.name.split(' ')[0]) for consistency with how demo data labels them.
- βSet PROXY_BASE. Update the
PROXY_BASEvariable at the top of the script to your Function App URL if it is on a different origin from the HTML file. Leave empty for same-origin. - βSet DEMO_MODE = false. Flip the flag so fetch failures appear in browser DevTools and you can diagnose proxy issues.
- βProxy returns valid JSON for all three endpoints. Open browser DevTools β Network tab. Trigger a refresh (Ctrl+R). Confirm each proxy endpoint returns 200 with a JSON body matching the documented shape.
- βPlatform IDs match exactly. The
idfield in each platform object must match one of:ninja,auvik,forti,s1,huntress,backup,cw,email,azure. Any mismatch causes that tile to render with default values. - βAlert priority values are lowercase. The drawer filter compares
a.priority === 'crit'β exact lowercase match.'CRIT'or'Critical'will not match and alerts will disappear from the drawer. - βTrend arrays have correct lengths.
/api/ops/trend24must return exactly 24 items (h: 0β23)./api/ops/trend7dreturns 5 items. Shorter arrays produce blank chart sections. - βAPI key in Key Vault, not in proxy code. Verify the Function App's Application Settings show Key Vault reference strings (
@Microsoft.KeyVault(...)), not raw key values. - βRemove the enforced-critical logic from makeData(). The
makeData()fallback forces at least one critical platform. This is fine for demo but should be removed from the fallback if the fallback is still in use alongside live data, to avoid mixing real and forced values.
| SYMPTOM | CAUSE | FIX |
|---|---|---|
| Dashboard shows demo data despite proxy being up | apiFetch() caught an error and fell back silently | Set DEMO_MODE = false, then open DevTools β Console. The fallback warning will appear with the actual error message. |
| Platform tiles show wrong platform names | Proxy platform id doesn't match the PLATFORMS array IDs | Ensure each platform object has exactly the correct id string. Check against the Platform Health Grid table in Section 04. |
| Alert drawer shows no alerts after filtering | Alert priority field is not lowercase 'crit'/'warn'/'info' | Normalise priority values in your proxy before returning. priority.toLowerCase() if sourcing from a mixed-case API. |
| Charts are blank on load | Chart.js CDN failed to load | Check the browser console for a Script error on the Chart.js CDN URL. Either fix CDN access or host Chart.js locally. |
| Charts go blank after a few refreshes | Chart instances not destroyed before re-render | All chart rendering goes through mkChart() which calls chart.destroy() before creating a new instance. If you add custom charts that bypass mkChart(), add explicit destroy calls. |
| Environment score ring shows wrong color | Score is outside the expected 0β100 range | Verify your proxy clamps all platform scores to 0β100. A score above 100 causes the SVG arc calculation to exceed full-circle and wrap. |
| Refresh button stays dimmed after a refresh | fetchData() promise rejected without being caught | A render function receiving unexpected data shape may throw uncaught. Open DevTools β Console. Look for TypeError or undefined property errors. |
| Drawer opens blank | DATA is null when drawer is triggered | A toast "Loadingβ¦" appears and drawer opens empty if initial fetch hasn't completed. This resolves itself β wait for the first refresh cycle to finish. |