Skip to content

Dashboard Analytics

zach115th edited this page Jul 17, 2026 · 1 revision

Dashboard Analytics

Dashboard tabs

/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.


Metrics tab

GET /api/v2/dashboard/metrics?start=&end=&ci_year=&tag_year=

Sectors card

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).

Case tagging card

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)

3-state flip card

Both cards cycle through three states on click:

  1. Table — per-sector / per-quarter counts
  2. Donut chart — relative distribution
  3. 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 from position: absolute to normal flow takes one frame; Chart.js reads canvas dimensions synchronously, so calling render() in the same tick produces a zero-height chart.

Time tracking section

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".


Analyst time tracking

Logging time

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.

API

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".

Design rule

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.


Analyst skills catalog

A built-in skill catalog (34 skills across 8 categories, seeded at startup) lets administrators tag analysts with the cyber/DFIR skills they hold.

Categories and seeded skills (representative sample)

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

Managing skills

Admins — user-edit modal → Skills tab (/manage/users/<id>)
Self-service — profile page /user/settings → Skills section (analysts edit their own)

API

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

Per-case team building

Team composition

Every case has:

  • Lead — always the case owner (immutable)
  • Assigned analystsCaseAnalystLink rows (many, with lead or analyst role)
  • Required skillsCaseRequiredSkill rows (auto-derived or manually set)

✨ Suggest button

The ✨ Suggest pill in the case-info modal runs a greedy set-cover algorithm:

  1. Auto-derives required skills from the case classification + MISP tags (rule-based, no LLM)
  2. Scores each unassigned analyst by how many uncovered required skills they hold
  3. Returns the top candidates, excluding the owner, reviewer, and anyone already assigned

Setting up the team

In the case-info modal → Team section:

  1. The analyst multi-select picker shows currently assigned analysts
  2. Click ✨ Suggest to populate candidates based on required skills
  3. Select analysts from the picker and save

API

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

/manage/teams page

A management view showing:

  1. Analyst Skill Coverage table — all active analysts with their skill chips, grouped by category
  2. 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.


Sector enforcement

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(...).

Clone this wiki locally