ae51276a03
Web console - Pairing/Library/Stats refactored into self-contained subsections that each own their own queries + mutations; a shared slot-based layout (view.tsx) is filled by the live page (containers) and Storybook (pure cards + fixtures) so the layout can't drift. - All paired devices in one list on Pairing with a protocol column (punktfunk/1 + Moonlight), routing each unpair to the right endpoint; the redundant Clients page is removed. - Library: overview grid split from the add/edit form into separate files. - Login screen links out to the docs. Docs - "Console login password" section on every host page (apt/RPM/Bazzite/SteamOS/Windows) plus a new "Forgot your Password?" troubleshooting page, linked from the login screen. - Console served as HTTP/1.1 over TLS (drop the unusable HTTP/3 advertising) across the Bun entry, launchers, systemd units, and packaging. Tooling - Biome now respects .gitignore (stops linting generated code), config migrated to 2.5.1; all lint issues fixed cleanly. Also includes this branch's in-progress host, Apple client, packaging, and CI changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
242 lines
5.9 KiB
TypeScript
242 lines
5.9 KiB
TypeScript
// Mock API payloads for the page stories — typed against the generated models so
|
|
// they stay honest if the OpenAPI schema changes.
|
|
import type { AvailableCompositor } from "@/api/gen/model/availableCompositor";
|
|
import type { Capture } from "@/api/gen/model/capture";
|
|
import type { CaptureMeta } from "@/api/gen/model/captureMeta";
|
|
import type { GameEntry } from "@/api/gen/model/gameEntry";
|
|
import type { HostInfo } from "@/api/gen/model/hostInfo";
|
|
import type { NativeClient } from "@/api/gen/model/nativeClient";
|
|
import type { NativePairStatus } from "@/api/gen/model/nativePairStatus";
|
|
import type { PairedClient } from "@/api/gen/model/pairedClient";
|
|
import type { PairingStatus } from "@/api/gen/model/pairingStatus";
|
|
import type { PendingDevice } from "@/api/gen/model/pendingDevice";
|
|
import type { RuntimeStatus } from "@/api/gen/model/runtimeStatus";
|
|
import type { StatsSample } from "@/api/gen/model/statsSample";
|
|
import type { StatsStatus } from "@/api/gen/model/statsStatus";
|
|
|
|
export const hostInfo: HostInfo = {
|
|
abi_version: 2,
|
|
app_version: "7.1.450.0",
|
|
codecs: ["h264", "h265", "av1"],
|
|
gfe_version: "3.23.0.74",
|
|
hostname: "ENRICOS-DESKTOP",
|
|
local_ip: "192.168.1.173",
|
|
ports: {
|
|
audio: 48000,
|
|
control: 47999,
|
|
http: 47989,
|
|
https: 47984,
|
|
mgmt: 47990,
|
|
rtsp: 48010,
|
|
video: 47998,
|
|
},
|
|
uniqueid: "0f8a1c3e9b7d4a62",
|
|
version: "0.2.0",
|
|
};
|
|
|
|
export const compositors: AvailableCompositor[] = [
|
|
{ id: "kwin", label: "KWin (Plasma)", available: true, default: true },
|
|
{ id: "gamescope", label: "gamescope", available: true, default: false },
|
|
{ id: "mutter", label: "Mutter (GNOME)", available: false, default: false },
|
|
{ id: "wlroots", label: "Sway / wlroots", available: false, default: false },
|
|
];
|
|
|
|
export const statusActive: RuntimeStatus = {
|
|
video_streaming: true,
|
|
audio_streaming: true,
|
|
paired_clients: 3,
|
|
pin_pending: false,
|
|
session: { width: 5120, height: 1440, fps: 240 },
|
|
stream: {
|
|
codec: "h265",
|
|
width: 5120,
|
|
height: 1440,
|
|
fps: 240,
|
|
bitrate_kbps: 150_000,
|
|
min_fec: 5,
|
|
packet_size: 1392,
|
|
},
|
|
};
|
|
|
|
export const statusIdle: RuntimeStatus = {
|
|
video_streaming: false,
|
|
audio_streaming: false,
|
|
paired_clients: 1,
|
|
pin_pending: true,
|
|
session: null,
|
|
stream: null,
|
|
};
|
|
|
|
export const pairedClients: PairedClient[] = [
|
|
{
|
|
fingerprint:
|
|
"a1b2c3d4e5f60718293a4b5c6d7e8f90112233445566778899aabbccddeeff00",
|
|
subject: "enricos-macbook",
|
|
not_before_unix: 1_718_000_000,
|
|
not_after_unix: 2_030_000_000,
|
|
},
|
|
{
|
|
fingerprint:
|
|
"ff00eeddccbbaa998877665544332211009f8e7d6c5b4a39281706f5e4d3c2b1",
|
|
subject: "living-room-tv",
|
|
not_before_unix: 1_718_500_000,
|
|
not_after_unix: 2_030_000_000,
|
|
},
|
|
{
|
|
fingerprint:
|
|
"0011223344556677889900aabbccddeeff112233445566778899aabbccddeeff",
|
|
subject: null,
|
|
},
|
|
];
|
|
|
|
const noArt = { header: null, hero: null, logo: null, portrait: null };
|
|
export const library: GameEntry[] = [
|
|
{
|
|
id: "steam:1245620",
|
|
store: "steam",
|
|
title: "Elden Ring",
|
|
art: noArt,
|
|
launch: null,
|
|
},
|
|
{
|
|
id: "steam:1086940",
|
|
store: "steam",
|
|
title: "Baldur's Gate 3",
|
|
art: noArt,
|
|
launch: null,
|
|
},
|
|
{
|
|
id: "steam:413150",
|
|
store: "steam",
|
|
title: "Stardew Valley",
|
|
art: noArt,
|
|
launch: null,
|
|
},
|
|
{
|
|
id: "custom:retroarch",
|
|
store: "custom",
|
|
title: "RetroArch",
|
|
art: noArt,
|
|
launch: null,
|
|
},
|
|
];
|
|
|
|
// --- Performance (stats) page ------------------------------------------------
|
|
|
|
export const statsStatusIdle: StatsStatus = {
|
|
armed: false,
|
|
kind: "native",
|
|
sample_count: 0,
|
|
started_unix_ms: 0,
|
|
};
|
|
|
|
// A native-path pipeline: capture → submit → encode → send. Deterministic (no
|
|
// Math.random) so the screenshot is byte-stable across CI runs; a gentle sine
|
|
// gives the charts a realistic shape without a live capture.
|
|
const STAGE_BASE_US: Record<string, number> = {
|
|
capture: 320,
|
|
submit: 90,
|
|
encode: 760,
|
|
send: 140,
|
|
};
|
|
const STAGE_ORDER = ["capture", "submit", "encode", "send"];
|
|
|
|
function buildSamples(n: number): StatsSample[] {
|
|
const out: StatsSample[] = [];
|
|
for (let i = 0; i < n; i++) {
|
|
const wobble = Math.sin(i / 4);
|
|
out.push({
|
|
t_ms: i * 1000,
|
|
session_id: 1,
|
|
fps: 240,
|
|
repeat_fps: i % 3 === 0 ? 2 : 1,
|
|
mbps: 920 + wobble * 55,
|
|
bitrate_kbps: 150_000,
|
|
frames_dropped: i % 17 === 0 ? 1 : 0,
|
|
packets_dropped: i % 9 === 0 ? 2 : 0,
|
|
send_dropped: 0,
|
|
fec_recovered: i % 5 === 0 ? 3 : 1,
|
|
stages: STAGE_ORDER.map((name) => {
|
|
const base = STAGE_BASE_US[name] ?? 100;
|
|
const p50 = Math.round(base + wobble * base * 0.15);
|
|
return { name, p50_us: p50, p99_us: Math.round(p50 * 1.8) };
|
|
}),
|
|
});
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export const captureMetas: CaptureMeta[] = [
|
|
{
|
|
id: "cap-20260628-2041",
|
|
client: "enricos-macbook",
|
|
kind: "native",
|
|
codec: "h265",
|
|
width: 5120,
|
|
height: 1440,
|
|
fps: 240,
|
|
duration_ms: 92_000,
|
|
sample_count: 92,
|
|
started_unix_ms: 1_782_415_260_000,
|
|
},
|
|
{
|
|
id: "cap-20260628-1903",
|
|
client: "living-room-tv",
|
|
kind: "gamestream",
|
|
codec: "av1",
|
|
width: 3840,
|
|
height: 2160,
|
|
fps: 120,
|
|
duration_ms: 240_000,
|
|
sample_count: 240,
|
|
started_unix_ms: 1_782_409_380_000,
|
|
},
|
|
];
|
|
|
|
export const captureDetail: Capture = {
|
|
meta: captureMetas[0] as CaptureMeta,
|
|
samples: buildSamples(60),
|
|
};
|
|
|
|
// --- Pairing page ------------------------------------------------------------
|
|
|
|
export const nativePairArmed: NativePairStatus = {
|
|
enabled: true,
|
|
armed: true,
|
|
pin: "4827",
|
|
expires_in_secs: 98,
|
|
paired_clients: 2,
|
|
};
|
|
|
|
export const pendingDevices: PendingDevice[] = [
|
|
{
|
|
id: 1,
|
|
name: "studio-deck",
|
|
fingerprint:
|
|
"9f8e7d6c5b4a39281706f5e4d3c2b1a0998877665544332211ffeeddccbbaa00",
|
|
age_secs: 8,
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "Mac Mini",
|
|
fingerprint:
|
|
"ff00eeddccbbaa998877665544332211009f8e7d6c5b4a39281706f5e4d3c2b1",
|
|
age_secs: 30,
|
|
},
|
|
];
|
|
|
|
export const nativeClients: NativeClient[] = [
|
|
{
|
|
name: "enricos-macbook",
|
|
fingerprint:
|
|
"a1b2c3d4e5f60718293a4b5c6d7e8f90112233445566778899aabbccddeeff00",
|
|
},
|
|
{
|
|
name: "living-room-tv",
|
|
fingerprint:
|
|
"ff00eeddccbbaa998877665544332211009f8e7d6c5b4a39281706f5e4d3c2b1",
|
|
},
|
|
];
|
|
|
|
export const pairingIdle: PairingStatus = { pin_pending: false };
|