KRAWTECH CONSOLE SUITE // KNOWLEDGE BASE
ENGINEER TRIAGE CONSOLE
The L1 Engineer Triage Console is the human-facing intake layer of the KrawTech AI Automation Platform. It surfaces Copilot-enriched tickets from ConnectWise Manage, presents AI-generated remediation suggestions, and lets engineers approve or dispatch actions into the L4 execution layer — without requiring direct access to any vendor tool.
File
dashboard-IVR-triage-console.html
Layer
L1 // User Interaction
Proxy Port
localhost:3001
Refresh
30 s (startAutoRefresh)
Status
Demo — proxy not live
Overview
What Is This Tool

The L1 Triage Console is a sub-page of the KrawTech Automation Platform. It does not include standalone authentication or its own navigation — both are injected by the parent platform's nav.js. The console exists at the top of the dispatch pipeline: a human engineer sees a Copilot-surfaced queue of tickets, reads AI-generated context, and with a single button press approves or cancels an automated action.

No engineer needs to log into ConnectWise Manage, NinjaRMM, or SentinelOne directly. All vendor API calls are proxied through a local Node.js server that holds credentials. The browser communicates only with that proxy.

Current state: All UI is fully built. Buttons, tabs, the dispatch bar, and stat cards render correctly in demo mode. All API calls are written, fully commented, and ready to activate — they are intentionally behind a proxy-offline guard that falls through to realistic demo data until the proxy is running.

Integration Status
Vendor Integration Map
Integration Status
DEMO MODE ACTIVE
ConnectWise Manage — ticket queue, counts, notes, status updates
Activates when proxy is live
NinjaRMM (NinjaOne) — device status, WoL, script dispatch, reboot approval
Activates when proxy is live
SentinelOne — endpoint isolation, threat context for IR tickets
Activates when proxy is live
ConnectWise Automate — remote command dispatch (alternative to NinjaRMM)
Activates when proxy is live
Copilot / AI Suggestions — pre-generated per ticket, injected into copilot-bar
Static in demo · LLM-generated in live
CSAT — client satisfaction scores
Not available — no CW Manage endpoint
Architecture
System Architecture

The console follows a browser → local proxy → vendor API pattern used across the entire KrawTech suite. The browser never holds vendor credentials. All secrets live in the proxy's environment variables.

