---
module: incident.dashboard
title: Dashboard
tab: Internal
order: 20
audience: internal
---

> **Internal ops/support view.** The Dashboard is **read-only** — there are no admin-only actions *on* this screen. It renders live counts from three GET endpoints and derives the charts client-side. Most "the dashboard is wrong" tickets are upstream data problems, not dashboard bugs.

## What it actually loads

Component: `hiveops-incident/frontend/src/components/Dashboard/Dashboard.svelte`. On mount and on each refresh it fires **three** calls (all against `hiveops-incident`, NGINX path `/api/incident/`):

| Frontend call | Endpoint | Feeds |
|---|---|---|
| `loadAtms()` | `GET /api/device-summary` (or `/api/device-summary/paginated?groupId=` when a group is active) | Device Status cards, Device Status panel, Agent Connection panel |
| `loadIncidentsForDashboard()` | `GET /api/incidents/paginated?page=0&size=2000&dateFrom=<90d ago>` | Incident Trends, Incidents by Type, "This Week" |
| `fetchIncidentStats()` | `GET /api/incident-management/stats/summary` | Ticket cards: Open / Assigned / In Progress / Critical |

Key split to remember when triaging: **ticket cards come from the server** (`stats/summary`, accurate across all time); **trend + type panels + "This Week" are computed in the browser** from the last-90-day / max-2000-record incident slice.

## Roles / access

- All three endpoints are **read-open to any authenticated user** — no `@PreAuthorize` on `stats/summary`, `incidents/paginated`, or `device-summary`. There is no MSP_ADMIN gate to *view* the Dashboard.
- The admin-only actions (`hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`) live on the incident *management* mutations (assign/transition/bulk/notes) — not on anything the Dashboard renders.
- Numbers are **institution-scoped** via `InstitutionContext.resolveScope()` (derived from the caller's JWT). A user only ever sees their own institution's devices/incidents. "User X sees the wrong institution's numbers" = a JWT/scope or stale-mirror issue, not a dashboard bug.

## First things to check when it misbehaves

1. **All device counts are 0 / stale (but devices exist in the Devices app)** → the `device_summary` mirror in `hiveiq_incident` is stale. This table is fed by Kafka (`hiveops.device.events`), **not** written by incident itself. Check: is `hiveops-devices` publishing, and is the incident consumer group `hiveops-incident-device-sync` alive / not lagging? See [platform.kafka].
2. **Ticket cards (Open/Assigned/In Progress/Critical) show 0 but incidents exist** → `stats/summary` call is failing. The frontend **swallows that error silently** (`catch {}`) and falls back to counting the capped 90-day store, so the UI looks "fine" while under-reporting. Curl `GET /api/incident-management/stats/summary` with a real JWT.
3. **Trends / "Incidents by Type" / "This Week" look low on a busy fleet** → expected limitation, not a bug: `loadIncidentsForDashboard()` pulls only the **last 90 days, capped at 2000 records**. A fleet with >2000 incidents in 90 days will truncate these panels. Ticket cards (server stats) stay correct.
4. **Header shows a group name and numbers look small** → a device group is selected (`defaultGroupId`). Both ATMs and incidents are then filtered to that group. Clear the group to see the whole fleet.
5. **"Group not found" / 404 on load** → `device-summary/paginated` 404s when the active `groupId` no longer exists. Reset the saved group.
6. **Nothing loads at all** → check NGINX routing to `/api/incident/` and JWT validity; the error banner at the top of the card shows the last store error.

## Common failure modes → fix

| Symptom | Likely cause | Fix |
|---|---|---|
| Device totals frozen / wrong | `device_summary` mirror stale (no reconciler exists) | Confirm devices publishing `hiveops.device.events`; restart/inspect `hiveops-incident-device-sync` consumer |
| Agent Connection "Disconnected" inflated | Any status that isn't `CONNECTED`/`NEVER_CONNECTED` (incl. null) buckets as Disconnected | Verify agents actually reporting; null status ≠ truly disconnected |
| Wrong institution's counts | JWT scope / stale mirror institutionKey | Check caller JWT scope + device_summary institution_key |
| Ticket cards zero, panels populated | `stats/summary` erroring (silent fallback) | Curl the endpoint; check incident service logs |
| Auto-refresh stopped | Toggle off (persisted in `localStorage` key `incident_dashboard_autoRefresh`) | Flip Auto-refresh back on; interval = `settings.dashboardRefreshIntervalSeconds` |

## Auto-refresh mechanics

- Toggle state persists per-browser in `localStorage['incident_dashboard_autoRefresh']`.
- Countdown resets to `settings.dashboardRefreshIntervalSeconds` each cycle; the **↻** button forces an immediate `doRefresh()` (all three calls in parallel).
