The ShadowProtect SPX Console is a self-contained HTML file that gives MSP technicians a unified real-time view of backup status across every client managed through the StorageCraft / ShadowProtect partner portal. It replaces manual portal browsing with a purpose-built dashboard that surfaces risk, generates ticket notes, and drives consistent daily NOC triage.
.html file. Open it in any modern browser. No server, no npm, no build step./admin-console/admin-accounts-list, /admin-console/admin-sessions-list) using a local reverse proxy to handle CORS and session injection. No official JSON API is required.node main.js) for live dataThe console sits entirely in the browser. It fetches data from the StorageCraft portal through a local Node.js reverse proxy that injects Basic Auth session cookies and strips CORS restrictions. No data is stored beyond the browser session.
| Layer | What it does | File / Component |
|---|---|---|
| UI Shell | All HTML/CSS rendering, panels, gauges, theming | console.html |
| Data Parser | parsePortalTable() — extracts rows from HTML tables. parseAccounts(), parseSessions() normalise columns |
console.html (inline JS) |
| API Object | The api object wraps all portal fetches: clients(), queryAll(), check(id), statusReport() |
console.html (inline JS) |
| Risk Engine | computeRisk() and computeDrift() produce numeric scores from session trends |
console.html (inline JS) |
| Proxy | Reverse-proxies all requests to the portal, injects credentials, handles CORS | main.js (Node.js, separate) |
| Credential Store | Uses browser Credential Management API (PasswordCredential) with sessionStorage fallback |
Browser storage |
The sticky header contains every primary action. Left to right:
| Element | Function |
|---|---|
| Tech Name | Free-text field for the technician's name. Persisted to localStorage. Stamped at the bottom of every generated ticket note. |
| Save | Saves the tech name to localStorage and, if a ticket note exists, immediately rebuilds it with the updated name. |
| Theme swatches | 8 color themes (Ice, Red, Matrix, Purple, Amber, Cobalt, Mono, Tesla Black). Selection persists in localStorage under key spTheme. |
| Last Scan | Badge showing time since the most recent successful check. Turns green when under 30 minutes. |
| ↺ Refresh | Re-fetches the client list from the portal (accounts + onboarding check in parallel) without running a full session scan. Use at session start. |
| ◈ Scan All | Fetches both the accounts list and full session history simultaneously, scores every client, and populates all five gauges. Shows the loading modal with live progress. |
| ⬡ API Key | Opens the credential modal to enter or update portal Host / Username / Password. Also accessible when already connected to update credentials. |
| ⊡ Zen | Hides the gauges, NOC row, and header to expand the main panels to full screen. Toggle with Ctrl+Shift+Z. Press again or click Exit Zen to restore. |
Five donut-chart gauges run across the top. Each is also a clickable filter — clicking a gauge filters the client list to only matching clients. Click again to clear the filter.
| Gauge | Formula | What triggers color change |
|---|---|---|
| Total Devices | Count of all allClients entries |
Always blue — informational only |
| Backup Health | okN / (okN + failN) — excludes ONBOARDING clients |
Green ≥90%, Yellow ≥70%, Red below 70% |
| Backed Up <24h | Count of non-onboarding clients where lastSuccessTime is within 24 hours. Falls back to nothing — only real success timestamps count. |
Green ≥90%, Yellow ≥70%, Red below 70% |
| Onboarding | Count with lastConcern === 'ONBOARDING' |
Always purple — no alarm threshold |
| Issues | Count with CONCERNED, CRITICAL, or WATCH status (not ONBOARDING) | Green = 0, Yellow = any issues but no CRITICAL, Red = any CRITICAL |
lastConcern, which is only set after a Scan All or an individual client
check. After a plain Refresh, those gauges will show "Run Scan All" until a full scan runs.
Total Devices and Backed Up <24h can populate from client list data alone.
The thin row between the gauges and the main panels is the NOC operations bar. It provides at-a-glance triage data and quick filter controls without requiring you to interact with the client list.
| Section | What it shows |
|---|---|
| ⚠ Risks | Top 4 highest-risk clients by computed risk score. Each chip is clickable — selecting one opens that client's detail panel. Populated after a Scan All. |
| Vaults | One chip per vault. Green = healthy, Yellow = degraded replication or high capacity, Red = offline. In live mode, vaults are derived from client-to-vault groupings. In demo mode, uses DEMO_VAULTS with Vault-03 forced offline. |
| Filter | 5 quick filter buttons — All, Critical (risk ≥60), Failures (risk ≥30), Drift (last success ≥24h ago), Healthy (risk <21, not onboarding). Overrides gauge filter. |
| Auto-scan | Starts a 5-minute countdown timer. When it fires, re-runs a full Scan All and resets the counter. Useful for unattended NOC screens. The countdown displays as M:SS. |
The left panel shows all clients as rows. Each row has a colored left border indicating status, a risk score badge, and optional drift badge. Clicking a row runs a live check and opens the detail view. The panel can be collapsed with the ‹ button to give the detail panel more space.
Xh DRIFT or X.Xd DRIFT when last success was ≥24h ago and client is not ONBOARDING.
The right panel loads when a client is selected. It runs a live api.check() call,
parses the session list for that client, and renders three tabs.
| Tab | Contents |
|---|---|
| ◈ Overview | Concern banner with summary and triage reason. Backup statistics accordion (last success, last failure, consecutive fails, 7-day rate, overall rate). Snapshot sparkbar (14 most recent backups, color-coded). Backup history chart (30-day bar + success-rate line). Action buttons. Ticket note (auto-generated, editable additional notes). Compare mini-grid. |
| ⬡ AI Insights | Rule-based analysis panel: predicted failure risk score, estimated hours to next failure, suggested action text, and tags (Consecutive Fails, High Fail Rate, Stale Backup, Healthy, No Activity). Fully deterministic — no LLM required. |
| ⊞ Compare | Mini cards for Shift+clicked clients or all scanned clients (via Load All Scanned button). Shows name, type, status badge, and 7-day fail estimate per card. |
The ⚠ Queue tab in the left panel surfaces every client with a non-zero risk score, sorted by risk score descending, then by most recent failure time. It functions as a prioritized work queue for the NOC session.
runCheck() for that client, identical to clicking in the client list.
Concern levels are derived by deriveConcern(data, fromBulk) from session history,
trend data, and timestamps. The function is called both in bulk scan mode and per-client check mode.
| Level | Trigger Conditions | Priority |
|---|---|---|
| OK | Outcome is OK, no failures, 7-day fail rate = 0% | Lowest |
| WATCH | 7-day fail rate >0% but <20%, consecutive fails = 0 | Low |
| CONCERNED | Consecutive fails ≥1, OR 7-day fail rate ≥20%; outcome is NOT OK | Medium |
| CRITICAL | Consecutive fails ≥3, OR 7-day fail rate ≥50%, OR vault offline | Highest |
| ONBOARDING | No session activity in the last 30 days; overrides all scoring | Separate |
| UNKNOWN | Bulk scan mode with no direct concern field and insufficient data to classify | N/A |
fromBulk=true), the derivation does not use snapshot
timestamps to detect ONBOARDING — those aren't available from the sessions list alone.
ONBOARDING is only reliably detected via the dedicated onboardingClients() call
(accounts without sessions in the last 30 days) or during a per-client check().
The risk engine runs in computeRisk(client) on every client in the list and returns
a score from 0–100. Scores drive the NOC risk chips, the failure queue sort order, and the
filter buttons. ONBOARDING clients are always forced to score 0.
| Score Range | Classification | List badge | Filter match |
|---|---|---|---|
| 0–29 | OK / Healthy | OK | Healthy filter |
| 30–59 | Watch / Concerned | WATCH | Failures filter |
| 60–100 | Critical | CRITICAL | Critical filter |
Drift is the elapsed time since the last successful backup. It is computed in
computeDrift(client) using lastSuccessTime. A client can be
technically "OK" (last job succeeded) while also drifting (if that last success was 40h ago
and it's normally nightly).
| Threshold | Level | Badge label example | Risk points added |
|---|---|---|---|
| < 24h | OK | 14h ago | 0 |
| 24–36h | WARN | 28h DRIFT | +8 |
| 36–72h | FAIL | 1.8d DRIFT | +15 |
| ≥ 72h | CRITICAL | 4.2d DRIFT | +25 |
| No data | CRITICAL | No backup | +25 |
Recommended morning triage sequence for a standard NOC shift:
Every client check auto-generates a structured ticket note via buildTicketNote().
The note is displayed in the detail panel and copied to clipboard with one click (or Ctrl+Enter).
Additional notes appended in the text area appear as a separate "Additional notes to consider" section.
Auto-scan runs a full queryAll() every 5 minutes, re-renders the client list,
updates all gauges, refreshes the NOC row and failure queue. In demo mode it refreshes
gauges and NOC data from cached demo clients instead of making portal calls.
Credentials are managed through a first-run modal. The console uses the
browser Credential Management API (PasswordCredential) where available,
falling back to sessionStorage. Credentials clear on tab close when using the fallback.
backup.securewebportal.net), Username, and Password for the ShadowProtect web services account. Click 🔐 Connect & Save.GET /auth request is sent to the local proxy with X-Target-Host, X-Auth-User, and X-Auth-Pass headers. The proxy validates against the portal. Success clears the modal and begins the startup scan.loadCredentials() (browser credential manager first, then sessionStorage). If found, attemptConnect() fires automatically — no modal shown.localhost. The proxy injects credentials as HTTP headers when forwarding to
the portal. Nothing leaves your local machine.
The console cannot contact the StorageCraft portal directly from a browser due to CORS
restrictions and cookie requirements. The local Node.js proxy (main.js) acts as
a middleware: it accepts requests from the browser, injects the session cookies captured
from the Basic Auth login, and forwards the request to the portal.
| Proxy Mode | When to use |
|---|---|
| localhost:3000 | Default. Use when opening console.html as a local file (file://) or from a local web server. The proxy must be running before connecting. |
| Direct (same-origin) | Use when the HTML file is served from the same host as the portal (rare). All fetch calls route directly without the proxy prefix. |
_proxyPort. This value
is read on every fetch — you can switch modes mid-session and the next request will use the new
setting. Default is 3000.
Demo mode loads a full set of mock clients with pre-configured session histories. It activates automatically when the console is opened without saved credentials, and is the default state for new installs. No proxy or portal connection is required.
| Client | Type | Vault | Status | Scenario |
|---|---|---|---|---|
| ACME Corp — DC01 | Windows Server | Vault-01 | CRITICAL | 3 consecutive fails |
| ACME Corp — SQL01 | SQL Server | Vault-01 | OK | All healthy |
| Globex Industries | File Server | Vault-01 | CONCERNED | Intermittent — 50% fail rate |
| Initech — Domain | Windows Server | Vault-02 | OK | All healthy |
| Contoso Web | Linux VM | Vault-02 | WATCH | 1 recent failure, low rate |
| Fabrikam Inc | Windows Server | Vault-02 | OK | All healthy |
| Northwind Traders | Windows Server | Vault-02 | ONBOARDING | No sessions — new client |
| Tailspin Toys | Business | Vault-03 | CRITICAL | Vault-03 OFFLINE |
| Alpine Ski House | Windows Server | Vault-03 | CRITICAL | Vault-03 OFFLINE |
| Coho Vineyard | File Server | Vault-01 | OK | All healthy |
| Litware Inc — SQL | SQL Server | Vault-02 | OK | All healthy |
| Adventure Works | Windows Server | Vault-01 | WATCH | Last backup 38h ago — drift |
All portal interactions are routed through the api object. Each method calls
portalFetch(path), which prepends the proxy base URL and adds the target host header.
A 30-second abort signal is attached to every request.
| Method | Portal endpoint(s) | Returns |
|---|---|---|
| api.clients() | /admin-console/admin-accounts-list |
{ok, clients[]} — normalised account objects |
| api.onboardingClients() | Both accounts-list and sessions-list in parallel | Accounts with no sessions in last 30 days — flagged ONBOARDING |
| api.queryAll() | Both accounts-list and sessions-list in parallel | Full merged client array with outcome, trend, last success/failure, checked timestamp |
| api.check(id) | /admin-console/admin-sessions-list |
Per-client check result with snapshots array, concern, trend data |
| api.statusReport() | /admin-console/status-report |
Raw HTML of the portal status report |
| api.eventLog() | /admin-console/manage/eventlog |
Parsed table rows from the event log page |
| api.auditLog() | /admin-console/manage/auditlog |
Parsed table rows from the audit log page |
parseAccounts() and parseSessions() try multiple column name variants
('Account Name', 'Name', 'Client', etc.) before falling back
to positional indexing. If the portal updates column names, check these parser functions first.
Every entry in allClients (also exposed as window.allClients) follows this shape:
| Theme key | Name | Accent color | Best for |
|---|---|---|---|
| ice | KrawTech Default | Blue #2d8ff5 | Standard NOC use — default |
| red | Incident Red | Red #ff2d55 | Incident response / high-alert situations |
| matrix | Matrix Green | Green #00ff41 | After-hours monitoring, SOC aesthetic |
| purple | Deep Space | Purple #a78bfa | Low-light environments |
| amber | Amber Industrial | Amber #f59e0b | Warm lighting environments |
| cobalt | Electric Cobalt | Cobalt #2563eb | Alternate blue palette |
| mono | Graphite Mono | Gray #e2e8f0 | Presentations, printing screenshots |
| tesla-black | Tesla Black | Blue-gray #4888cc | Dark environments, minimal distraction |
Theme selection is stored in localStorage under key spTheme and
restored on every page load. To reset to default: open DevTools console and run
localStorage.removeItem('spTheme'), then reload.
lastConcern) which is only populated by Scan All or individual
client checks. A plain Refresh only re-fetches the accounts list. Run ◈ Scan All
once per session to fully populate all gauges.
node main.js in the console directory.
Verify localhost:3000 is selected in the Proxy dropdown. Check that no firewall is
blocking port 3000.
/proxy?path=...admin-accounts-list response,
and check the actual column headers. Update the fallback arrays in parseAccounts()
to match.
parseSessions() with the actual session HTML. If all sessions return with
no isOk or isFail matches, every client will fall through to ONBOARDING.
document.getElementById('loadingModal').classList.add('hidden')
in the DevTools console.
localStorage under key spTechName. If localStorage
is cleared or the browser is in private/incognito mode, it will not persist. Enter and Save the
name at the start of each session in that case.
portalFetch() has a 30-second AbortSignal. Slow portal responses or
network latency to backup.securewebportal.net can trigger this. If consistent, check
portal uptime or increase the timeout value in the setTimeout call inside
portalFetch().
SPX CONSOLE KB · KrawTech MSP Tooling · console.html · v1.0