fix(web-console): the paired-clients card counts both pairing planes

GameStream and native (punktfunk/1) clients pair into separate stores, and
`/status` only ever reported the GameStream certificate count. Native is the
DEFAULT plane, so a host whose clients had all paired normally — and which
Moonlight had never touched — showed "0 paired" on the dashboard.

Report the native count alongside it, the way the tray's own summary route
already does, and sum the two in the card.

Closes #10

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-28 19:13:19 +02:00
co-authored by Claude Opus 5
parent 198b1d5e80
commit f98547c41f
4 changed files with 22 additions and 3 deletions
+8 -1
View File
@@ -6355,6 +6355,7 @@
"audio_streaming",
"pin_pending",
"paired_clients",
"native_paired_clients",
"active_sessions",
"games"
],
@@ -6376,10 +6377,16 @@
},
"description": "Every launched game the host is tracking: one row per live session that launched a title, plus\nany game whose session has ended and which is waiting out its reconnect window before being\nended (`state: \"grace\"`). Empty when nothing was launched — a plain desktop stream has no game."
},
"native_paired_clients": {
"type": "integer",
"format": "int32",
"description": "Number of paired native (punktfunk/1) devices — the default plane, so on a host that has\nnever been touched by Moonlight this is the only non-zero one of the pair.",
"minimum": 0
},
"paired_clients": {
"type": "integer",
"format": "int32",
"description": "Number of pinned (paired) client certificates.",
"description": "Number of pinned (paired) GameStream client certificates. Native (punktfunk/1) devices pair\nagainst a separate store and are counted in `native_paired_clients` — sum the two for\n\"how many clients are paired with this host\".",
"minimum": 0
},
"pin_pending": {
+7 -1
View File
@@ -100,8 +100,13 @@ pub(crate) struct RuntimeStatus {
/// True while a pairing handshake is parked waiting for the user's PIN
/// (submit it via `POST /api/v1/pair/pin`).
pin_pending: bool,
/// Number of pinned (paired) client certificates.
/// Number of pinned (paired) GameStream client certificates. Native (punktfunk/1) devices pair
/// against a separate store and are counted in `native_paired_clients` — sum the two for
/// "how many clients are paired with this host".
paired_clients: u32,
/// Number of paired native (punktfunk/1) devices — the default plane, so on a host that has
/// never been touched by Moonlight this is the only non-zero one of the pair.
native_paired_clients: u32,
/// Number of live streaming sessions across BOTH planes (GameStream + native punktfunk/1). The
/// native server admits concurrent sessions, so this can exceed 1; `session`/`stream` below
/// describe a single representative session for the detail card.
@@ -420,6 +425,7 @@ pub(crate) async fn get_status(State(st): State<Arc<MgmtState>>) -> Json<Runtime
.lock()
.unwrap_or_else(|e| e.into_inner())
.len() as u32,
native_paired_clients: st.native.as_ref().map_or(0, |n| n.status().paired_clients),
active_sessions: native.len() as u32 + u32::from(gs_video),
session,
stream,
+5 -1
View File
@@ -54,13 +54,17 @@ export const DashboardView: FC<{
label={m.status_audio()}
on={s.audio_streaming}
/>
{/* Both planes. GameStream and native (punktfunk/1) devices pair
into SEPARATE stores, and native is the DEFAULT one counting
only the GameStream certs read as "0 paired" on a host every
one of whose clients was in fact paired. */}
<Card>
<CardContent className="flex flex-1 items-center justify-between p-4 sm:pt-6">
<span className="text-sm text-muted-foreground">
{m.status_paired_count()}
</span>
<span className="text-2xl font-semibold tabular-nums">
{s.paired_clients}
{s.paired_clients + s.native_paired_clients}
</span>
</CardContent>
</Card>
+2
View File
@@ -46,6 +46,7 @@ export const statusActive: RuntimeStatus = {
video_streaming: true,
audio_streaming: true,
paired_clients: 3,
native_paired_clients: 2,
pin_pending: false,
active_sessions: 1,
session: { width: 5120, height: 1440, fps: 240 },
@@ -75,6 +76,7 @@ export const statusIdle: RuntimeStatus = {
video_streaming: false,
audio_streaming: false,
paired_clients: 1,
native_paired_clients: 0,
pin_pending: true,
active_sessions: 0,
session: null,