┌──────────────────────────────────────────────────────────┐ │ L1 Triage Console (browser) │ │ loadDashboard() → apiFetch('/cw/service/tickets') │ │ Action buttons → apiFetch('/sentinelone/...') │ │ Dispatch bar → apiFetch('/cw/service/tickets/{id}/notes') │ └──────────────────────┬───────────────────────────────────┘ │ HTTP (localhost:3001) ▼ ┌──────────────────────────────────────────────────────────┐ │ Node.js Proxy (server.js / proxy.js) │ │ Holds: CW API key, NinjaRMM OAuth2 token │ │ SentinelOne API token, Automate credentials │ │ Routes: /cw/* → CW Manage REST API │ │ /ninja/* → NinjaRMM v2 API │ │ /sentinelone/* → S1 v2.1 API │ │ /copilot/* → LLM orchestration layer │ └──────────────────────┬───────────────────────────────────┘ │ HTTPS ┌──────────────┼──────────────┬──────────────┐ ▼ ▼ ▼ ▼ CW Manage NinjaRMM SentinelOne Automate REST v2024_1 REST v2 REST v2.1 REST v1
Sub-page pattern This console is a sub-page. It does not contain its own <header> or top navigation. The parent platform injects nav.js which renders the top bar and sidebar. This file only owns everything inside .wrap — the topbar identity badge, pipeline strip, ticket queue, and dispatch bar.
Panels & UI
Stat Cards

Four stat cards show real-time queue health. In demo mode they display hardcoded values with a random drift applied every 30 seconds. When the proxy is live, they are populated by loadDashboard() on initial load and re-populated by refreshAll() every 30 seconds.

Stat Card Data Sources
PROXY-READY
CardValueReal Data Source
Critical / High Count of P1+P2 tickets GET /cw/service/tickets?conditions=priority/name="Critical" OR priority/name="High"
Awaiting Triage Open tickets in "New" or "Needs Triage" status GET /cw/service/tickets?conditions=status/name="New"
In-Flight Tickets in "In Progress" or "Dispatched" GET /cw/service/tickets?conditions=status/name="In Progress"
Resolved Today Tickets closed today GET /cw/service/tickets?conditions=dateClosedSince=[today]&status/name="Closed"
Loading state Stat cards show a shimmer animation while data loads (CSS class .loading on the card). This is stripped when applyStats() is called successfully.
Panels & UI
Pipeline Indicator

The pipeline strip shows the four layers of the KrawTech automation stack. L1 is always active (highlighted). This is a static visual element — it reflects architecture, not real-time runbook state. When runbook execution status is added in a future version, the active step will advance dynamically based on a runbook progress endpoint.

StepLayerDescriptionReal-time?
1L1 // TriageHuman review and approvalAlways active
2Intent DispatchCopilot → L2 intent routingStatic — future: runbook status API
3Task RoutingL2 → L3 task assignmentStatic — future: runbook status API
4Execution DispatchL3 → L4 vendor executionStatic — future: runbook status API
Panels & UI
Ticket Queue

Three tab views — Triage, In-Flight, and Resolved — each rendered by renderTickets(listId, tickets). Tickets are generated from the API response shape or, in demo mode, from the DEMO object which mirrors that shape exactly.

Each ticket card contains: severity badge, ticket ID, age, title, client name, Copilot suggestion bar, and 1–3 action buttons. Severity classes drive the left accent stripe color and badge color.

Severity Classes
ClassColorCW Priority Mapping
.sev-criticalRedPriority: Critical (P1)
.sev-highOrangePriority: High (P2)
.sev-mediumYellowPriority: Medium (P3)
.sev-lowCyanPriority: Low (P4)
.sev-infoBlueInformational / monitoring
.sev-doneGreenResolved / closed
Ticket source: CW Manage REST API GET /v4_6_release/apis/3.0/service/tickets — filtered by status and priority. The mapCWTickets() function (in proxy or browser layer) transforms the CW response shape into the internal ticket format consumed by renderTickets().
Panels & UI
Copilot Suggestion Bar

Each ticket contains a cyan-bordered .copilot-bar showing an AI-generated triage suggestion. In demo mode this text is hardcoded in the DEMO object. In live mode, the Copilot suggestion is either:

Option A — Pre-generated: stored as a ticket note in CW Manage (tagged with a Copilot prefix) and retrieved with GET /service/tickets/{id}/notes.

Option B — On-demand: the proxy calls an LLM (Claude via Anthropic API) with ticket context and returns a suggestion inline at ticket load time.

The current build uses Option A in demo mode (hardcoded) and routes to the proxy's /copilot/suggest endpoint in live mode.

Panels & UI
Copilot Dispatch Bar

The bottom dispatch bar accepts free-text commands. On Enter or clicking DISPATCH, sendDispatch(text) fires. It appends the command as an internal note on the most recently active ticket via POST /cw/service/tickets/{id}/notes.

In demo mode a toast confirms the command was "queued". In live mode the proxy parses the intent, selects the correct runbook, and forwards it to the appropriate L4 tool (Automate, NinjaRMM, or direct API call).

Dispatch Flow
DEMO — activates when proxy live
User types: "isolate ACME-WS-04 and open IR case" → POST /copilot/dispatch { command: "..." } → Proxy parses intent → selects runbook: "ir-ransomware" → POST /cw/service/tickets/{id}/notes { text: "COPILOT_DISPATCH:ir-ransomware" } → POST /sentinelone/agents/actions/disconnect → POST /cw/service/tickets (create IR sub-ticket) → Returns: { dispatched: true, runbook: "ir-ransomware", ticketId: "CW-44822" }
Panels & UI
Action Buttons
Button → API Mapping
PROXY-READY
ButtonHandlerVendor API (via proxy)
🔒 Confirm Isolate isolateEndpoint() POST /sentinelone/agents/actions/disconnect
SentinelOne v2.1 — body: { filter: { ids: [agentId] } }
▶ Dispatch IR Runbook dispatchRunbook() POST /cw/service/tickets/{id}/notes
CW Manage — appends COPILOT_DISPATCH:ir-ransomware note
▶ WoL + Restart RPC wolRestart() POST /ninja/device/{id}/script
NinjaRMM v2 — runs WoL + Restart-Service RpcSs PowerShell
▶ Push Enrollment dispatchRunbook() POST /cw/service/tickets/{id}/notes
Runbook: mfa-bulk-enroll → L4 Graph API script
⚙ Fix CA Policy dispatchRunbook() POST /cw/service/tickets/{id}/notes
Runbook: ca-policy-fix → L4 Graph API script
✓ Approve Reboots approveReboots() POST /ninja/device/{id}/reboot
NinjaRMM v2 — per device in patch job
⛔ Halt Runbook haltRunbook() POST /runbook/halt { ticketId }
Custom proxy endpoint — cancels active Automate/Ninja job
▶ Purge + Rerun Backup dispatchRunbook() POST /cw/service/tickets/{id}/notes
Runbook: disk-purge-backup → Automate script
Reopen reopenTicket() PATCH /cw/service/tickets/{id}
Body: [{ op:"replace", path:"/status/name", value:"New" }]
View Evidence / Report viewEvidence() GET /cw/service/tickets/{id}/notes
Returns internal notes with evidence attachments
Ping Devices pingDevices() GET /ninja/device/{id}/status
NinjaRMM v2 — agent online/offline state
Defer Patch Window deferPatch() PATCH /cw/service/tickets/{id}
Updates sub-type to Deferred in CW Manage
API & Endpoints
Exact API Endpoints
ConnectWise Manage
Demo → Live

Base URL: https://{cw-host}/v4_6_release/apis/3.0
Auth: Basic — company+publicKey:privateKey (Base64) or OAuth2 bearer token
Docs: developer.connectwise.com/Products/Manage/REST

MethodEndpointUsed For
GET/service/ticketsLoad triage queue, in-flight, and stat counts. Params: conditions, pageSize, orderBy.
GET/service/tickets?conditions=dateClosedSince=[today]Resolved today count
GET/service/tickets/{id}/notesView evidence, retrieve Copilot suggestion notes
POST/service/tickets/{id}/notesDispatch runbook intent, cancel jobs, Copilot command
PATCH/service/tickets/{id}Reopen ticket, defer patch window, escalate, update status
NinjaRMM (NinjaOne)
Demo → Live

Base URL: https://app.ninjarmm.com/v2
Auth: OAuth2 client_credentials — client_id + client_secret
Docs: app.ninjarmm.com/apidocs

MethodEndpointUsed For
GET/devicesList managed devices, agent online status
GET/device/{id}/statusPing device — returns agent connectivity state
POST/device/{id}/scriptDispatch PowerShell (WoL, Restart-Service RpcSs)
POST/device/{id}/rebootApprove pending reboots in patch deployment
SentinelOne
Demo → Live

Base URL: https://{tenant}.sentinelone.net/web/api/v2.1
Auth: Authorization: ApiToken {token} header
Docs: usea1.sentinelone.net/api-doc

MethodEndpointUsed For
POST/agents/actions/disconnectNetwork isolation — removes endpoint from network. Body: { filter: { ids: [agentId] } }
GET/threatsRetrieve active threat context for IR ticket Copilot bar
ConnectWise Automate
Demo → Live

Base URL: https://{automate-host}/cwa/api/v1
Auth: Basic or token
Docs: docs.connectwise.com/ConnectWise_Automate

MethodEndpointUsed For
POST/computers/{id}/commandsDispatch remote script (backup purge, Mimecast, etc.)
GET/computers/{id}Computer health, disk status for Copilot context
API & Endpoints
Documented Vendor Limitations
CSAT — Not Available in ConnectWise Manage ConnectWise Manage has no CSAT or client satisfaction API endpoint in its public REST API (v2024_1). Client satisfaction scores must be sourced from a third-party integration: SmileBack (smileback.com) or Nicereply. Both integrate with CW Manage via their own APIs and webhooks. There is no workaround using native CW endpoints.
Runbook Dispatch — No Native CW Manage Endpoint CW Manage has no "dispatch runbook" endpoint. The current pattern appends an internal note tagged COPILOT_DISPATCH:{runbookId}. The proxy watches for these notes via webhook or polling and triggers the actual runbook execution in Automate or NinjaRMM. This is a deliberate architectural choice — CW Manage is the record of intent, not the executor.
Ticket Note as Dispatch Primitive POST /service/tickets/{id}/notes is the only CW Manage mechanism for injecting Copilot intent. The note body uses a structured prefix (COPILOT_DISPATCH:) that the proxy parses. This is by design — it ensures all dispatched actions are logged in the ticket audit trail.
SentinelOne — Agent IDs Must Be Known POST /agents/actions/disconnect requires the S1 internal agent ID, not the hostname. The proxy must maintain a hostname → agent ID lookup table populated from GET /agents on startup or refreshed on each isolation request. Ticket metadata (client + device name) is used to resolve the correct agent ID.
Configuration
Proxy Configuration

The proxy URL is set at the top of the dashboard JS in the PROXY object. Change base to your deployed proxy address. The proxy holds all vendor credentials in environment variables — never in the browser.

// In dashboard-IVR-triage-console.html const PROXY = { base: 'http://localhost:3001', // ← change to deployed URL timeout: 8000, // ms before fetch aborts };
Required Proxy Environment Variables
VariableValue
CW_HOSTYour CW Manage host (e.g. na.myconnectwise.net)
CW_COMPANYCW company ID
CW_PUBLIC_KEYCW API member public key
CW_PRIVATE_KEYCW API member private key
NINJA_CLIENT_IDNinjaRMM OAuth2 client ID
NINJA_CLIENT_SECRETNinjaRMM OAuth2 client secret
S1_HOSTSentinelOne management URL
S1_API_TOKENSentinelOne API token
AUTOMATE_HOSTCW Automate host
AUTOMATE_TOKENAutomate API token
Configuration
Proxy Activation Checklist
Steps to Activate Live Mode
01
Deploy the proxy Start server.js on localhost:3001 (or your deployed host). Confirm GET http://localhost:3001/health returns { ok: true }.
02
Set environment variables Populate all variables in the table above in the proxy's .env file. Run npm run verify-creds to confirm each vendor connection succeeds.
03
Update PROXY.base in dashboard Change PROXY.base in the dashboard JS from localhost:3001 to your deployed proxy URL if not running locally.
04
Uncomment real fetch() calls In the dashboard JS, each apiFetch() call block has the real endpoint commented above the mock throw. Remove the await new Promise(...); throw lines and uncomment the real fetch(url, {...}) blocks.
05
Verify CW Manage ticket format The proxy's mapCWTickets() function must transform CW response fields (priority.name, status.name, etc.) into the dashboard's internal ticket shape. Confirm field names match your CW Manage API version.
06
Build S1 agent ID lookup On proxy startup, call GET /agents (SentinelOne) and cache a hostname → agentId map. This is needed for the Confirm Isolate button to resolve the correct agent.
07
Confirm demo banner is gone When the proxy responds to loadDashboard() successfully, setStatus('live') is called and the yellow demo banner automatically hides. If it stays visible, the proxy is still returning an error.
Configuration
Troubleshooting
Common Issues
SymptomCauseFix
Demo banner stays visible after proxy is running CORS not configured on proxy, or proxy URL mismatch Add Access-Control-Allow-Origin: * header to proxy. Verify PROXY.base matches proxy address exactly.
Stat cards stay in loading shimmer applyStats() not called — proxy returned error Check browser console for fetch errors. Confirm /cw/service/tickets route is implemented in proxy.
Buttons fire but toast says "[DEMO]" Expected — proxy is offline. Real is the correct result in demo. Activate proxy and uncomment real fetch() blocks as described in step 04 of activation checklist.
Isolate button fails with 404 S1 agent ID not resolved Build hostname → agentId lookup map on proxy startup. Verify SentinelOne tenant URL and API token are correct.
Ticket list is empty after live data loads mapCWTickets() field mapping mismatch Log the raw CW API response. Verify priority.name, status.name, and id field paths match your CW version. CW Manage field names vary by version.
Clock shows "--:--" JavaScript not loaded Check for JS errors in console. Confirm no Content Security Policy blocks inline scripts.
Tab switching stops working event.target.closest('.tab') fails if event context is lost Pass the button element explicitly: change onclick to onclick="switchTab('triage', this)" and update the function signature.
KRAWTECH // L1 ENGINEER TRIAGE CONSOLE // KB v1.0 // 2026