import { t } from "../../lib/console-text"; import { copyTextToClipboard } from "../../../clipboard"; import type { MachineView } from "../../../machines"; import { machineDisplayName, machineScopeLabel } from "../../../sandbox-label-model"; import { CheckIcon, CopyIcon, ExternalLinkIcon, LaptopIcon, Loader2Icon, ServerIcon, UserIcon, UsersIcon, } from "lucide-react"; import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react"; import { Button } from "../../../ui/button"; import { ChoiceCard, ChoiceCards } from "../../../ui/choice-cards"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../../../ui/dialog"; import { Disclosure } from "../../../ui/disclosure"; import { LogoTile } from "../../../ui/logo-tile"; import { Notice } from "../../../ui/notice"; import { SegmentedControl } from "../../../ui/segmented-control"; import { StatusBadge } from "../../../ui/status-badge"; import { Switch } from "../../../ui/switch"; import { useAppContext } from "../../app-context"; import { userErrorText } from "../../../ui/api-error"; import { detectConnectOs, deviceVerificationUri, installCommandFor, type ConnectOs, } from "../../lib/deployment"; import { isMachineComputeSelectable } from "../../lib/machine-selectability"; import { computerConnectUrl } from "@opengeni/sdk/app-links"; import { desktopDownloadsFor, desktopDownloadUrl } from "../../lib/desktop-download"; /** Who can use the machine: the person connecting it, or the whole workspace. */ export type ConnectMachineScope = "user" | "workspace"; /** * The choice that reconnects a machine without changing who can use it. A * command cannot carry organization access, so an organization machine maps to * This workspace (pairing with a code is the way to keep organization access). */ export function connectScopeFor(machine: Pick): ConnectMachineScope { return machine.scope === "user" ? "user" : "workspace"; } /** The choices a connect command is minted for. Both are baked into the token. */ export type ConnectChoice = { scope: ConnectMachineScope; allowScreenControl: boolean }; export type MintedConnectToken = ConnectChoice & { /** The workspace it was minted for; a command is never shown for another. */ workspaceId: string; value: string; expiresAt: string; expiresInSeconds: number; }; const OS_OPTIONS = [ { value: "macos", label: "Mac" }, { value: "linux", label: "Linux" }, { value: "windows", label: "Windows" }, ] as const satisfies readonly { value: ConnectOs; label: string }[]; const PASTE_HINT: Record = { macos: "Paste this into Terminal on the Mac you want to connect.", linux: "Paste this into a terminal on the Linux machine you want to connect.", windows: "Paste this into PowerShell on the Windows PC you want to connect.", }; function sameChoice(left: ConnectChoice, right: ConnectChoice): boolean { return left.scope === right.scope && left.allowScreenControl === right.allowScreenControl; } /** * Mints a single-use connect command in this browser for the current choices. * Every request remembers the choices it was made for and only the latest * request may settle, so a slow response for an earlier choice never shows. * The command is usable only while its token matches the current choices, and * the server's echo of what it granted is checked rather than trusted blindly. */ export function useConnectToken(workspaceId: string, choice: ConnectChoice) { const { client } = useAppContext(); // Minting follows the choices; a new client identity on re-render never mints. const clientRef = useRef(client); clientRef.current = client; const [token, setToken] = useState(null); const [pending, setPending] = useState(null); const [error, setError] = useState(null); const [attempt, setAttempt] = useState(0); const { scope, allowScreenControl } = choice; useEffect(() => { const requested = { scope, allowScreenControl }; let current = true; setPending(requested); setError(null); void (async () => { try { const result = await clientRef.current.mintEnrollToken(workspaceId, requested); if (!current) return; // A server that predates scoped commands omits the echo and always // mints a workspace-shared token. const granted: ConnectChoice = { scope: result.scope ?? "workspace", allowScreenControl: result.allowScreenControl ?? requested.allowScreenControl, }; if (!sameChoice(granted, requested)) { setToken(null); setError( requested.scope === "user" ? "This server can't connect a machine for you only yet. Choose This workspace, or ask an admin to update Opengeni." : "The command didn't match your choices. Try again.", ); return; } setToken({ ...granted, workspaceId, value: result.token, expiresAt: result.expiresAt, expiresInSeconds: result.expiresInSeconds, }); } catch (failure) { if (!current) return; setToken(null); setError(userErrorText(failure, "Try again.")); } finally { if (current) setPending(null); } })(); return () => { current = false; }; }, [workspaceId, scope, allowScreenControl, attempt]); const matching = token && token.workspaceId === workspaceId && sameChoice(token, choice) && pending === null ? token : null; const retry = useCallback(() => setAttempt((value) => value + 1), []); return { token: matching, minting: pending !== null, error, retry }; } /** "2:05 PM" for today, otherwise a date and time; relative when unparseable. */ export function formatConnectExpiry(expiresAt: string, expiresInSeconds: number): string { const at = new Date(expiresAt); if (Number.isNaN(at.getTime())) { return `about ${Math.max(1, Math.round(expiresInSeconds / 60))} min from now`; } const sameDay = at.toDateString() === new Date().toDateString(); return sameDay ? at.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }) : at.toLocaleString([], { dateStyle: "medium", timeStyle: "short" }); } function connectableMachines(machines: readonly MachineView[]): MachineView[] { return machines.filter((machine) => !machine.isSessionGroup && machine.enrollmentId); } /** * The machine that came online while the dialog was open: one that is new, or * was unreachable when the dialog opened. A machine with the chosen access wins * when several qualify. The first one found stays the answer. The baseline is * the first list that actually loaded: a failed load shows no machines, and * every existing machine would otherwise look new once a later load succeeds. */ function useNewlyConnectedMachine( machines: readonly MachineView[], loading: boolean, loadFailed: boolean, scope: ConnectMachineScope, ): MachineView | null { const fleet = useMemo(() => connectableMachines(machines), [machines]); const [known, setKnown] = useState | null>(null); const [connectedId, setConnectedId] = useState(null); useEffect(() => { if (known !== null || loading || loadFailed) return; setKnown( new Map( fleet.map((machine) => [machine.sandboxId, isMachineComputeSelectable(machine.state)]), ), ); }, [fleet, known, loading, loadFailed]); useEffect(() => { if (!known || connectedId !== null) return; const fresh = fleet.filter( (machine) => isMachineComputeSelectable(machine.state) && known.get(machine.sandboxId) !== true, ); const chosen = fresh.find((machine) => machine.scope === scope) ?? fresh[0]; if (chosen) setConnectedId(chosen.sandboxId); }, [connectedId, fleet, known, scope]); return connectedId ? (fleet.find((machine) => machine.sandboxId === connectedId) ?? null) : null; } export type ConnectMachineDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; workspaceId: string; /** The deployment origin the commands dial (the API origin, else this page). */ origin: string; /** Return to this chat after opening the installed app. */ sessionId?: string | undefined; /** The workspace's machines, polled by the host while the dialog is open. */ machines: readonly MachineView[]; machinesLoading: boolean; /** The latest machines load failure, if any. Success waits for a good load. */ machinesError?: unknown | undefined; /** A personal workspace: "This workspace" is still only its owner. */ personalWorkspace?: boolean | undefined; /** Who can use it, to start with (a reconnect keeps the machine's access). */ defaultScope?: ConnectMachineScope | undefined; /** Screen control's starting value (on when reconnecting to turn it on). */ defaultAllowScreenControl?: boolean | undefined; /** Called once when a machine comes online while the dialog is open. */ onConnected?: ((machine: MachineView) => void) | undefined; /** * From a chat: finish by moving the chat onto the new machine through the * existing attach flow. Without it, the dialog finishes with Done. */ useInChat?: { canUse: boolean; onUse: (machine: MachineView) => void } | undefined; }; /** * Connect a machine: choose who can use it and whether agents may control its * screen, copy one command for the machine's OS, and stay here until the * machine connects. Shared by the Machines page and the chat's machine card. */ export function ConnectMachineDialog({ open, onOpenChange, ...body }: ConnectMachineDialogProps) { return ( {/* Mounted only while open, so each opening mints a fresh command. Keyed by workspace: a switch (browser back or forward) starts over, so the other workspace's machines never look newly connected. */} {open ? ( onOpenChange(false)} /> ) : null} ); } function ConnectMachineBody({ workspaceId, sessionId, origin, machines, machinesLoading, machinesError = null, personalWorkspace = false, defaultScope = "user", defaultAllowScreenControl = false, onConnected, useInChat, onClose, }: Omit & { onClose: () => void }) { const [scope, setScope] = useState(defaultScope); const [allowScreenControl, setAllowScreenControl] = useState(defaultAllowScreenControl); const [advanced, setAdvanced] = useState(defaultAllowScreenControl); const connected = useNewlyConnectedMachine( machines, machinesLoading, machinesError !== null && machinesError !== undefined, scope, ); const onConnectedRef = useRef(onConnected); onConnectedRef.current = onConnected; const reported = useRef(null); useEffect(() => { if (!connected || reported.current === connected.sandboxId) return; reported.current = connected.sandboxId; onConnectedRef.current?.(connected); }, [connected]); if (connected) { return ; } return ( <> Use your computer {advanced ? "For a server without the desktop app, run the command below on that server." : "Install the desktop app, then open it from here to connect to this workspace. Only you can use your computer."} {!advanced ? ( ) : null} {advanced ? ( ) : null} ); } function DesktopConnectSetup({ workspaceId, sessionId, origin, }: { workspaceId: string; sessionId?: string | undefined; origin: string; }) { const { accessContext } = useAppContext(); const userAgent = typeof navigator === "undefined" ? undefined : navigator.userAgent; const [os, setOs] = useState(() => detectConnectOs(userAgent)); const [handoffAttempted, setHandoffAttempted] = useState(false); const mobile = /android|iphone|ipad|ipod/i.test(userAgent ?? ""); const subjectId = accessContext?.subjectId; const webUrl = new URL( `/workspaces/${encodeURIComponent(workspaceId)}${sessionId ? `/sessions/${encodeURIComponent(sessionId)}` : ""}`, origin, ).toString(); const appUrl = subjectId ? computerConnectUrl({ webUrl, subjectId }) : null; return (
{mobile ? ( Open this chat on the computer you want to connect. The desktop app doesn't run on phones or tablets. ) : null}
{desktopDownloadsFor(os, userAgent).map((download, index) => ( ))}
{os === "linux" ? (

Choose .deb for Ubuntu or Debian, .rpm for Fedora, or AppImage for other Linux desktops.

) : null}

After installing, return here and open the app. It will ask you to sign in if needed, then confirm this computer's access. You won't need a setup code.

{appUrl && !mobile ? ( ) : !subjectId ? (

Sign in to connect this computer to your account.

) : null} {handoffAttempted ? ( If the app doesn't open or opens without connecting, install the latest app using a download above, replacing the old version. Then return here and choose Open app and connect again. ) : null}
); } function ConnectSetup({ workspaceId, origin, personalWorkspace, scope, onScopeChange, allowScreenControl, onAllowScreenControlChange, }: { workspaceId: string; origin: string; personalWorkspace: boolean; scope: ConnectMachineScope; onScopeChange: (scope: ConnectMachineScope) => void; allowScreenControl: boolean; onAllowScreenControlChange: (allow: boolean) => void; }) { const [os, setOs] = useState(() => detectConnectOs(typeof navigator === "undefined" ? undefined : navigator.userAgent), ); const screenControlId = useId(); const screenControlHintId = useId(); const choice = useMemo(() => ({ scope, allowScreenControl }), [scope, allowScreenControl]); const { token, minting, error, retry } = useConnectToken(workspaceId, choice); const command = token ? installCommandFor(os, origin, { enrollToken: token.value }) : ""; // A copy result belongs to the exact command copied. A later command (other // choices, OS or token) never shows as copied, even when the clipboard // answers after the change. const [copyResult, setCopyResult] = useState<{ command: string; ok: boolean } | null>(null); const shownCopy = command !== "" && copyResult?.command === command ? copyResult : null; const copied = shownCopy?.ok === true; const copyFailed = shownCopy?.ok === false; async function copy() { const copying = command; if (!copying) return; const ok = await copyTextToClipboard(copying); setCopyResult({ command: copying, ok }); } return (
onScopeChange(value as ConnectMachineScope)} > } title="Only me" description="Just you, in every workspace you can open. Nobody else can use it." /> } title="This workspace" description={ personalWorkspace ? "Only in this personal workspace." : "Everyone in this workspace can run chats on it." } /> {/* Screen control is baked into the command, so it is chosen before copying. */}
onAllowScreenControlChange(checked)} />

