Add link session preparation flow

- wire front link resolve/prepare routes
- add orchestrator session command handling
- update admin dashboards and device/link logic
This commit is contained in:
user
2026-03-28 17:47:03 +02:00
parent 5da61ed853
commit 0a11be5006
20 changed files with 1099 additions and 175 deletions

View File

@@ -27,7 +27,7 @@ function buildStreamUrl(host: string, wsPort: string): string | null {
const baseWss = WS_SCRCPY_URL.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
const wsParam =
`${baseWss}/?action=proxy-adb&remote=tcp:${SCRCPY_SERVER_PORT}&udid=${encodeURIComponent(udid)}`;
`${baseWss}/?action=proxy-adb&remote=${encodeURIComponent(`tcp:${SCRCPY_SERVER_PORT}`)}&udid=${encodeURIComponent(udid)}`;
const hash =
`#!action=stream&udid=${encodeURIComponent(udid)}&player=mse&ws=${encodeURIComponent(wsParam)}`;

View File

@@ -26,10 +26,27 @@ class LinkViewModel {
revokingId = $state<number | null>(null);
showCreateDialog = $state(false);
/**
* SvelteKit's query() caches the RemoteQuery object in an internal query_map.
* Subsequent calls to listLinksSQ() return the same cached Query with stale data.
* We must call refresh() on the cached query to force a fresh server request.
*/
private _linksQuery: ReturnType<typeof listLinksSQ> | null = null;
async fetchLinks() {
this.loading = true;
try {
const result = await listLinksSQ();
const query = listLinksSQ();
// On subsequent calls the query is cached and stale — refresh it
if (query.ready) {
await query.refresh();
}
// After refresh, read .current; on first load, await the initial fetch
const result = query.ready ? query.current : await query;
this._linksQuery = query;
if (result?.error || !result?.data) {
toast.error(
result?.error?.message || "Failed to fetch links",

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { page } from "$app/state";
import Icon from "$lib/components/atoms/icon.svelte";
import MaxWidthWrapper from "$lib/components/molecules/max-width-wrapper.svelte";
import { Badge } from "$lib/components/ui/badge";
@@ -6,12 +7,11 @@
import * as Card from "$lib/components/ui/card";
import { Separator } from "$lib/components/ui/separator";
import { Skeleton } from "$lib/components/ui/skeleton";
import { mainNavTree } from "$lib/core/constants";
import DeviceForm from "$lib/domains/device/device-form.svelte";
import { mainNavTree, WS_SCRCPY_URL } from "$lib/core/constants";
import { deviceDetailsVM } from "$lib/domains/device/device-details.vm.svelte";
import DeviceForm from "$lib/domains/device/device-form.svelte";
import { deviceVM } from "$lib/domains/device/device.vm.svelte";
import { breadcrumbs } from "$lib/global.stores";
import { page } from "$app/state";
import ExternalLink from "@lucide/svelte/icons/external-link";
import MonitorSmartphone from "@lucide/svelte/icons/monitor-smartphone";
import RefreshCw from "@lucide/svelte/icons/refresh-cw";
@@ -29,11 +29,9 @@
let editWsPort = $state("");
let editIsActive = $state(false);
let editInUse = $state(false);
let editStatus = $state("offline" as
| "online"
| "offline"
| "busy"
| "error");
let editStatus = $state(
"offline" as "online" | "offline" | "busy" | "error",
);
function formatDate(value: Date | string | null | undefined): string {
if (!value) return "—";
@@ -133,7 +131,8 @@
<div class="flex items-center gap-2">
<Icon icon={Smartphone} cls="h-5 w-5 text-primary" />
<Card.Title>
{currentDevice?.title || `Device #${page.params.id}`}
{currentDevice?.title ||
`Device #${page.params.id}`}
</Card.Title>
</div>
@@ -141,7 +140,9 @@
<div
class="text-muted-foreground flex flex-wrap items-center gap-2 text-sm"
>
<Badge variant={statusVariant(currentDevice.status)}>
<Badge
variant={statusVariant(currentDevice.status)}
>
{currentDevice.status}
</Badge>
<span>
@@ -226,7 +227,10 @@
<Card.Root>
<Card.Header>
<div class="flex items-center gap-2">
<Icon icon={Server} cls="h-4 w-4 text-primary" />
<Icon
icon={Server}
cls="h-4 w-4 text-primary"
/>
<Card.Title class="text-base">
Metadata
</Card.Title>
@@ -235,27 +239,45 @@
<Card.Content class="space-y-4">
<div class="grid gap-3 text-sm">
<div>
<p class="text-muted-foreground text-xs">Device ID</p>
<p class="font-medium">{currentDevice.id}</p>
<p class="text-muted-foreground text-xs">
Device ID
</p>
<p class="font-medium">
{currentDevice.id}
</p>
</div>
<div>
<p class="text-muted-foreground text-xs">Host</p>
<p class="text-muted-foreground text-xs">
Host
</p>
<p class="break-all font-mono text-xs">
{currentDevice.host}
</p>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<p class="text-muted-foreground text-xs">WS Port</p>
<p
class="text-muted-foreground text-xs"
>
WS Port
</p>
<p>{currentDevice.wsPort}</p>
</div>
<div>
<p class="text-muted-foreground text-xs">In Use</p>
<p>{currentDevice.inUse ? "Yes" : "No"}</p>
<p
class="text-muted-foreground text-xs"
>
In Use
</p>
<p>
{currentDevice.inUse ? "Yes" : "No"}
</p>
</div>
</div>
<div>
<p class="text-muted-foreground text-xs">Container ID</p>
<p class="text-muted-foreground text-xs">
Container ID
</p>
<p class="break-all font-mono text-xs">
{currentDevice.containerId}
</p>
@@ -267,12 +289,28 @@
<div class="grid gap-3 text-sm">
<div class="grid grid-cols-2 gap-3">
<div>
<p class="text-muted-foreground text-xs">Created</p>
<p class="text-xs">{formatDate(currentDevice.createdAt)}</p>
<p
class="text-muted-foreground text-xs"
>
Created
</p>
<p class="text-xs">
{formatDate(
currentDevice.createdAt,
)}
</p>
</div>
<div>
<p class="text-muted-foreground text-xs">Updated</p>
<p class="text-xs">{formatDate(currentDevice.updatedAt)}</p>
<p
class="text-muted-foreground text-xs"
>
Updated
</p>
<p class="text-xs">
{formatDate(
currentDevice.updatedAt,
)}
</p>
</div>
</div>
<div>
@@ -280,7 +318,8 @@
Viewer URL
</p>
<p class="break-all font-mono text-xs">
{streamUrl || "Missing host configuration"}
{streamUrl ||
"Missing host configuration"}
</p>
</div>
</div>
@@ -290,16 +329,25 @@
<Card.Root>
<Card.Header>
<div class="flex items-center gap-2">
<Icon icon={RefreshCw} cls="h-4 w-4 text-primary" />
<Icon
icon={RefreshCw}
cls="h-4 w-4 text-primary"
/>
<Card.Title class="text-base">
Edit Device
</Card.Title>
</div>
<Card.Description>
Manually update connection details or override
<code class="bg-muted rounded px-1 text-xs">isActive</code>,
<code class="bg-muted rounded px-1 text-xs">inUse</code>, and
<code class="bg-muted rounded px-1 text-xs">status</code> when needed.
<code class="bg-muted rounded px-1 text-xs"
>isActive</code
>,
<code class="bg-muted rounded px-1 text-xs"
>inUse</code
>, and
<code class="bg-muted rounded px-1 text-xs"
>status</code
> when needed.
</Card.Description>
</Card.Header>
<Card.Content>
@@ -335,9 +383,9 @@
Live Device Session
</Card.Title>
</div>
{#if streamUrl}
<div class="flex items-center gap-1">
<a
href={streamUrl}
href={WS_SCRCPY_URL}
target="_blank"
rel="noreferrer"
class={buttonVariants({
@@ -345,10 +393,30 @@
size: "sm",
})}
>
<Icon icon={ExternalLink} cls="mr-1.5 h-3.5 w-3.5" />
Pop out
<Icon
icon={ExternalLink}
cls="mr-1.5 h-3.5 w-3.5"
/>
Device Viewer Home
</a>
{/if}
{#if streamUrl}
<a
href={streamUrl}
target="_blank"
rel="noreferrer"
class={buttonVariants({
variant: "ghost",
size: "sm",
})}
>
<Icon
icon={ExternalLink}
cls="mr-1.5 h-3.5 w-3.5"
/>
Pop out
</a>
{/if}
</div>
</div>
<Card.Description>
Full interactive device access is embedded here so