Datto Backup / BCDR Console — Knowledge Base
● DEMO MODE ⚠ PROXY PENDING Rev 1.0 · 2026-03-23
01

What This Tool Is

A real-time BCDR operations console for Datto Backup — surfaces protected asset status, backup job health, screenshot verification, storage utilization, and appliance state across all managed clients.

Purpose & ScopeBCDR Console

The Datto Backup BCDR Console is a single-page HTML dashboard that connects to the Datto Backup REST API v1 (base URL: https://{partner}.dattobackup.com/api/v1/). It gives MSP NOC technicians a unified view of all BCDR assets without navigating the Datto partner portal.

The dashboard replaced an incorrectly-submitted file that was wired to the Datto RMM API (devices, patches, job queue) — an entirely different product and endpoint set. This console is rebuilt from scratch against the actual Datto Backup API schema. All KPIs, panels, mock data fields, and modal detail all match the real API response shapes documented at portal.dattobackup.com/integrations/api.

In demo mode all data is generated by deterministic mock functions whose object schemas exactly mirror the real API responses. Switching to live data requires only configuring the proxy and flipping DEMO_MODE = false.

Who Uses It

NOC Technicians — Monitor backup success rates, triage failures, trigger manual backups, and check screenshot verification without logging into the Datto portal. Alert feed surfaces the most critical items immediately.

Backup Administrators — Storage view and appliance view give a full picture of local and offsite utilization across all SIRIS, ALTO, and Cloud devices.

Service Delivery Managers — KPI strip shows overall backup success rate, failure count, and screenshot pass rate suitable for morning stand-up or wall-board display.

Engineers — API-audited wiring and documented limitations make this the ground truth for what the Datto Backup API can and cannot surface in an integration.

02

API Audit Findings

The originally submitted file was wired entirely to the Datto RMM API. Every endpoint, KPI, and data field has been corrected to match the Datto Backup (BCDR) API.

Critical Finding
The submitted dashboard (dashboard-datto-backup.html) was a Datto RMM console re-titled as a backup tool. None of the following original endpoints exist in the Datto Backup API: GET /devices (RMM agents), GET /patches (patch management), GET /jobs (job queue), GET /sites (RMM sites). Every single KPI, table, and panel has been replaced.
Original vs Corrected Wiring
Original (RMM — Wrong)Corrected (Backup API — Correct)Notes
/devices → device table/bcdr/asset → protected assetsCompletely different resource. BCDR assets = backed-up agents, not RMM-managed endpoints.
/patches → patch pending/bcdr/asset → lastSuccessfulBackupNo patch concept in Backup API. Patch pending KPI replaced by backup failure count.
/jobs → job queue viewDerived from /bcdr/asset stateNo job queue endpoint in Backup API. Retry actions call device-level controls only.
/sites → sites view/bcdr/device → appliances viewBackup "sites" are represented as appliances (SIRIS/ALTO), not named site objects.
/reports → charts/bcdr/asset/backupVolumes → storageStorage volume detail is the primary reporting surface in the Backup API.
KPI: Total Devices / Online / OfflineKPI: Protected Assets / Backups OK / FailuresCore metric is backup success, not device online status.
KPI: Patch Pending / Active SitesKPI: Screenshot Fails / Local Storage TB / AppliancesBCDR-specific metrics: screenshot verification and storage are primary health indicators.
KTC home bar + HOME link presentRemoved per suite directiveDemo bar and home link stripped. Only console name shown in topbar.
Buttons Audited — Can Each Be Fulfilled?
Button / ActionAPI EndpointFulfillable?Notes
Force Backup (all)POST /bcdr/asset/{sn}/actionsPartialAPI accepts per-asset action triggers. Bulk "all" requires client-side iteration.
Force Backup (single)POST /bcdr/asset/{sn}/actionsYesDocumented in Datto Backup API. Requires asset serialNumber.
Restore SnapshotPortal only — no REST endpointNoRestores must be initiated in the Datto partner portal. No REST restore endpoint exists.
Check ScreenshotPOST /bcdr/asset/{sn}/actionsPartialScreenshot verification can be triggered via actions endpoint. Status read from GET /bcdr/device/screenshots.
Enable RansomwarePATCH /bcdr/asset/{sn}YesransomwareDetectionEnabled is a patchable field on the asset.
Retry failed backupPOST /bcdr/asset/{sn}/actionsYesaction type: "startBackup"
Resolve AlertN/A — local state onlyLocalNo alert acknowledgment endpoint. Resolved state is client-side only until next poll.
Approve All (patches)Removed — not a Backup API conceptRemovedPatch approval belongs to Datto RMM, not the Backup API. Panel has been removed.
03

Integration Status

Current state of each layer between the browser and the Datto Backup API.

⚠ Proxy Required
The dashboard runs in demo mode. Set DEMO_MODE = false in the script block after configuring the proxy. No render or UI changes are needed — only the fetch wrapper needs a real base URL and injected auth headers.
Layer StatusProxy Pending
LayerStatusRequired Action
Dashboard HTML / JS✓ ReadyAll panels, KPIs, views, and interaction logic are fully implemented against the correct Backup API schema.
Proxy / MiddlewareNot configuredNeeds a reverse proxy or Node middleware that injects X-API-KEY and X-API-SECRET headers and forwards to your Datto partner hostname.
Datto Backup APINot reachableAPI must be enabled in the Datto partner portal under Integrations → API. Generate a public/private key pair there.
Auth (API Key)Not configuredDatto Backup uses HTTP Basic with X-API-KEY and X-API-SECRET headers (not OAuth). No token expiry — keys are long-lived until revoked.
Restore ActionsNot availableNo REST restore endpoint exists in the Datto Backup API. Restores must use the partner portal or Datto RMM agent commands.
Mock Data GeneratorsActive in Demo
Mock Function / ArrayProducesReplaces
ASSETS array58 asset objects matching the /bcdr/asset response schema with client, OS, backup state, storage, screenshot statusGET /bcdr/asset
APPLIANCES array7 appliance objects matching /bcdr/device response schema with model, storage, online state, last offsiteGET /bcdr/device
VOLUMES arrayOne volume per asset, matching /bcdr/asset/backupVolumes with local/offsite sizes and statusGET /bcdr/asset/backupVolumes
renderActivitySeed()10 activity log entries derived from asset backup eventsBackup event log (no dedicated API endpoint)
04

Architecture

Single self-contained HTML file. No build step, no npm dependencies. All logic runs in-browser. The proxy layer is the only external dependency needed for live data.

Refresh Pattern30-Second Poll
// Startup sequence: 1. loadDashboard() → dattoFetch('/bcdr/asset', mock) → assets[] → dattoFetch('/bcdr/device', mock) → appliances[] → dattoFetch('/bcdr/asset/backupVolumes', mock) → volumes[] → updateKPIs() · renderAssetTable() · renderAlertFeed() → renderStorageSummary() · renderActivitySeed() 2. startAutoRefresh(30000) → setInterval(refreshAll, 30000) // dattoFetch wrapper — proxy-ready pattern: async function dattoFetch(path, mockFn) { if (!DEMO_MODE) { // Uncomment to go live: // const r = await fetch(`${DATTO_BASE}${path}`); // if (!r.ok) throw new Error(`Datto API ${r.status}`); // return r.json(); } return mockFn(); // remove when proxy live }
Auth Model

Datto Backup uses HTTP Basic authentication with two custom headers — not Bearer tokens. Keys are long-lived (no expiry by default) and must be stored securely in the proxy environment, never in the browser-side HTML.

// Headers required on every request to /api/v1/*: 'X-API-KEY': 'your-public-key' // from Datto partner portal 'X-API-SECRET': 'your-private-key' // never expose in browser JS 'Content-Type': 'application/json' // Partner hostname (set per account): const DATTO_BASE = 'https://{partner-hostname}.dattobackup.com/api/v1';
06

KPI Strip

Six equal-width metric cards driven by updateKPIs(), which runs on every refreshAll() call.

KPI Definitions
KPICalculationAPI SourceColor
Protected Assetsassets.lengthGET /bcdr/asset → totalCountCyan
Backups OKassets.filter(a => a.backedUp).lengthGET /bcdr/asset → backedUp=trueGreen
Backup Failuresassets.filter(a => !a.backedUp).lengthGET /bcdr/asset → backedUp=falseRed
Screenshot Failsassets.filter(a => a.ssStatus==='failed').lengthGET /bcdr/device/screenshotsAmber
Local Storagesum(localUsedGb) / 1024 → TBGET /bcdr/asset/backupVolumes → localUsedSizePurple
Appliancesappliances.lengthGET /bcdr/device → totalCountCyan
07

Protected Asset Status Table

Main center panel on the dashboard. Lists all protected assets with their most recent backup state. Searchable and filterable.

Asset Status TableDashboard · Center
Rendered by renderAssetTable(). Applies applyAssetFilters() (text search + status filter) then slices to 50 rows. Clicking a row opens the asset modal.
GET /bcdr/asset
Column Definitions
ColumnAPI FieldDisplay Logic
HOSTNAMEasset.hostnameBold, white. Clickable — opens modal.
CLIENTasset.client.nameMono, muted, truncated to 18 chars.
TYPEasset.operatingSystemAbbreviated chip: "WServer 2022" → "WServer2022", "Windows 11 Pro" → "W11 Pro".
LAST BACKUPasset.lastSuccessfulBackup (computed as hrs ago)Green <8h, Amber 8–24h, Red >24h.
LOCALasset.localUsedGb → fmtBytes()Formatted as GB or TB.
OFFSITEasset.offsiteUsedGb → fmtBytes()Muted color — secondary metric.
STATUSasset.backedUpGreen "OK" badge or blinking red "FAILED" badge.
Filters
FilterLogic
Search inputhostname.includes(q) || client.name.includes(q) — case-insensitive, fires on every keypress via oninput
Status dropdown: ALLNo filter applied
Status dropdown: OKa.backedUp === true
Status dropdown: FAILEDa.backedUp === false
Status dropdown: WARNINGa.backedUp && a.ssStatus === 'pending'
08

Alert Feed

Right column on the dashboard. Shows up to 10 active alerts derived from asset backup failures and screenshot verification failures.

API Limitation
The Datto Backup API has no dedicated alert stream endpoint. Alerts are entirely computed client-side from the asset list: !a.backedUp → Critical, a.ssStatus === 'failed' → Warning. This means alert state only updates when the 30-second refreshAll() poll completes.
Alert FeedDashboard · Right Column
Rendered by renderAlertFeed(). First collects failed backup assets (red stripe), then adds screenshot failures for otherwise healthy assets (amber stripe). Limited to 10 items total.
09

Storage Health Panel

Dashboard bottom-right. Summary view of local and offsite storage utilization across all backup volumes.

Storage HealthDashboard · Bottom Right
Rendered by renderStorageSummary() from the volumes array. Shows: local TB used, avg utilisation progress bar, offsite TB, offsite replication percentage, and counts of OK vs warning volumes.
GET /bcdr/asset/backupVolumes
10

Backup Activity Log

Dashboard bottom-left. Chronological log of backup events, API sync completions, and replication status messages.

API Limitation
The Datto Backup API has no dedicated backup event log or audit trail endpoint. The activity log is populated from renderActivitySeed() which derives entries from the asset state. In production this should be supplemented by a CW Manage or PSA ticket event feed if a timeline of backup actions is needed.
11

Screenshot Verification View

Sidebar nav → "Screenshots". Grid of all protected assets with their latest bootability verification result.

Screenshot VerificationDedicated View
Rendered by renderScreenshots(). Displays 30 assets in a 3-column card grid. Each card shows hostname, client, and screenshot status (PASSED ✅ / FAILED ❌ / PENDING ⏳). Card border color matches status. Click opens asset modal.
GET /bcdr/device/screenshots
API Limitation
The GET /bcdr/device/screenshots endpoint returns the most recent screenshot result per asset only. Historical per-snapshot screenshot pass/fail data is only available in the Datto partner portal UI. There is no public bulk historical screenshot endpoint.
12

Failures View

Sidebar nav → "Failures". Full table of all assets with failed or overdue backups.

Backup FailuresDedicated View
Rendered by renderFailures(). Filters assets where !backedUp || hoursAgo > 24. Shows hostname, client, last success timestamp, inferred failure reason, hours missed, and action buttons (RETRY / DETAIL).
Derived from GET /bcdr/asset — no dedicated failures endpoint
Failure Reason Mapping

The Datto Backup API does not return a structured failure reason string. In demo mode, failure reasons are randomly assigned from a pool of common causes. In production, failure reason text must be extracted from the Datto portal logs or from agent-level status fields where available on the individual GET /bcdr/asset/{serialNumber} response.

Demo ReasonReal Source
Snapshot failed: VSS errorCheck Datto agent logs on the asset
Agent offlineasset.pairingState !== 'paired' or lastSeenDate stale
Network unreachableAgent last seen timestamp + network monitoring
Volume lockedWindows VSS status on the protected system
Insufficient storagebackupVolume.localUsedSize / localTotalSize > 0.95
13

Storage View

Sidebar nav → "Storage". Four summary KPIs and a full volume detail table.

Backup StorageDedicated View
Rendered by renderStorage() from the volumes array. Table columns: asset name, client, local used, local total, local % (with progress bar), offsite used, status badge.
GET /bcdr/asset/backupVolumes
Volume Schema
API FieldTypeDescription
assetSerialNumberstringLinks volume to agent via GET /bcdr/asset/{sn}
assetNamestringHostname of the backed-up asset
clientNamestringClient account name
localUsedSizeinteger (GB)Local backup storage used in gigabytes
localTotalSizeinteger (GB)Total local backup capacity in gigabytes
offsiteUsedSizeinteger (GB)Offsite (Datto Cloud) storage used
statusstring"ok" or "warning". Warning threshold set at >85% local utilisation in dashboard logic.
14

Appliances View

Sidebar nav → "Appliances". Card grid of all BCDR appliances (SIRIS, ALTO, Cloud) with storage gauges and online status.

BCDR AppliancesDedicated View
Rendered by renderAppliances() from the appliances array. Each card shows: name, model, IP, serial, assets protected count, local/offsite storage, online status, and a local storage progress bar.
GET /bcdr/device
Appliance Schema
API FieldTypeDescription
serialNumberstringUnique appliance identifier
namestringFriendly name set in the partner portal
modelstringHardware model: SIRIS 5, SIRIS 4, ALTO 4+, ALTO 3, or Datto Cloud
internalIPstringLAN IP address of the appliance
lastSeenDateISO timestampLast heartbeat from the appliance
localStorageUsedinteger (GB)Used local storage on the appliance
localStorageAvailableinteger (GB)Available local storage remaining
offsiteStorageUsedinteger (GB)Offsite cloud storage used by this appliance
latestOffsiteISO timestampTimestamp of the most recent successful offsite sync
16

All API Endpoints

Every Datto Backup REST endpoint used or referenced in this dashboard. Base URL: https://{partner}.dattobackup.com/api/v1

Read EndpointsGET
GET/bcdr/assetAll protected BCDR agents. Primary data source. Fields: serialNumber, hostname, client, operatingSystem, backedUp, lastSuccessfulBackup, localStorageUsed, offsiteStorageUsed, ransomwareDetectionEnabled, pairingState.Proxy Required
GET/bcdr/asset/{serialNumber}Single asset by serial. Used in asset modal for full detail. Same schema as the list endpoint with additional per-asset fields.Proxy Required
GET/bcdr/asset/backupVolumesBackup storage volumes for all assets. Fields: assetSerialNumber, assetName, clientName, localUsedSize, localTotalSize, offsiteUsedSize, status. Feeds storage view and storage health panel.Proxy Required
GET/bcdr/deviceBCDR appliances (SIRIS, ALTO, Cloud). Fields: serialNumber, name, model, internalIP, lastSeenDate, localStorageUsed, localStorageAvailable, offsiteStorageUsed, latestOffsite. Feeds appliances view and appliance KPI.Proxy Required
GET/bcdr/device/screenshotsLatest screenshot verification results per asset. Returns most recent bootability check status (passed/failed/pending) per serialNumber. Feeds screenshot verification view.Proxy Required
Write EndpointsActivates When Proxy Is Live
POST/bcdr/asset/{serialNumber}/actionsTrigger an action on an asset. Action types include startBackup (force backup) and startScreenshot (trigger verification). Called by Force Backup and Check Screenshot buttons in asset modal.Simulated
POST/bcdr/asset/{serialNumber}/actionsRetry a failed backup. Same endpoint as force backup with action type startBackup. Called by RETRY button in failures view.Simulated
POST/bcdr/asset/{serialNumber}Update asset settings. Used to enable/disable ransomwareDetectionEnabled. Called by the Enable Ransomware button in the asset modal (shown only when detection is off).Simulated
Endpoints That Do Not Exist in Datto Backup APINot Available
Requested FeatureStatusWorkaround
Snapshot restore via RESTNo endpointRestores must be initiated in the Datto partner portal UI only.
Backup event log / audit trailNo endpointDerive from lastSuccessfulBackup timestamps on the asset list.
Per-snapshot screenshot historyLatest only/bcdr/device/screenshots returns the most recent result only. History requires portal access.
Alert/notification streamNo endpointAlerts are computed client-side from asset state. For push alerts, use Datto webhook integrations if available on the account.
CSAT / satisfaction dataNo endpointNot applicable to Datto Backup. Use CW Manage or PSA for CSAT data.
Patch managementWrong productPatches are a Datto RMM concept. Not present in the Backup API.
17

Config & Fields Reference

All configurable constants in the dashboard script block.

Constants
ConstantDefaultEffect
DATTO_BASEPlaceholder URLSet to your partner hostname: https://{partner}.dattobackup.com/api/v1
DEMO_MODEtrueSet to false when proxy is live. Switches all dattoFetch() calls from mock to real fetch.
startAutoRefresh(ms)30000msPoll interval. Lower to 15000ms for faster updates; increase to 60000ms to reduce API load.
CLIENTS array7 clientsDemo client roster. In production, derive from unique asset.client.name values.
ASSETS length58 assetsDemo asset count. In production, populated from GET /bcdr/asset response.
Storage warning threshold85%In VOLUMES map and renderStorage(): localUsedSize / localTotalSize > 0.85
Backup overdue threshold24hIn renderFailures(): assets with hoursAgo > 24 are flagged regardless of backedUp field.
18

Documented Limitations

Known constraints of the current implementation and the Datto Backup API itself.

Limitation Register
IDLimitationRoot CauseResolution
L-01All data is simulatedDEMO_MODE = true. Proxy not configured.Set DEMO_MODE = false after proxy setup. See §19.
L-02No backup event log endpointDatto Backup API has no audit trail or event log resource.Derive from lastSuccessfulBackup timestamps. Supplement with PSA ticket events.
L-03Screenshot history is latest-onlyGET /bcdr/device/screenshots returns only the most recent result per asset.Link to Datto partner portal for historical screenshot review.
L-04Failure reasons are inferred, not returnedDatto Backup API does not include a structured failureReason field on asset objects.Parse agent logs via RMM or portal. Map from pairingState and storage fields.
L-05Restore cannot be triggered via APINo REST restore endpoint exists in the Datto Backup public API.Restores must use the partner portal or be scripted via Datto RMM agent commands.
L-06Alert state not persistedNo alert acknowledgment endpoint. Resolved state is in-memory only.Use PSA ticket creation as the durable acknowledgment record.
L-07Bulk action not natively supportedPOST /bcdr/asset/{sn}/actions is per-asset. "Force Backup All" must iterate in a loop.Client-side iteration with Promise.allSettled() in the fetch wrapper.
L-08No RBAC on dashboard buttonsStatic HTML — any user who loads the file sees all buttons including Retry and Enable Ransomware.Enforce at proxy layer: restrict write endpoints by authenticated role.
19

Proxy Activation Checklist

Step-by-step to move from demo mode to live Datto Backup API data.

Prerequisite
API integration must be enabled in your Datto partner portal: Integrations → API → Generate Keys. You will receive a public key and a private key. Keep the private key in your proxy environment only — never in browser JS.
Steps
1
Enable API in Datto partner portal
Log into your Datto partner portal → Integrations → API. Generate a public/private key pair. Note your partner hostname (used as the subdomain in the API base URL).
2
Test the API credentials manually
Using curl or Postman: GET https://{partner}.dattobackup.com/api/v1/bcdr/asset with headers X-API-KEY and X-API-SECRET. A 200 response with an asset array confirms credentials are valid.
3
Deploy a proxy with CORS and header injection
The proxy must: (a) inject X-API-KEY and X-API-SECRET headers from environment variables, (b) forward requests to the Datto base URL, (c) add Access-Control-Allow-Origin for the dashboard's domain. Options: nginx, Node/Express, or Cloudflare Worker.
4
Update DATTO_BASE and set DEMO_MODE = false
In the dashboard script block: set const DATTO_BASE = 'https://your-proxy.example.com/api/v1' and const DEMO_MODE = false. Uncomment the real fetch block inside dattoFetch().
5
Verify GET /bcdr/asset returns real assets
Open DevTools → Network. Trigger a manual refresh. The /bcdr/asset request should return HTTP 200 with a real asset array. The KPI "Protected Assets" count should match your actual BCDR agent count in the portal.
6
Add field normalization if needed
The dashboard expects the exact field names from the Datto API (backedUp, lastSuccessfulBackup, client.name, etc.). If the real API response uses different casing or nesting, add a normalizer function between dattoFetch() and the render functions.
7
Validate the screenshot and storage views
Navigate to Screenshots and Storage views. Confirm the grid and table populate with real appliance and asset data. Screenshot cards should reflect actual pass/fail states from your agents.
8
Test write endpoints on a non-critical agent
Before enabling Force Backup and Check Screenshot in production, test POST /bcdr/asset/{sn}/actions against a non-critical test agent to confirm the action fires correctly and the asset shows updated state on next poll.
Go-Live Checklist
  • API keys generated in Datto partner portal (Integrations → API)
  • Partner hostname confirmed — base URL validated via curl/Postman
  • Proxy deployed with X-API-KEY and X-API-SECRET injected from environment
  • CORS headers configured for dashboard origin
  • DATTO_BASE updated and DEMO_MODE set to false in script block
  • Real fetch block uncommented inside dattoFetch()
  • GET /bcdr/asset returns real assets — confirmed in DevTools Network tab
  • KPI count matches agent count in Datto partner portal
  • Screenshot view shows real pass/fail states from GET /bcdr/device/screenshots
  • Storage view shows real GB values from GET /bcdr/asset/backupVolumes
  • Write endpoints tested on non-critical agent before production enable
  • RBAC enforced at proxy layer — write endpoints restricted by role
20

Troubleshooting

Common issues during deployment and daily operation. Open DevTools Console and Network tabs before anything else.

Dashboard still shows demo data after DEMO_MODE = false
Check that the real fetch block is uncommented inside dattoFetch(). Setting DEMO_MODE = false gates the real fetch path but doesn't automatically uncomment it — the return mockFn() line must also be removed or commented out. Open DevTools → Network and confirm the /bcdr/asset request fires and returns a 200 with a real response body rather than falling through to the mock.
CORS error: blocked by Access-Control-Allow-Origin
The proxy is not adding CORS headers. Add Access-Control-Allow-Origin: https://your-dashboard-domain (or * for development) to every proxied response. If using nginx: add_header 'Access-Control-Allow-Origin' '$http_origin';. Do not attempt to call the Datto API directly from the browser — you cannot add the auth headers from a browser context without exposing the private key.
401 Unauthorized from Datto API
X-API-KEY or X-API-SECRET header is missing or incorrect. Verify both headers are being injected by the proxy and match the keys generated in your Datto partner portal exactly. Key generation must be done under your partner account — child/sub-account keys will not work for partner-level API calls. If you recently regenerated keys, the old values are immediately invalidated.
Asset table is empty after switching to live data
Field name mismatch. The dashboard expects asset.backedUp, asset.client.name, asset.lastSuccessfulBackup etc. If the real API response uses different names (some Datto API versions return camelCase differently), add a normalization function that maps the real field names to the dashboard's expected schema before passing the data to render functions.
Screenshot grid shows all "PENDING" after going live
The /bcdr/device/screenshots endpoint may return a different status value than expected. The dashboard checks for the string "passed", "failed", or "pending". If the real API returns different strings (e.g., "success", "error", "queued"), update the ssStatus mapping in the normalization layer. Also note that newly paired agents may genuinely show PENDING if no screenshot verification has completed yet.
Force Backup button does nothing in live mode
The POST /bcdr/asset/{sn}/actions endpoint is wired in the modal but currently fires a showToast() in demo mode. To enable it: in the asset modal action buttons, replace the showToast call for "Force Backup" with a real dattoFetch('/bcdr/asset/' + serial + '/actions', ...) call with body { "action": "startBackup" }. Verify the action type string against current Datto API documentation — it may vary by account configuration.
KPI counts don't match the Datto partner portal
The dashboard computes failure count from backedUp === false. The Datto portal may count failures differently (e.g., including paused or excluded agents). Check whether your GET /bcdr/asset response includes hidden or inactive agents — the API may return all assets by default including those with hidden: true. Add a filter for !a.hidden in loadDashboard() if needed.