# 04 — UI Guide

Every screen under `/lab` in the cloud ERP. Navigation is the seven tabs at
the top of each page:

```
Dashboard | Agents | Devices | Worklist | Results | Logs | Settings
```

Required permission for each tab: `lab.access_module` (for any view) plus
the per-tab one listed below. All `lab.*` permissions are granted to the
business Admin role at install time.

## Dashboard

`/lab` — landing page after installing LabCore.

What you see:
- **Stat boxes:** Bridge Agents, Analyzers, Pending Worklist, Results
  Today, Total Results, Unacknowledged Errors. Counts are scoped to the
  current business.
- **Registered Vendors** card: every vendor module currently installed
  (e.g. *Abaxis VetScan*, *NX600 Analyzer*). If a vendor is missing here
  but you expect it, that vendor module isn't installed yet — see
  [01 Installation](01-installation.md).
- **Recent Sessions** / **Recent Activity** tables: latest 8 sessions +
  latest 10 communication logs.

Permission: `lab.access_module`.

## Agents

`/lab/agents` — manage **LabBridge Agents**, the clinic-side services that
relay analyzers to the cloud.

### Add an agent

Fill **Name** (display label, e.g. *Main Clinic*) and **Code** (a slug used
by the agent at registration, e.g. `main-clinic` — letters / numbers /
dashes only). Submit. The flash message at the top displays the **API
token** — copy it now into the agent's `config.yaml`. The token is **not
shown again**; if you lose it, regenerate.

### Table columns

| Column | Meaning |
|---|---|
| Name | Display label |
| Code | Slug |
| Devices | Number of registered analyzers using this agent |
| Status | `active` / `inactive` — soft-disable without deleting |
| Connectivity | **Online** if last heartbeat is within `LABCORE_HEARTBEAT_TIMEOUT` (default 120s); **Offline** otherwise |
| Last Heartbeat | Human-readable ("2 minutes ago") |
| Actions | Key icon = regenerate API token; trash = delete agent |

### Regenerate API token

Click the key icon. A new token is issued and shown in the flash. The
**old token immediately stops working** — the running agent will start
getting `401 invalid_agent_token` on its next call. Update `config.yaml`
on the clinic PC and restart the agent.

### Delete an agent

Trash icon → confirm. This removes the agent row but **does not** delete
its devices or stored sessions/results. To fully decommission a clinic,
delete its devices first, then the agent.

Permission: `lab.manage_agents`.

## Devices

`/lab/devices` — register the analyzers each agent is responsible for.

### Add an analyzer

Left card:

| Field | Notes |
|---|---|
| Name | Display label |
| Vendor | Dropdown fed by registered drivers — pick `abaxis`, `nx600`, etc. |
| Model | Free text (e.g. `VS2`, `HM5`, `NX600`) |
| Agent | The agent that hosts this device's listener |
| Connection Type | `TCP/IP`, `Serial RS-232`, or `Serial-to-Ethernet` |
| Comm Protocol | e.g. `astm-e1394`, `nx600-type-1` |
| Host / IP, Port | Fill for `tcp` / `serial2eth` |
| COM Port, Baud | Fill for `serial` (defaults: 9600 8-N-1) |
| Status | `online` / `offline` / `disabled` — the agent flips this on real connections |

After adding, **restart the agent** so it picks up the new device list.

### Table columns

Name, Vendor, Model, Agent, Connection (type + the host:port or COM port),
Status, Delete.

Editing a device isn't in the UI yet — delete and re-add to change
connection settings.

Permission: `lab.manage_devices`.

## Worklist

`/lab/worklist` — pending **orders** that bidirectional analyzers query
for. Manual entries here; entries can also be raised automatically from a
pet's profile when [VetClinic sync](06-vetclinic-sync.md) is enabled.

### Add an entry

Click **New Entry** (top-right) → fill the form:

| Field | Notes |
|---|---|
| Vendor | Which vendor's worklist this is for |
| Device | (optional) Pin to a specific device |
| Sample No | The sample ID the analyzer will report under |
| Patient ID, Name | Shown on the printed report |
| Species, Sex, Age | Optional — analyzer-dependent |
| Tests | Comma-separated analyte codes (e.g. `GLU,BUN,CRE`) — leave blank for whole-panel |

