Skip to content

Commit e421a2c

Browse files
committed
fix: rdp/vnc dupliocated icon, mobile hover fix for menu, and guacd hosts not working when ssh is enabeld
1 parent 4816844 commit e421a2c

7 files changed

Lines changed: 79 additions & 16 deletions

File tree

src/backend/guacamole/routes.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,17 @@ router.post("/token", async (req, res) => {
144144
* schema:
145145
* type: integer
146146
* description: Host ID to connect to
147+
* requestBody:
148+
* required: false
149+
* content:
150+
* application/json:
151+
* schema:
152+
* type: object
153+
* properties:
154+
* protocol:
155+
* type: string
156+
* enum: [rdp, vnc, telnet]
157+
* description: Override the host's default connection type
147158
* responses:
148159
* 200:
149160
* description: Connection token generated successfully
@@ -205,13 +216,27 @@ router.post(
205216
}
206217
}
207218

208-
const connectionType = (host.connectionType as string) || "ssh";
219+
const requestedProtocol = req.body?.protocol as string | undefined;
220+
const connectionType =
221+
requestedProtocol || (host.connectionType as string);
222+
209223
if (!["rdp", "vnc", "telnet"].includes(connectionType)) {
210224
return res.status(400).json({
211225
error: `Connection type '${connectionType}' is not supported for remote desktop. Only RDP, VNC, and Telnet are supported.`,
212226
});
213227
}
214228

229+
const protocolEnabledMap: Record<string, boolean> = {
230+
rdp: !!host.enableRdp,
231+
vnc: !!host.enableVnc,
232+
telnet: !!host.enableTelnet,
233+
};
234+
if (!protocolEnabledMap[connectionType]) {
235+
return res.status(400).json({
236+
error: `${connectionType.toUpperCase()} is not enabled for this host.`,
237+
});
238+
}
239+
215240
let guacConfig: Record<string, unknown> = {};
216241
if (host.guacamoleConfig) {
217242
try {

src/ui/features/guacamole/GuacamoleApp.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ import type { SSHHost } from "@/types";
1515
interface GuacamoleAppProps {
1616
hostId?: string;
1717
tabId?: string;
18+
protocol?: "rdp" | "vnc" | "telnet";
1819
}
1920

20-
const GuacamoleApp: React.FC<GuacamoleAppProps> = ({ hostId, tabId }) => {
21+
const GuacamoleApp: React.FC<GuacamoleAppProps> = ({
22+
hostId,
23+
tabId,
24+
protocol,
25+
}) => {
2126
const { t } = useTranslation();
2227

2328
return (
@@ -76,6 +81,7 @@ const GuacamoleApp: React.FC<GuacamoleAppProps> = ({ hostId, tabId }) => {
7681
hostId={parseInt(hostId, 10)}
7782
hostConfig={hostConfig}
7883
tabId={tabId}
84+
protocol={protocol}
7985
/>
8086
);
8187
}}
@@ -87,12 +93,14 @@ interface GuacamoleAppInnerProps {
8793
hostId: number;
8894
hostConfig: Pick<SSHHost, "connectionType">;
8995
tabId?: string;
96+
protocol?: "rdp" | "vnc" | "telnet";
9097
}
9198

9299
const GuacamoleAppInner: React.FC<GuacamoleAppInnerProps> = ({
93100
hostId,
94101
hostConfig,
95102
tabId,
103+
protocol,
96104
}) => {
97105
const { t } = useTranslation();
98106
const [token, setToken] = useState<string | null>(null);
@@ -110,7 +118,7 @@ const GuacamoleAppInner: React.FC<GuacamoleAppInnerProps> = ({
110118
setError(t("guacamole.guacdUnavailable"));
111119
return;
112120
}
113-
return getGuacamoleTokenFromHost(hostId);
121+
return getGuacamoleTokenFromHost(hostId, protocol);
114122
})
115123
.then((result) => {
116124
if (result) setToken(result.token);
@@ -172,14 +180,21 @@ const GuacamoleAppInner: React.FC<GuacamoleAppInnerProps> = ({
172180
<SimpleLoader
173181
visible={true}
174182
message={t("guacamole.connecting", {
175-
type: (hostConfig.connectionType || "remote").toUpperCase(),
183+
type: (
184+
protocol ||
185+
hostConfig.connectionType ||
186+
"remote"
187+
).toUpperCase(),
176188
})}
177189
/>
178190
</div>
179191
);
180192
}
181193

182-
const protocol = hostConfig.connectionType as "rdp" | "vnc" | "telnet";
194+
const resolvedProtocol = (protocol ?? hostConfig.connectionType) as
195+
| "rdp"
196+
| "vnc"
197+
| "telnet";
183198

184199
return (
185200
<div className="relative w-full h-full">
@@ -213,13 +228,17 @@ const GuacamoleAppInner: React.FC<GuacamoleAppInnerProps> = ({
213228
<GuacamoleDisplay
214229
key={token}
215230
ref={displayRef}
216-
connectionConfig={{ token, protocol, type: protocol }}
231+
connectionConfig={{
232+
token,
233+
protocol: resolvedProtocol,
234+
type: resolvedProtocol,
235+
}}
217236
isVisible={true}
218237
onError={(err) => setConnectionError(err)}
219238
/>
220239
<GuacamoleToolbar
221240
displayRef={displayRef}
222-
protocol={protocol}
241+
protocol={resolvedProtocol}
223242
onReconnect={handleReconnect}
224243
/>
225244
</div>

src/ui/main-axios.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4360,9 +4360,13 @@ export async function getGuacamoleToken(
43604360

43614361
export async function getGuacamoleTokenFromHost(
43624362
hostId: number,
4363+
protocol?: "rdp" | "vnc" | "telnet",
43634364
): Promise<GuacamoleTokenResponse> {
43644365
try {
4365-
const response = await authApi.post(`/guacamole/connect-host/${hostId}`);
4366+
const response = await authApi.post(
4367+
`/guacamole/connect-host/${hostId}`,
4368+
protocol ? { protocol } : {},
4369+
);
43664370
return response.data;
43674371
} catch (error) {
43684372
throw handleApiError(error, "get guacamole token from host");

src/ui/shell/CommandPalette.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
KeyRound,
2828
LayoutDashboard,
2929
Monitor,
30+
MousePointerClick,
3031
Clock,
3132
Folder,
3233
Pencil,
@@ -48,7 +49,7 @@ const ACTIVITY_ICONS: Record<string, React.ReactNode> = {
4849
tunnel: <Network className="size-3.5" />,
4950
docker: <Box className="size-3.5" />,
5051
telnet: <MessagesSquare className="size-3.5" />,
51-
vnc: <Monitor className="size-3.5" />,
52+
vnc: <MousePointerClick className="size-3.5" />,
5253
rdp: <Monitor className="size-3.5" />,
5354
};
5455

@@ -424,7 +425,7 @@ export function CommandPalette({
424425
}}
425426
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"
426427
>
427-
<Monitor className="size-3" />
428+
<MousePointerClick className="size-3" />
428429
VNC
429430
</button>
430431
)}

src/ui/shell/tabUtils.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,13 @@ export function renderTabContent(
230230
return (
231231
<EmptyState icon={Monitor} messageKey="guacamole.noHostSelected" />
232232
);
233-
return <GuacamoleApp hostId={host.id} tabId={tab.id} />;
233+
return (
234+
<GuacamoleApp
235+
hostId={host.id}
236+
tabId={tab.id}
237+
protocol={tab.type as "rdp" | "vnc" | "telnet"}
238+
/>
239+
);
234240

235241
case "network_graph":
236242
return <NetworkGraphCard embedded={false} />;

src/ui/sidebar/HostManager.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
Lock,
3232
Monitor,
3333
Network,
34+
MousePointerClick,
3435
Palette,
3536
Pencil,
3637
Plus,
@@ -220,7 +221,7 @@ function makeHostTabs(t: (key: string) => string): HostTab[] {
220221
{
221222
id: "vnc",
222223
label: t("hosts.tabVnc"),
223-
icon: <Monitor className="size-3" />,
224+
icon: <MousePointerClick className="size-3" />,
224225
},
225226
{
226227
id: "telnet",
@@ -687,7 +688,7 @@ function HostEditor({
687688
proto: "enableVnc" as const,
688689
label: t("hosts.tabVnc"),
689690
desc: t("hosts.virtualNetwork"),
690-
icon: <Monitor className="size-4" />,
691+
icon: <MousePointerClick className="size-4" />,
691692
portField: "vncPort" as const,
692693
},
693694
{

src/ui/sidebar/SidebarTree.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
MessagesSquare,
1717
Monitor,
1818
MoreHorizontal,
19+
MousePointerClick,
1920
Network,
2021
Pencil,
2122
Pin,
@@ -215,11 +216,17 @@ export function HostItem({
215216
? "bg-muted/20"
216217
: ""
217218
} ${isMenuOpen ? "bg-muted/40" : ""}`}
218-
onClick={() => {
219+
onClick={(e) => {
219220
if (selectionMode) {
220221
onToggleSelect?.();
221222
return;
222223
}
224+
// On touch devices open the action tray instead of immediately launching a tab
225+
if (window.matchMedia("(hover: none)").matches) {
226+
e.stopPropagation();
227+
onMenuOpenChange?.(!isMenuOpen);
228+
return;
229+
}
223230
if (host.enableSsh) onOpenTab("terminal");
224231
else if (host.enableRdp) onOpenTab("rdp");
225232
else if (host.enableVnc) onOpenTab("vnc");
@@ -360,7 +367,7 @@ export function HostItem({
360367
}}
361368
className="flex items-center justify-center size-7 rounded text-muted-foreground/50 hover:text-foreground hover:bg-muted-foreground/10 transition-colors"
362369
>
363-
<Monitor className="size-3.5" />
370+
<MousePointerClick className="size-3.5" />
364371
</button>
365372
)}
366373
{host.enableTelnet && (
@@ -526,7 +533,7 @@ export function HostItem({
526533
toast.success(t("hosts.vncUrlCopied"));
527534
}}
528535
>
529-
<Monitor className="size-3.5 mr-2" />
536+
<MousePointerClick className="size-3.5 mr-2" />
530537
{t("hosts.copyVncUrlAction")}
531538
</DropdownMenuItem>
532539
)}

0 commit comments

Comments
 (0)