---
module: analytics.uptime
title: Uptime
tab: Claude
order: 40
audience: dev
---

> Terse agent reference for `analytics.uptime`. Only confirmed facts. Act, don't guess.

## Owner

- **Service:** `hiveops-analytics` — port **8089**, DB **`hiveiq_analytics`**.
- **Endpoint class:** `UptimeController` → `UptimeService`.
- **Frontend:** `hiveops-analytics/frontend/src/components/tabs/UptimeTab.svelte`, `api.getUptime()`.

## Endpoints (METHOD + path)

| Method | Path | Service | Auth |
|---|---|---|---|
| GET | `/api/analytics/uptime` | analytics | JWT (any authenticated; **no role gate**) |
| GET | `/api/atms/paginated?size=500&agentStatus=DISCONNECTED,NEVER_CONNECTED` | incident | caller's Bearer forwarded |
| GET | `/api/atms/summary` | devices | snapshot collector (internal token) |
| GET | `/api/incident-management/stats/summary` | incident | snapshot collector |
| GET | `/api/fleet/tasks/stats` | fleet | snapshot collector |
| GET | `/api/journal/transactions/summary?dateFrom=&dateTo=` | journal | snapshot collector |

- Frontend `apiBase` default: `http://localhost:8089/api/analytics`; prod via `window.__APP_CONFIG__.apiUrl`.

## Tables

| Table | DB | Access |
|---|---|---|
| `analytics_snapshots` | `hiveiq_analytics` | read by `UptimeService`, written by `SnapshotCollectionService`; unique `(snapshot_date, snapshot_hour)` |

## Kafka topics

| Direction | Topic | Group |
|---|---|---|
| consume | `hiveops.incidents.created` / `.updated` / `.resolved` | `hiveops-analytics-incidents` |
| consume | `journal.file-parsed` | `hiveops-analytics-journal` |
| produce | `analytics.snapshot-ready` | — |

- Any consumed event → `SnapshotCollectionService.collect()`. Cron fallback: `0 0 */6 * * *`.

## Roles

- `SecurityConfig`: `permitAll` on `/actuator/health` + `/api/public/**`; everything else `.anyRequest().authenticated()`.
- **No `@PreAuthorize`, no `hasAnyRole(...)` on Uptime.** MSP_ADMIN/BCOS_ADMIN not required to read.
- Snapshot collector uses `InternalTokenGenerator` (`sub=0`, `role=ADMIN`, 300s).

## Gotchas

- `UptimeService` queries **today only** (`from = to = LocalDate.now()`). Therefore `sevenDayAvgPct == thirtyDayAvgPct == today's avg`, and `dailyTrend` has **1 point** → frontend shows **"Not enough data"** (`trend.length < 2`). Not corruption — code limitation.
- **NOW/7d/30d/trend = snapshot data** (≤6 h stale). **CURRENTLY DOWN count/table = live incident call.** They can disagree.
- Down-list failure is swallowed (`catch → log.warn("failed to fetch down ATMs")`) → empty list, count 0. Check analytics logs, not an error response.
- Down-list still calls **incident** (`services.incident.url`, default `http://hiveops-incident-backend:8080`) even though ATM counts come from **devices**.
- Prod DB uses split `DB_HOST/DB_PORT/DB_NAME/DB_USERNAME/DB_PASSWORD`, **not** `DB_URL`.
- Snapshot upsert is per `(date, hour)` — a re-run in the same hour overwrites, not appends.

## Do NOT

- Do NOT assume a role check exists — Uptime is readable by any authenticated JWT.
- Do NOT expect the 30-day trend / distinct 7d vs 30d numbers to render — code only loads today.
- Do NOT look for Uptime data on the ATM, devices, or incident DB — history lives in `analytics_snapshots` (`hiveiq_analytics`).
- Do NOT trust the NOW ring as live truth; only the "Currently Down" list is live.
- Do NOT invent an admin/write endpoint for Uptime — it is read-only (`GET /api/analytics/uptime`).
- Do NOT confuse consumer groups: incidents = `hiveops-analytics-incidents`, journal = `hiveops-analytics-journal`.
