What This Tool Is
A real-time hardware lifecycle and asset intelligence console built on ScalePad Lifecycle Manager. Surfaces warranty status, end-of-life risk, fleet health scores, and pipeline opportunities across all managed clients.
The ScalePad Asset Intelligence Console is a single-page HTML operations dashboard that connects to the ScalePad Lifecycle Manager GraphQL API (https://api.scalepad.com/graphql). It is built for MSP account managers, vCIOs, and NOC engineers who need immediate visibility into hardware warranty expiry, end-of-life risk, and refresh opportunity pipelines across their entire managed client fleet.
The dashboard uses a fixed-layout design optimised for wall-board display and quick triage. All data panels are derived from ScalePad's asset graph. In demo mode the dashboard renders with fully synthetic but structurally accurate data matching the ScalePad API schema — switching to live data requires only a proxy configuration change and one flag in the script block.
vCIO / Account Managers — Use the fleet health scores, EOL risk panel, and pipeline opportunities to drive quarterly business review conversations. The client list panel sorts by health score for immediate at-risk identification.
NOC Engineers — Hardware table provides fast lookup of any device's warranty status with search, filter, and sort. The expiring ≤90 days KPI filters directly to at-risk devices.
Procurement & Operations — Breakdown charts show fleet composition by manufacturer, client tier, and warranty split. Opportunity pipeline surfaces devices due for refresh with estimated value.
Practice Management — Fleet health gauge and average client score provide an aggregate metric for MSP leadership reporting without needing portal access.
API Audit Findings
Every data-fetching action, button, and displayed value audited against the ScalePad Lifecycle Manager API documentation. All gaps documented and corrected.
loadDashboard(), no startAutoRefresh(), and no proxy pattern. The KTC home bar style block and div were also present. All of these have been corrected in Directive 1.
| Panel / Value | Correct API | Status | Notes |
|---|---|---|---|
| KPI — Total Assets, Active/Expiring/Expired | GraphQL: assets query | ✓ Wired | Computed from warrantyEndDate on asset nodes. See §14. |
| KPI — EOL Now, EOL ≤12mo | GraphQL: assets query | ✓ Wired | Computed from endOfLifeDate field on asset nodes. |
| KPI — Fleet Health (avg score) | Derived from assets | ⚠ Derived | ScalePad does not return a pre-computed health score. Score is computed client-side from warranty/EOL ratios per client. |
| KPI — Open Pipeline ($) | No ScalePad endpoint | ⚠ Gap | ScalePad does not expose opportunities via API. Value is demo-only. In production: source from PSA (CW Manage, HaloPSA). |
| Gauges — Warranty Coverage, Fleet Health, EOL Risk | GraphQL: assets query | ✓ Wired | All three derived from asset data. Animates from real data on proxy activation. |
| Gauge — Pipeline Win Rate | No ScalePad endpoint | ⚠ Gap | No opportunity win/loss data in ScalePad API. Demo only. Source from PSA in production. |
| Client List Panel | GraphQL: partners query | ✓ Wired | Partner/client list with health score, asset count, and at-risk counts from partners query. |
| Hardware Table | GraphQL: assets query | ✓ Wired | Full asset list with warranty, EOL, manufacturer, type per device. |
| EOL Risk Panel | GraphQL: assets query | ✓ Wired | Filtered subset of assets where endOfLifeDate is within 24 months. |
| Activity Feed | No dedicated endpoint | ⚠ Derived | No audit-log or activity-stream endpoint in ScalePad API. Activity is derived from expired/EOL asset state at query time. Documented in comments. |
| Opportunities Pipeline Table | No ScalePad endpoint | ⚠ Gap | ScalePad public API does not expose opportunities. Table is demo-only. In production: source from PSA system's opportunity/proposal module. |
| Fleet Breakdown (mfr, tier, warranty) | GraphQL: assets query | ✓ Wired | All breakdown bars derived from asset and partner data from live API queries. |
| Topbar API Key Input | N/A — disabled by design | ⚠ Design | Input is intentionally disabled. Authentication must happen server-side in the proxy. Exposing the API key client-side is a security risk. Input shows users where to configure; actual key lives in the proxy env. |
| Change | Reason |
|---|---|
Removed <style id="ktc-home-bar-style"> | Suite directive: remove KTC home bar style entirely |
Removed #ktc-demo-bar div and HOME link | Suite directive: topbar shows only tool name, no home nav |
Added var DEMO_MODE = true | Single flag controls demo vs. live branch in refreshAll() |
Added var SCALEPAD_PROXY constant | Proxy base path — maps to ScalePad GraphQL endpoint |
Added async function scalePadFetch(query, variables) | Proxy-ready GraphQL fetch wrapper with real fetch commented above mock fallback |
Added async function loadDashboard() | Suite-standard init: calls scalePadFetch(), normalises data, then refreshAll() |
Added function startAutoRefresh(ms) | Suite-standard 30-second polling wrapper |
Added function refreshAll() with DEMO/live branch | All render functions called here; footer text shows Demo Mode vs Live |
Replaced DOMContentLoaded with loadDashboard(); startAutoRefresh(30000); | Suite-standard boot sequence |
| Added API limitations block in code comments | Documents Opportunities gap, Activity gap, Health Score derivation, and key security note |
Integration Status
Current state of each integration layer. All live features activate when the proxy endpoint is configured and DEMO_MODE is set to false.
scalePadFetch() with a real ScalePad API key injected server-side.
| Layer | Status | Required Action |
|---|---|---|
| Dashboard HTML/JS | ✓ Ready | All render functions, wiring pattern, and GraphQL query stubs are implemented. |
| Proxy / Backend | Not configured | Needs a reverse proxy or serverless function that injects the ScalePad API key and forwards POST requests to https://api.scalepad.com/graphql. |
| ScalePad GraphQL API | Not reachable | ScalePad API access requires an API key generated in ScalePad Portal → Settings → Integrations. |
| Auth / Token | Not configured | Bearer token obtained from ScalePad Settings. Does not expire automatically; rotate on a schedule. Must live in the proxy environment — never in browser JS. |
| Opportunities Pipeline | Not available in ScalePad API | No public API for opportunities. Requires PSA integration (e.g. CW Manage GET /opportunities) as a separate data source. See §16. |
Architecture
Single self-contained HTML file. No build step, no dependencies. All logic runs in-browser. The proxy layer is the only external dependency needed for live data.
ScalePad uses API key authentication passed as a Bearer token. Keys are generated in the ScalePad Partner Portal and do not expire by default. All authentication must happen server-side in the proxy — never expose the key in client-side JavaScript or HTML.
| Variable | Type | Purpose |
|---|---|---|
| CL | Array | Client/partner list. Populated from partners GraphQL query in live mode. |
| HW | Array | Hardware asset list. Populated from assets GraphQL query in live mode. |
| LC | Array | Lifecycle-at-risk subset of HW where endOfLifeDate < now + 2 years. |
| OPPS | Array | Opportunities pipeline. Demo-only — no ScalePad API endpoint for this data. |
| CS | Object | Client health scores keyed by client ID. Computed client-side from HW data. |
| hwP | Number | Current hardware table page number. |
| hwPS | Number | Page size for hardware table (default: 18). |
| hwWF | String | Active warranty filter: 'all' | 'active' | 'expiring' | 'expired'. |
| hwS | String | Hardware table search query string. |
| hwCF | String|null | Active client filter ID (null = show all clients). |
| hwSort | Object | Sort state: { col: 'dw', dir: 1 }. Column key and direction (1=asc, -1=desc). |
| DEMO_MODE | Boolean | If true, scalePadFetch() returns null and synthetic data is used. Set false when proxy is live. |
Topbar
44px fixed header. Contains the ScalePad brand, an API key connection input (disabled by design), and sync/mode status indicators.
| Element | ID/Class | Behavior |
|---|---|---|
| Brand logo + name | .tb-brand | Static. "SP" logo mark + "ScalePad" in Exo 2 / "LIFECYCLE INSIGHTS" sub. |
| API key input | .tb-api-in | Intentionally disabled. Authentication happens server-side in the proxy. The input is visible UI only — it does not send the key to any endpoint. |
| Connect button | .tb-api-btn | Disabled. Proxy must be configured server-side before this becomes functional. |
| Demo badge | .demo-badge | Pulses with CSS animation while in demo mode. Removed from view when DEMO_MODE=false and footer updates to "Live". |
| Status dot | .sdot | Green dot. In live mode this reflects real API connectivity status. |
| Sync time | #syncTime | Set to new Date().toLocaleTimeString() on every refreshAll() call. |
KPI Row
Eight KPI cards spanning the full dashboard width. The first four are clickable and apply warranty filters to the hardware table. Rendered by buildRow1().
#row1 via buildRow1(). Each KPI has a label, value, sub-label, and a 2px color accent bar at the top.warrantyEndDate, endOfLifeDate fields| KPI | Calculation | Click Action | Color |
|---|---|---|---|
| Total Assets | HW.length | Filters hardware table to All | Blue |
| Active Warranty | dw ≥ 90 (days to warranty end) | Filters hardware table to Active | Green |
| Expiring ≤90d | 0 ≤ dw < 90 | Filters hardware table to Expiring | Yellow |
| Expired | dw < 0 | Filters hardware table to Expired | Red |
| EOL Now | de < 0 (days to end-of-life) | Non-clickable | Orange |
| EOL ≤12mo | 0 ≤ de < 365 | Non-clickable | Yellow |
| Open Pipeline | Sum of open+pending opportunity values | Non-clickable | Purple |
| Fleet Health | Average of all client health scores (0–100) | Non-clickable | Accent |
NOC Gauges (×4)
Four SVG donut gauges at the right end of Row 1. Each gauge animates its stroke-dashoffset on render to fill the ring proportionally.
| Gauge | SVG ID | Metric | API Source |
|---|---|---|---|
| Warranty Coverage | g-cov | active / HW.length × 100% | assets query — warrantyEndDate |
| Fleet Health Score | g-hlt | Average client health score (0–100) | Derived from asset data — not returned by API |
| EOL Risk | g-eol | (eolNow + eolSoon) / LC.length × 100% | assets query — endOfLifeDate |
| Pipeline Win Rate | g-win | won / (won + lost) × 100% | Demo only — no ScalePad API endpoint |
Clients Panel
Left column of Row 2. Scrollable list of all clients sorted by health score (descending). Clicking a client filters the hardware table and lifecycle panel to that client's assets.
buildClients(). Each row shows: client name, city/state/vertical, a health score progress bar, the numeric score and letter grade (A–D), and chips for asset count, expired warranty count, EOL count, and open opportunity count.id, name, city, state, vertical, tier fieldsHealth score is computed client-side — ScalePad does not return a pre-computed score. Starting from 100, penalties are applied:
Hardware Table
Center panel of Row 2. Sortable, filterable, searchable table of all hardware assets. 18 rows per page. Rendered by buildHW().
| Column | Field | Sortable | Notes |
|---|---|---|---|
| Device | a.name | ✓ (col: name) | Manufacturer + model name |
| Client | a.cli | ✓ (col: cli) | Owning client name |
| Maker | a.mfr | ✓ (col: mfr) | Manufacturer (Dell, HP, Lenovo, etc.) |
| Type | a.type | ✓ (col: type) | Laptop / Desktop / Workstation / Server / Tablet |
| Purchased | a.pd (purchaseDate) | ✓ (col: pd) | Formatted as DD Mon YYYY |
| Warranty Ends | a.we (warrantyEndDate) | ✓ (col: we) | Date of warranty expiry |
| Days Left | a.dw (daysToWarrantyEnd) | ✓ Default sort (col: dw) | Green ≥90d, Yellow 0–89d, Red = expired (shows as "Nd ago") |
| Status | wSt(a.dw) | — | ACTIVE / EXPIRING / EXPIRED badge |
EOL Risk Panel
Right column top panel. Shows the 25 highest-risk lifecycle items (EOL or EOL soon) sorted by urgency and days to end-of-life. Rendered by buildLC().
endOfLifeDate < now + 730 days. Sorted: EOL devices first (de<0), then EOL soon (de<365), then remainder by ascending days. Each row shows a lifecycle progress bar (age as % of 5-year lifespan) and an EOL / SOON / OK chip.endOfLifeDate field per asset nodeActivity Feed
Right column bottom panel. A short chronological list of asset-state change events. Rendered by buildActivity().
Opportunities Pipeline
Row 3 left panel. Table of hardware refresh and renewal opportunities, sorted by value descending. Rendered by buildOpps().
CW Manage:
GET /v4_6_release/apis/3.0/sales/opportunitiesHaloPSA:
GET /api/OpportunityAutotask:
GET /atservicesrest/v1.0/Opportunities
Fleet Breakdown
Row 3 right panel. Two columns of horizontal bar charts: manufacturer distribution, client tier distribution, and warranty split. Rendered by buildBreakdown().
API Endpoints
ScalePad Lifecycle Manager uses a single GraphQL endpoint. All queries and mutations are POST requests to https://api.scalepad.com/graphql.
Authorization: Bearer API_KEY header. All data access (assets, partners, lifecycle insights) goes through this endpoint.
Proxy Required
| Query | Returns | Used For | Status |
|---|---|---|---|
| partners { id name city state vertical tier } | Array of partner/client objects | Client list panel, tier breakdown, health score computation | ✓ Wired |
| assets(pageSize: N) { nodes { id systemName manufacturer modelNumber deviceType serialNumber purchaseDate warrantyEndDate endOfLifeDate partner { id name } } } | Paginated array of hardware asset nodes | Hardware table, KPI cards, EOL risk panel, breakdown charts, gauges | ✓ Wired |
| lifecycleInsights { endOfLifeAlerts { assetId assetName endOfLifeDate } } | Pre-computed EOL alert objects | Alternative to filtering endOfLifeDate on assets manually | Optional |
| Feature | Status | Alternative |
|---|---|---|
| Opportunities / Pipeline | Not available | CW Manage GET /sales/opportunities, HaloPSA GET /api/Opportunity |
| Activity / Audit Log | Not available | Derive from asset state changes; enrich with PSA/RMM webhooks |
| Pre-computed Health Score | Derived only | Compute client-side from warranty/EOL ratios as implemented |
| Win Rate / Opportunity Status | Not available | Source from PSA opportunity win/loss fields |
Config & Fields
All configurable constants and threshold values in the dashboard script block.
| Constant | Default | Effect |
|---|---|---|
| DEMO_MODE | true | Set to false when proxy is live. Controls whether scalePadFetch() fires real requests. |
| SCALEPAD_PROXY | '/api/scalepad' | Base path for the proxy. Maps to https://api.scalepad.com/graphql on the server. |
| hwPS | 18 | Hardware table page size. Increase for larger screens. |
| startAutoRefresh(ms) | 30000 | Poll interval in ms. Reduce to 15000 for more frequent updates; increase to 60000 to reduce API load. |
| LC filter threshold | 730 days | EOL risk panel shows assets with endOfLifeDate within 2 years (de < 730). |
| wSt expiring threshold | 90 days | Assets with dw < 90 && dw ≥ 0 are flagged "expiring". Change inline in wSt(). |
| Health score: expired weight | 40 | Penalty multiplier for expired warranty ratio in client health score. |
| Health score: expiring weight | 20 | Penalty multiplier for near-expiry ratio. |
| Health score: EOL weight | 30 | Penalty multiplier for EOL device ratio. |
| Health score minimum | 20 | No client score drops below 20 regardless of fleet state. |
| Demo Field | ScalePad API Field | Type |
|---|---|---|
| a.id | id | String (UUID) |
| a.name | systemName | String — display name in ScalePad |
| a.mfr | manufacturer | String |
| a.model | modelNumber | String |
| a.type | deviceType | String enum |
| a.sn | serialNumber | String |
| a.cid | partner.id | String (UUID) |
| a.cli | partner.name | String |
| a.pd | purchaseDate | ISO 8601 date string |
| a.we | warrantyEndDate | ISO 8601 date string |
| a.eol | endOfLifeDate | ISO 8601 date string |
| a.dw | Computed | Math.round((new Date(warrantyEndDate) - now) / 86400000) |
| a.de | Computed | Math.round((new Date(endOfLifeDate) - now) / 86400000) |
Documented Limitations
Known constraints of the ScalePad API and the current dashboard implementation, with recommended mitigations.
| # | Limitation | Impact | Mitigation |
|---|---|---|---|
| L-01 | No Opportunities API | Opportunities pipeline shows demo data only. | Source from PSA opportunity module (CW Manage, HaloPSA, Autotask). Wire as a separate psaFetch() call in loadDashboard(). |
| L-02 | No Activity/Audit Log Endpoint | Activity feed derived from static asset state, not real timestamps. | Supplement with PSA ticket notes or RMM event webhooks for real timestamped events. |
| L-03 | Health Score is Computed, Not Returned | Low — derived value is accurate. | Current algorithm is sound. If ScalePad adds a native health score field in future, swap CS[c.id].s to use the API value. |
| L-04 | GraphQL Pagination Required | Large fleets (>1000 devices) require cursor-based pagination. | The loadDashboard() query stub includes pageInfo { hasNextPage endCursor }. Implement a loop in scalePadFetch() that follows the cursor until hasNextPage = false. |
| L-05 | API Key Must Never Be Client-Side | Security risk if key is embedded in HTML. | The topbar API key input is disabled by design. All authentication happens in the proxy. The key lives in the proxy environment variable only. |
| L-06 | No Real-Time Push Notifications | Low — 30-second polling is sufficient for this use case. | The 30-second startAutoRefresh() interval provides near-real-time data. ScalePad does not support WebSocket subscriptions via the public API. |
Proxy Activation Checklist
Step-by-step guide to move the dashboard from demo mode to live ScalePad data. Complete all steps before setting DEMO_MODE = false.
SCALEPAD_API_KEY.• Accepts POST requests at
/api/scalepad• Adds
Authorization: Bearer $SCALEPAD_API_KEY to the request headers• Forwards the request body to
https://api.scalepad.com/graphql• Returns the response JSON to the browser
curl -X POST /api/scalepad -H "Content-Type: application/json" -d '{"query":"query{partners{id name}}"}'• Set
var DEMO_MODE = false;• Confirm
var SCALEPAD_PROXY = '/api/scalepad'; matches your proxy route.• Uncomment the real fetch block inside
scalePadFetch().loadDashboard(), uncomment the scalePadFetch() calls and add a normaliser function that maps the ScalePad API field names (systemName, warrantyEndDate, etc.) to the dashboard's internal field names (name, we, etc.) as documented in §15.pageInfo.hasNextPage and pageInfo.endCursor fields returned by the assets query to fetch all pages before rendering.psaFetch() function alongside scalePadFetch() and populate OPPS from your PSA's opportunity/proposal endpoint. Update the footer to reflect the PSA source.- ScalePad API key generated and stored in proxy environment variable
- Proxy endpoint deployed and tested with curl (returns real ScalePad data)
- DEMO_MODE set to
falsein dashboard script block - Real fetch block uncommented inside
scalePadFetch() - Asset normaliser function implemented mapping ScalePad fields to internal fields
- Partners normaliser function implemented
- Pagination handled if fleet exceeds 1000 assets
- Browser console shows no errors on load
- Footer text shows "Live" (not "Demo Mode")
- KPI counts match ScalePad portal values
- Opportunities panel wired to PSA (or documented as pending)
Troubleshooting
Common issues encountered when activating live data or operating the dashboard.
scalePadFetch(). The DEMO_MODE flag gates the branch but the function returns null unless the fetch() call inside it is active. Also ensure the browser has no cached version of the old file (hard refresh with Ctrl+Shift+R).Authorization: Bearer YOUR_KEY — not ApiKey or any other scheme. Confirm the key was copied correctly from the ScalePad portal and has not been rotated or revoked.Access-Control-Allow-Origin headers. Ensure SCALEPAD_PROXY points to your own proxy server, not to https://api.scalepad.com directly. The proxy must add CORS headers to its responses.a.name, a.mfr, a.we, a.eol. ScalePad returns systemName, manufacturer, warrantyEndDate, endOfLifeDate. Add a normaliser in loadDashboard() to map the API fields to internal names before assigning to the HW array.pageInfo.hasNextPage and pageInfo.endCursor. Implement a loop in scalePadFetch() that continues fetching with assets(after: $cursor, pageSize: 500) until hasNextPage = false, then concatenates all node arrays before rendering.OPPS array is populated with synthetic data and will not be replaced by scalePadFetch(). To show real data, add a separate psaFetch() function that calls your PSA's opportunity endpoint and normalises the response into the OPPS array format expected by buildOpps().startAutoRefresh() from 30000ms to 60000ms (1 minute) or 120000ms (2 minutes). ScalePad rate limits vary by account tier. If you hit rate limits, check the proxy response headers for Retry-After or X-RateLimit-* headers and implement exponential backoff in scalePadFetch().