# Tunnel operator console

> Read the tunnel overview KPIs, egress and estimated cost, and manage rnp accounts and their registered devices.

The tunnel screens in the ClutchCall console are the operator view of the
`rnp` control plane. Two screens cover it: an **overview** of modality-wide
counters, and a searchable **account directory** with a per-account device
registry.

Both screens read the control plane over tRPC only. Neither one calls the
tunnel engine, so nothing here depends on a live engine RPC path.

## What's in the console

| Screen             | tRPC procedures                                                                                  | What it answers                                                    |
| ------------------ | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ |
| Tunnel overview    | `adminTunnel.overview`                                                                            | How many accounts and devices exist, how much egress this period, wallet float, estimated bill, and which accounts are moving the most bytes. |
| Users & devices    | `adminTunnel.users.list`, `adminTunnel.users.devices`, `adminTunnel.users.removeDevice`, `adminTunnel.overview` | Who an account is, what it is spending, which devices it has registered, and how to unregister one. |

The overview header shows the wildcard base domain the control plane reports
(`*.<baseDomain>`) and the current period, so you can confirm which deployment
and which period you are looking at before reading any number below it.

## Read the overview KPIs

`adminTunnel.overview` returns five counters. The console renders each as a
meter card:

| Card              | Field                                   | Counts                                                                 |
| ----------------- | --------------------------------------- | ---------------------------------------------------------------------- |
| **Accounts**      | `users`                                 | OIDC identities known to the control plane. An account appears on its first `rnp login`. |
| **Devices**       | `devices`                               | Registered boxes across all accounts, not just the ones currently connected. |
| **Egress**        | `egressBytes`                            | Bytes egressed during the period named underneath the value (`period`). |
| **Wallet float**  | `walletTotalMinor`                       | The sum of all prepaid wallet balances, in minor units of `currency`.  |
| **Billed (est.)** | `billedEstMinor`                         | The estimated charge for this period's egress.                         |

The Devices card counts registrations, and the Accounts card counts identities.
Neither is a liveness signal — a device that has not connected for weeks still
counts until someone removes it.

The overview query refetches every 30 seconds while the screen is open, so the
KPI cards move without a manual refresh.

## How egress is counted and when it refreshes

Egress is not read from the engine. `rnpd` flushes byte counters into Redis
usage keys of the form:

```
clutchcall:rnp:usage:<sub>:<period>
```

on roughly a 15-second cadence, and the overview procedure sums those keys.
Two consequences:

- **It is near-live, not instant.** Traffic shows up within seconds of a tunnel
  moving bytes — the overview's empty state says exactly that — but a figure you
  read can lag the wire by about one flush interval.
- **The counter is keyed by period.** Because `<period>` is part of the key, a
  new period starts from a fresh counter rather than continuing the previous
  one. The `Egress` card's subtitle and the page header both name the period the
  figure covers; read them together, since a value with no period attached is
  ambiguous.

Per-account egress in the directory (`egressBytes` on each row) comes from the
same per-`sub` usage keys, so the directory and the overview agree for the same
period.

## Free allowance, per-GB price and the estimated bill

The `Billed (est.)` card's subtitle spells out the pricing inputs the control
plane returned:

```
{pricePerGbMinor}/GB after {freeGb} GB free
```

- `freeGb` — the included allowance before any egress is chargeable.
- `pricePerGbMinor` — the per-GB price, in minor currency units.
- `billedEstMinor` — the resulting estimate for this period.

This is an **estimate** — the card is labelled as such — and it is derived from
the same Redis usage counters described above, so it carries the same flush lag.
Treat it as an operational signal, not as an invoice.

Per-account, the directory shows the same idea in the **Est. cost** column
(`costMinor`). Accounts with a zero cost render `—` rather than a zero amount,
which makes the chargeable accounts easy to pick out at a glance.

## Top accounts by egress, and why the list is partial

Below the KPIs, the overview lists the accounts moving the most bytes this
period, with email, `sub`, handle (`<handle>.<baseDomain>`), plan tag, and
egress.

Two filters apply, and both matter when you are hunting for a specific account:

1. **Only accounts with non-zero egress are shown.** Rows where
   `egressBytes` is `0` are dropped, and if no account has egress at all the
   table is replaced by a "No egress this period" empty state.
2. **The scan is bounded.** The card subtitle reads
   `this period · <scannedUsers> most-recently-active accounts scanned`. The
   procedure inspects that many recently-active accounts rather than every
   account in the directory, so this is a top-of-the-recently-active list, not a
   global ranking. An account that has been dormant long enough to fall outside
   the scanned set can still be moving bytes and not appear here.

If an account you expect is missing, look it up directly in **Users & devices**
instead of inferring anything from its absence.

## Wallet float and prepaid balances

Wallets are prepaid. The overview's **Wallet float** is the aggregate of every
account's balance (`walletTotalMinor`), and each directory row shows that
account's own balance in the **Wallet** column (`walletMinor`).