{PASTE_HINT[os]}

{error ? ( Try again } > {error} ) : (
{token ? (
                {command}
              
) : (

Preparing your command…

)}
)}
{copyFailed ? (

Couldn't copy automatically. Select the command and copy it.

) : null} {/* Only with a command: while preparing or after a failure, Try again is the one action. */} {token ? (
{copied ? ( Waiting for the machine to connect… ) : ( {`Works for one machine until ${formatConnectExpiry(token.expiresAt, token.expiresInSeconds)}. Keep it private.`} )}
) : null}
); } /** * The device-code path for a terminal that cannot take a pasted command. The * installer prints a code; the approval page asks who can use the machine. */ function ManualPairing({ workspaceId, origin, os, }: { workspaceId: string; origin: string; os: ConnectOs; }) { const command = installCommandFor(os, origin, { workspaceId }); const verificationUri = deviceVerificationUri(origin); // Copied only while the copied command is the one shown (a late clipboard // answer for another OS never marks this one). const [copiedCommand, setCopiedCommand] = useState(null); const copied = copiedCommand === command; return (
  1. Run this on the machine. It prints a short code.
    {command}
  2. Enter the code on the approval page. You choose who can use the machine there.
); } function ConnectedStep({ machine, useInChat, onClose, }: { machine: MachineView; useInChat: ConnectMachineDialogProps["useInChat"]; onClose: () => void; }) { const name = machineDisplayName(t, machine); const Icon = machine.os === "linux" && !machine.hasDisplay ? ServerIcon : LaptopIcon; const canUse = Boolean(useInChat?.canUse); return ( <> Machine connected {canUse ? "Use it for this chat now, or pick it later from the machine list." : "It's ready to run chats."}
} />
{name} · {machineScopeLabel(t, machine.scope)}
{canUse ? ( <> ) : ( )} ); }