import { Tooltip, TooltipTrigger, TooltipPopup } from "../ui/tooltip"; import { AppleIcon, AndroidIcon } from "../Icons"; import { DeviceHostAvailability } from "../device/DeviceHostAvailability"; import { Spinner } from "../ui/spinner"; import type { DevicePlatformAvailability, EnvironmentId, SshDeviceHostConfig, } from "@t3tools/contracts"; import * as Cause from "effect/Cause"; import { randomUUID } from "../../lib/utils"; import { useState } from "react"; import { deviceEnvironment, useDeviceState } from "../../state/device"; import { serverEnvironment } from "../../state/server"; import { useAtomCommand } from "../../state/use-atom-command"; import { Button } from "../ui/button"; import { Input } from "../ui/input"; import { MoreVertical, PlusIcon } from "lucide-react"; import { Menu, MenuTrigger, MenuPopup, MenuItem } from "../ui/menu"; import { SettingsRow } from "./settingsLayout"; /** Host names and identity paths belong to the selected environment, never all environments. */ export function DeviceHostsSettings(props: { environmentId: EnvironmentId | null; hosts: ReadonlyArray; }) { const update = useAtomCommand(serverEnvironment.updateSettings); const test = useAtomCommand(deviceEnvironment.testHost, { reportFailure: false }); const { state } = useDeviceState(props.environmentId); const [editing, setEditing] = useState(null); const [busy, setBusy] = useState(false); const validPort = (port: number | undefined) => port === undefined || (Number.isInteger(port) && port >= 1 && port <= 65535); const [checks, setChecks] = useState< Record< string, { pending?: boolean; platforms?: ReadonlyArray; error?: string } > >({}); const setCheck = (id: string, value: (typeof checks)[string]) => setChecks((current) => ({ ...current, [id]: value })); const save = async (hosts: ReadonlyArray) => { if (!props.environmentId) return; setBusy(true); try { const saved = await update({ environmentId: props.environmentId, input: { patch: { deviceHosts: hosts } }, }); if (saved._tag === "Success") { setEditing(null); } } finally { setBusy(false); } }; const testConnection = async (host: SshDeviceHostConfig) => { if (!props.environmentId || checks[host.id]?.pending) return; setCheck(host.id, { pending: true }); try { const summary = await test({ environmentId: props.environmentId, input: host }); setCheck( host.id, summary._tag === "Failure" ? { error: Cause.pretty(summary.cause) } : { platforms: summary.value.platforms }, ); } catch (error) { setCheck(host.id, { error: error instanceof Error ? error.message : String(error) }); } }; return ( { setEditing({ id: randomUUID(), label: "", target: "" }); }} > Add host } >
{!props.environmentId ? (

Select one connected environment to manage its device hosts.

) : ( <> {props.hosts.map((host) => { const status = state.hostStatuses[host.id]; const check = checks[host.id]; const platforms = check?.platforms ?? state.hosts.find((value) => value.id === host.id)?.platforms ?? []; const progress = check?.pending ? "Checking connection…" : status?.status === "installing" ? "Installing device support…" : status?.status === "starting" ? "Connecting…" : null; const error = check?.error ?? (status?.status === "failed" ? status.detail : undefined); return (

{host.label}

{platforms .filter((platform) => platform.available) .map((platform) => ( } > {platform.platform === "ios" ? ( ) : ( )} {platform.platform === "ios" ? "iOS available" : "Android available"} ))}

{host.target}

{error ? (
Connection failed

{error}

) : null}
{progress ? ( {progress} ) : null} } > { setEditing(host); }} > Edit void save(props.hosts.filter((value) => value.id !== host.id)) } > Remove
); })} {editing ? (
{ event.preventDefault(); void save([...props.hosts.filter((host) => host.id !== editing.id), editing]); }} >
{checks[editing.id]?.pending ? ( Checking connection… ) : null} {checks[editing.id]?.platforms ? ( ) : null} {checks[editing.id]?.error ? (

{checks[editing.id]?.error}

) : null} ) : null} )}
); }