# Device registry & force-disconnect

> What a QuickDesk device row contains, how online status and last-seen are derived from the client heartbeat, and what a force-disconnect does to a live session.

The QuickDesk **Devices** screen is a view onto the `quickdesk_device`
registry: one row per client that has ever heartbeated in to
ClutchCall. A device appears in the registry the first time the
QuickDesk client checks in, and stays there after it goes offline —
the registry is the durable directory, not a list of who is connected
right now.

Two different data sources feed the table. The row itself (hostname, OS,
user, version) comes from the registry record. The online pip, the
active-connection count, and last-seen come from live state in Redis
that the client's heartbeat refreshes.

## What a device row contains

Each row in the table maps to one registry record:

| Field         | Column      | Meaning                                                                 |
| ------------- | ----------- | ----------------------------------------------------------------------- |
| `id`          | Device      | The QuickDesk (RustDesk) device id. This is the id an operator types to reach the device, and the id that force-disconnect takes. Rendered in small mono text under the device name. |
| `guid`        | —           | The registry primary key. Not shown as a column, but it is the identity that tags and other per-resource metadata hang off. |
| `hostname`    | Device      | The machine name reported by the client. The table shows this as the primary label and falls back to `id` when the client reported no hostname. |
| `os`          | OS          | The OS string the client reported. Shown as `—` when absent.            |
| `username`    | User        | The account the client is running as. Shown as `—` when absent.         |
| `version`     | Version     | The client build, rendered with a `v` prefix (`v1.2.3`). Shown as `—` when absent. |
| `activeConns` | Conns       | How many remote-control connections to this device are live right now. `0` renders as `0` and disables the **Disconnect** button. |
| `online`      | Device      | Live boolean. Drives the green/grey pip and the `online` badge.          |
| `last_seen`   | Last seen   | Timestamp of the last heartbeat, rendered as relative time.             |

`hostname`, `os`, `username` and `version` are all self-reported by the
client at check-in. They describe what the device said about itself the
last time it heartbeated — treat them as a report, not as an attestation.

## Device id vs internal guid

The two identifiers are not interchangeable, and mixing them up is the
most common integration bug on this screen:

- **`id`** — the device id an operator recognises and connects to. This
  is what `devices.disconnect` takes.
- **`guid`** — the registry row's primary key. This is what tags are
  attached to (`kind: "quickdesk_device"`, `resourceId: guid`).

If you build your own tooling against the registry, key persistent
metadata off `guid`. A device id is the user-facing handle; the `guid`
is the row.

## How online status and last seen are computed

The QuickDesk client heartbeats every **15 seconds**. That heartbeat is
the only thing that makes a device look online:

- Each heartbeat refreshes the device's live state in Redis and updates
  `last_seen`.
- `online` is derived from that live state, not from the registry row.
  A device that stops heartbeating (process killed, machine slept,
  network cut) falls back to offline on its own once the live state
  lapses — nothing has to mark it down.
- `activeConns` is also live state, counted from the connection ids
  currently registered for that device.

Practical consequences when you read the screen:

- **Last seen is a heartbeat clock, not an activity clock.** An idle but
  powered-on device refreshes `last_seen` every 15 s even though nobody
  is using it.
- **Offline is inferred, not reported.** A client that dies without a
  clean shutdown looks online until its live state lapses, so expect
  offline to trail reality by roughly one heartbeat interval.
- **`activeConns > 0` with `online` false is a stale-state smell.** It
  means connections were registered for a device whose heartbeat has
  since lapsed.

The page subtitle states the device count and reminds you that online
status is live from the heartbeat, precisely because the rest of the row
is not.

## Searching and paging the registry

The search box matches on **device id, hostname, or user**. Searching is
explicit: typing filters nothing until you submit the form (press Enter
or click **Search**). The submitted term is what goes to the server as
`search` — the input value on its own has no effect on the query.

Search runs server-side over the whole registry, then the screen
requests a page of results (`limit`, `offset`). The console screen asks
for the first 100 matches. `total` is returned alongside the page and is
what the header count reflects.

Empty results are disambiguated on purpose:

- **Still loading** — "Loading…", while the directory request is in
  flight.
- **A search with no hits** — names the term you searched for and
  suggests a different id, hostname or user, or clearing the search.
  This is *not* the same as having no fleet.
- **A genuinely empty registry** — "No devices". Devices appear only
  once a QuickDesk client heartbeats in, so a fresh deployment shows
  this until the first client checks in.

## Force-disconnect: what it closes and when it takes effect

**Disconnect** calls `devices.disconnect` with the device's `id`. It is
a session-level action, not a device-level block:

1. The device's currently active connection ids are queued into a **kick
   set**.
2. The client heartbeat **drains** that set on its next check-in and
   tears down the listed connections.
3. The console invalidates the device list, so the row refetches and
   `activeConns` / `online` re-render from fresh live state.

What that means in practice:

- **It closes live remote-control sessions, nothing else.** It does not
  delete the registry row, does not unregister the device, and does not
  stop the device from being connected to again. The device keeps
  heartbeating and stays in the list.
- **It is not instantaneous.** The kick is queued and collected by the
  client on heartbeat, so it lands within about one heartbeat interval
  (15 s) rather than at the moment you click. A row that still shows a
  connection immediately after the click has not necessarily failed.
- **It only targets connections that were active when you clicked.**
  Connection ids queued for the kick are the ones live at that moment. A
  session established after the queueing is not in the set.
- **The button is disabled when there is nothing to close.** It is
  greyed out when `activeConns` is `0`, and while a disconnect mutation
  is in flight, so a double-click cannot queue the same kick twice.

Because the effect arrives on the device's own heartbeat, a device that
is offline (heartbeat lapsed) will not process a kick until it comes
back. Force-disconnect is a way to end a session, not a way to guarantee
a device stays unreachable.

## Tagging devices for filtering

Each row has a tag cell, backed by the shared tagging system with
resource kind `quickdesk_device`. Tags are attached to the device's
`guid` and scoped to the current org, so the same device id in a
different org carries its own tags.

The tag filter in the page header narrows the table **client-side**: it
filters the rows already fetched, and reports how many of them matched
out of the total fetched. Two things follow from that:

- Tag filtering composes with search, but it applies *after* the search
  and after paging. A tagged device that did not come back in the
  fetched page cannot be surfaced by the tag filter alone — narrow with
  search first.
- The matched/total readout in the filter refers to the rows on screen,
  not to the registry-wide `total` in the page subtitle.

Editing a tag on a row refreshes the tag data for the visible devices,
so a newly added tag becomes filterable without reloading the screen.
