ConnectWise PSA skills and tools under ops/connectwise (hub-sanitized 2026-11-01: client intel, internal workflow/credential/member/company ids, archived-page apiToken/sessionId stripped to placeholders)
This commit is contained in:
commit
9c96092034
36 files changed
+310875
No files matched your search
@@ -0,0 +1,107 @@
|
||||
# ConnectWise SLA Evaluation Recipe
|
||||
|
||||
Reference: `sla_evaluation.md` — How to evaluate SLA state and score tickets for prioritization using the CW v3 API.
|
||||
|
||||
## Fields to Read
|
||||
|
||||
| Field | Type | Meaning |
|
||||
|-------|------|---------|
|
||||
| `isInSla` | bool | False = breached |
|
||||
| `slaStatus` | string | e.g. "Respond by Tue 04/21 3:37 PM UTC-07" |
|
||||
| `respondMinutes` | int | Minutes in "respond" phase |
|
||||
| `resPlanMinutes` | int | Minutes in "resplan" phase |
|
||||
| `resolveMinutes` | int | Minutes in "resolve" phase |
|
||||
| `escalationLevel` | int | Tier (higher = worse) |
|
||||
| `priority.level` | string | Critical / High / Medium / Low |
|
||||
| `severity` | string | Critical / High / Medium / Low |
|
||||
| `impact` | string | Critical / High / Medium / Low |
|
||||
| `_info.dateEntered` | ISO string | Canonical creation time |
|
||||
|
||||
## Composite Scoring Formula
|
||||
|
||||
```python
|
||||
score = 0
|
||||
|
||||
if sla_overdue:
|
||||
score += 1000
|
||||
elif sla_state == 'Due <24h':
|
||||
score += 500
|
||||
elif sla_state == 'Due <72h':
|
||||
score += 200
|
||||
|
||||
score += (escalation or 0) * 50
|
||||
|
||||
prio_map = {
|
||||
'Priority 1 - Critical': 400,
|
||||
'Priority 2 - High': 200,
|
||||
'Priority 3 - Medium': 50,
|
||||
'Priority 4 - Low': 10
|
||||
}
|
||||
score += prio_map.get(priority_name, 0)
|
||||
|
||||
if severity in ('Critical', 'High'):
|
||||
score += 100
|
||||
if impact in ('Critical', 'High'):
|
||||
score += 100
|
||||
|
||||
if date_entered:
|
||||
age_days = (now - date_entered).total_seconds() / 86400
|
||||
score += min(int(age_days) * 5, 300)
|
||||
```
|
||||
|
||||
## SLA State Parsing
|
||||
|
||||
```python
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
def parse_sla(sla_status_text):
|
||||
if not sla_status_text or sla_status_text == 'Resolved':
|
||||
return sla_status_text or 'No SLA'
|
||||
m = re.search(r'(Respond|Resolve)\s+by\s+(.+)', sla_status_text, re.IGNORECASE)
|
||||
if m:
|
||||
due_text = m.group(2).strip()
|
||||
try:
|
||||
dt = datetime.strptime(due_text, '%a %m/%d %I:%M %p %Z%z')
|
||||
if dt < now:
|
||||
return 'Overdue'
|
||||
hours = (dt - now).total_seconds() / 3600
|
||||
if hours < 24:
|
||||
return 'Due <24h'
|
||||
elif hours < 72:
|
||||
return 'Due <72h'
|
||||
return 'On Track'
|
||||
except ValueError:
|
||||
return 'Due (unparsed)'
|
||||
return sla_status_text
|
||||
```
|
||||
|
||||
## RMM Alert Categories (for filtering / triage)
|
||||
|
||||
Keyword-based categorization of common Sophos/RMM-generated summaries:
|
||||
|
||||
| Category | Keywords |
|
||||
|----------|----------|
|
||||
| Firewall Sophos Offline | `firewall` + `sophos` |
|
||||
| Firewall Heartbeat | `not sending heartbeat` |
|
||||
| Gateway Down | `gateway` + `down` |
|
||||
| Malware Detection | `malicious`, `c2/` |
|
||||
| Device Offline | `has not contacted` |
|
||||
| Disk/Storage | `disk`, `drive`, `storage`, `c:` |
|
||||
| CPU Alert | `cpu` |
|
||||
| Memory Alert | `memory`, `ram` |
|
||||
| Reboot Required | `reboot`, `restart` |
|
||||
| AV Alert | `antivirus`, `virus` |
|
||||
|
||||
## Bulk Close Recipe
|
||||
|
||||
Update stale "new from RMM" tickets to "Self-Healed":
|
||||
|
||||
```python
|
||||
# 1. Fetch all open tickets
|
||||
# 2. Filter: status == 'new from RMM' AND _info.dateEntered > 7 days ago
|
||||
# 3. PATCH each: [{"op": "replace", "path": "/status/id", "value": 425}]
|
||||
# Status ID 425 = "Self-Healed" on board 6 (Managed Service Alerts)
|
||||
```
|
||||
|
||||
**Never auto-close malware detections without human review.**
|
||||
@@ -0,0 +1,12 @@
|
||||
# ConnectWise PSA Ticket Report Logic
|
||||
Reference for the `cw_open_tickets.py` script logic.
|
||||
|
||||
## Filtering Logic
|
||||
- **URL Condition**: `closedFlag = false` (Must be URL-encoded).
|
||||
- **Post-Processing**:
|
||||
- `owner.identifier == 'SBrown'`
|
||||
- `board.name == 'VistaNet Help Desk'`
|
||||
- (Optional) `status.name != '>Completed'`
|
||||
|
||||
## Production Notes
|
||||
- Historically, `id >= 110000` was required to avoid the 2011 legacy mirror. Current implementation prioritizes `closedFlag=false`.
|
||||
Reference in new issue
Block a user