mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 04:43:36 +00:00
fix: rdp/vnc dupliocated icon, mobile hover fix for menu, and guacd hosts not working when ssh is enabeld
This commit is contained in:
@@ -144,6 +144,17 @@ router.post("/token", async (req, res) => {
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: Host ID to connect to
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* protocol:
|
||||
* type: string
|
||||
* enum: [rdp, vnc, telnet]
|
||||
* description: Override the host's default connection type
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Connection token generated successfully
|
||||
@@ -205,13 +216,27 @@ router.post(
|
||||
}
|
||||
}
|
||||
|
||||
const connectionType = (host.connectionType as string) || "ssh";
|
||||
const requestedProtocol = req.body?.protocol as string | undefined;
|
||||
const connectionType =
|
||||
requestedProtocol || (host.connectionType as string);
|
||||
|
||||
if (!["rdp", "vnc", "telnet"].includes(connectionType)) {
|
||||
return res.status(400).json({
|
||||
error: `Connection type '${connectionType}' is not supported for remote desktop. Only RDP, VNC, and Telnet are supported.`,
|
||||
});
|
||||
}
|
||||
|
||||
const protocolEnabledMap: Record<string, boolean> = {
|
||||
rdp: !!host.enableRdp,
|
||||
vnc: !!host.enableVnc,
|
||||
telnet: !!host.enableTelnet,
|
||||
};
|
||||
if (!protocolEnabledMap[connectionType]) {
|
||||
return res.status(400).json({
|
||||
error: `${connectionType.toUpperCase()} is not enabled for this host.`,
|
||||
});
|
||||
}
|
||||
|
||||
let guacConfig: Record<string, unknown> = {};
|
||||
if (host.guacamoleConfig) {
|
||||
try {
|
||||
|
||||
@@ -15,9 +15,14 @@ import type { SSHHost } from "@/types";
|
||||
interface GuacamoleAppProps {
|
||||
hostId?: string;
|
||||
tabId?: string;
|
||||
protocol?: "rdp" | "vnc" | "telnet";
|
||||
}
|
||||
|
||||
const GuacamoleApp: React.FC<GuacamoleAppProps> = ({ hostId, tabId }) => {
|
||||
const GuacamoleApp: React.FC<GuacamoleAppProps> = ({
|
||||
hostId,
|
||||
tabId,
|
||||
protocol,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -76,6 +81,7 @@ const GuacamoleApp: React.FC<GuacamoleAppProps> = ({ hostId, tabId }) => {
|
||||
hostId={parseInt(hostId, 10)}
|
||||
hostConfig={hostConfig}
|
||||
tabId={tabId}
|
||||
protocol={protocol}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
@@ -87,12 +93,14 @@ interface GuacamoleAppInnerProps {
|
||||
hostId: number;
|
||||
hostConfig: Pick<SSHHost, "connectionType">;
|
||||
tabId?: string;
|
||||
protocol?: "rdp" | "vnc" | "telnet";
|
||||
}
|
||||
|
||||
const GuacamoleAppInner: React.FC<GuacamoleAppInnerProps> = ({
|
||||
hostId,
|
||||
hostConfig,
|
||||
tabId,
|
||||
protocol,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [token, setToken] = useState<string | null>(null);
|
||||
@@ -110,7 +118,7 @@ const GuacamoleAppInner: React.FC<GuacamoleAppInnerProps> = ({
|
||||
setError(t("guacamole.guacdUnavailable"));
|
||||
return;
|
||||
}
|
||||
return getGuacamoleTokenFromHost(hostId);
|
||||
return getGuacamoleTokenFromHost(hostId, protocol);
|
||||
})
|
||||
.then((result) => {
|
||||
if (result) setToken(result.token);
|
||||
@@ -172,14 +180,21 @@ const GuacamoleAppInner: React.FC<GuacamoleAppInnerProps> = ({
|
||||
<SimpleLoader
|
||||
visible={true}
|
||||
message={t("guacamole.connecting", {
|
||||
type: (hostConfig.connectionType || "remote").toUpperCase(),
|
||||
type: (
|
||||
protocol ||
|
||||
hostConfig.connectionType ||
|
||||
"remote"
|
||||
).toUpperCase(),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const protocol = hostConfig.connectionType as "rdp" | "vnc" | "telnet";
|
||||
const resolvedProtocol = (protocol ?? hostConfig.connectionType) as
|
||||
| "rdp"
|
||||
| "vnc"
|
||||
| "telnet";
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full">
|
||||
@@ -213,13 +228,17 @@ const GuacamoleAppInner: React.FC<GuacamoleAppInnerProps> = ({
|
||||
<GuacamoleDisplay
|
||||
key={token}
|
||||
ref={displayRef}
|
||||
connectionConfig={{ token, protocol, type: protocol }}
|
||||
connectionConfig={{
|
||||
token,
|
||||
protocol: resolvedProtocol,
|
||||
type: resolvedProtocol,
|
||||
}}
|
||||
isVisible={true}
|
||||
onError={(err) => setConnectionError(err)}
|
||||
/>
|
||||
<GuacamoleToolbar
|
||||
displayRef={displayRef}
|
||||
protocol={protocol}
|
||||
protocol={resolvedProtocol}
|
||||
onReconnect={handleReconnect}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4360,9 +4360,13 @@ export async function getGuacamoleToken(
|
||||
|
||||
export async function getGuacamoleTokenFromHost(
|
||||
hostId: number,
|
||||
protocol?: "rdp" | "vnc" | "telnet",
|
||||
): Promise<GuacamoleTokenResponse> {
|
||||
try {
|
||||
const response = await authApi.post(`/guacamole/connect-host/${hostId}`);
|
||||
const response = await authApi.post(
|
||||
`/guacamole/connect-host/${hostId}`,
|
||||
protocol ? { protocol } : {},
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "get guacamole token from host");
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
KeyRound,
|
||||
LayoutDashboard,
|
||||
Monitor,
|
||||
MousePointerClick,
|
||||
Clock,
|
||||
Folder,
|
||||
Pencil,
|
||||
@@ -48,7 +49,7 @@ const ACTIVITY_ICONS: Record<string, React.ReactNode> = {
|
||||
tunnel: <Network className="size-3.5" />,
|
||||
docker: <Box className="size-3.5" />,
|
||||
telnet: <MessagesSquare className="size-3.5" />,
|
||||
vnc: <Monitor className="size-3.5" />,
|
||||
vnc: <MousePointerClick className="size-3.5" />,
|
||||
rdp: <Monitor className="size-3.5" />,
|
||||
};
|
||||
|
||||
@@ -424,7 +425,7 @@ export function CommandPalette({
|
||||
}}
|
||||
className="flex items-center gap-1 px-2 h-6 rounded text-xs font-medium text-muted-foreground/70 hover:text-foreground hover:bg-muted-foreground/10 transition-colors border border-border/40"
|
||||
>
|
||||
<Monitor className="size-3" />
|
||||
<MousePointerClick className="size-3" />
|
||||
VNC
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -230,7 +230,13 @@ export function renderTabContent(
|
||||
return (
|
||||
<EmptyState icon={Monitor} messageKey="guacamole.noHostSelected" />
|
||||
);
|
||||
return <GuacamoleApp hostId={host.id} tabId={tab.id} />;
|
||||
return (
|
||||
<GuacamoleApp
|
||||
hostId={host.id}
|
||||
tabId={tab.id}
|
||||
protocol={tab.type as "rdp" | "vnc" | "telnet"}
|
||||
/>
|
||||
);
|
||||
|
||||
case "network_graph":
|
||||
return <NetworkGraphCard embedded={false} />;
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
Lock,
|
||||
Monitor,
|
||||
Network,
|
||||
MousePointerClick,
|
||||
Palette,
|
||||
Pencil,
|
||||
Plus,
|
||||
@@ -220,7 +221,7 @@ function makeHostTabs(t: (key: string) => string): HostTab[] {
|
||||
{
|
||||
id: "vnc",
|
||||
label: t("hosts.tabVnc"),
|
||||
icon: <Monitor className="size-3" />,
|
||||
icon: <MousePointerClick className="size-3" />,
|
||||
},
|
||||
{
|
||||
id: "telnet",
|
||||
@@ -687,7 +688,7 @@ function HostEditor({
|
||||
proto: "enableVnc" as const,
|
||||
label: t("hosts.tabVnc"),
|
||||
desc: t("hosts.virtualNetwork"),
|
||||
icon: <Monitor className="size-4" />,
|
||||
icon: <MousePointerClick className="size-4" />,
|
||||
portField: "vncPort" as const,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
MessagesSquare,
|
||||
Monitor,
|
||||
MoreHorizontal,
|
||||
MousePointerClick,
|
||||
Network,
|
||||
Pencil,
|
||||
Pin,
|
||||
@@ -215,11 +216,17 @@ export function HostItem({
|
||||
? "bg-muted/20"
|
||||
: ""
|
||||
} ${isMenuOpen ? "bg-muted/40" : ""}`}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
if (selectionMode) {
|
||||
onToggleSelect?.();
|
||||
return;
|
||||
}
|
||||
// On touch devices open the action tray instead of immediately launching a tab
|
||||
if (window.matchMedia("(hover: none)").matches) {
|
||||
e.stopPropagation();
|
||||
onMenuOpenChange?.(!isMenuOpen);
|
||||
return;
|
||||
}
|
||||
if (host.enableSsh) onOpenTab("terminal");
|
||||
else if (host.enableRdp) onOpenTab("rdp");
|
||||
else if (host.enableVnc) onOpenTab("vnc");
|
||||
@@ -360,7 +367,7 @@ export function HostItem({
|
||||
}}
|
||||
className="flex items-center justify-center size-7 rounded text-muted-foreground/50 hover:text-foreground hover:bg-muted-foreground/10 transition-colors"
|
||||
>
|
||||
<Monitor className="size-3.5" />
|
||||
<MousePointerClick className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
{host.enableTelnet && (
|
||||
@@ -526,7 +533,7 @@ export function HostItem({
|
||||
toast.success(t("hosts.vncUrlCopied"));
|
||||
}}
|
||||
>
|
||||
<Monitor className="size-3.5 mr-2" />
|
||||
<MousePointerClick className="size-3.5 mr-2" />
|
||||
{t("hosts.copyVncUrlAction")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user