Knowledge Base
ROC Leaders Board
KrawTech Console Suite
ROC Leaders Board
Real-time engineer performance dashboard for the Network Operations Center. Surfaces ticket throughput, hours, CSAT, SLA compliance, and per-engineer breakdowns from ConnectWise Manage. Operates in DEMO mode until a reverse proxy is deployed.

01 What Is It

The ROC Leaders Board is a single-file HTML dashboard designed for wall display or NOC browser tabs. It aggregates operational metrics from ConnectWise Manage and presents them as animated KPI panels, bar charts, gauge arcs, and an SLA trend line — all filterable by engineer. No backend server is required for rendering; a lightweight reverse proxy is required only to satisfy CORS when the dashboard is hosted outside the CW Manage domain.

The file is self-contained: HTML, CSS, and JavaScript in one document. Chart rendering uses Chart.js 4.4.1 loaded from cdnjs. No other runtime dependencies exist.

Design System

Part of the KrawTech Console Suite. Shares color tokens (--bg: #020406), typography (Share Tech Mono / Barlow Condensed / Barlow), and interaction patterns with the SPX Console and other suite tools.

02 Integration Status

Data SourceStatusNotes
CW Manage — Tickets Proxy Required GET /service/tickets — wired, activates when proxy is live
CW Manage — Time Entries Proxy Required GET /time/entries — wired, activates when proxy is live
CW Manage — SLA % Proxy Required Derived from ticket slaStatus fields — no native SLA% endpoint
CSAT Scores Not Available No CW Manage REST endpoint. Requires SmileBack or external CSAT tool
Monthly Ticket Trend Proxy Required Requires one GET /service/tickets/count per month bucket
Demo Mode Live Now Randomised sample data — all values fictional

03 Architecture

Component Map

ComponentPurpose
CW objectRuntime config store: server, company, keys, board names, member list
DEMO flagBoolean — true = demo data, false = live API via cwFetch
cwFetch(path, params)Proxy-ready fetch wrapper. Real fetch() commented above mock fallback
loadDashboard()Orchestrates all CW API calls in parallel; populates data object; calls renderAll()
demoData()Generates randomised sample data set matching the live data schema
refreshAll()DEMO branch: re-runs demoData(). LIVE branch: calls loadDashboard()
startAutoRefresh(ms)Sets a non-stacking interval; default 30 000 ms
renderAll(d)Updates all DOM elements and rebuilds all charts from data object d
buildCharts(d, ef)Destroys and recreates all Chart.js canvases; ef = engineer filter
openInCW(id)Opens CW Manage deep-link for ticket id in new tab (live mode only)

Data Flow

DOMContentLoaded
  → loadDashboard()
      if DEMO  → demoData() → renderAll()
      if LIVE  → cwFetch() × 4 (parallel Promise.all)
                 → build data object
                 → renderAll()
  → startAutoRefresh(30000)
       every 30 s: same branch logic

Proxy Pattern

All CW Manage requests route through a local reverse proxy at /proxy. The proxy accepts a JSON body with { url, method, headers } and forwards the request server-side. This keeps API credentials out of the browser and avoids CORS rejections from CW Manage.

POST /proxy
Content-Type: application/json

{
  "url":     "https://na.myconnectwise.net/v4_6_release/apis/3.0/service/tickets?...",
  "method":  "GET",
  "headers": {
    "Authorization": "Basic base64(company+pubkey:privkey)",
    "clientId":      "your-client-id",
    "Content-Type":  "application/json"
  }
}

04 Dashboard Panels — Row 1

Hours Worked · WTD

Stacked bar chart showing billable (blue) and unbillable (orange) hours per engineer for the current week-to-date. Click bar segments to filter all charts to that engineer. Opens hours detail drawer.

Proxy Required Source: GET /time/entries

Utilized Hours · This Week

SVG gauge arc showing total hours logged this week against a 275-hour team target. Animated counter. Range 0–275.

Proxy Required Source: GET /time/entries

Avg CSAT · WTD

Large numeric display showing week-to-date average CSAT score out of 5.00. Color-coded: green ≥ 4.0, blue ≥ 3.0, orange < 3.0. Shows 0.00 when no CSAT source is wired.

No CW Endpoint Requires external CSAT tool

CSAT By Engineer · WTD

Bar chart showing individual engineer CSAT averages. N/A displayed for engineers with no surveys in the period. Target ≥ 4.0.

No CW Endpoint Requires external CSAT tool

05 Dashboard Panels — Row 2

Support Team · Closes WTD

Bar chart of tickets closed from the Support board (CW.board2) per engineer this week. Click bars to filter.

Proxy Required Source: GET /service/tickets — support board filter

Service Team · Closes WTD

Bar chart of tickets closed from the Service board (CW.board) per engineer this week.

Proxy Required Source: GET /service/tickets — service board filter

Billable Hours · WTD

SVG gauge arc for total billable hours against a 160-hour weekly target. Animated counter.

Proxy Required Source: GET /time/entries (billableOption = "Billable")

Tickets Closed · This Week

SVG gauge arc for total tickets closed across both boards this week. Target 350. Green gradient fill.

Proxy Required Source: both board close counts combined

Tickets Currently Open

Bar chart of currently open tickets per engineer. Bars turn red when an engineer exceeds 25 open tickets. Threshold line drawn at 20. Clicking opens the "All Open Tickets" drawer.

Proxy Required Source: GET /service/tickets (status != "Closed")

06 Dashboard Panels — Row 3

P1 Tickets Closed · WTD CRITICAL

Large red numeric display. Panel pulses with a red glow animation when any P1 tickets are currently open. Click to open P1 drawer.

Proxy Required Source: closed WTD filtered priority = Priority 1

P2 Tickets Closed · WTD

Large orange numeric display. Click to open P2 drawer.

Proxy Required Source: closed WTD filtered priority = Priority 2

Tickets Worked · Monthly Trend

Bar chart showing ticket volume per calendar month for the last 5 months. Each bar requires a separate count query per month range.

Proxy Required Source: GET /service/tickets/count × N months. Not yet wired in live mode — returns empty in current build. Add per-month count calls to the Promise.all in loadDashboard().

Tickets Touched · WTD by Engineer (Full-Width)

Horizontal bar chart spanning all 4 columns. Shows tickets touched (closed support + closed service) per engineer. Top performer highlighted green. Target line at 40 tickets/week. Click bars or the panel to open the touched breakdown drawer.

Proxy Required Derived from suppByMember + svcByMember sums

07 Dashboard Panels — Row 4

Open Monthly Checkups

Large teal numeric display showing the count of open monthly checkup tickets. Requires a dedicated board/type filter to identify checkup tickets. Currently uses demo value.

Proxy Required Needs custom conditions filter for checkup ticket type

Lifetime P1 SLA% · Day Shift

Three animated SLA progress bars for P1, P2, and P3 showing percentage of tickets responded to within 15 minutes during day shift (Mon–Fri 08:00–18:00). Derived from slaStatus fields — no native endpoint.

Proxy Required Derived from ticket.slaStatus.respondBreached fields

Utilized Hours · Prev Week

SVG gauge arc for prior-week total hours. Requires adding a second time/entries fetch for the prior week date range to the loadDashboard() Promise.all.

Proxy Required Activates when prior-week time entry fetch is added

Avg CSAT · Previous Week

Large green numeric for the prior week's average CSAT. Same CSAT limitation applies — no CW Manage native endpoint.

No CW Endpoint Requires external CSAT source

08 Dashboard Panels — Row 5

Day Shift Alert Weekly % SLA Met <15 Min (Full-Width)

Line chart plotting weekly SLA-met percentage over a rolling 28-week window (labeled W1–W28). Target is 100%. The chart displays the per-week percentage of P1 tickets responded to within 15 minutes during day-shift hours. In demo mode, weeks 1–25 show 100% with a realistic dip in weeks 26–28.

In live mode this requires historical weekly aggregation — not a single CW REST call. The recommended approach is to cache weekly SLA summaries in the proxy layer and serve them as a pre-aggregated array.

Proxy Required Historical weekly aggregation — proxy-layer caching recommended

09 Topbar KPIs

Five sticky KPI counters render across the top bar. Each is clickable and opens the relevant detail drawer.

KPIValue SourceColorClick Action
Open TicketsCount of open tickets across both boardsTeal (accent)Opens "All Open" drawer
P1 OpenCount of open P1 ticketsRed (crit)Opens P1 drawer
P2 OpenCount of open P2 ticketsOrange (high)Opens P2 drawer
CSAT WTDWeek-to-date average CSATGreen (low)Opens CSAT drawer
SLA% <15 MinCurrent P1 SLA percentageBlueOpens SLA drawer

The DEMO / LIVE badge and week range label also appear in the topbar. The live dot pulses yellow in demo mode; switching to live turns it green.

10 Ticker & Engineer Filter Bar

Alert Ticker

A scrolling ticker below the topbar surfaces real-time alerts. Items include open P1/P2 counts, weekly closed count, SLA percentages, CSAT average, hours logged, and the first 5 open ticket titles with assignees. Hover to pause scrolling. Click any item to show it in the toast notification.

Engineer Filter Bar

A pill bar below the ticker lists all configured engineers. Clicking an engineer pill filters all bar charts to highlight that engineer's bars (others fade to 15% opacity). Clicking the active engineer again resets to "All." The filter also works by clicking a bar segment directly in any chart or by clicking an engineer name in any detail drawer row.

The engineer list is driven by CW.members — set this in the Config modal or pre-populate the CW state object.

11 Detail Drawer

A slide-in panel from the right edge exposes deep-dive data for each metric category. Drawer types:

TypeTriggerContents
open / p1 / p2 / checkupTopbar KPIs, open tickets panel, P1/P2 panels4-cell stat grid + sortable ticket cards with priority badge, assignee, age. Click card fires openInCW() for deep-link.
hoursHours panels, utilized gauge, billable gauge4-cell stat grid (total hrs, billable hrs, prev wk hrs, billable rate) + per-engineer bar breakdown with billable sub-bar. Click engineer to filter charts.
csatCSAT panelsWTD avg + prev week avg + per-engineer bar. N/A for engineers with no surveys. Click engineer to filter.
closesSupport/Service closes panels, closed gaugeTotal closed + P1/P2 closed + checkups open + per-engineer support/service breakdown bars.
slaSLA bars panelP1/P2/P3/P4 percentage cells + weekly SLA trend mini-chart + target notes.
touchedTickets Touched full-width panelPer-engineer bar list with star for top performer. Click to filter charts. Target: 40+ tickets/week.

Close with the ✕ button, clicking the overlay, or pressing Escape. Press / to jump directly to the open tickets drawer.

12 Config Modal Fields

Open via the ⚙ Config button in the topbar. Settings are persisted to localStorage under the key roc-cw-cfg. On next page load, stored config is restored automatically and DEMO mode is disabled if server + keys are present.

FieldKeyRequiredDescription
ServerCW.serverYesCW Manage hostname, e.g. na.myconnectwise.net
Company IDCW.companyYesYour CW Manage company identifier (used in Basic auth token)
Client IDCW.clientIdYesUUID registered at developer.connectwise.com — required since CW 2019.4
Public KeyCW.publicKeyYesAPI key public component — generate at System > Members > [you] > API Keys
Private KeyCW.privateKeyYesAPI key private component (stored encrypted in localStorage)
Service BoardCW.boardNoBoard name for the service board. Default: "ROC Service"
Support BoardCW.board2NoBoard name for the support board. Default: "ROC Support"
MembersCW.membersNoComma-separated list of CW member identifiers to include. Leave blank to use default demo names.
Security Note

API keys are stored in browser localStorage. For wall-display kiosks, use a dedicated read-only CW API account with minimum permissions (Service — Read, Time — Read). Do not use an admin account.

13 Proxy Activation Checklist

The dashboard uses a proxy-ready wiring pattern. The real fetch() call is commented out above the mock fallback in the cwFetch() function. Complete these steps to go live:

  • Deploy a reverse proxy — Node/Express, Nginx, or a simple serverless function that accepts POST /proxy with body {url, method, headers} and forwards the request to CW Manage.
  • Register a Client ID — Go to developer.connectwise.com, register your application, and obtain a Client ID UUID. Required on every CW REST request since 2019.4.
  • Generate API Keys — In CW Manage: System > Members > [member] > API Keys > Add. Copy public and private keys.
  • Open cwFetch() in the dashboard source — Uncomment the real fetch() block (lines ~20 lines inside cwFetch). Remove or comment the mock fallback line below it.
  • Verify board names match exactly — Board names in the Config modal must match the exact board names in CW Manage (case-sensitive).
  • Verify member identifiers match CW logins — The Members field uses CW member identifiers (login names), not display names.
  • Save config in the dashboard — Fill all fields in ⚙ Config and click "Connect to CW →". The DEMO badge switches to LIVE and a full data load fires.
  • Test with browser DevTools open — Watch the Network tab for /proxy calls. Check console for any "CW API HTTP" errors from cwFetch.
  • Confirm priority name strings — CW Manage priority names vary by account (e.g. "Priority 1 - Critical" vs "P1 - Critical"). Update the priority name comparisons in loadDashboard() to match your CW instance.

14 Exact API Endpoints Used

All endpoints are from the ConnectWise Manage REST API v3.0. Base URL: https://{server}/v4_6_release/apis/3.0/

Documentation: developer.connectwise.com

GET/service/tickets
Fetch open or closed tickets. Used for: open ticket count, P1/P2 counts, closed WTD totals, per-engineer open/closed breakdowns, ticket list for drawer detail, SLA status fields.
Key params: conditions, fields, pageSize (max 1000). Paginate via page param if ticket count exceeds 1000.
GET/service/tickets/count
Returns an integer count matching the conditions filter. Used for monthly ticket trend buckets — one call per calendar month. No body returned, just an integer.
Not yet wired in loadDashboard(). Add per-month calls to the Promise.all array for the monthly trend chart.
GET/time/entries
Fetch time entry records. Used for: total hours WTD per engineer, billable hours WTD per engineer (billableOption = "Billable"), prior-week hours (separate call).
Key fields: actualHours, billableOption, member/identifier, timeStart. Filter by timeStart>=[weekStartISO].
GET/system/members
Fetch member records to resolve display names from identifiers. Used optionally to validate the members list against CW Manage accounts.
Not called in the current build — members are configured manually. Wire if auto-discovery of team members is needed.
DEEP/v4_6_release/services/system_io/router/openrecord.rails?locale=en_US&rectype=ticket&recid={id}
CW Manage deep-link URL to open a ticket record in the browser. Used by openInCW() when a ticket card is clicked in the detail drawer (live mode only).
Documented at: developer.connectwise.com > Deep Links > Service Tickets

15 Documented Vendor API Limitations

Limitation 1 — No CSAT Endpoint

ConnectWise Manage has no public REST endpoint for CSAT scores. The Survey module data is not exposed via the v3.0 REST API. CSAT values on this dashboard require an external source such as SmileBack (which has its own API), SurveyMonkey, or a custom web survey tool. Until wired, CSAT displays 0.00 in live mode. The csatByMember and csatAvg fields in the data object are reserved for external injection.

Limitation 2 — No Pre-Computed SLA% Endpoint

CW Manage exposes slaStatus fields on individual ticket records (including respondByDateTime and respondBreached), but provides no endpoint that returns a pre-aggregated SLA percentage. The dashboard derives SLA % client-side by counting tickets where respondBreached = false divided by total eligible tickets. For high-volume boards this is compute-intensive; the proxy layer should cache weekly SLA summaries.

Limitation 3 — No Bulk Histogram / Trend Endpoint

There is no endpoint that returns ticket counts grouped by time period. Monthly trend data requires one separate GET /service/tickets/count call per month bucket. For a 12-month trend, this means 12 API calls. Implement caching or a proxy-side aggregation job to reduce API load.

Limitation 4 — Tickets Touched vs Tickets Worked

"Tickets Touched" is not a native CW Manage metric. The dashboard approximates it as the sum of closed support + closed service tickets per engineer (WTD). A more accurate "touched" count would require querying ticket activity logs via GET /service/tickets/{id}/activities for every open and closed ticket — impractical at scale without proxy-layer caching. The current approximation is suitable for NOC leaderboard purposes.

Limitation 5 — CORS

CW Manage does not permit direct browser fetch requests from third-party origins. A reverse proxy is required in all deployment scenarios unless the dashboard is hosted on the exact CW Manage domain.

16 Troubleshooting

SymptomLikely CauseFix
All values show demo data after saving config cwFetch() mock fallback still active Uncomment the real fetch() block in cwFetch() and remove the mock fallback line
Network request to /proxy returns 404 Proxy not deployed or wrong path Deploy proxy server and confirm it listens at the same origin on /proxy
CW API returns 401 Unauthorized Wrong auth token format or expired keys Verify btoa(company+"+"+publicKey+":"+privateKey) — company must match CW Manage exactly
CW API returns 403 Forbidden Missing clientId header or invalid Client ID Register application at developer.connectwise.com and add the UUID to Config > Client ID
Tickets return but engineer names don't match bars CW member identifiers differ from names in CW.members CW.members must contain exact member identifier strings (login names, not display names)
SLA % always shows 100% in live mode Tickets missing slaStatus fields Add slaStatus to the fields param in cwFetch calls. Verify SLA plans are configured in CW Manage.
P1/P2 counts are 0 in live mode Priority name string mismatch Check actual priority name in CW Manage (e.g. "Priority 1 - Critical"). Update comparisons in loadDashboard().
CSAT shows 0.00 in live mode Expected — no CW native CSAT endpoint Wire an external CSAT API. Inject values into csatByMember and csatAvg in the loadDashboard() data object.
Charts flicker or duplicate on refresh Chart.js instances not destroyed before recreate buildCharts() calls mc() which destroys and recreates each canvas. If this persists, check that CHARTS[id].destroy() completes before new Chart().
Config not saved between page reloads localStorage blocked (private/incognito mode) Use a standard browser session or pre-populate the CW state object in the source.

17 Demo vs Live Mode

BehaviorDemo Mode (DEMO = true)Live Mode (DEMO = false)
Data sourcedemoData() — randomised synthetic dataloadDashboard() — CW Manage REST API via cwFetch()
Auto-refreshRe-runs demoData() every 30 s (random drift)Re-fires loadDashboard() every 30 s
Mode badgeYellow DEMO pill in topbarGreen LIVE pill in topbar
Ticket clickToast notification (IDs are fictional)Opens CW Manage deep-link in new tab
Config saveEntering valid credentials triggers DEMO = false and calls loadDashboard()Already live
On API errorN/AFalls back to DEMO = true and shows error toast
CSATRandomised 0–5 valuesAlways 0.00 until external CSAT source wired
Activation Shortcut

To bypass demo mode without touching source code: open ⚙ Config, fill all required fields (Server, Company ID, Client ID, Public Key, Private Key), and click "Connect to CW →". The dashboard immediately calls loadDashboard() and switches to live data.