| 1 | import { |
| 2 | type ConnectionTarget, |
| 3 | connectionRouteId, |
| 4 | connectionRouteLabel, |
| 5 | connectionRoutes, |
| 6 | } from "@t3tools/client-runtime/connection"; |
| 7 | import type { DesktopSshEnvironmentTarget, EnvironmentMachineKind } from "@t3tools/contracts"; |
| 8 | import * as Option from "effect/Option"; |
| 9 | import type { ReactNode } from "react"; |
| 10 | |
| 11 | import { cn } from "~/lib/utils"; |
| 12 | import type { EnvironmentPresentation } from "~/state/environments"; |
| 13 | import { isDesktopLocalConnectionTarget } from "~/connection/desktopLocal"; |
| 14 | import { EnvironmentMachineIcon } from "../EnvironmentMachineIcon"; |
| 15 | |
| 16 | export 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 | */ |
| 26 | export 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 | */ |
| 61 | export 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 | } |