---
module: dashboard.overview
title: Fleet Overview
tab: Architect
order: 30
audience: dev
---

## Shape of the feature
**Fleet Overview** is a **read-only aggregation view with no backend of its own.** `hiveops-dashboard` is a static Svelte/Vite SPA (port 5181, `dashboard.bcos.cloud`). All state lives in three owning services; the SPA is a thin client that composes their responses into KPI cards, health bars, task stats, and a recent-incidents table.

Component: `hiveops-dashboard/frontend/src/components/Dashboard/Dashboard.svelte`
Data layer: `frontend/src/lib/api.ts` (single axios instance) + `frontend/src/lib/stores.ts` (`atmSummary`, `dashboardStats`, `fleetStats` writables, `loadAll()`).

## Services involved (three-way fan-out)
The SPA has **one** base URL (`window.__APP_CONFIG__.apiUrl` from `VITE_API_URL`, injected by `frontend/entrypoint.sh` into `config.js` at container start). It calls four relative paths that are **owned by three different services**, so the deployment relies on NGINX path-routing to fan a single origin out to the right backend (the exact nginx map is in the ops repo, not here — verify there if a path 404s):

| Path | Service | Handler | Backing store |
|---|---|---|---|
| `GET /atms/summary` | hiveops-devices | `AtmController#getAtmSummary` (`@RequestMapping({"/api/atms","/api/devices"})`) | `hiveiq_devices.atms` (+ `atm_properties.last_heartbeat`) |
| `GET /atms/dashboard/stats` | hiveops-incident | `AtmDashboardController#getDashboardStats` (`/api/atms/dashboard/stats`) → `AtmService#getDashboardStats` | `hiveiq_incident.incidents`, `hiveiq_incident.atms` |
| `GET /fleet/tasks/stats` | hiveops-fleet | `FleetApiController#getStats` (`/api/fleet/{stats,tasks/stats}`) → `FleetTaskService#getStats` | `hiveiq_fleet.fleet_tasks`, `hiveiq_fleet.fleet_artifacts` |
| `GET /users/me` | hiveops-incident | `UserController#getCurrentUser` (`/api/users/me`) | JWT claims only (no DB) |

## Data flow
1. `onMount` in `Dashboard.svelte` calls `loadAll()` → `Promise.all([loadAtmSummary(), loadDashboardStats(), loadFleetStats()])`, then repeats on a 60s tick (auto-refresh) or manual button.
2. Each loader wraps its call in an isolated `try/catch`; a failure zeroes only that store, so cards degrade independently.
3. `App.svelte` separately calls `GET /users/me` to resolve `userRole` (CUSTOMER hides the "View all" incidents link; Electron `electronAPI.getUserProfile` fallback).

### Per-service internals
- **devices `/atms/summary`** — loads institution-scoped ATMs, computes `connected/disconnected/neverConnected` from `AtmDTO.agentConnectionStatus` (derived from `last_heartbeat`) and a `byModel` histogram. Pure read; no Kafka in the request path.
- **incident `/atms/dashboard/stats`** — `@Cacheable("dashboardStats")` keyed by institution scope. Reads `incidents` (open/critical/this-week counts + last 10 for the table) and `atms` (`totalAtms`, `operationalAtms` where `status == IN_SERVICE`). Institution names resolved via `institutionKeyService.resolveInstitutionNames`.
- **fleet `/fleet/tasks/stats`** — `taskRepository.count()` for total, `countByTaskKindAndStatus(AUTO_UPDATE, PENDING)` for pending, `findByStatus(RUNNING).size()` for running, `artifactRepository.count()` for artifacts. `completed`, `failed`, and `totalModulesReporting` are **not computed** (see uncertainties). No institution filter applied.

## Kafka / mirrors (context, not request-path)
This feature reads each service's **own** primary tables, so Kafka is not in the live read path. It is relevant to correctness because device identity is mirrored fleet-wide: hiveops-devices publishes `hiveops.device.events`, which hiveops-fleet (and incident/reports/recon) consume into a local `device_summary` mirror. The dashboard's ATM counts come from the devices `atms` table directly; fleet task stats come from `fleet_tasks`. If device naming/institution on the dashboard disagrees with Fleet, suspect a stale `device_summary` mirror rather than the dashboard. See [platform.kafka], [platform.data-architecture].

## Ownership boundaries
- Device inventory & connection status → **hiveops-devices** (source of truth).
- Incidents & the recent-incident list → **hiveops-incident**.
- Fleet task/artifact aggregates → **hiveops-fleet**.
- The dashboard SPA owns **nothing** — it must never grow its own endpoints or tables; new numbers belong in the owning service and are surfaced here. See [platform.service-ownership].

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