| 1 | import type { ReactNode } from "react"; |
| 2 | import type { DeviceToolVersions as ToolVersions } from "@t3tools/contracts"; |
| 3 | import { InlineButton } from "~/components/ui/button"; |
| 4 | import { Popover, PopoverPopup, PopoverTitle, PopoverTrigger } from "~/components/ui/popover"; |
| 5 | |
| 6 | export function DeviceToolVersions({ |
| 7 | tools, |
| 8 | action, |
| 9 | kind, |
| 10 | owner, |
| 11 | error, |
| 12 | }: { |
| 13 | tools: ToolVersions | undefined; |
| 14 | action?: ReactNode; |
| 15 | kind?: keyof ToolVersions; |
| 16 | owner?: string | undefined; |
| 17 | error?: string | undefined; |
| 18 | }) { |
| 19 | const selected = kind ? tools?.[kind] : undefined; |
| 20 | const version = |
| 21 | selected?.runningVersion ?? |
| 22 | (selected?.installedVersions.includes(selected.requiredVersion) |
| 23 | ? selected.requiredVersion |
| 24 | : selected?.installedVersions |
| 25 | .toSorted((a, b) => a.localeCompare(b, undefined, { numeric: true })) |
| 26 | .at(-1)); |
| 27 | const label = kind === "hub" ? "Device hub" : "Agent device"; |
| 28 | return ( |
| 29 | <Popover> |
| 30 | <PopoverTrigger |
| 31 | aria-label={ |
| 32 | kind |
| 33 | ? `${label}: ${version ? `version ${version}` : selected ? "not installed" : "version unknown"}. Show details` |
| 34 | : undefined |
| 35 | } |
| 36 | render={<InlineButton tone="muted" />} |
| 37 | > |
| 38 | {kind |
| 39 | ? version |
| 40 | ? `v${version}` |
| 41 | : selected |
| 42 | ? "Not installed" |
| 43 | : "Version unknown" |
| 44 | : error |
| 45 | ? "Versions unavailable" |
| 46 | : "Versions"} |
| 47 | </PopoverTrigger> |
| 48 | <PopoverPopup align="end" width="md"> |
| 49 | <PopoverTitle>{kind ? label : "Device tools"}</PopoverTitle> |
| 50 | {tools ? ( |
| 51 | <div className="mt-4 divide-y divide-border/50"> |
| 52 | {( |
| 53 | [ |
| 54 | ["Device hub", tools.hub], |
| 55 | ["Agent device", tools.agent], |
| 56 | ] as const |
| 57 | ) |
| 58 | .filter(([name]) => !kind || name === label) |
| 59 | .map(([name, tool]) => ( |
| 60 | <div key={name} className="space-y-2 py-3 first:pt-0 last:pb-0"> |
| 61 | {!kind ? <p className="text-xs font-medium">{name}</p> : null} |
| 62 | <dl className="grid grid-cols-[auto_1fr] gap-x-6 gap-y-1 text-xs"> |
| 63 | <dt className="text-muted-foreground">Running</dt> |
| 64 | <dd className="text-right font-mono">{tool.runningVersion ?? "Not running"}</dd> |
| 65 | <dt className="text-muted-foreground">Required</dt> |
| 66 | <dd className="text-right font-mono">{tool.requiredVersion}</dd> |
| 67 | <dt className="text-muted-foreground">Installed</dt> |
| 68 | <dd className="text-right font-mono break-words"> |
| 69 | {tool.installedVersions.join(", ") || "None"} |
| 70 | </dd> |
| 71 | </dl> |
| 72 | </div> |
| 73 | ))} |
| 74 | </div> |
| 75 | ) : ( |
| 76 | <p className="mt-3 text-xs text-muted-foreground">Versions have not been checked.</p> |
| 77 | )} |
| 78 | <p className="mt-4 border-t border-border/50 pt-3 text-xs text-muted-foreground"> |
| 79 | {owner ? `Managed by ${owner}. ` : ""}Tools update automatically on this host when needed. |
| 80 | </p> |
| 81 | {error ? ( |
| 82 | <p role="status" className="mt-2 text-xs text-destructive"> |
| 83 | {error} |
| 84 | </p> |
| 85 | ) : null} |
| 86 | {action ? <div className="mt-3">{action}</div> : null} |
| 87 | </PopoverPopup> |
| 88 | </Popover> |
| 89 | ); |
| 90 | } |