feat: enhance Guacamole support with RDP and VNC connection settings and UI updates
This commit is contained in:
@@ -36,6 +36,13 @@ const clientOptions = {
|
|||||||
guacLogger.error(args.join(" "), { operation: "guac_error" });
|
guacLogger.error(args.join(" "), { operation: "guac_error" });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Allow width, height, and dpi to be passed as query parameters
|
||||||
|
// This allows the client to request the appropriate resolution at connection time
|
||||||
|
allowedUnencryptedConnectionSettings: {
|
||||||
|
rdp: ["width", "height", "dpi"],
|
||||||
|
vnc: ["width", "height", "dpi"],
|
||||||
|
telnet: ["width", "height"],
|
||||||
|
},
|
||||||
connectionDefaultSettings: {
|
connectionDefaultSettings: {
|
||||||
rdp: {
|
rdp: {
|
||||||
security: "any",
|
security: "any",
|
||||||
@@ -46,10 +53,15 @@ const clientOptions = {
|
|||||||
"disable-audio": false,
|
"disable-audio": false,
|
||||||
"enable-drive": false,
|
"enable-drive": false,
|
||||||
"resize-method": "display-update",
|
"resize-method": "display-update",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
dpi: 96,
|
||||||
},
|
},
|
||||||
vnc: {
|
vnc: {
|
||||||
"swap-red-blue": false,
|
"swap-red-blue": false,
|
||||||
"cursor": "remote",
|
"cursor": "remote",
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
},
|
},
|
||||||
telnet: {
|
telnet: {
|
||||||
"terminal-type": "xterm-256color",
|
"terminal-type": "xterm-256color",
|
||||||
|
|||||||
@@ -345,11 +345,26 @@ export interface TabContextTab {
|
|||||||
| "server"
|
| "server"
|
||||||
| "admin"
|
| "admin"
|
||||||
| "file_manager"
|
| "file_manager"
|
||||||
| "user_profile";
|
| "user_profile"
|
||||||
|
| "rdp"
|
||||||
|
| "vnc";
|
||||||
title: string;
|
title: string;
|
||||||
hostConfig?: SSHHost;
|
hostConfig?: SSHHost;
|
||||||
terminalRef?: any;
|
terminalRef?: any;
|
||||||
initialTab?: string;
|
initialTab?: string;
|
||||||
|
connectionConfig?: {
|
||||||
|
type: "rdp" | "vnc" | "telnet";
|
||||||
|
hostname: string;
|
||||||
|
port: number;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
domain?: string;
|
||||||
|
security?: string;
|
||||||
|
"ignore-cert"?: boolean;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
dpi?: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SplitLayout = "2h" | "2v" | "3l" | "3r" | "3t" | "4grid";
|
export type SplitLayout = "2h" | "2v" | "3l" | "3r" | "3t" | "4grid";
|
||||||
|
|||||||
@@ -155,7 +155,9 @@ function AppContent() {
|
|||||||
const showTerminalView =
|
const showTerminalView =
|
||||||
currentTabData?.type === "terminal" ||
|
currentTabData?.type === "terminal" ||
|
||||||
currentTabData?.type === "server" ||
|
currentTabData?.type === "server" ||
|
||||||
currentTabData?.type === "file_manager";
|
currentTabData?.type === "file_manager" ||
|
||||||
|
currentTabData?.type === "rdp" ||
|
||||||
|
currentTabData?.type === "vnc";
|
||||||
const showHome = currentTabData?.type === "home";
|
const showHome = currentTabData?.type === "home";
|
||||||
const showSshManager = currentTabData?.type === "ssh_manager";
|
const showSshManager = currentTabData?.type === "ssh_manager";
|
||||||
const showAdmin = currentTabData?.type === "admin";
|
const showAdmin = currentTabData?.type === "admin";
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ export const GuacamoleDisplay = forwardRef<GuacamoleDisplayHandle, GuacamoleDisp
|
|||||||
ref
|
ref
|
||||||
) {
|
) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const displayRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null); // Outer container for measuring size
|
||||||
|
const displayRef = useRef<HTMLDivElement>(null); // Inner div for guacamole canvas
|
||||||
const clientRef = useRef<Guacamole.Client | null>(null);
|
const clientRef = useRef<Guacamole.Client | null>(null);
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const [isConnecting, setIsConnecting] = useState(false);
|
const [isConnecting, setIsConnecting] = useState(false);
|
||||||
@@ -86,7 +87,7 @@ export const GuacamoleDisplay = forwardRef<GuacamoleDisplayHandle, GuacamoleDisp
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const getWebSocketUrl = useCallback(async (): Promise<string | null> => {
|
const getWebSocketUrl = useCallback(async (containerWidth: number, containerHeight: number): Promise<string | null> => {
|
||||||
const jwtToken = getCookie("jwt");
|
const jwtToken = getCookie("jwt");
|
||||||
if (!jwtToken) {
|
if (!jwtToken) {
|
||||||
setConnectionError("Authentication required");
|
setConnectionError("Authentication required");
|
||||||
@@ -118,7 +119,13 @@ export const GuacamoleDisplay = forwardRef<GuacamoleDisplayHandle, GuacamoleDisp
|
|||||||
|
|
||||||
const { token } = await response.json();
|
const { token } = await response.json();
|
||||||
|
|
||||||
// Build WebSocket URL
|
// 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
|
||||||
|
const width = connectionConfig.width || containerWidth || 1280;
|
||||||
|
const height = connectionConfig.height || containerHeight || 720;
|
||||||
|
const dpi = connectionConfig.dpi || 96;
|
||||||
|
|
||||||
const wsBase = isDev
|
const wsBase = isDev
|
||||||
? `ws://localhost:30007`
|
? `ws://localhost:30007`
|
||||||
: isElectron()
|
: isElectron()
|
||||||
@@ -128,7 +135,7 @@ export const GuacamoleDisplay = forwardRef<GuacamoleDisplayHandle, GuacamoleDisp
|
|||||||
})()
|
})()
|
||||||
: `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}/guacamole/websocket/`;
|
: `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}/guacamole/websocket/`;
|
||||||
|
|
||||||
return `${wsBase}?token=${encodeURIComponent(token)}`;
|
return `${wsBase}?token=${encodeURIComponent(token)}&width=${width}&height=${height}&dpi=${dpi}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||||
setConnectionError(errorMessage);
|
setConnectionError(errorMessage);
|
||||||
@@ -142,7 +149,21 @@ export const GuacamoleDisplay = forwardRef<GuacamoleDisplayHandle, GuacamoleDisp
|
|||||||
setIsConnecting(true);
|
setIsConnecting(true);
|
||||||
setConnectionError(null);
|
setConnectionError(null);
|
||||||
|
|
||||||
const wsUrl = await getWebSocketUrl();
|
// Get container dimensions for the WebSocket URL
|
||||||
|
// Use the outer container ref which has h-full w-full
|
||||||
|
let containerWidth = containerRef.current?.clientWidth || 0;
|
||||||
|
let containerHeight = containerRef.current?.clientHeight || 0;
|
||||||
|
|
||||||
|
console.log(`[Guacamole] Container size: ${containerWidth}x${containerHeight}`);
|
||||||
|
|
||||||
|
// If container size is too small or unavailable, use 720p default
|
||||||
|
if (containerWidth < 100 || containerHeight < 100) {
|
||||||
|
console.log(`[Guacamole] Container too small, using 720p default`);
|
||||||
|
containerWidth = 1280;
|
||||||
|
containerHeight = 720;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wsUrl = await getWebSocketUrl(containerWidth, containerHeight);
|
||||||
if (!wsUrl) {
|
if (!wsUrl) {
|
||||||
setIsConnecting(false);
|
setIsConnecting(false);
|
||||||
return;
|
return;
|
||||||
@@ -154,26 +175,35 @@ export const GuacamoleDisplay = forwardRef<GuacamoleDisplayHandle, GuacamoleDisp
|
|||||||
|
|
||||||
// Set up display
|
// Set up display
|
||||||
const display = client.getDisplay();
|
const display = client.getDisplay();
|
||||||
|
const displayElement = display.getElement();
|
||||||
|
|
||||||
if (displayRef.current) {
|
if (displayRef.current) {
|
||||||
displayRef.current.innerHTML = "";
|
displayRef.current.innerHTML = "";
|
||||||
const displayElement = display.getElement();
|
|
||||||
displayElement.style.width = "100%";
|
|
||||||
displayElement.style.height = "100%";
|
|
||||||
displayRef.current.appendChild(displayElement);
|
displayRef.current.appendChild(displayElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle display sync (when frames arrive) - scale to fit container
|
// Function to rescale display to fit container
|
||||||
display.onresize = (width: number, height: number) => {
|
const rescaleDisplay = () => {
|
||||||
if (displayRef.current) {
|
if (!containerRef.current) return;
|
||||||
const containerWidth = displayRef.current.clientWidth;
|
|
||||||
const containerHeight = displayRef.current.clientHeight;
|
const cWidth = containerRef.current.clientWidth;
|
||||||
const scale = Math.min(containerWidth / width, containerHeight / height);
|
const cHeight = containerRef.current.clientHeight;
|
||||||
|
const displayWidth = display.getWidth();
|
||||||
|
const displayHeight = display.getHeight();
|
||||||
|
|
||||||
|
if (displayWidth > 0 && displayHeight > 0 && cWidth > 0 && cHeight > 0) {
|
||||||
|
const scale = Math.min(cWidth / displayWidth, cHeight / displayHeight);
|
||||||
display.scale(scale);
|
display.scale(scale);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up mouse input
|
// Handle display sync (when frames arrive)
|
||||||
const mouse = new Guacamole.Mouse(displayRef.current!);
|
display.onresize = () => {
|
||||||
|
rescaleDisplay();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up mouse input on the display element (not the container)
|
||||||
|
const mouse = new Guacamole.Mouse(displayElement);
|
||||||
mouse.onmousedown = mouse.onmouseup = mouse.onmousemove = (state: Guacamole.Mouse.State) => {
|
mouse.onmousedown = mouse.onmouseup = mouse.onmousemove = (state: Guacamole.Mouse.State) => {
|
||||||
client.sendMouseState(state);
|
client.sendMouseState(state);
|
||||||
};
|
};
|
||||||
@@ -237,12 +267,8 @@ export const GuacamoleDisplay = forwardRef<GuacamoleDisplayHandle, GuacamoleDisp
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connect with display size
|
// Connect - the width/height/dpi are already in the WebSocket URL
|
||||||
const width = connectionConfig.width || displayRef.current?.clientWidth || 1024;
|
client.connect();
|
||||||
const height = connectionConfig.height || displayRef.current?.clientHeight || 768;
|
|
||||||
const dpi = connectionConfig.dpi || 96;
|
|
||||||
|
|
||||||
client.connect(`width=${width}&height=${height}&dpi=${dpi}`);
|
|
||||||
}, [isConnecting, isConnected, getWebSocketUrl, connectionConfig, onConnect, onDisconnect, onError, t]);
|
}, [isConnecting, isConnected, getWebSocketUrl, connectionConfig, onConnect, onDisconnect, onError, t]);
|
||||||
|
|
||||||
// Track if we've initiated a connection to prevent re-triggering
|
// Track if we've initiated a connection to prevent re-triggering
|
||||||
@@ -264,26 +290,40 @@ export const GuacamoleDisplay = forwardRef<GuacamoleDisplayHandle, GuacamoleDisp
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Handle window resize
|
// Handle window resize - rescale display to fit container
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (clientRef.current && displayRef.current) {
|
if (clientRef.current && containerRef.current) {
|
||||||
const display = clientRef.current.getDisplay();
|
const display = clientRef.current.getDisplay();
|
||||||
const width = displayRef.current.clientWidth;
|
const cWidth = containerRef.current.clientWidth;
|
||||||
const height = displayRef.current.clientHeight;
|
const cHeight = containerRef.current.clientHeight;
|
||||||
display.scale(Math.min(width / display.getWidth(), height / display.getHeight()));
|
const displayWidth = display.getWidth();
|
||||||
|
const displayHeight = display.getHeight();
|
||||||
|
|
||||||
|
if (displayWidth > 0 && displayHeight > 0 && cWidth > 0 && cHeight > 0) {
|
||||||
|
const scale = Math.min(cWidth / displayWidth, cHeight / displayHeight);
|
||||||
|
display.scale(scale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
return () => window.removeEventListener("resize", handleResize);
|
// Also trigger on initial render after a short delay
|
||||||
|
const initialTimeout = setTimeout(handleResize, 100);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
clearTimeout(initialTimeout);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full relative bg-black">
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className="h-full w-full relative bg-black flex items-center justify-center overflow-hidden"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
ref={displayRef}
|
ref={displayRef}
|
||||||
className="h-full w-full"
|
className="relative"
|
||||||
style={{ cursor: isConnected ? "none" : "default" }}
|
style={{ cursor: isConnected ? "none" : "default" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|||||||
import { PasswordInput } from "@/components/ui/password-input";
|
import { PasswordInput } from "@/components/ui/password-input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Monitor, MonitorPlay, Terminal } from "lucide-react";
|
import { Monitor, MonitorPlay, Terminal } from "lucide-react";
|
||||||
import { GuacamoleDisplay, GuacamoleConnectionConfig } from "./GuacamoleDisplay";
|
import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext";
|
||||||
|
import type { GuacamoleConnectionConfig } from "./GuacamoleDisplay";
|
||||||
|
|
||||||
interface GuacamoleTestDialogProps {
|
interface GuacamoleTestDialogProps {
|
||||||
trigger?: React.ReactNode;
|
trigger?: React.ReactNode;
|
||||||
@@ -21,8 +22,7 @@ interface GuacamoleTestDialogProps {
|
|||||||
|
|
||||||
export function GuacamoleTestDialog({ trigger }: GuacamoleTestDialogProps) {
|
export function GuacamoleTestDialog({ trigger }: GuacamoleTestDialogProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isConnecting, setIsConnecting] = useState(false);
|
const { addTab } = useTabs();
|
||||||
const [connectionConfig, setConnectionConfig] = useState<GuacamoleConnectionConfig | null>(null);
|
|
||||||
|
|
||||||
const [connectionType, setConnectionType] = useState<"rdp" | "vnc" | "telnet">("rdp");
|
const [connectionType, setConnectionType] = useState<"rdp" | "vnc" | "telnet">("rdp");
|
||||||
const [hostname, setHostname] = useState("");
|
const [hostname, setHostname] = useState("");
|
||||||
@@ -48,22 +48,22 @@ export function GuacamoleTestDialog({ trigger }: GuacamoleTestDialogProps) {
|
|||||||
"ignore-cert": true,
|
"ignore-cert": true,
|
||||||
};
|
};
|
||||||
|
|
||||||
setConnectionConfig(config);
|
// Add a new tab for the remote desktop connection
|
||||||
setIsConnecting(true);
|
const tabType = connectionType === "rdp" ? "rdp" : connectionType === "vnc" ? "vnc" : "rdp";
|
||||||
};
|
const title = `${connectionType.toUpperCase()} - ${hostname}`;
|
||||||
|
|
||||||
const handleDisconnect = () => {
|
addTab({
|
||||||
setConnectionConfig(null);
|
type: tabType,
|
||||||
setIsConnecting(false);
|
title,
|
||||||
};
|
connectionConfig: config,
|
||||||
|
});
|
||||||
|
|
||||||
const handleClose = () => {
|
// Close the dialog
|
||||||
handleDisconnect();
|
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={(open) => open ? setIsOpen(true) : handleClose()}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
{trigger || (
|
{trigger || (
|
||||||
<Button variant="outline" className="gap-2">
|
<Button variant="outline" className="gap-2">
|
||||||
@@ -72,16 +72,15 @@ export function GuacamoleTestDialog({ trigger }: GuacamoleTestDialogProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className={isConnecting ? "sm:max-w-4xl h-[80vh]" : "sm:max-w-md"}>
|
<DialogContent className="sm:max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<Monitor className="w-5 h-5" />
|
<Monitor className="w-5 h-5" />
|
||||||
{isConnecting ? `Connected to ${hostname}` : "Test Remote Connection"}
|
Remote Connection
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
{!isConnecting ? (
|
<div className="space-y-4">
|
||||||
<div className="space-y-4">
|
|
||||||
<Tabs value={connectionType} onValueChange={(v) => {
|
<Tabs value={connectionType} onValueChange={(v) => {
|
||||||
setConnectionType(v as "rdp" | "vnc" | "telnet");
|
setConnectionType(v as "rdp" | "vnc" | "telnet");
|
||||||
setPort("");
|
setPort("");
|
||||||
@@ -177,16 +176,6 @@ export function GuacamoleTestDialog({ trigger }: GuacamoleTestDialogProps) {
|
|||||||
Connect
|
Connect
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className="flex-1 h-full min-h-[500px]">
|
|
||||||
<GuacamoleDisplay
|
|
||||||
connectionConfig={connectionConfig!}
|
|
||||||
isVisible={true}
|
|
||||||
onDisconnect={handleDisconnect}
|
|
||||||
onError={(err) => console.error("Guacamole error:", err)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState, useMemo } from "react";
|
|||||||
import { Terminal } from "@/ui/desktop/apps/terminal/Terminal.tsx";
|
import { Terminal } from "@/ui/desktop/apps/terminal/Terminal.tsx";
|
||||||
import { Server as ServerView } from "@/ui/desktop/apps/server/Server.tsx";
|
import { Server as ServerView } from "@/ui/desktop/apps/server/Server.tsx";
|
||||||
import { FileManager } from "@/ui/desktop/apps/file-manager/FileManager.tsx";
|
import { FileManager } from "@/ui/desktop/apps/file-manager/FileManager.tsx";
|
||||||
|
import { GuacamoleDisplay, type GuacamoleConnectionConfig } from "@/ui/desktop/apps/guacamole/GuacamoleDisplay.tsx";
|
||||||
import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext.tsx";
|
import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext.tsx";
|
||||||
import {
|
import {
|
||||||
ResizablePanelGroup,
|
ResizablePanelGroup,
|
||||||
@@ -16,7 +17,6 @@ import {
|
|||||||
TERMINAL_THEMES,
|
TERMINAL_THEMES,
|
||||||
DEFAULT_TERMINAL_CONFIG,
|
DEFAULT_TERMINAL_CONFIG,
|
||||||
} from "@/constants/terminal-themes";
|
} from "@/constants/terminal-themes";
|
||||||
import { SSHAuthDialog } from "@/ui/desktop/navigation/SSHAuthDialog.tsx";
|
|
||||||
|
|
||||||
interface TabData {
|
interface TabData {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -30,6 +30,7 @@ interface TabData {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
hostConfig?: any;
|
hostConfig?: any;
|
||||||
|
connectionConfig?: GuacamoleConnectionConfig;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +59,9 @@ export function AppView({
|
|||||||
(tab: TabData) =>
|
(tab: TabData) =>
|
||||||
tab.type === "terminal" ||
|
tab.type === "terminal" ||
|
||||||
tab.type === "server" ||
|
tab.type === "server" ||
|
||||||
tab.type === "file_manager",
|
tab.type === "file_manager" ||
|
||||||
|
tab.type === "rdp" ||
|
||||||
|
tab.type === "vnc",
|
||||||
),
|
),
|
||||||
[tabs],
|
[tabs],
|
||||||
);
|
);
|
||||||
@@ -317,6 +320,19 @@ export function AppView({
|
|||||||
isTopbarOpen={isTopbarOpen}
|
isTopbarOpen={isTopbarOpen}
|
||||||
embedded
|
embedded
|
||||||
/>
|
/>
|
||||||
|
) : t.type === "rdp" || t.type === "vnc" ? (
|
||||||
|
t.connectionConfig ? (
|
||||||
|
<GuacamoleDisplay
|
||||||
|
connectionConfig={t.connectionConfig}
|
||||||
|
isVisible={effectiveVisible}
|
||||||
|
onDisconnect={() => removeTab(t.id)}
|
||||||
|
onError={(err) => console.error("Guacamole error:", err)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center h-full text-red-500">
|
||||||
|
Missing connection configuration
|
||||||
|
</div>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<FileManager
|
<FileManager
|
||||||
embedded
|
embedded
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Server as ServerIcon,
|
Server as ServerIcon,
|
||||||
Folder as FolderIcon,
|
Folder as FolderIcon,
|
||||||
User as UserIcon,
|
User as UserIcon,
|
||||||
|
Monitor as MonitorIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
interface TabProps {
|
interface TabProps {
|
||||||
@@ -119,11 +120,14 @@ export function Tab({
|
|||||||
tabType === "terminal" ||
|
tabType === "terminal" ||
|
||||||
tabType === "server" ||
|
tabType === "server" ||
|
||||||
tabType === "file_manager" ||
|
tabType === "file_manager" ||
|
||||||
tabType === "user_profile"
|
tabType === "user_profile" ||
|
||||||
|
tabType === "rdp" ||
|
||||||
|
tabType === "vnc"
|
||||||
) {
|
) {
|
||||||
const isServer = tabType === "server";
|
const isServer = tabType === "server";
|
||||||
const isFileManager = tabType === "file_manager";
|
const isFileManager = tabType === "file_manager";
|
||||||
const isUserProfile = tabType === "user_profile";
|
const isUserProfile = tabType === "user_profile";
|
||||||
|
const isRemoteDesktop = tabType === "rdp" || tabType === "vnc";
|
||||||
|
|
||||||
const displayTitle =
|
const displayTitle =
|
||||||
title ||
|
title ||
|
||||||
@@ -133,7 +137,9 @@ export function Tab({
|
|||||||
? t("nav.fileManager")
|
? t("nav.fileManager")
|
||||||
: isUserProfile
|
: isUserProfile
|
||||||
? t("nav.userProfile")
|
? t("nav.userProfile")
|
||||||
: t("nav.terminal"));
|
: isRemoteDesktop
|
||||||
|
? tabType.toUpperCase()
|
||||||
|
: t("nav.terminal"));
|
||||||
|
|
||||||
const { base, suffix } = splitTitle(displayTitle);
|
const { base, suffix } = splitTitle(displayTitle);
|
||||||
|
|
||||||
@@ -153,6 +159,8 @@ export function Tab({
|
|||||||
<FolderIcon className="h-4 w-4 flex-shrink-0" />
|
<FolderIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
) : isUserProfile ? (
|
) : isUserProfile ? (
|
||||||
<UserIcon className="h-4 w-4 flex-shrink-0" />
|
<UserIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
|
) : isRemoteDesktop ? (
|
||||||
|
<MonitorIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
<TerminalIcon className="h-4 w-4 flex-shrink-0" />
|
<TerminalIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user