Add a tab system to the Metric Graphs panel on the ReportingPage, with an "Ivanti Findings" tab (existing donuts) and an "Atlas Coverage" tab (three new donut charts). A new `GET /api/atlas/metrics` endpoint aggregates cached Atlas action plan data into chart-ready metrics. All backend changes stay within `backend/routes/atlas.js`. Frontend changes are in `frontend/src/components/pages/ReportingPage.js`.
## Tasks
- [x] 1. Implement the Atlas metrics aggregation endpoint
- [x] 1.1 Add `GET /metrics` route inside the existing `createAtlasRouter` factory function in `backend/routes/atlas.js`
- Query all rows from `atlas_action_plans_cache` using the existing `dbAll` helper: `SELECT has_action_plan, plans_json FROM atlas_action_plans_cache`
- Extract the aggregation logic into a pure function `aggregateAtlasMetrics(rows)` that takes an array of `{ has_action_plan, plans_json }` objects and returns `{ totalHosts, hostsWithPlans, hostsWithoutPlans, plansByType, plansByStatus, totalPlans }`
- For each row: count hosts with/without plans based on `has_action_plan`; parse `plans_json` and count plans by `plan_type` and `status`; skip plan details for rows with invalid JSON
- Return 503 if Atlas is not configured; return 500 on DB errors; require authentication via `requireAuth(db)`
- Return all-zero metrics with empty objects when the cache table is empty
- Do NOT modify `server.js` — the route is added inside the existing router factory
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_
- [x] 1.2 Write property test for metrics aggregation (Property 1)
- **Property 1: Metrics aggregation correctness**
- Extract `aggregateAtlasMetrics` as a pure exported function for testability
- Use fast-check to generate arrays of objects with `has_action_plan` (0 or 1) and `plans_json` (valid JSON arrays of `{ plan_type, status }` objects or invalid strings)
- Verify: `totalHosts === rows.length`, `hostsWithPlans + hostsWithoutPlans === totalHosts`, `hostsWithPlans` equals count of rows where `has_action_plan === 1`, `totalPlans` equals sum of valid plan array lengths, `plansByType` and `plansByStatus` counts match individual plan fields, rows with invalid JSON are counted in host totals but excluded from plan counts
- **Validates: Requirements 1.3, 1.4, 1.5**
- [ ]* 1.3 Write unit tests for the metrics endpoint
- Test empty cache returns all-zero metrics
- Test correct host counting with seeded data
- Test correct plansByType and plansByStatus aggregation
- Test rows with invalid `plans_json` are handled gracefully
- Test 503 response when Atlas is not configured
- Test 401 response for unauthenticated requests
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_
- [x] 2. Checkpoint — Verify backend endpoint
- Ensure all tests pass, ask the user if questions arise.
- [x] 3. Add tab system to the Metric Graphs panel
- [x] 3.1 Add tab state and tab bar UI to the Metric Graphs panel header in `ReportingPage.js`
- Add `metricsTab` state initialized to `'ivanti'`
- Render a horizontal tab bar to the right of the "Metric Graphs" title with two tabs: "Ivanti Findings" and "Atlas Coverage"
- Active tab styling: `color: #F59E0B`, `borderBottom: 2px solid #F59E0B`
- Inactive tab styling: `color: #64748B`, no bottom border
- Hover on inactive: `background: rgba(245, 158, 11, 0.06)`
- Test Coverage donut empty state message when totalHosts is 0
- Test Plan type donut empty state message when totalPlans is 0
- Test Plan status donut empty state message when totalPlans is 0
- Test SVG dimensions are 180px with correct outer/inner radius
- Test color assignments for each plan type and status
- _Requirements: 3.5, 4.5, 5.5, 3.6, 4.6, 5.6_
- [x] 6. Wire Atlas donuts into the Atlas Coverage tab
- [x] 6.1 Render Atlas donut charts in the Atlas Coverage tab content area
- When `metricsTab === 'atlas'`: render `AtlasCoverageDonut`, `AtlasPlanTypeDonut`, `AtlasPlanStatusDonut` in a horizontal flex row with dividers (same layout pattern as Ivanti donuts)
- Pass data from `atlasMetrics` state to each donut component
- Show loading indicator while `atlasMetricsLoading` is true
- Show error message when `atlasMetricsError` is set