feat: Enhance Guacamole integration with extended configuration options
- Added detailed Guacamole configuration interface for RDP/VNC/Telnet connections, including display, audio, performance, and session settings. - Implemented logging for token requests and received options for better debugging. - Updated HostManagerEditor to support new Guacamole configuration fields with validation and default values. - Integrated Guacamole configuration parsing in HostManagerViewer and Host components. - Enhanced API requests to include extended Guacamole configuration parameters in the token request. - Refactored code to convert camelCase configuration keys to kebab-case for compatibility with Guacamole API.
This commit is contained in:
@@ -500,6 +500,7 @@ const migrateSchema = () => {
|
|||||||
addColumnIfNotExists("ssh_data", "domain", "TEXT");
|
addColumnIfNotExists("ssh_data", "domain", "TEXT");
|
||||||
addColumnIfNotExists("ssh_data", "security", "TEXT");
|
addColumnIfNotExists("ssh_data", "security", "TEXT");
|
||||||
addColumnIfNotExists("ssh_data", "ignore_cert", "INTEGER NOT NULL DEFAULT 0");
|
addColumnIfNotExists("ssh_data", "ignore_cert", "INTEGER NOT NULL DEFAULT 0");
|
||||||
|
addColumnIfNotExists("ssh_data", "guacamole_config", "TEXT");
|
||||||
|
|
||||||
addColumnIfNotExists("ssh_credentials", "private_key", "TEXT");
|
addColumnIfNotExists("ssh_credentials", "private_key", "TEXT");
|
||||||
addColumnIfNotExists("ssh_credentials", "public_key", "TEXT");
|
addColumnIfNotExists("ssh_credentials", "public_key", "TEXT");
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ export const sshData = sqliteTable("ssh_data", {
|
|||||||
domain: text("domain"),
|
domain: text("domain"),
|
||||||
security: text("security"),
|
security: text("security"),
|
||||||
ignoreCert: integer("ignore_cert", { mode: "boolean" }).default(false),
|
ignoreCert: integer("ignore_cert", { mode: "boolean" }).default(false),
|
||||||
|
// RDP/VNC extended configuration (stored as JSON)
|
||||||
|
guacamoleConfig: text("guacamole_config"),
|
||||||
createdAt: text("created_at")
|
createdAt: text("created_at")
|
||||||
.notNull()
|
.notNull()
|
||||||
.default(sql`CURRENT_TIMESTAMP`),
|
.default(sql`CURRENT_TIMESTAMP`),
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ router.post(
|
|||||||
domain,
|
domain,
|
||||||
security,
|
security,
|
||||||
ignoreCert,
|
ignoreCert,
|
||||||
|
guacamoleConfig,
|
||||||
} = hostData;
|
} = hostData;
|
||||||
if (
|
if (
|
||||||
!isNonEmptyString(userId) ||
|
!isNonEmptyString(userId) ||
|
||||||
@@ -299,6 +300,7 @@ router.post(
|
|||||||
domain: domain || null,
|
domain: domain || null,
|
||||||
security: security || null,
|
security: security || null,
|
||||||
ignoreCert: ignoreCert ? 1 : 0,
|
ignoreCert: ignoreCert ? 1 : 0,
|
||||||
|
guacamoleConfig: guacamoleConfig ? JSON.stringify(guacamoleConfig) : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (effectiveAuthType === "password") {
|
if (effectiveAuthType === "password") {
|
||||||
@@ -363,6 +365,9 @@ router.post(
|
|||||||
dockerConfig: createdHost.dockerConfig
|
dockerConfig: createdHost.dockerConfig
|
||||||
? JSON.parse(createdHost.dockerConfig as string)
|
? JSON.parse(createdHost.dockerConfig as string)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
guacamoleConfig: createdHost.guacamoleConfig
|
||||||
|
? JSON.parse(createdHost.guacamoleConfig as string)
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolvedHost = (await resolveHostCredentials(baseHost)) || baseHost;
|
const resolvedHost = (await resolveHostCredentials(baseHost)) || baseHost;
|
||||||
@@ -490,6 +495,7 @@ router.put(
|
|||||||
domain,
|
domain,
|
||||||
security,
|
security,
|
||||||
ignoreCert,
|
ignoreCert,
|
||||||
|
guacamoleConfig,
|
||||||
} = hostData;
|
} = hostData;
|
||||||
if (
|
if (
|
||||||
!isNonEmptyString(userId) ||
|
!isNonEmptyString(userId) ||
|
||||||
@@ -540,6 +546,7 @@ router.put(
|
|||||||
domain: domain || null,
|
domain: domain || null,
|
||||||
security: security || null,
|
security: security || null,
|
||||||
ignoreCert: ignoreCert ? 1 : 0,
|
ignoreCert: ignoreCert ? 1 : 0,
|
||||||
|
guacamoleConfig: guacamoleConfig ? JSON.stringify(guacamoleConfig) : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (effectiveAuthType === "password") {
|
if (effectiveAuthType === "password") {
|
||||||
@@ -622,6 +629,9 @@ router.put(
|
|||||||
dockerConfig: updatedHost.dockerConfig
|
dockerConfig: updatedHost.dockerConfig
|
||||||
? JSON.parse(updatedHost.dockerConfig as string)
|
? JSON.parse(updatedHost.dockerConfig as string)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
guacamoleConfig: updatedHost.guacamoleConfig
|
||||||
|
? JSON.parse(updatedHost.guacamoleConfig as string)
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolvedHost = (await resolveHostCredentials(baseHost)) || baseHost;
|
const resolvedHost = (await resolveHostCredentials(baseHost)) || baseHost;
|
||||||
@@ -730,6 +740,9 @@ router.get(
|
|||||||
terminalConfig: row.terminalConfig
|
terminalConfig: row.terminalConfig
|
||||||
? JSON.parse(row.terminalConfig as string)
|
? JSON.parse(row.terminalConfig as string)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
guacamoleConfig: row.guacamoleConfig
|
||||||
|
? JSON.parse(row.guacamoleConfig as string)
|
||||||
|
: undefined,
|
||||||
forceKeyboardInteractive: row.forceKeyboardInteractive === "true",
|
forceKeyboardInteractive: row.forceKeyboardInteractive === "true",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -805,6 +818,9 @@ router.get(
|
|||||||
terminalConfig: host.terminalConfig
|
terminalConfig: host.terminalConfig
|
||||||
? JSON.parse(host.terminalConfig)
|
? JSON.parse(host.terminalConfig)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
guacamoleConfig: host.guacamoleConfig
|
||||||
|
? JSON.parse(host.guacamoleConfig)
|
||||||
|
: undefined,
|
||||||
forceKeyboardInteractive: host.forceKeyboardInteractive === "true",
|
forceKeyboardInteractive: host.forceKeyboardInteractive === "true",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,24 @@ router.post("/token", async (req, res) => {
|
|||||||
return res.status(400).json({ error: "Invalid connection type. Must be rdp, vnc, or telnet" });
|
return res.status(400).json({ error: "Invalid connection type. Must be rdp, vnc, or telnet" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log received options for debugging
|
||||||
|
guacLogger.info("Guacamole token request received", {
|
||||||
|
operation: "guac_token_request",
|
||||||
|
type,
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
optionKeys: Object.keys(options),
|
||||||
|
optionsCount: Object.keys(options).length,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Log specific option values for debugging
|
||||||
|
if (Object.keys(options).length > 0) {
|
||||||
|
guacLogger.info("Guacamole options received", {
|
||||||
|
operation: "guac_token_options",
|
||||||
|
options: JSON.stringify(options),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let token: string;
|
let token: string;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|||||||
@@ -27,6 +27,97 @@ export interface DockerConfig {
|
|||||||
|
|
||||||
export type HostConnectionType = "ssh" | "rdp" | "vnc" | "telnet";
|
export type HostConnectionType = "ssh" | "rdp" | "vnc" | "telnet";
|
||||||
|
|
||||||
|
// Guacamole configuration for RDP/VNC/Telnet connections
|
||||||
|
export interface GuacamoleConfig {
|
||||||
|
// Display settings
|
||||||
|
colorDepth?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
dpi?: number;
|
||||||
|
resizeMethod?: string;
|
||||||
|
forceLossless?: boolean;
|
||||||
|
// Audio settings
|
||||||
|
disableAudio?: boolean;
|
||||||
|
enableAudioInput?: boolean;
|
||||||
|
// RDP Performance settings
|
||||||
|
enableWallpaper?: boolean;
|
||||||
|
enableTheming?: boolean;
|
||||||
|
enableFontSmoothing?: boolean;
|
||||||
|
enableFullWindowDrag?: boolean;
|
||||||
|
enableDesktopComposition?: boolean;
|
||||||
|
enableMenuAnimations?: boolean;
|
||||||
|
disableBitmapCaching?: boolean;
|
||||||
|
disableOffscreenCaching?: boolean;
|
||||||
|
disableGlyphCaching?: boolean;
|
||||||
|
disableGfx?: boolean;
|
||||||
|
// RDP Device redirection
|
||||||
|
enablePrinting?: boolean;
|
||||||
|
printerName?: string;
|
||||||
|
enableDrive?: boolean;
|
||||||
|
driveName?: string;
|
||||||
|
drivePath?: string;
|
||||||
|
createDrivePath?: boolean;
|
||||||
|
disableDownload?: boolean;
|
||||||
|
disableUpload?: boolean;
|
||||||
|
enableTouch?: boolean;
|
||||||
|
// RDP Session settings
|
||||||
|
clientName?: string;
|
||||||
|
console?: boolean;
|
||||||
|
initialProgram?: string;
|
||||||
|
serverLayout?: string;
|
||||||
|
timezone?: string;
|
||||||
|
// RDP Gateway settings
|
||||||
|
gatewayHostname?: string;
|
||||||
|
gatewayPort?: number;
|
||||||
|
gatewayUsername?: string;
|
||||||
|
gatewayPassword?: string;
|
||||||
|
gatewayDomain?: string;
|
||||||
|
// RDP RemoteApp settings
|
||||||
|
remoteApp?: string;
|
||||||
|
remoteAppDir?: string;
|
||||||
|
remoteAppArgs?: string;
|
||||||
|
// RDP Preconnection settings (Hyper-V)
|
||||||
|
preconnectionId?: number;
|
||||||
|
preconnectionBlob?: string;
|
||||||
|
// RDP Load balancing
|
||||||
|
loadBalanceInfo?: string;
|
||||||
|
// Clipboard settings
|
||||||
|
normalizeClipboard?: string;
|
||||||
|
disableCopy?: boolean;
|
||||||
|
disablePaste?: boolean;
|
||||||
|
// VNC specific settings
|
||||||
|
cursor?: string;
|
||||||
|
swapRedBlue?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
|
// VNC Repeater settings
|
||||||
|
destHost?: string;
|
||||||
|
destPort?: number;
|
||||||
|
// VNC Reverse connection
|
||||||
|
reverseConnect?: boolean;
|
||||||
|
listenTimeout?: number;
|
||||||
|
// Common SFTP settings (for RDP/VNC file transfer)
|
||||||
|
enableSftp?: boolean;
|
||||||
|
sftpHostname?: string;
|
||||||
|
sftpPort?: number;
|
||||||
|
sftpUsername?: string;
|
||||||
|
sftpPassword?: string;
|
||||||
|
sftpPrivateKey?: string;
|
||||||
|
sftpDirectory?: string;
|
||||||
|
// Recording settings
|
||||||
|
recordingPath?: string;
|
||||||
|
recordingName?: string;
|
||||||
|
createRecordingPath?: boolean;
|
||||||
|
recordingExcludeOutput?: boolean;
|
||||||
|
recordingExcludeMouse?: boolean;
|
||||||
|
recordingIncludeKeys?: boolean;
|
||||||
|
// Wake-on-LAN settings
|
||||||
|
wolSendPacket?: boolean;
|
||||||
|
wolMacAddr?: string;
|
||||||
|
wolBroadcastAddr?: string;
|
||||||
|
wolUdpPort?: number;
|
||||||
|
wolWaitTime?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SSHHost {
|
export interface SSHHost {
|
||||||
id: number;
|
id: number;
|
||||||
connectionType: HostConnectionType;
|
connectionType: HostConnectionType;
|
||||||
@@ -62,10 +153,12 @@ export interface SSHHost {
|
|||||||
statsConfig?: string;
|
statsConfig?: string;
|
||||||
dockerConfig?: string;
|
dockerConfig?: string;
|
||||||
terminalConfig?: TerminalConfig;
|
terminalConfig?: TerminalConfig;
|
||||||
// RDP/VNC specific fields
|
// RDP/VNC specific fields (basic)
|
||||||
domain?: string;
|
domain?: string;
|
||||||
security?: string;
|
security?: string;
|
||||||
ignoreCert?: boolean;
|
ignoreCert?: boolean;
|
||||||
|
// RDP/VNC extended configuration (stored as JSON)
|
||||||
|
guacamoleConfig?: GuacamoleConfig | string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
@@ -107,10 +200,12 @@ export interface SSHHostData {
|
|||||||
statsConfig?: string | Record<string, unknown>;
|
statsConfig?: string | Record<string, unknown>;
|
||||||
dockerConfig?: DockerConfig | string;
|
dockerConfig?: DockerConfig | string;
|
||||||
terminalConfig?: TerminalConfig;
|
terminalConfig?: TerminalConfig;
|
||||||
// RDP/VNC specific fields
|
// RDP/VNC specific fields (basic)
|
||||||
domain?: string;
|
domain?: string;
|
||||||
security?: string;
|
security?: string;
|
||||||
ignoreCert?: boolean;
|
ignoreCert?: boolean;
|
||||||
|
// RDP/VNC extended configuration
|
||||||
|
guacamoleConfig?: GuacamoleConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SSHFolder {
|
export interface SSHFolder {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ import {
|
|||||||
DEFAULT_TERMINAL_CONFIG,
|
DEFAULT_TERMINAL_CONFIG,
|
||||||
} from "@/constants/terminal-themes";
|
} from "@/constants/terminal-themes";
|
||||||
import { TerminalPreview } from "@/ui/desktop/apps/terminal/TerminalPreview.tsx";
|
import { TerminalPreview } from "@/ui/desktop/apps/terminal/TerminalPreview.tsx";
|
||||||
import type { TerminalConfig } from "@/types";
|
import type { TerminalConfig, SSHHost } from "@/types";
|
||||||
import { Plus, X, Check, ChevronsUpDown } from "lucide-react";
|
import { Plus, X, Check, ChevronsUpDown } from "lucide-react";
|
||||||
|
|
||||||
interface JumpHostItemProps {
|
interface JumpHostItemProps {
|
||||||
@@ -277,46 +277,6 @@ function QuickActionItem({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SSHHost {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
ip: string;
|
|
||||||
port: number;
|
|
||||||
username: string;
|
|
||||||
folder: string;
|
|
||||||
tags: string[];
|
|
||||||
pin: boolean;
|
|
||||||
authType: string;
|
|
||||||
password?: string;
|
|
||||||
key?: string;
|
|
||||||
keyPassword?: string;
|
|
||||||
keyType?: string;
|
|
||||||
enableTerminal: boolean;
|
|
||||||
enableTunnel: boolean;
|
|
||||||
enableFileManager: boolean;
|
|
||||||
defaultPath: string;
|
|
||||||
tunnelConnections: Array<{
|
|
||||||
sourcePort: number;
|
|
||||||
endpointPort: number;
|
|
||||||
endpointHost: string;
|
|
||||||
maxRetries: number;
|
|
||||||
retryInterval: number;
|
|
||||||
autoStart: boolean;
|
|
||||||
}>;
|
|
||||||
jumpHosts?: Array<{
|
|
||||||
hostId: number;
|
|
||||||
}>;
|
|
||||||
quickActions?: Array<{
|
|
||||||
name: string;
|
|
||||||
snippetId: number;
|
|
||||||
}>;
|
|
||||||
statsConfig?: StatsConfig;
|
|
||||||
terminalConfig?: TerminalConfig;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
credentialId?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SSHManagerHostEditorProps {
|
interface SSHManagerHostEditorProps {
|
||||||
editingHost?: SSHHost | null;
|
editingHost?: SSHHost | null;
|
||||||
onFormSubmit?: (updatedHost?: SSHHost) => void;
|
onFormSubmit?: (updatedHost?: SSHHost) => void;
|
||||||
@@ -448,6 +408,77 @@ export function HostManagerEditor({
|
|||||||
domain: z.string().optional(),
|
domain: z.string().optional(),
|
||||||
security: z.string().optional(),
|
security: z.string().optional(),
|
||||||
ignoreCert: z.boolean().default(false),
|
ignoreCert: z.boolean().default(false),
|
||||||
|
// RDP/VNC extended configuration
|
||||||
|
guacamoleConfig: z.object({
|
||||||
|
// Display settings
|
||||||
|
colorDepth: z.coerce.number().optional(),
|
||||||
|
width: z.coerce.number().optional(),
|
||||||
|
height: z.coerce.number().optional(),
|
||||||
|
dpi: z.coerce.number().optional(),
|
||||||
|
resizeMethod: z.string().optional(),
|
||||||
|
forceLossless: z.boolean().optional(),
|
||||||
|
// Audio settings
|
||||||
|
disableAudio: z.boolean().optional(),
|
||||||
|
enableAudioInput: z.boolean().optional(),
|
||||||
|
// RDP Performance settings
|
||||||
|
enableWallpaper: z.boolean().optional(),
|
||||||
|
enableTheming: z.boolean().optional(),
|
||||||
|
enableFontSmoothing: z.boolean().optional(),
|
||||||
|
enableFullWindowDrag: z.boolean().optional(),
|
||||||
|
enableDesktopComposition: z.boolean().optional(),
|
||||||
|
enableMenuAnimations: z.boolean().optional(),
|
||||||
|
disableBitmapCaching: z.boolean().optional(),
|
||||||
|
disableOffscreenCaching: z.boolean().optional(),
|
||||||
|
disableGlyphCaching: z.boolean().optional(),
|
||||||
|
disableGfx: z.boolean().optional(),
|
||||||
|
// RDP Device redirection
|
||||||
|
enablePrinting: z.boolean().optional(),
|
||||||
|
printerName: z.string().optional(),
|
||||||
|
enableDrive: z.boolean().optional(),
|
||||||
|
driveName: z.string().optional(),
|
||||||
|
drivePath: z.string().optional(),
|
||||||
|
createDrivePath: z.boolean().optional(),
|
||||||
|
disableDownload: z.boolean().optional(),
|
||||||
|
disableUpload: z.boolean().optional(),
|
||||||
|
enableTouch: z.boolean().optional(),
|
||||||
|
// RDP Session settings
|
||||||
|
clientName: z.string().optional(),
|
||||||
|
console: z.boolean().optional(),
|
||||||
|
initialProgram: z.string().optional(),
|
||||||
|
serverLayout: z.string().optional(),
|
||||||
|
timezone: z.string().optional(),
|
||||||
|
// RDP Gateway settings
|
||||||
|
gatewayHostname: z.string().optional(),
|
||||||
|
gatewayPort: z.coerce.number().optional(),
|
||||||
|
gatewayUsername: z.string().optional(),
|
||||||
|
gatewayPassword: z.string().optional(),
|
||||||
|
gatewayDomain: z.string().optional(),
|
||||||
|
// RDP RemoteApp settings
|
||||||
|
remoteApp: z.string().optional(),
|
||||||
|
remoteAppDir: z.string().optional(),
|
||||||
|
remoteAppArgs: z.string().optional(),
|
||||||
|
// Clipboard settings
|
||||||
|
normalizeClipboard: z.string().optional(),
|
||||||
|
disableCopy: z.boolean().optional(),
|
||||||
|
disablePaste: z.boolean().optional(),
|
||||||
|
// VNC specific settings
|
||||||
|
cursor: z.string().optional(),
|
||||||
|
swapRedBlue: z.boolean().optional(),
|
||||||
|
readOnly: z.boolean().optional(),
|
||||||
|
// Recording settings
|
||||||
|
recordingPath: z.string().optional(),
|
||||||
|
recordingName: z.string().optional(),
|
||||||
|
createRecordingPath: z.boolean().optional(),
|
||||||
|
recordingExcludeOutput: z.boolean().optional(),
|
||||||
|
recordingExcludeMouse: z.boolean().optional(),
|
||||||
|
recordingIncludeKeys: z.boolean().optional(),
|
||||||
|
// Wake-on-LAN settings
|
||||||
|
wolSendPacket: z.boolean().optional(),
|
||||||
|
wolMacAddr: z.string().optional(),
|
||||||
|
wolBroadcastAddr: z.string().optional(),
|
||||||
|
wolUdpPort: z.coerce.number().optional(),
|
||||||
|
wolWaitTime: z.coerce.number().optional(),
|
||||||
|
}).optional(),
|
||||||
credentialId: z.number().optional().nullable(),
|
credentialId: z.number().optional().nullable(),
|
||||||
overrideCredentialUsername: z.boolean().optional(),
|
overrideCredentialUsername: z.boolean().optional(),
|
||||||
password: z.string().optional(),
|
password: z.string().optional(),
|
||||||
@@ -692,6 +723,76 @@ export function HostManagerEditor({
|
|||||||
domain: "",
|
domain: "",
|
||||||
security: "",
|
security: "",
|
||||||
ignoreCert: false,
|
ignoreCert: false,
|
||||||
|
guacamoleConfig: {
|
||||||
|
// Display settings
|
||||||
|
colorDepth: undefined,
|
||||||
|
width: undefined,
|
||||||
|
height: undefined,
|
||||||
|
dpi: 96,
|
||||||
|
resizeMethod: "display-update",
|
||||||
|
forceLossless: false,
|
||||||
|
// Audio settings
|
||||||
|
disableAudio: false,
|
||||||
|
enableAudioInput: false,
|
||||||
|
// RDP Performance settings
|
||||||
|
enableWallpaper: false,
|
||||||
|
enableTheming: false,
|
||||||
|
enableFontSmoothing: true,
|
||||||
|
enableFullWindowDrag: false,
|
||||||
|
enableDesktopComposition: false,
|
||||||
|
enableMenuAnimations: false,
|
||||||
|
disableBitmapCaching: false,
|
||||||
|
disableOffscreenCaching: false,
|
||||||
|
disableGlyphCaching: false,
|
||||||
|
disableGfx: false,
|
||||||
|
// RDP Device redirection
|
||||||
|
enablePrinting: false,
|
||||||
|
printerName: "",
|
||||||
|
enableDrive: false,
|
||||||
|
driveName: "",
|
||||||
|
drivePath: "",
|
||||||
|
createDrivePath: false,
|
||||||
|
disableDownload: false,
|
||||||
|
disableUpload: false,
|
||||||
|
enableTouch: false,
|
||||||
|
// RDP Session settings
|
||||||
|
clientName: "",
|
||||||
|
console: false,
|
||||||
|
initialProgram: "",
|
||||||
|
serverLayout: "en-us-qwerty",
|
||||||
|
timezone: "",
|
||||||
|
// RDP Gateway settings
|
||||||
|
gatewayHostname: "",
|
||||||
|
gatewayPort: 443,
|
||||||
|
gatewayUsername: "",
|
||||||
|
gatewayPassword: "",
|
||||||
|
gatewayDomain: "",
|
||||||
|
// RDP RemoteApp settings
|
||||||
|
remoteApp: "",
|
||||||
|
remoteAppDir: "",
|
||||||
|
remoteAppArgs: "",
|
||||||
|
// Clipboard settings
|
||||||
|
normalizeClipboard: "preserve",
|
||||||
|
disableCopy: false,
|
||||||
|
disablePaste: false,
|
||||||
|
// VNC specific settings
|
||||||
|
cursor: "remote",
|
||||||
|
swapRedBlue: false,
|
||||||
|
readOnly: false,
|
||||||
|
// Recording settings
|
||||||
|
recordingPath: "",
|
||||||
|
recordingName: "",
|
||||||
|
createRecordingPath: false,
|
||||||
|
recordingExcludeOutput: false,
|
||||||
|
recordingExcludeMouse: false,
|
||||||
|
recordingIncludeKeys: false,
|
||||||
|
// Wake-on-LAN settings
|
||||||
|
wolSendPacket: false,
|
||||||
|
wolMacAddr: "",
|
||||||
|
wolBroadcastAddr: "",
|
||||||
|
wolUdpPort: 9,
|
||||||
|
wolWaitTime: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -768,6 +869,81 @@ export function HostManagerEditor({
|
|||||||
console.error("Failed to parse dockerConfig:", error);
|
console.error("Failed to parse dockerConfig:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse guacamoleConfig if it exists - merge with defaults
|
||||||
|
const defaultGuacamoleConfig = {
|
||||||
|
colorDepth: undefined,
|
||||||
|
width: undefined,
|
||||||
|
height: undefined,
|
||||||
|
dpi: 96,
|
||||||
|
resizeMethod: "display-update",
|
||||||
|
forceLossless: false,
|
||||||
|
disableAudio: false,
|
||||||
|
enableAudioInput: false,
|
||||||
|
enableWallpaper: false,
|
||||||
|
enableTheming: false,
|
||||||
|
enableFontSmoothing: true,
|
||||||
|
enableFullWindowDrag: false,
|
||||||
|
enableDesktopComposition: false,
|
||||||
|
enableMenuAnimations: false,
|
||||||
|
disableBitmapCaching: false,
|
||||||
|
disableOffscreenCaching: false,
|
||||||
|
disableGlyphCaching: false,
|
||||||
|
disableGfx: false,
|
||||||
|
enablePrinting: false,
|
||||||
|
printerName: "",
|
||||||
|
enableDrive: false,
|
||||||
|
driveName: "",
|
||||||
|
drivePath: "",
|
||||||
|
createDrivePath: false,
|
||||||
|
disableDownload: false,
|
||||||
|
disableUpload: false,
|
||||||
|
enableTouch: false,
|
||||||
|
clientName: "",
|
||||||
|
console: false,
|
||||||
|
initialProgram: "",
|
||||||
|
serverLayout: "en-us-qwerty",
|
||||||
|
timezone: "",
|
||||||
|
gatewayHostname: "",
|
||||||
|
gatewayPort: 443,
|
||||||
|
gatewayUsername: "",
|
||||||
|
gatewayPassword: "",
|
||||||
|
gatewayDomain: "",
|
||||||
|
remoteApp: "",
|
||||||
|
remoteAppDir: "",
|
||||||
|
remoteAppArgs: "",
|
||||||
|
normalizeClipboard: "preserve",
|
||||||
|
disableCopy: false,
|
||||||
|
disablePaste: false,
|
||||||
|
cursor: "remote",
|
||||||
|
swapRedBlue: false,
|
||||||
|
readOnly: false,
|
||||||
|
recordingPath: "",
|
||||||
|
recordingName: "",
|
||||||
|
createRecordingPath: false,
|
||||||
|
recordingExcludeOutput: false,
|
||||||
|
recordingExcludeMouse: false,
|
||||||
|
recordingIncludeKeys: false,
|
||||||
|
wolSendPacket: false,
|
||||||
|
wolMacAddr: "",
|
||||||
|
wolBroadcastAddr: "",
|
||||||
|
wolUdpPort: 9,
|
||||||
|
wolWaitTime: 0,
|
||||||
|
};
|
||||||
|
let parsedGuacamoleConfig = { ...defaultGuacamoleConfig };
|
||||||
|
try {
|
||||||
|
if (cleanedHost.guacamoleConfig) {
|
||||||
|
console.log("[HostManagerEditor] Loading host guacamoleConfig:", cleanedHost.guacamoleConfig);
|
||||||
|
const parsed =
|
||||||
|
typeof cleanedHost.guacamoleConfig === "string"
|
||||||
|
? JSON.parse(cleanedHost.guacamoleConfig)
|
||||||
|
: cleanedHost.guacamoleConfig;
|
||||||
|
parsedGuacamoleConfig = { ...defaultGuacamoleConfig, ...parsed };
|
||||||
|
console.log("[HostManagerEditor] Merged guacamoleConfig:", parsedGuacamoleConfig);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to parse guacamoleConfig:", error);
|
||||||
|
}
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
connectionType: (cleanedHost.connectionType || "ssh") as "ssh" | "rdp" | "vnc" | "telnet",
|
connectionType: (cleanedHost.connectionType || "ssh") as "ssh" | "rdp" | "vnc" | "telnet",
|
||||||
name: cleanedHost.name || "",
|
name: cleanedHost.name || "",
|
||||||
@@ -816,6 +992,8 @@ export function HostManagerEditor({
|
|||||||
domain: cleanedHost.domain || "",
|
domain: cleanedHost.domain || "",
|
||||||
security: cleanedHost.security || "",
|
security: cleanedHost.security || "",
|
||||||
ignoreCert: Boolean(cleanedHost.ignoreCert),
|
ignoreCert: Boolean(cleanedHost.ignoreCert),
|
||||||
|
// Guacamole config for RDP/VNC
|
||||||
|
guacamoleConfig: parsedGuacamoleConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (defaultAuthType === "password") {
|
if (defaultAuthType === "password") {
|
||||||
@@ -904,6 +1082,12 @@ export function HostManagerEditor({
|
|||||||
isSubmittingRef.current = true;
|
isSubmittingRef.current = true;
|
||||||
setFormError(null);
|
setFormError(null);
|
||||||
|
|
||||||
|
// Debug: log form data being submitted
|
||||||
|
console.log("[HostManagerEditor] Form data on submit:", data);
|
||||||
|
console.log("[HostManagerEditor] data.guacamoleConfig:", data.guacamoleConfig);
|
||||||
|
console.log("[HostManagerEditor] data.guacamoleConfig.enableWallpaper:", data.guacamoleConfig?.enableWallpaper);
|
||||||
|
console.log("[HostManagerEditor] form.getValues('guacamoleConfig'):", form.getValues("guacamoleConfig"));
|
||||||
|
|
||||||
if (!data.name || data.name.trim() === "") {
|
if (!data.name || data.name.trim() === "") {
|
||||||
data.name = `${data.username}@${data.ip}`;
|
data.name = `${data.username}@${data.ip}`;
|
||||||
}
|
}
|
||||||
@@ -956,8 +1140,13 @@ export function HostManagerEditor({
|
|||||||
domain: data.domain || null,
|
domain: data.domain || null,
|
||||||
security: data.security || null,
|
security: data.security || null,
|
||||||
ignoreCert: Boolean(data.ignoreCert),
|
ignoreCert: Boolean(data.ignoreCert),
|
||||||
|
// Guacamole configuration for RDP/VNC
|
||||||
|
guacamoleConfig: data.guacamoleConfig || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Debug: log what we're submitting
|
||||||
|
console.log("[HostManagerEditor] submitData.guacamoleConfig:", submitData.guacamoleConfig);
|
||||||
|
|
||||||
submitData.credentialId = null;
|
submitData.credentialId = null;
|
||||||
submitData.password = null;
|
submitData.password = null;
|
||||||
submitData.key = null;
|
submitData.key = null;
|
||||||
@@ -1274,6 +1463,38 @@ export function HostManagerEditor({
|
|||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
)}
|
)}
|
||||||
|
{/* RDP tabs */}
|
||||||
|
{form.watch("connectionType") === "rdp" && (
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="general">
|
||||||
|
{t("hosts.general")}
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="display">
|
||||||
|
{t("hosts.display", "Display")}
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="audio">
|
||||||
|
{t("hosts.audio", "Audio")}
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="performance">
|
||||||
|
{t("hosts.performance", "Performance")}
|
||||||
|
</TabsTrigger>
|
||||||
|
|
||||||
|
</TabsList>
|
||||||
|
)}
|
||||||
|
{/* VNC tabs */}
|
||||||
|
{form.watch("connectionType") === "vnc" && (
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="general">
|
||||||
|
{t("hosts.general")}
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="display">
|
||||||
|
{t("hosts.display", "Display")}
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="audio">
|
||||||
|
{t("hosts.audio", "Audio")}
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
)}
|
||||||
<TabsContent value="general" className="pt-2">
|
<TabsContent value="general" className="pt-2">
|
||||||
<FormLabel className="mb-3 font-bold">
|
<FormLabel className="mb-3 font-bold">
|
||||||
{t("hosts.connectionType", "Connection Type")}
|
{t("hosts.connectionType", "Connection Type")}
|
||||||
@@ -3816,6 +4037,521 @@ export function HostManagerEditor({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
{/* RDP/VNC Display Tab */}
|
||||||
|
<TabsContent value="display" className="pt-2 space-y-4">
|
||||||
|
<FormLabel className="mb-3 font-bold">
|
||||||
|
{t("hosts.displaySettings", "Display Settings")}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.width"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("hosts.width", "Width")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="Auto"
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={(e) => field.onChange(e.target.value ? parseInt(e.target.value) : undefined)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.widthDesc", "Display width in pixels (leave empty for auto)")}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.height"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("hosts.height", "Height")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="Auto"
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={(e) => field.onChange(e.target.value ? parseInt(e.target.value) : undefined)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.heightDesc", "Display height in pixels (leave empty for auto)")}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.dpi"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("hosts.dpi", "DPI")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="96"
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={(e) => field.onChange(e.target.value ? parseInt(e.target.value) : undefined)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.dpiDesc", "Display resolution in DPI")}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.colorDepth"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("hosts.colorDepth", "Color Depth")}</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={field.value?.toString() || "auto"}
|
||||||
|
onValueChange={(value) => field.onChange(value === "auto" ? undefined : parseInt(value))}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t("hosts.selectColorDepth", "Select color depth")} />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="auto">Auto</SelectItem>
|
||||||
|
<SelectItem value="8">8-bit (256 colors)</SelectItem>
|
||||||
|
<SelectItem value="16">16-bit (65536 colors)</SelectItem>
|
||||||
|
<SelectItem value="24">24-bit (True color)</SelectItem>
|
||||||
|
<SelectItem value="32">32-bit (True color + alpha)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.colorDepthDesc", "Color depth for the remote display")}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{form.watch("connectionType") === "rdp" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.resizeMethod"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("hosts.resizeMethod", "Resize Method")}</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={field.value || "display-update"}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="display-update">Display Update</SelectItem>
|
||||||
|
<SelectItem value="reconnect">Reconnect</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.resizeMethodDesc", "Method to use when resizing the display")}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.forceLossless"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.forceLossless", "Force Lossless")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.forceLosslessDesc", "Force lossless compression (higher bandwidth)")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{form.watch("connectionType") === "vnc" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.cursor"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("hosts.cursor", "Cursor Mode")}</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={field.value || "remote"}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="remote">Remote (server-side)</SelectItem>
|
||||||
|
<SelectItem value="local">Local (client-side)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.cursorDesc", "How to render the mouse cursor")}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.swapRedBlue"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.swapRedBlue", "Swap Red/Blue")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.swapRedBlueDesc", "Swap red and blue color components")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.readOnly"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.readOnly", "Read Only")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.readOnlyDesc", "View only mode - no input sent to server")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
{/* RDP/VNC Audio Tab */}
|
||||||
|
<TabsContent value="audio" className="pt-2 space-y-4">
|
||||||
|
<FormLabel className="mb-3 font-bold">
|
||||||
|
{t("hosts.audioSettings", "Audio Settings")}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.disableAudio"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.disableAudio", "Disable Audio")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.disableAudioDesc", "Disable audio playback from the remote session")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{form.watch("connectionType") === "rdp" && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.enableAudioInput"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.enableAudioInput", "Enable Audio Input")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.enableAudioInputDesc", "Enable microphone input to the remote session")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
{/* RDP Performance Tab */}
|
||||||
|
<TabsContent value="performance" className="pt-2 space-y-4">
|
||||||
|
<FormLabel className="mb-3 font-bold">
|
||||||
|
{t("hosts.performanceSettings", "Performance Settings")}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.enableWallpaper"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.enableWallpaper", "Wallpaper")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.enableWallpaperDesc", "Show desktop wallpaper")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
console.log("[HostManagerEditor] Wallpaper toggled to:", checked);
|
||||||
|
field.onChange(checked);
|
||||||
|
// Log the full guacamoleConfig after change
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("[HostManagerEditor] After toggle, guacamoleConfig:", form.getValues("guacamoleConfig"));
|
||||||
|
}, 0);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.enableTheming"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.enableTheming", "Theming")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.enableThemingDesc", "Enable window theming")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.enableFontSmoothing"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.enableFontSmoothing", "Font Smoothing")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.enableFontSmoothingDesc", "Enable ClearType font smoothing")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value ?? true}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.enableFullWindowDrag"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.enableFullWindowDrag", "Full Window Drag")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.enableFullWindowDragDesc", "Show window contents while dragging")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.enableDesktopComposition"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.enableDesktopComposition", "Desktop Composition")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.enableDesktopCompositionDesc", "Enable Aero glass effects")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.enableMenuAnimations"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.enableMenuAnimations", "Menu Animations")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.enableMenuAnimationsDesc", "Enable menu animations")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormLabel className="mb-3 font-bold pt-4">
|
||||||
|
{t("hosts.cachingSettings", "Caching Settings")}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.disableBitmapCaching"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.disableBitmapCaching", "Disable Bitmap Caching")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.disableBitmapCachingDesc", "Disable bitmap caching")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.disableOffscreenCaching"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.disableOffscreenCaching", "Disable Offscreen Caching")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.disableOffscreenCachingDesc", "Disable offscreen caching")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.disableGlyphCaching"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.disableGlyphCaching", "Disable Glyph Caching")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.disableGlyphCachingDesc", "Disable glyph caching")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="guacamoleConfig.disableGfx"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.disableGfx", "Disable GFX")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.disableGfxDesc", "Disable graphics pipeline extension")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value || false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|||||||
@@ -1496,6 +1496,11 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
|
|||||||
});
|
});
|
||||||
} else if (connectionType === "rdp" || connectionType === "vnc") {
|
} else if (connectionType === "rdp" || connectionType === "vnc") {
|
||||||
try {
|
try {
|
||||||
|
// Parse guacamoleConfig if it's a string
|
||||||
|
const guacConfig = typeof host.guacamoleConfig === "string"
|
||||||
|
? JSON.parse(host.guacamoleConfig)
|
||||||
|
: host.guacamoleConfig;
|
||||||
|
|
||||||
const tokenResponse = await getGuacamoleToken({
|
const tokenResponse = await getGuacamoleToken({
|
||||||
protocol: connectionType,
|
protocol: connectionType,
|
||||||
hostname: host.ip,
|
hostname: host.ip,
|
||||||
@@ -1505,6 +1510,7 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
|
|||||||
domain: host.domain,
|
domain: host.domain,
|
||||||
security: host.security,
|
security: host.security,
|
||||||
ignoreCert: host.ignoreCert,
|
ignoreCert: host.ignoreCert,
|
||||||
|
guacamoleConfig: guacConfig,
|
||||||
});
|
});
|
||||||
addTab({
|
addTab({
|
||||||
type: connectionType,
|
type: connectionType,
|
||||||
|
|||||||
@@ -36,30 +36,7 @@ import { Button } from "@/components/ui/button.tsx";
|
|||||||
import { FolderCard } from "@/ui/desktop/navigation/hosts/FolderCard.tsx";
|
import { FolderCard } from "@/ui/desktop/navigation/hosts/FolderCard.tsx";
|
||||||
import { getSSHHosts, getSSHFolders } from "@/ui/main-axios.ts";
|
import { getSSHHosts, getSSHFolders } from "@/ui/main-axios.ts";
|
||||||
import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext.tsx";
|
import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext.tsx";
|
||||||
import type { SSHFolder } from "@/types/index.ts";
|
import type { SSHFolder, SSHHost } from "@/types/index.ts";
|
||||||
|
|
||||||
interface SSHHost {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
ip: string;
|
|
||||||
port: number;
|
|
||||||
username: string;
|
|
||||||
folder: string;
|
|
||||||
tags: string[];
|
|
||||||
pin: boolean;
|
|
||||||
authType: string;
|
|
||||||
password?: string;
|
|
||||||
key?: string;
|
|
||||||
keyPassword?: string;
|
|
||||||
keyType?: string;
|
|
||||||
enableTerminal: boolean;
|
|
||||||
enableTunnel: boolean;
|
|
||||||
enableFileManager: boolean;
|
|
||||||
defaultPath: string;
|
|
||||||
tunnelConnections: unknown[];
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|||||||
@@ -115,6 +115,16 @@ export function Host({ host: initialHost }: HostProps): React.ReactElement {
|
|||||||
addTab({ type: "terminal", title, hostConfig: host });
|
addTab({ type: "terminal", title, hostConfig: host });
|
||||||
} else if (connectionType === "rdp" || connectionType === "vnc") {
|
} else if (connectionType === "rdp" || connectionType === "vnc") {
|
||||||
try {
|
try {
|
||||||
|
// Parse guacamoleConfig if it's a string
|
||||||
|
const guacConfig = typeof host.guacamoleConfig === "string"
|
||||||
|
? JSON.parse(host.guacamoleConfig)
|
||||||
|
: host.guacamoleConfig;
|
||||||
|
|
||||||
|
// Debug: log what guacamoleConfig we have
|
||||||
|
console.log("[Host.tsx] host.guacamoleConfig type:", typeof host.guacamoleConfig);
|
||||||
|
console.log("[Host.tsx] host.guacamoleConfig:", host.guacamoleConfig);
|
||||||
|
console.log("[Host.tsx] Parsed guacConfig:", guacConfig);
|
||||||
|
|
||||||
// Get guacamole token for RDP/VNC connection
|
// Get guacamole token for RDP/VNC connection
|
||||||
const tokenResponse = await getGuacamoleToken({
|
const tokenResponse = await getGuacamoleToken({
|
||||||
protocol: connectionType,
|
protocol: connectionType,
|
||||||
@@ -125,6 +135,7 @@ export function Host({ host: initialHost }: HostProps): React.ReactElement {
|
|||||||
domain: host.domain,
|
domain: host.domain,
|
||||||
security: host.security,
|
security: host.security,
|
||||||
ignoreCert: host.ignoreCert,
|
ignoreCert: host.ignoreCert,
|
||||||
|
guacamoleConfig: guacConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
addTab({
|
addTab({
|
||||||
|
|||||||
@@ -878,6 +878,8 @@ export async function createSSHHost(hostData: SSHHostData): Promise<SSHHost> {
|
|||||||
domain: hostData.domain || null,
|
domain: hostData.domain || null,
|
||||||
security: hostData.security || null,
|
security: hostData.security || null,
|
||||||
ignoreCert: Boolean(hostData.ignoreCert),
|
ignoreCert: Boolean(hostData.ignoreCert),
|
||||||
|
// Guacamole configuration for RDP/VNC
|
||||||
|
guacamoleConfig: hostData.guacamoleConfig || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!submitData.enableTunnel) {
|
if (!submitData.enableTunnel) {
|
||||||
@@ -955,6 +957,8 @@ export async function updateSSHHost(
|
|||||||
domain: hostData.domain || null,
|
domain: hostData.domain || null,
|
||||||
security: hostData.security || null,
|
security: hostData.security || null,
|
||||||
ignoreCert: Boolean(hostData.ignoreCert),
|
ignoreCert: Boolean(hostData.ignoreCert),
|
||||||
|
// Guacamole configuration for RDP/VNC
|
||||||
|
guacamoleConfig: hostData.guacamoleConfig || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!submitData.enableTunnel) {
|
if (!submitData.enableTunnel) {
|
||||||
@@ -3142,16 +3146,171 @@ export interface GuacamoleTokenRequest {
|
|||||||
domain?: string;
|
domain?: string;
|
||||||
security?: string;
|
security?: string;
|
||||||
ignoreCert?: boolean;
|
ignoreCert?: boolean;
|
||||||
|
// Extended guacamole configuration
|
||||||
|
guacamoleConfig?: {
|
||||||
|
// Display settings
|
||||||
|
colorDepth?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
dpi?: number;
|
||||||
|
resizeMethod?: string;
|
||||||
|
forceLossless?: boolean;
|
||||||
|
// Audio settings
|
||||||
|
disableAudio?: boolean;
|
||||||
|
enableAudioInput?: boolean;
|
||||||
|
// RDP Performance settings
|
||||||
|
enableWallpaper?: boolean;
|
||||||
|
enableTheming?: boolean;
|
||||||
|
enableFontSmoothing?: boolean;
|
||||||
|
enableFullWindowDrag?: boolean;
|
||||||
|
enableDesktopComposition?: boolean;
|
||||||
|
enableMenuAnimations?: boolean;
|
||||||
|
disableBitmapCaching?: boolean;
|
||||||
|
disableOffscreenCaching?: boolean;
|
||||||
|
disableGlyphCaching?: boolean;
|
||||||
|
disableGfx?: boolean;
|
||||||
|
// RDP Device redirection
|
||||||
|
enablePrinting?: boolean;
|
||||||
|
printerName?: string;
|
||||||
|
enableDrive?: boolean;
|
||||||
|
driveName?: string;
|
||||||
|
drivePath?: string;
|
||||||
|
createDrivePath?: boolean;
|
||||||
|
disableDownload?: boolean;
|
||||||
|
disableUpload?: boolean;
|
||||||
|
enableTouch?: boolean;
|
||||||
|
// RDP Session settings
|
||||||
|
clientName?: string;
|
||||||
|
console?: boolean;
|
||||||
|
initialProgram?: string;
|
||||||
|
serverLayout?: string;
|
||||||
|
timezone?: string;
|
||||||
|
// RDP Gateway settings
|
||||||
|
gatewayHostname?: string;
|
||||||
|
gatewayPort?: number;
|
||||||
|
gatewayUsername?: string;
|
||||||
|
gatewayPassword?: string;
|
||||||
|
gatewayDomain?: string;
|
||||||
|
// RDP RemoteApp settings
|
||||||
|
remoteApp?: string;
|
||||||
|
remoteAppDir?: string;
|
||||||
|
remoteAppArgs?: string;
|
||||||
|
// Clipboard settings
|
||||||
|
normalizeClipboard?: string;
|
||||||
|
disableCopy?: boolean;
|
||||||
|
disablePaste?: boolean;
|
||||||
|
// VNC specific settings
|
||||||
|
cursor?: string;
|
||||||
|
swapRedBlue?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
|
// Recording settings
|
||||||
|
recordingPath?: string;
|
||||||
|
recordingName?: string;
|
||||||
|
createRecordingPath?: boolean;
|
||||||
|
recordingExcludeOutput?: boolean;
|
||||||
|
recordingExcludeMouse?: boolean;
|
||||||
|
recordingIncludeKeys?: boolean;
|
||||||
|
// Wake-on-LAN settings
|
||||||
|
wolSendPacket?: boolean;
|
||||||
|
wolMacAddr?: string;
|
||||||
|
wolBroadcastAddr?: string;
|
||||||
|
wolUdpPort?: number;
|
||||||
|
wolWaitTime?: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GuacamoleTokenResponse {
|
export interface GuacamoleTokenResponse {
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to convert camelCase to kebab-case for guacamole parameters
|
||||||
|
function toGuacamoleParams(config: GuacamoleTokenRequest["guacamoleConfig"]): Record<string, unknown> {
|
||||||
|
if (!config) return {};
|
||||||
|
|
||||||
|
const params: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
// Map camelCase to guacamole's kebab-case parameter names
|
||||||
|
const mappings: Record<string, string> = {
|
||||||
|
colorDepth: "color-depth",
|
||||||
|
resizeMethod: "resize-method",
|
||||||
|
forceLossless: "force-lossless",
|
||||||
|
disableAudio: "disable-audio",
|
||||||
|
enableAudioInput: "enable-audio-input",
|
||||||
|
enableWallpaper: "enable-wallpaper",
|
||||||
|
enableTheming: "enable-theming",
|
||||||
|
enableFontSmoothing: "enable-font-smoothing",
|
||||||
|
enableFullWindowDrag: "enable-full-window-drag",
|
||||||
|
enableDesktopComposition: "enable-desktop-composition",
|
||||||
|
enableMenuAnimations: "enable-menu-animations",
|
||||||
|
disableBitmapCaching: "disable-bitmap-caching",
|
||||||
|
disableOffscreenCaching: "disable-offscreen-caching",
|
||||||
|
disableGlyphCaching: "disable-glyph-caching",
|
||||||
|
disableGfx: "disable-gfx",
|
||||||
|
enablePrinting: "enable-printing",
|
||||||
|
printerName: "printer-name",
|
||||||
|
enableDrive: "enable-drive",
|
||||||
|
driveName: "drive-name",
|
||||||
|
drivePath: "drive-path",
|
||||||
|
createDrivePath: "create-drive-path",
|
||||||
|
disableDownload: "disable-download",
|
||||||
|
disableUpload: "disable-upload",
|
||||||
|
enableTouch: "enable-touch",
|
||||||
|
clientName: "client-name",
|
||||||
|
initialProgram: "initial-program",
|
||||||
|
serverLayout: "server-layout",
|
||||||
|
gatewayHostname: "gateway-hostname",
|
||||||
|
gatewayPort: "gateway-port",
|
||||||
|
gatewayUsername: "gateway-username",
|
||||||
|
gatewayPassword: "gateway-password",
|
||||||
|
gatewayDomain: "gateway-domain",
|
||||||
|
remoteApp: "remote-app",
|
||||||
|
remoteAppDir: "remote-app-dir",
|
||||||
|
remoteAppArgs: "remote-app-args",
|
||||||
|
normalizeClipboard: "normalize-clipboard",
|
||||||
|
disableCopy: "disable-copy",
|
||||||
|
disablePaste: "disable-paste",
|
||||||
|
swapRedBlue: "swap-red-blue",
|
||||||
|
readOnly: "read-only",
|
||||||
|
recordingPath: "recording-path",
|
||||||
|
recordingName: "recording-name",
|
||||||
|
createRecordingPath: "create-recording-path",
|
||||||
|
recordingExcludeOutput: "recording-exclude-output",
|
||||||
|
recordingExcludeMouse: "recording-exclude-mouse",
|
||||||
|
recordingIncludeKeys: "recording-include-keys",
|
||||||
|
wolSendPacket: "wol-send-packet",
|
||||||
|
wolMacAddr: "wol-mac-addr",
|
||||||
|
wolBroadcastAddr: "wol-broadcast-addr",
|
||||||
|
wolUdpPort: "wol-udp-port",
|
||||||
|
wolWaitTime: "wol-wait-time",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(config)) {
|
||||||
|
if (value !== undefined && value !== null && value !== "") {
|
||||||
|
const paramName = mappings[key] || key;
|
||||||
|
// Guacamole expects boolean values as strings "true" or "false"
|
||||||
|
if (typeof value === "boolean") {
|
||||||
|
params[paramName] = value ? "true" : "false";
|
||||||
|
} else {
|
||||||
|
params[paramName] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getGuacamoleToken(
|
export async function getGuacamoleToken(
|
||||||
request: GuacamoleTokenRequest,
|
request: GuacamoleTokenRequest,
|
||||||
): Promise<GuacamoleTokenResponse> {
|
): Promise<GuacamoleTokenResponse> {
|
||||||
try {
|
try {
|
||||||
|
// Convert guacamoleConfig to guacamole parameter format
|
||||||
|
const guacParams = toGuacamoleParams(request.guacamoleConfig);
|
||||||
|
|
||||||
|
// Debug: log guacamoleConfig and converted params
|
||||||
|
console.log("[Guacamole] Request guacamoleConfig:", request.guacamoleConfig);
|
||||||
|
console.log("[Guacamole] Converted params:", guacParams);
|
||||||
|
console.log("[Guacamole] Param count:", Object.keys(guacParams).length);
|
||||||
|
|
||||||
// Use authApi (port 30001 without /ssh prefix) since guacamole routes are at /guacamole
|
// Use authApi (port 30001 without /ssh prefix) since guacamole routes are at /guacamole
|
||||||
const response = await authApi.post("/guacamole/token", {
|
const response = await authApi.post("/guacamole/token", {
|
||||||
type: request.protocol,
|
type: request.protocol,
|
||||||
@@ -3162,6 +3321,7 @@ export async function getGuacamoleToken(
|
|||||||
domain: request.domain,
|
domain: request.domain,
|
||||||
security: request.security,
|
security: request.security,
|
||||||
"ignore-cert": request.ignoreCert,
|
"ignore-cert": request.ignoreCert,
|
||||||
|
...guacParams,
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user