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:
@@ -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)}`;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user