---
module: dashboard.overview
title: Fleet Overview
tab: Claude
order: 40
audience: dev
---

## Identity
- Frontend: `hiveops-dashboard` (Svelte/Vite SPA, port 5181, `dashboard.bcos.cloud`). **No backend, no DB, no Kafka of its own.**
- Component: `hiveops-dashboard/frontend/src/components/Dashboard/Dashboard.svelte`; data: `frontend/src/lib/{api.ts,stores.ts}`.
- Single axios base: `window.__APP_CONFIG__.apiUrl` (`VITE_API_URL`, default `http://localhost:8080`), set by `frontend/entrypoint.sh`.

## Endpoints (METHOD + path, relative to `apiUrl`)
| Method | Path | Owner service | Backend mapping | Auth |
|---|---|---|---|---|
| GET | `/atms/summary` | hiveops-devices | `AtmController` `/api/atms` + `/api/devices` → `/summary` | JWT, institution-scoped, no `@PreAuthorize` |
| GET | `/atms/dashboard/stats` | hiveops-incident | `AtmDashboardController` `/api/atms/dashboard/stats` | JWT, institution-scoped, `@Cacheable("dashboardStats")` |
| GET | `/fleet/tasks/stats` | hiveops-fleet | `FleetApiController` `/api/fleet/{stats,tasks/stats}` | `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','BCOS_ADMIN')` |
| GET | `/users/me` | hiveops-incident | `UserController` `/api/users/me` | JWT (claims only) |

## Owning service per data element
- ATM totals / connected / disconnected / neverConnected / byModel → **devices** (`atms` table, status from `agentConnectionStatus`/`last_heartbeat`).
- openIncidents / criticalIncidents / incidentsThisWeek / recentIncidents / totalAtms / operationalAtms → **incident** (`incidents`, `atms`).
- totalTasks / pendingTasks / runningTasks / completedTasks / failedTasks / totalArtifacts / totalModulesReporting → **fleet** (`fleet_tasks`, `fleet_artifacts`).

## Tables
- `hiveiq_devices.atms`, `hiveiq_devices.atm_properties`
- `hiveiq_incident.incidents`, `hiveiq_incident.atms`
- `hiveiq_fleet.fleet_tasks`, `hiveiq_fleet.fleet_artifacts` (also `fleet_task_history`, `device_summary` — NOT read by this feature's stats)

## Kafka
- `hiveops.device.events` — published by devices, consumed into `device_summary` mirrors in fleet/incident/reports/recon. **Not in this feature's request path** (dashboard reads primary tables directly).

## Gotchas
- `/atms/dashboard/stats` exists in **both** hiveops-incident AND hiveops-devices. Only the **incident** one populates incident fields. Routing determines which answers.
- `/atms/summary` (devices) and `/atms/dashboard/stats` (incident) share the `/atms/` prefix but land on **different services** — relies on nginx path routing (ops repo, not in tree).
- `FleetTaskService.getStats()` hard-codes `completedTasks=0`, `failedTasks=0`; never sets `totalModulesReporting` (→ always 0 on the page). Do NOT treat as an outage.
- `getStats()` `pendingTasks` = only `AUTO_UPDATE` PENDING (undercounts other kinds).
- `getStats()` ignores its `institutionKeys` arg → task/artifact totals are fleet-wide even for scoped users (inconsistent with scoped ATM/incident numbers).
- Each SPA loader `try/catch`es independently: one failing call zeroes only its card, silently (`console.error` only).
- CUSTOMER role only hides the "View all" incidents link; not an admin surface.
- Auto-refresh state in `localStorage['dashboard_autoRefresh']`; 60s tick.

## Do NOT
- Do NOT add endpoints or tables to `hiveops-dashboard` — it is a pure client; put new data in the owning service.
- Do NOT assume `/atms/dashboard/stats` hits incident — verify routing before diagnosing wrong incident counts.
- Do NOT report Completed/Failed/Modules-Reporting = 0 as a bug in the dashboard; it originates in `FleetTaskService.getStats`.
- Do NOT expect fleet task/artifact totals to respect institution scope.
- Do NOT write to `device_summary` to "fix" dashboard ATM counts — those come from the devices `atms` table.
- Do NOT add role guards to `/atms/summary` or `/atms/dashboard/stats` assuming MSP_ADMIN-only; current auth is any valid JWT, institution-scoped.

Refs: [platform.service-ownership] · [platform.kafka] · [platform.data-architecture] · [dashboard.overview]
