-
-
Notifications
You must be signed in to change notification settings - Fork 0
Dashboard Analytics
/dashboard has four top-level tabs:
| Tab | Purpose |
|---|---|
| Operational | Assigned cases, open alerts, recent activity, attributed open tasks |
| Metrics | Sector matrices, case-tagging health, time-tracking aggregates |
| Correlation | Cross-case IOC clusters, D3 graph, campaign tags, AI narratives |
| Inventory | Physical evidence drive tracking, barcode lookup, drive lifecycle |
The Correlation tab is covered in IOC Correlation.
The Inventory tab is covered in Evidence Inventory.
GET /api/v2/dashboard/metrics?start=&end=&ci_year=&tag_year=
Counts cases in each DHS CIIP sector (and other configured sectors) for the selected
year. ci_year query parameter controls the year independently of the global
date range.
Sectors follow a multi-taxonomy model:
- DHS CIIP sectors —
dhs-ciip-sectors:DHS-critical-sectors=<slug>tags - Additional sectors — e.g.
threatmatch:sector="Education"(one<option data-tag=…>line in the create-case modal to add another sector)
New cases inherit sector from the customer if the create payload doesn't include one. Missing sector tags are logged as warnings (soft enforcement, not a hard reject).
Tracks what fraction of cases carry at least one non-noise tag per quarter.
tag_year controls the year independently of the Sectors card.
Excluded from "tagged" counts:
- Sector tags (their own card)
-
tlp:*tags (applied by policy to every case — not a signal)
Both cards cycle through three states on click:
- Table — per-sector / per-quarter counts
- Donut chart — relative distribution
- Multi-year grouped bar — compare across years
The bar state lazy-fetches
GET /api/v2/dashboard/metrics/bar-data?section=tagging|ci&years=<comma-list>.
A year-checkbox picker inside the bar slot controls which years appear;
all available years are pre-checked on first entry.
Dev note (Chart.js in a flip-card back face): defer the
render()call 50 ms after showing the chart slot. The CSS transition fromposition: absoluteto normal flow takes one frame; Chart.js reads canvas dimensions synchronously, so callingrender()in the same tick produces a zero-height chart.
Aggregates logged analyst time across cases, broken down by:
- By customer — total hours per client
- By person — total hours per analyst
- By sector — derived from the case's sector tag at report time
- By incident type — derived from the case's incident-type tag at report time
The card shows average time per case in each bucket (not raw totals) — this normalises for buckets that have more cases. The CSV export includes both average and raw total per bucket.
Analyst hourly rate. When a rate is set on a user, the case-edit modal Time tab shows an "Est. cost" badge. Analysts with no rate contribute $0 and flag the estimate as "partial".
A clock icon in the case header opens the Log Time modal. Entries require:
- Minutes — must be a multiple of 15
- Date — defaults to today
- Task (optional) — links the entry to a specific task
- Note (optional) — links the entry to a specific note
Entries are locked when the case is closed (close_date IS NOT NULL) and unlocked
on reopen.
| Method | Path | Description |
|---|---|---|
GET |
/api/v2/cases/<cid>/time-entries |
List entries for a case |
POST |
/api/v2/cases/<cid>/time-entries |
Log time (body: minutes, date, optional task_id/note_id) |
PUT |
/api/v2/cases/<cid>/time-entries/<eid> |
Update (case must be open) |
DELETE |
/api/v2/cases/<cid>/time-entries/<eid> |
Delete (case must be open) |
GET |
/api/v2/cases/time-nudge |
Nudge check ("you haven't logged time recently") |
The nudge is opt-in (off by default): /manage/settings → Analyst tab →
"Enable time-logging nudge".
Sector, incident type, and customer dimensions are derived at report time by joining through the case — analysts never enter them. This means re-classifying or reassigning a case retroactively fixes all historic time entries without manual correction.
A built-in skill catalog (34 skills across 8 categories, seeded at startup) lets administrators tag analysts with the cyber/DFIR skills they hold.
| Category | Example skills |
|---|---|
| Forensics | Disk forensics, Memory forensics, Network forensics |
| Malware | Malware analysis, Reverse engineering |
| Threat intel | MISP, ATT&CK, Threat hunting |
| Cloud | AWS, Azure, GCP forensics |
| Incident response | Triage, Containment, Eradication |
Admins — user-edit modal → Skills tab (/manage/users/<id>)
Self-service — profile page /user/settings → Skills section (analysts edit their own)
| Method | Path | Description |
|---|---|---|
GET |
/api/v2/teams/analyst-skills |
All active non-service analysts + their skill IDs |
POST |
/manage/users/<id>/skills/update |
Admin bulk-set skills for a user |
POST |
/user/skills/update |
Self-service: update your own skills |
Every case has:
- Lead — always the case owner (immutable)
-
Assigned analysts —
CaseAnalystLinkrows (many, withleadoranalystrole) -
Required skills —
CaseRequiredSkillrows (auto-derived or manually set)
The ✨ Suggest pill in the case-info modal runs a greedy set-cover algorithm:
- Auto-derives required skills from the case classification + MISP tags (rule-based, no LLM)
- Scores each unassigned analyst by how many uncovered required skills they hold
- Returns the top candidates, excluding the owner, reviewer, and anyone already assigned
In the case-info modal → Team section:
- The analyst multi-select picker shows currently assigned analysts
- Click ✨ Suggest to populate candidates based on required skills
- Select analysts from the picker and save
| Method | Path | Description |
|---|---|---|
GET |
/api/v2/cases/<cid>/team |
Get assigned analysts + lead |
PUT |
/api/v2/cases/<cid>/team |
Set assigned analysts |
GET |
/api/v2/cases/<cid>/team/skills |
Get required skills for this case |
PUT |
/api/v2/cases/<cid>/team/skills |
Set required skills manually |
GET |
/api/v2/cases/<cid>/team/suggest |
Greedy suggest (excludes owner + reviewer + already assigned) |
GET |
/api/v2/cases/<cid>/team/skills/derive |
Auto-derive skills from classification + tags |
A management view showing:
- Analyst Skill Coverage table — all active analysts with their skill chips, grouped by category
- Active Cases with Team Assignments — all open cases with Lead, assigned analysts, and skill-coverage percentage
Coverage percentage counts skills held by the owner + all assigned analysts combined.
The page fetches GET /api/v2/teams/analyst-skills once and shares the
_userSkillMap[user_id] lookup across both tables.
New cases must carry a DHS CIIP (or other configured) sector tag. Enforcement is soft: the create-case modal blocks submission if no sector is selected (client-side), but the API only logs a warning and does not reject (to avoid breaking existing automation).
To flip to hard enforcement, replace the warning block in business/cases.py::cases_create
with raise BusinessProcessingError(...).