feat: Add support for RDP and VNC connections in SSH host management

- Introduced connectionType field to differentiate between SSH, RDP, VNC, and Telnet in host data structures.
- Updated backend routes to handle RDP/VNC specific fields: domain, security, and ignoreCert.
- Enhanced the HostManagerEditor to include RDP/VNC specific settings and authentication options.
- Implemented token retrieval for RDP/VNC connections using Guacamole API.
- Updated UI components to reflect connection type changes and provide appropriate connection buttons.
- Removed the GuacamoleTestDialog component as its functionality is integrated into the HostManagerEditor.
- Adjusted the TopNavbar and Host components to accommodate new connection types and their respective actions.
This commit is contained in:
starhound
2025-12-19 16:08:27 -05:00
parent 3ac7ad0bd7
commit 776f581377
12 changed files with 540 additions and 353 deletions

View File

@@ -15,8 +15,12 @@ import { Loader2 } from "lucide-react";
export type GuacamoleConnectionType = "rdp" | "vnc" | "telnet";
export interface GuacamoleConnectionConfig {
type: GuacamoleConnectionType;
hostname: string;
// Pre-fetched token (preferred) - if provided, skip token fetch
token?: string;
protocol?: GuacamoleConnectionType;
// Legacy fields for backward compatibility (used if token not provided)
type?: GuacamoleConnectionType;
hostname?: string;
port?: number;
username?: string;
password?: string;
@@ -89,37 +93,45 @@ export const GuacamoleDisplay = forwardRef<GuacamoleDisplayHandle, GuacamoleDisp
}));
const getWebSocketUrl = useCallback(async (containerWidth: number, containerHeight: number): Promise<string | null> => {
const jwtToken = getCookie("jwt");
if (!jwtToken) {
setConnectionError("Authentication required");
return null;
}
// First, get an encrypted token from the backend
try {
const baseUrl = isDev
? "http://localhost:30001"
: isElectron()
? (window as { configuredServerUrl?: string }).configuredServerUrl || "http://127.0.0.1:30001"
: `${window.location.origin}`;
let token: string;
const response = await fetch(`${baseUrl}/guacamole/token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${jwtToken}`,
},
body: JSON.stringify(connectionConfig),
credentials: "include",
});
// If token is pre-fetched, use it directly
if (connectionConfig.token) {
token = connectionConfig.token;
} else {
// Otherwise, fetch token from backend (legacy behavior)
const jwtToken = getCookie("jwt");
if (!jwtToken) {
setConnectionError("Authentication required");
return null;
}
if (!response.ok) {
const err = await response.json();
throw new Error(err.error || "Failed to get connection token");
const baseUrl = isDev
? "http://localhost:30001"
: isElectron()
? (window as { configuredServerUrl?: string }).configuredServerUrl || "http://127.0.0.1:30001"
: `${window.location.origin}`;
const response = await fetch(`${baseUrl}/guacamole/token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${jwtToken}`,
},
body: JSON.stringify(connectionConfig),
credentials: "include",
});
if (!response.ok) {
const err = await response.json();
throw new Error(err.error || "Failed to get connection token");
}
const data = await response.json();
token = data.token;
}
const { token } = await response.json();
// Build WebSocket URL with width/height/dpi as query parameters
// These are passed as unencrypted settings to guacamole-lite
// Use actual container dimensions, fall back to 720p
@@ -353,7 +365,7 @@ export const GuacamoleDisplay = forwardRef<GuacamoleDisplayHandle, GuacamoleDisp
<div className="flex flex-col items-center gap-4">
<Loader2 className="w-8 h-8 animate-spin text-primary" />
<span className="text-muted-foreground">
{t("guacamole.connecting", { type: connectionConfig.type.toUpperCase() })}
{t("guacamole.connecting", { type: (connectionConfig.protocol || connectionConfig.type || "remote").toUpperCase() })}
</span>
</div>
</div>