diff --git a/src/backend/guacamole/routes.ts b/src/backend/guacamole/routes.ts index bd8cae6e..d1f7bdf8 100644 --- a/src/backend/guacamole/routes.ts +++ b/src/backend/guacamole/routes.ts @@ -144,6 +144,17 @@ router.post("/token", async (req, res) => { * schema: * type: integer * description: Host ID to connect to + * requestBody: + * required: false + * content: + * application/json: + * schema: + * type: object + * properties: + * protocol: + * type: string + * enum: [rdp, vnc, telnet] + * description: Override the host's default connection type * responses: * 200: * description: Connection token generated successfully @@ -205,13 +216,27 @@ router.post( } } - const connectionType = (host.connectionType as string) || "ssh"; + const requestedProtocol = req.body?.protocol as string | undefined; + const connectionType = + requestedProtocol || (host.connectionType as string); + if (!["rdp", "vnc", "telnet"].includes(connectionType)) { return res.status(400).json({ error: `Connection type '${connectionType}' is not supported for remote desktop. Only RDP, VNC, and Telnet are supported.`, }); } + const protocolEnabledMap: Record = { + rdp: !!host.enableRdp, + vnc: !!host.enableVnc, + telnet: !!host.enableTelnet, + }; + if (!protocolEnabledMap[connectionType]) { + return res.status(400).json({ + error: `${connectionType.toUpperCase()} is not enabled for this host.`, + }); + } + let guacConfig: Record = {}; if (host.guacamoleConfig) { try { diff --git a/src/ui/features/guacamole/GuacamoleApp.tsx b/src/ui/features/guacamole/GuacamoleApp.tsx index 9bdfa0c0..9a8df234 100644 --- a/src/ui/features/guacamole/GuacamoleApp.tsx +++ b/src/ui/features/guacamole/GuacamoleApp.tsx @@ -15,9 +15,14 @@ import type { SSHHost } from "@/types"; interface GuacamoleAppProps { hostId?: string; tabId?: string; + protocol?: "rdp" | "vnc" | "telnet"; } -const GuacamoleApp: React.FC = ({ hostId, tabId }) => { +const GuacamoleApp: React.FC = ({ + hostId, + tabId, + protocol, +}) => { const { t } = useTranslation(); return ( @@ -76,6 +81,7 @@ const GuacamoleApp: React.FC = ({ hostId, tabId }) => { hostId={parseInt(hostId, 10)} hostConfig={hostConfig} tabId={tabId} + protocol={protocol} /> ); }} @@ -87,12 +93,14 @@ interface GuacamoleAppInnerProps { hostId: number; hostConfig: Pick; tabId?: string; + protocol?: "rdp" | "vnc" | "telnet"; } const GuacamoleAppInner: React.FC = ({ hostId, hostConfig, tabId, + protocol, }) => { const { t } = useTranslation(); const [token, setToken] = useState(null); @@ -110,7 +118,7 @@ const GuacamoleAppInner: React.FC = ({ setError(t("guacamole.guacdUnavailable")); return; } - return getGuacamoleTokenFromHost(hostId); + return getGuacamoleTokenFromHost(hostId, protocol); }) .then((result) => { if (result) setToken(result.token); @@ -172,14 +180,21 @@ const GuacamoleAppInner: React.FC = ({ ); } - const protocol = hostConfig.connectionType as "rdp" | "vnc" | "telnet"; + const resolvedProtocol = (protocol ?? hostConfig.connectionType) as + | "rdp" + | "vnc" + | "telnet"; return (
@@ -213,13 +228,17 @@ const GuacamoleAppInner: React.FC = ({ setConnectionError(err)} />
diff --git a/src/ui/main-axios.ts b/src/ui/main-axios.ts index 0404cc34..0bbca77a 100644 --- a/src/ui/main-axios.ts +++ b/src/ui/main-axios.ts @@ -4360,9 +4360,13 @@ export async function getGuacamoleToken( export async function getGuacamoleTokenFromHost( hostId: number, + protocol?: "rdp" | "vnc" | "telnet", ): Promise { try { - const response = await authApi.post(`/guacamole/connect-host/${hostId}`); + const response = await authApi.post( + `/guacamole/connect-host/${hostId}`, + protocol ? { protocol } : {}, + ); return response.data; } catch (error) { throw handleApiError(error, "get guacamole token from host"); diff --git a/src/ui/shell/CommandPalette.tsx b/src/ui/shell/CommandPalette.tsx index 4f2edfa1..02c4f250 100644 --- a/src/ui/shell/CommandPalette.tsx +++ b/src/ui/shell/CommandPalette.tsx @@ -27,6 +27,7 @@ import { KeyRound, LayoutDashboard, Monitor, + MousePointerClick, Clock, Folder, Pencil, @@ -48,7 +49,7 @@ const ACTIVITY_ICONS: Record = { tunnel: , docker: , telnet: , - vnc: , + vnc: , rdp: , }; @@ -424,7 +425,7 @@ export function CommandPalette({ }} className="flex items-center gap-1 px-2 h-6 rounded text-xs font-medium text-muted-foreground/70 hover:text-foreground hover:bg-muted-foreground/10 transition-colors border border-border/40" > - + VNC )} diff --git a/src/ui/shell/tabUtils.tsx b/src/ui/shell/tabUtils.tsx index d644966f..ebee0a26 100644 --- a/src/ui/shell/tabUtils.tsx +++ b/src/ui/shell/tabUtils.tsx @@ -230,7 +230,13 @@ export function renderTabContent( return ( ); - return ; + return ( + + ); case "network_graph": return ; diff --git a/src/ui/sidebar/HostManager.tsx b/src/ui/sidebar/HostManager.tsx index dedc5cea..ae620f21 100644 --- a/src/ui/sidebar/HostManager.tsx +++ b/src/ui/sidebar/HostManager.tsx @@ -31,6 +31,7 @@ import { Lock, Monitor, Network, + MousePointerClick, Palette, Pencil, Plus, @@ -220,7 +221,7 @@ function makeHostTabs(t: (key: string) => string): HostTab[] { { id: "vnc", label: t("hosts.tabVnc"), - icon: , + icon: , }, { id: "telnet", @@ -687,7 +688,7 @@ function HostEditor({ proto: "enableVnc" as const, label: t("hosts.tabVnc"), desc: t("hosts.virtualNetwork"), - icon: , + icon: , portField: "vncPort" as const, }, { diff --git a/src/ui/sidebar/SidebarTree.tsx b/src/ui/sidebar/SidebarTree.tsx index dfe58c6f..72f4d366 100644 --- a/src/ui/sidebar/SidebarTree.tsx +++ b/src/ui/sidebar/SidebarTree.tsx @@ -16,6 +16,7 @@ import { MessagesSquare, Monitor, MoreHorizontal, + MousePointerClick, Network, Pencil, Pin, @@ -215,11 +216,17 @@ export function HostItem({ ? "bg-muted/20" : "" } ${isMenuOpen ? "bg-muted/40" : ""}`} - onClick={() => { + onClick={(e) => { if (selectionMode) { onToggleSelect?.(); return; } + // On touch devices open the action tray instead of immediately launching a tab + if (window.matchMedia("(hover: none)").matches) { + e.stopPropagation(); + onMenuOpenChange?.(!isMenuOpen); + return; + } if (host.enableSsh) onOpenTab("terminal"); else if (host.enableRdp) onOpenTab("rdp"); else if (host.enableVnc) onOpenTab("vnc"); @@ -360,7 +367,7 @@ export function HostItem({ }} className="flex items-center justify-center size-7 rounded text-muted-foreground/50 hover:text-foreground hover:bg-muted-foreground/10 transition-colors" > - + )} {host.enableTelnet && ( @@ -526,7 +533,7 @@ export function HostItem({ toast.success(t("hosts.vncUrlCopied")); }} > - + {t("hosts.copyVncUrlAction")} )}