All money in both screens is expressed in minor units of a single currency —
`currency` from the overview payload — and formatted for display. The directory
takes the currency from the cached overview query; if that query has not
resolved, it falls back to `INR` for formatting only. If your amounts look like
they are labelled with the wrong currency on the Users & devices screen, load
the overview once so the real value is cached.

## When the overview is unavailable

The overview screen never renders a blank body. When there is no payload it
keeps the page header up and distinguishes two states:

| State                    | What it means                                                          |
| ------------------------ | ---------------------------------------------------------------------- |
| **Loading overview…**    | The query is still in flight, fetching accounts, devices and egress from the control plane. |
| **Overview unavailable** | The query settled and returned no data.                                |

"Overview unavailable" is a control-plane reachability problem, not an empty
deployment — a deployment with zero accounts still returns a payload of zeros.
Refresh to retry; if it persists, check platform status.

The directory makes the same distinction. While `users.list` is in flight the
card header says `loading…` and the body stays empty. If it settles with no data
at all you get **Directory unavailable**. Only when it settles with an actual
empty list do you get **No accounts yet** (or **No matches** when a search is
active).

## Look up an account in the directory

The **Users & devices** screen is the `rnp` account directory. Accounts are OIDC
identities; no passwords are stored.

- **Search** accepts an email, a handle, or a `sub`. It runs as you type after a
  300 ms debounce, and pressing Enter submits immediately.
- **Ordering** is newest activity first, as stated in the card subtitle.
- **Paging** is 50 rows per page, driven by the `limit`/`offset` inputs to
  `users.list`. The header shows the current window against `total` and enables
  Prev/Next only when there is more than one page. Changing or clearing the
  search resets the offset to the start.

Each row carries:

| Column        | Field           | Notes                                                                 |
| ------------- | --------------- | --------------------------------------------------------------------- |
| Account       | `email`, `sub`  | Email on the first line, the shortened OIDC `sub` underneath. Either identity field can be absent and renders as `—`. |
| Handle        | `handle`        | The account's tunnel handle, or `—` if it has not claimed one.        |
| Plan          | `plan`          | Rendered as a tag; `free` is styled neutrally, any other plan as active. |
| Devices       | `devices`       | The number of registered devices.                                     |
| Egress        | `egressBytes`   | This period, from the usage keys.                                     |
| Est. cost     | `costMinor`     | `—` when zero.                                                        |
| Wallet        | `walletMinor`   | Prepaid balance.                                                      |
| Last login    | `last_login_at` | Relative time.                                                        |

The `sub` is truncated for display. Use full-text search on it when you are
correlating with a usage key or a log line that carries the complete value.

## Inspect and remove devices

Click any account row to expand its device registry. Expanding issues
`adminTunnel.users.devices({ sub })`, so the fetch is lazy — nothing loads until
you open a row. Each device shows its name, a shortened device id, and when it
was last seen.

**Remove** calls `adminTunnel.users.removeDevice({ sub, deviceId })`. This is the
same operation as the CLI's `rnp devices rm`, exposed for support cases where a
user has lost a machine and is wedged at their device cap.

Removal is confirmed first, because the effect is not purely administrative:

> Its session token stops minting; active tunnels drop at the next re-register.

Read that precisely. The device's session token stops minting immediately, but a
tunnel that is already up does not die at the instant you click Remove — it dies
at the device's next re-register, when there is no longer a valid token to renew
with. If you are removing a device to cut off traffic, expect that gap.

On success the console invalidates both the device list for that `sub` and the
account list, so the row's **Devices** count updates without a reload.

## Plans and device caps

The plan tag on each row is what gates device registration. The free plan is
gated at **five** devices; once an account has five registered devices, further
registration attempts fail with a quota error until a device is removed. That is
the specific wedge the operator-side **Remove** button exists to clear — a user
who lost a laptop cannot unregister it themselves, and cannot register the
replacement.

Non-`free` plans are surfaced only as a tag in the console; the screens do not
expose their device caps.

## Where the numbers come from

Useful when a figure looks wrong and you need to know which system to check:

| Number                                    | Source                                                                 |
| ----------------------------------------- | ---------------------------------------------------------------------- |
| Accounts, devices, plans, wallets, last login | The `rnp_*` tables in the control-plane database.                   |
| Egress, estimated cost                    | `clutchcall:rnp:usage:<sub>:<period>` Redis keys, flushed by `rnpd`. |
| Longer-term traffic history               | Per-tenant traffic samples in ClickHouse `tenant_traffic_sample`, with `subject_kind = "tunnel"`. |

The console reads the first two. If the KPI cards are stale but the directory is
fine, suspect the usage flush rather than the directory query — they are
different backing stores.

## Related

- [Telemetry](/platform/telemetry) — the metric, trace and ClickHouse streams the gateway emits
- [Authentication](/concepts/authentication) — how session tokens and scopes are minted
- [Telephony metrics](/glossary/metrics) — definitions for the operational numbers elsewhere in the console