The entry sits in **pending** status until the analyzer pulls it (or the
agent serves it back via a worklist query). Status flips to **sent** when
delivered, **completed** when results land for that sample.

### Bidirectional vs unidirectional

Unidirectional analyzers (most VetScan VS2 firmware) **don't query** the
worklist — they push results under their own sample numbers regardless.
The worklist is informational only in that case.

Bidirectional analyzers (and the IDEXX VetLab Station) **do** query, but
agent-side serving of worklist back to analyzers via ASTM `Q` records is
not yet wired in V1 — the agent caches the worklist locally but doesn't
serve it. See [03 Device integration](03-device-integration.md) for the
roadmap.

Permission: `lab.manage_worklist`.

## Results

`/lab/results` — every analyzer run.

### List

Paginated 25 per page. Columns: Sample No, Patient (+ species), Vendor,
Date, Analytes (count), Flag (Normal / Abnormal), View.

A row is **Abnormal** if *any* of its results has `is_abnormal=true` —
ASTM `H/L/HH/LL/A` or NX600's warn flags.

### Detail (`/lab/results/{id}`)

Two-column layout:

**Sample panel (left):** Sample No, Patient, Patient ID, Species, Sex,
Age, Vendor, Device, Date, Source (`agent` / `manual` / `api`), and the
agent UUID (the idempotency key — useful for tracing in
`lab_communication_logs`).

**Results table (right):** one row per analyte:
- Analyte name (canonical from `lab_analyte_map`, with raw vendor code
  below)
- Result value (with `>` or `<` prefix if the value was out of measurement
  range)
- Unit
- Reference range
- Flag (red Abnormal badge with the raw flag, or green Normal)

Abnormal rows are highlighted with the AdminLTE `danger` row class.

### Delete

The **Delete Session** button at the bottom-right removes the session and
all its results (cascade). Requires `lab.manage_results`.

Permission: `lab.view_results` (list + detail), `lab.manage_results` (delete).

## Logs

`/lab/logs` — protocol-level event log.

Columns: Time, Level (info / success / warning / error), Category
(connection / worklist / result / error / sync / system), Vendor, Message.

Paginated 50 per page. **Clear** button (top-right) wipes the log
(confirmation prompt). Raw device bytes are also captured in the
`raw_message` column of `lab_communication_logs` — useful for debugging
parser mismatches; not shown in the UI yet, query the DB directly:

```sql
SELECT created_at, vendor, message, raw_message
FROM lab_communication_logs
WHERE category = 'result'
ORDER BY id DESC LIMIT 20;
```

Permission: `lab.view_logs`.

## Settings

`/lab/settings` — per-business config.

| Field | Effect |
|---|---|
| Clinic / Laboratory Name | Header shown on printed reports |
| Subtitle | Second line on reports |
| Phone, Email | Report footer contact info |
| VetClinic sync (checkbox) | Enables pet-profile linkage (see [06](06-vetclinic-sync.md)) |

Settings are stored in `lab_settings` (one row per `business_id` + `key`).
Save with the **Save Settings** button.

Permission: `lab.access_settings`.

## Sidebar entry

LabCore does **not** yet add itself to the main admin sidebar — navigate
to `/lab` directly. Adding a sidebar hook is a known follow-up; the
recommended workaround for now is to bookmark `/lab` in your browser or
add a custom link in your role/menu config.

## Common workflows

### "A vet just ran a sample — where is it?"

1. **Lab → Results** → look for the sample number at the top.
2. If missing, **Lab → Logs** filtered for `category=result` — was the
   message received?
3. If no log entry, the analyzer never reached the agent — check
   **Lab → Agents** for Online status, then check the clinic PC firewall.

### "I need to take an analyzer offline for service"

1. **Lab → Devices** → set the device's Status to `disabled`.
2. Restart the agent (it skips disabled devices).

### "I rotated the API token for security"

1. **Lab → Agents** → key icon on the row → copy the new token.
2. SSH/RDP to the clinic PC → edit `config.yaml` → `labbridge.exe restart`.
3. Watch **Lab → Agents** for the heartbeat to resume.
