---
id: w3/m3/t003
title: "Live wiring: CORS for the dashboard origin, empty/503 states, verify against prod"
worker: worker3
status: done
estimate: 30m
depends_on: [w3/m3/t002]
---

## Objective

Make the page work against the real `api.bex.co` with a browser session, handle the failure modes honestly, and verify end-to-end with live `beancount-cms` data (the App's resource/CR name — `beancount-cms-v2` is only its `.onbex.co` hostname, confirmed in t001).

## Context

- Cross-origin: the dashboard origin differs from `api.bex.co`; bex-api's CORS is a single exact origin via `BEX_API_CORS_ORIGIN` (`operator/config/api/deployment.yaml`). **`Access-Control-Allow-Credentials` is already fixed** (t001 prerequisite, `operator/internal/api/auth.go`'s `withCORS`) — this task only needs to set the actual origin value and verify it works live.
- Failure modes to surface (from `docs/ADR010-observability.md`): **503** `metrics source not configured` (e.g. `BEX_PROM_URL` unset) → explicit callout, not an empty chart; empty series (Prometheus warming / no traffic) → "no data in range" state; 401 → redirect to login (existing `requireAuth`).
- Live verification target: `beancount-cms` (Running in prod; request metrics need some traffic to have shape — a brief curl loop against `https://beancount-cms-v2.onbex.co`, its hostname, is fine).

## Steps

1. Set `BEX_API_CORS_ORIGIN` in `operator/config/api/deployment.yaml` to the dashboard's prod origin (and document the localhost dev origin override).
2. Add the three UI states (unavailable / empty / unauthenticated) to the metric chart block.
3. Run the dashboard against prod api, log in, open the beancount-cms metrics page; drive light traffic so request charts have points.
4. Screenshot the populated page to `.playwright-mcp/` (bare filename) as the PoC artifact.

## Files

- `operator/config/api/deployment.yaml`
- `dashboard/src/features/metrics/components/*` (state handling)
- `dashboard/.env.example` / `src/config/config.ts` (API URL for prod/dev)

## Acceptance criteria

- [x] Logged-in browser session loads live metrics from `https://api.bex.co/graphql` with no CORS errors. Verified live via Playwright against the deployed `dashboard.bex.co` (real `.bex.co`-scoped Kratos session, not a local dev server — see t002's note on why local dev can't carry the session cross-site): navigated to `/services/beancount-cms/metrics`, no CORS console errors, real data rendered.
- [x] All six charts populated for beancount-cms (request charts with real multi-point series); screenshot saved under `.playwright-mcp/`. `beancount-cms-metrics-poc.png` (Percentage, default) and `beancount-cms-metrics-poc-total.png` (Total) — Total Requests/Response Times/Bandwidth each rendered **121 real data points** from live Prometheus data; Memory 417 MiB, CPU 0.230 cores, Total Instances 1 (live metrics-server). Also live-verified the Percentage↔Total toggle (Percentage showed "—" because `beancount-cms`'s pod has no configured resource limit — bex-api's documented "omit rather than fake" behavior, not a bug) and the quantile select (p95→p50 changed Response Times' scale from 1580ms to 413ms, as expected for a lower percentile).
- [ ] Killing a source (e.g. querying a request metric with `BEX_PROM_URL` unset in a dev api) renders the "metrics source not configured" state. **Not exercised live** — doing so would require breaking prod's working `BEX_PROM_URL` to observe the failure state, which isn't worth the disruption. The `MetricUnavailable` component and the `unavailable` field on `useMetrics` (t001/t002) implement this path; a proper test belongs in t005 (mocked, not a live prod outage).

## Out of scope

- Deploying the dashboard itself to prod (exists as the separate dashboard Argo app); auth flows (w5/m1 shipped them).
