1import {
2 type ConnectionTarget,
3 connectionRouteId,
4 connectionRouteLabel,
5 connectionRoutes,
6} from "@t3tools/client-runtime/connection";
7import type { DesktopSshEnvironmentTarget, EnvironmentMachineKind } from "@t3tools/contracts";
8import * as Option from "effect/Option";
9import type { ReactNode } from "react";
10
11import { cn } from "~/lib/utils";
12import type { EnvironmentPresentation } from "~/state/environments";
13import { isDesktopLocalConnectionTarget } from "~/connection/desktopLocal";
14import { EnvironmentMachineIcon } from "../EnvironmentMachineIcon";
15
16export function formatDesktopSshTarget(target: DesktopSshEnvironmentTarget): string {
17 const authority = target.username ? `${target.username}@${target.hostname}` : target.hostname;
18 return target.port ? `${authority}:${target.port}` : authority;
19}
20
21/**
22 * How this client reaches a machine, printed first in every environment row so
23 * T3 Connect, SSH, WSL, and plain remote links are told apart without a legend.
24 * A machine with several routes names the one in use, or how many it has.
25 */
26export function environmentTransportLabel(
27 environment: EnvironmentPresentation,
28 activeTarget: ConnectionTarget | null = null,
29): string {
30 const { entry } = environment;
31 if (entry.target._tag === "PrimaryConnectionTarget") return "This machine";
32 const routes = connectionRoutes(entry);
33 if (routes.length > 1) {
34 const active =
35 activeTarget === null
36 ? undefined
37 : routes.find(
38 (route) => connectionRouteId(route.target) === connectionRouteId(activeTarget),
39 );
40 return active === undefined
41 ? connectionRouteLabel(routes[0]!)
42 : `via ${connectionRouteLabel(active)}`;
43 }
44 if (environment.relayManaged) return "T3 Connect";
45 if (isDesktopLocalConnectionTarget(entry.target)) return "WSL";
46 if (
47 entry.target._tag === "SshConnectionTarget" &&
48 Option.isSome(entry.profile) &&
49 entry.profile.value._tag === "SshConnectionProfile"
50 ) {
51 return `SSH ${formatDesktopSshTarget(entry.profile.value.target)}`;
52 }
53 return environment.displayUrl ?? "Remote link";
54}
55
56/**
57 * One machine in a grouped settings list: icon, name, a single subtitle line,
58 * and controls on the right. Every environment list on the Connections page
59 * uses this so the lists share one rhythm.
60 */
61export function EnvironmentRow({
62 kind,
63 label,
64 subtitle,
65 below,
66 detail,
67 dimmed = false,
68 className,
69 children,
70}: {
71 readonly kind: EnvironmentMachineKind;
72 readonly label: string;
73 readonly subtitle: ReactNode;
74 /** Extra content under the subtitle, such as update progress. */
75 readonly below?: ReactNode;
76 /** Expanded content under the whole row, aligned with the label, such as its routes. */
77 readonly detail?: ReactNode;
78 readonly dimmed?: boolean;
79 readonly className?: string;
80 readonly children?: ReactNode;
81}) {
82 return (
83 <div
84 className={cn(
85 "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-3 px-3 py-2.5 sm:px-4",
86 dimmed && "opacity-60",
87 className,
88 )}
89 >
90 <EnvironmentMachineIcon aria-hidden kind={kind} className="size-4 text-muted-foreground" />
91 <div className="min-w-0">
92 <p className="truncate text-sm font-medium text-foreground">{label}</p>
93 <div className="truncate text-xs text-muted-foreground">{subtitle}</div>
94 {below}
95 </div>
96 <div className="flex shrink-0 items-center gap-1">{children}</div>
97 {detail ? <div className="col-span-2 col-start-2 min-w-0">{detail}</div> : null}
98 </div>
99 );
100}