fix: Sidebar resize issues and issues with TOTP interfering with password auth
This commit is contained in:
@@ -298,8 +298,12 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
[systemDrag, clearSelection],
|
||||
);
|
||||
|
||||
const isConnectingRef = useRef(false);
|
||||
|
||||
async function initializeSSHConnection() {
|
||||
if (!currentHost) return;
|
||||
if (!currentHost || isConnectingRef.current) return;
|
||||
|
||||
isConnectingRef.current = true;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -318,6 +322,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
authType: currentHost.authType,
|
||||
credentialId: currentHost.credentialId,
|
||||
userId: currentHost.userId,
|
||||
forceKeyboardInteractive: currentHost.forceKeyboardInteractive,
|
||||
});
|
||||
|
||||
if (result?.requires_totp) {
|
||||
@@ -359,6 +364,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
isConnectingRef.current = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -311,6 +311,7 @@ export function HostManagerEditor({
|
||||
moshCommand: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
forceKeyboardInteractive: z.boolean().optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.authType === "none") {
|
||||
@@ -399,6 +400,7 @@ export function HostManagerEditor({
|
||||
tunnelConnections: [],
|
||||
statsConfig: DEFAULT_STATS_CONFIG,
|
||||
terminalConfig: DEFAULT_TERMINAL_CONFIG,
|
||||
forceKeyboardInteractive: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -473,6 +475,7 @@ export function HostManagerEditor({
|
||||
tunnelConnections: cleanedHost.tunnelConnections || [],
|
||||
statsConfig: parsedStatsConfig,
|
||||
terminalConfig: cleanedHost.terminalConfig || DEFAULT_TERMINAL_CONFIG,
|
||||
forceKeyboardInteractive: Boolean(cleanedHost.forceKeyboardInteractive),
|
||||
};
|
||||
|
||||
if (defaultAuthType === "password") {
|
||||
@@ -520,6 +523,7 @@ export function HostManagerEditor({
|
||||
tunnelConnections: [],
|
||||
statsConfig: DEFAULT_STATS_CONFIG,
|
||||
terminalConfig: DEFAULT_TERMINAL_CONFIG,
|
||||
forceKeyboardInteractive: false,
|
||||
};
|
||||
|
||||
form.reset(defaultFormData);
|
||||
@@ -577,6 +581,7 @@ export function HostManagerEditor({
|
||||
tunnelConnections: data.tunnelConnections || [],
|
||||
statsConfig: data.statsConfig || DEFAULT_STATS_CONFIG,
|
||||
terminalConfig: data.terminalConfig || DEFAULT_TERMINAL_CONFIG,
|
||||
forceKeyboardInteractive: Boolean(data.forceKeyboardInteractive),
|
||||
};
|
||||
|
||||
submitData.credentialId = null;
|
||||
@@ -1296,6 +1301,28 @@ export function HostManagerEditor({
|
||||
</Alert>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="forceKeyboardInteractive"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 mt-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>
|
||||
{t("hosts.forceKeyboardInteractive")}
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
{t("hosts.forceKeyboardInteractiveDesc")}
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="terminal" className="space-y-1">
|
||||
<FormField
|
||||
|
||||
@@ -698,7 +698,9 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
|
||||
localStorage.removeItem("jwt");
|
||||
|
||||
toast.error("Authentication failed. Please log in again.");
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import React, { useState } from "react";
|
||||
import { ChevronUp, User2, HardDrive, Menu, ChevronRight } from "lucide-react";
|
||||
import {
|
||||
ChevronUp,
|
||||
User2,
|
||||
HardDrive,
|
||||
Menu,
|
||||
ChevronRight,
|
||||
RotateCcw,
|
||||
} from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { isElectron, logoutUser } from "@/ui/main-axios.ts";
|
||||
|
||||
@@ -241,10 +248,9 @@ export function LeftSidebar({
|
||||
localStorage.setItem("leftSidebarOpen", JSON.stringify(isSidebarOpen));
|
||||
}, [isSidebarOpen]);
|
||||
|
||||
// Sidebar width state for resizing
|
||||
const [sidebarWidth, setSidebarWidth] = useState<number>(() => {
|
||||
const saved = localStorage.getItem("leftSidebarWidth");
|
||||
return saved !== null ? parseInt(saved, 10) : 320;
|
||||
return saved !== null ? parseInt(saved, 10) : 250;
|
||||
});
|
||||
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
@@ -350,151 +356,171 @@ export function LeftSidebar({
|
||||
|
||||
return (
|
||||
<div className="min-h-svh">
|
||||
<SidebarProvider
|
||||
<SidebarProvider
|
||||
open={isSidebarOpen}
|
||||
style={{ "--sidebar-width": `${sidebarWidth}px` } as React.CSSProperties}
|
||||
style={
|
||||
{ "--sidebar-width": `${sidebarWidth}px` } as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<div className="flex h-screen w-full">
|
||||
<Sidebar variant="floating" className="">
|
||||
<SidebarHeader>
|
||||
<SidebarGroupLabel className="text-lg font-bold text-white">
|
||||
Termix
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
className="w-[28px] h-[28px] absolute right-5"
|
||||
title={t("common.toggleSidebar")}
|
||||
>
|
||||
<Menu className="h-4 w-4" />
|
||||
</Button>
|
||||
</SidebarGroupLabel>
|
||||
</SidebarHeader>
|
||||
<Separator className="p-0.25" />
|
||||
<SidebarContent>
|
||||
<SidebarGroup className="!m-0 !p-0 !-mb-2">
|
||||
<Button
|
||||
className="m-2 flex flex-row font-semibold border-2 !border-dark-border"
|
||||
variant="outline"
|
||||
onClick={openSshManagerTab}
|
||||
disabled={!!sshManagerTab || isSplitScreenActive}
|
||||
title={
|
||||
sshManagerTab
|
||||
? t("interface.sshManagerAlreadyOpen")
|
||||
: isSplitScreenActive
|
||||
? t("interface.disabledDuringSplitScreen")
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<HardDrive strokeWidth="2.5" />
|
||||
{t("nav.hostManager")}
|
||||
</Button>
|
||||
</SidebarGroup>
|
||||
<Separator className="p-0.25" />
|
||||
<SidebarGroup className="flex flex-col gap-y-2 !-mt-2">
|
||||
<div className="!bg-dark-bg-input rounded-lg">
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder={t("placeholders.searchHostsAny")}
|
||||
className="w-full h-8 text-sm border-2 !bg-dark-bg-input border-dark-border rounded-md"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hostsError && (
|
||||
<div className="!bg-dark-bg-input rounded-lg">
|
||||
<div className="w-full h-8 text-sm border-2 !bg-dark-bg-input border-dark-border rounded-md px-3 py-1.5 flex items-center text-red-500">
|
||||
{t("leftSidebar.failedToLoadHosts")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hostsLoading && (
|
||||
<div className="px-4 pb-2">
|
||||
<div className="text-xs text-muted-foreground text-center">
|
||||
{t("hosts.loadingHosts")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sortedFolders.map((folder, idx) => (
|
||||
<FolderCard
|
||||
key={`folder-${folder}-${hostsByFolder[folder]?.length || 0}`}
|
||||
folderName={folder}
|
||||
hosts={getSortedHosts(hostsByFolder[folder])}
|
||||
isFirst={idx === 0}
|
||||
isLast={idx === sortedFolders.length - 1}
|
||||
/>
|
||||
))}
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<Separator className="p-0.25 mt-1 mb-1" />
|
||||
<SidebarFooter>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
className="data-[state=open]:opacity-90 w-full"
|
||||
disabled={disabled}
|
||||
>
|
||||
<User2 /> {username ? username : t("common.logout")}
|
||||
<ChevronUp className="ml-auto" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
side="top"
|
||||
align="start"
|
||||
sideOffset={6}
|
||||
className="min-w-[var(--radix-popper-anchor-width)] bg-sidebar-accent text-sidebar-accent-foreground border border-border rounded-md shadow-2xl p-1"
|
||||
<Sidebar variant="floating">
|
||||
<SidebarHeader>
|
||||
<SidebarGroupLabel className="text-lg font-bold text-white">
|
||||
Termix
|
||||
<div className="absolute right-5 flex gap-1">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setSidebarWidth(250)}
|
||||
className="w-[28px] h-[28px]"
|
||||
title="Reset sidebar width"
|
||||
>
|
||||
<DropdownMenuItem
|
||||
className="rounded px-2 py-1.5 hover:bg-white/15 hover:text-accent-foreground focus:bg-white/20 focus:text-accent-foreground cursor-pointer focus:outline-none"
|
||||
onClick={() => {
|
||||
openUserProfileTab();
|
||||
}}
|
||||
<RotateCcw className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
className="w-[28px] h-[28px]"
|
||||
title={t("common.toggleSidebar")}
|
||||
>
|
||||
<Menu className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</SidebarGroupLabel>
|
||||
</SidebarHeader>
|
||||
<Separator className="p-0.25" />
|
||||
<SidebarContent>
|
||||
<SidebarGroup className="!m-0 !p-0 !-mb-2">
|
||||
<Button
|
||||
className="m-2 flex flex-row font-semibold border-2 !border-dark-border"
|
||||
variant="outline"
|
||||
onClick={openSshManagerTab}
|
||||
disabled={!!sshManagerTab || isSplitScreenActive}
|
||||
title={
|
||||
sshManagerTab
|
||||
? t("interface.sshManagerAlreadyOpen")
|
||||
: isSplitScreenActive
|
||||
? t("interface.disabledDuringSplitScreen")
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<HardDrive strokeWidth="2.5" />
|
||||
{t("nav.hostManager")}
|
||||
</Button>
|
||||
</SidebarGroup>
|
||||
<Separator className="p-0.25" />
|
||||
<SidebarGroup className="flex flex-col gap-y-2 !-mt-2">
|
||||
<div className="!bg-dark-bg-input rounded-lg">
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder={t("placeholders.searchHostsAny")}
|
||||
className="w-full h-8 text-sm border-2 !bg-dark-bg-input border-dark-border rounded-md"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hostsError && (
|
||||
<div className="!bg-dark-bg-input rounded-lg">
|
||||
<div className="w-full h-8 text-sm border-2 !bg-dark-bg-input border-dark-border rounded-md px-3 py-1.5 flex items-center text-red-500">
|
||||
{t("leftSidebar.failedToLoadHosts")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hostsLoading && (
|
||||
<div className="px-4 pb-2">
|
||||
<div className="text-xs text-muted-foreground text-center">
|
||||
{t("hosts.loadingHosts")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sortedFolders.map((folder, idx) => (
|
||||
<FolderCard
|
||||
key={`folder-${folder}-${hostsByFolder[folder]?.length || 0}`}
|
||||
folderName={folder}
|
||||
hosts={getSortedHosts(hostsByFolder[folder])}
|
||||
isFirst={idx === 0}
|
||||
isLast={idx === sortedFolders.length - 1}
|
||||
/>
|
||||
))}
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<Separator className="p-0.25 mt-1 mb-1" />
|
||||
<SidebarFooter>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
className="data-[state=open]:opacity-90 w-full"
|
||||
disabled={disabled}
|
||||
>
|
||||
<User2 /> {username ? username : t("common.logout")}
|
||||
<ChevronUp className="ml-auto" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
side="top"
|
||||
align="start"
|
||||
sideOffset={6}
|
||||
className="min-w-[var(--radix-popper-anchor-width)] bg-sidebar-accent text-sidebar-accent-foreground border border-border rounded-md shadow-2xl p-1"
|
||||
>
|
||||
<span>{t("profile.title")}</span>
|
||||
</DropdownMenuItem>
|
||||
{isAdmin && (
|
||||
<DropdownMenuItem
|
||||
className="rounded px-2 py-1.5 hover:bg-white/15 hover:text-accent-foreground focus:bg-white/20 focus:text-accent-foreground cursor-pointer focus:outline-none"
|
||||
onClick={() => {
|
||||
if (isAdmin) openAdminTab();
|
||||
openUserProfileTab();
|
||||
}}
|
||||
>
|
||||
<span>{t("admin.title")}</span>
|
||||
<span>{t("profile.title")}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
className="rounded px-2 py-1.5 hover:bg-white/15 hover:text-accent-foreground focus:bg-white/20 focus:text-accent-foreground cursor-pointer focus:outline-none"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<span>{t("common.logout")}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
|
||||
{/* Resizable divider */}
|
||||
{isSidebarOpen && (
|
||||
<div
|
||||
className="w-4 cursor-col-resize h-screen z-50 bg-transparent hover:bg-dark-border/30 flex items-center justify-center"
|
||||
onMouseDown={handleMouseDown}
|
||||
title="Drag to resize sidebar"
|
||||
>
|
||||
<div
|
||||
className={`w-1 h-full transition-colors duration-200 ${
|
||||
isResizing
|
||||
? "bg-dark-active"
|
||||
: "bg-dark-border hover:bg-dark-border-hover"
|
||||
}`}
|
||||
{isAdmin && (
|
||||
<DropdownMenuItem
|
||||
className="rounded px-2 py-1.5 hover:bg-white/15 hover:text-accent-foreground focus:bg-white/20 focus:text-accent-foreground cursor-pointer focus:outline-none"
|
||||
onClick={() => {
|
||||
if (isAdmin) openAdminTab();
|
||||
}}
|
||||
>
|
||||
<span>{t("admin.title")}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
className="rounded px-2 py-1.5 hover:bg-white/15 hover:text-accent-foreground focus:bg-white/20 focus:text-accent-foreground cursor-pointer focus:outline-none"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<span>{t("common.logout")}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarFooter>
|
||||
{isSidebarOpen && (
|
||||
<div
|
||||
className="absolute top-0 h-full cursor-col-resize z-[60]"
|
||||
onMouseDown={handleMouseDown}
|
||||
style={{
|
||||
right: "-8px",
|
||||
width: "18px",
|
||||
backgroundColor: isResizing
|
||||
? "var(--dark-active)"
|
||||
: "transparent",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isResizing) {
|
||||
e.currentTarget.style.backgroundColor =
|
||||
"var(--dark-border-hover)";
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!isResizing) {
|
||||
e.currentTarget.style.backgroundColor = "transparent";
|
||||
}
|
||||
}}
|
||||
title="Drag to resize sidebar"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</Sidebar>
|
||||
|
||||
<SidebarInset>{children}</SidebarInset>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@ export function SSHAuthDialog({
|
||||
onSubmit,
|
||||
onCancel,
|
||||
hostInfo,
|
||||
backgroundColor = "#1e1e1e",
|
||||
backgroundColor = "#18181b",
|
||||
}: SSHAuthDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [authTab, setAuthTab] = useState<"password" | "key">("password");
|
||||
@@ -136,7 +136,10 @@ export function SSHAuthDialog({
|
||||
: `${hostInfo.username}@${hostInfo.ip}:${hostInfo.port}`;
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center bg-dark-bg">
|
||||
<div
|
||||
className="absolute inset-0 z-50 flex items-center justify-center bg-dark-bg"
|
||||
style={{ backgroundColor }}
|
||||
>
|
||||
<Card className="w-full max-w-2xl mx-4 border-2">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
|
||||
@@ -50,8 +50,8 @@ export function TopNavbar({
|
||||
allSplitScreenTab: number[];
|
||||
reorderTabs: (fromIndex: number, toIndex: number) => void;
|
||||
};
|
||||
// Use CSS variable for dynamic sidebar width + divider width (4px) + some padding
|
||||
const leftPosition = state === "collapsed" ? "26px" : "calc(var(--sidebar-width) + 20px)";
|
||||
const leftPosition =
|
||||
state === "collapsed" ? "26px" : "calc(var(--sidebar-width) + 8px)";
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [toolsSheetOpen, setToolsSheetOpen] = useState(false);
|
||||
@@ -301,7 +301,7 @@ export function TopNavbar({
|
||||
top: isTopbarOpen ? "0.5rem" : "-3rem",
|
||||
left: leftPosition,
|
||||
right: "17px",
|
||||
backgroundColor: "#1e1e21",
|
||||
backgroundColor: "#18181b",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@@ -491,8 +491,7 @@ export function TopNavbar({
|
||||
{!isTopbarOpen && (
|
||||
<div
|
||||
onClick={() => setIsTopbarOpen(true)}
|
||||
className="absolute top-0 left-0 w-full h-[10px] cursor-pointer z-20 flex items-center justify-center rounded-bl-md rounded-br-md"
|
||||
style={{ backgroundColor: "#1e1e21" }}
|
||||
className="absolute top-0 left-0 w-full h-[10px] cursor-pointer z-20 flex items-center justify-center rounded-bl-md rounded-br-md bg-dark"
|
||||
>
|
||||
<ChevronDown size={10} />
|
||||
</div>
|
||||
|
||||
@@ -316,7 +316,13 @@ function createApiInstance(
|
||||
if (status === 401) {
|
||||
const errorCode = (error.response?.data as Record<string, unknown>)
|
||||
?.code;
|
||||
const errorMessage = (error.response?.data as Record<string, unknown>)
|
||||
?.error;
|
||||
const isSessionExpired = errorCode === "SESSION_EXPIRED";
|
||||
const isInvalidToken =
|
||||
errorCode === "AUTH_REQUIRED" ||
|
||||
errorMessage === "Invalid token" ||
|
||||
errorMessage === "Authentication required";
|
||||
|
||||
if (isElectron()) {
|
||||
localStorage.removeItem("jwt");
|
||||
@@ -324,17 +330,22 @@ function createApiInstance(
|
||||
localStorage.removeItem("jwt");
|
||||
}
|
||||
|
||||
if (isSessionExpired && typeof window !== "undefined") {
|
||||
console.warn("Session expired - please log in again");
|
||||
if (
|
||||
(isSessionExpired || isInvalidToken) &&
|
||||
typeof window !== "undefined"
|
||||
) {
|
||||
console.warn(
|
||||
"Session expired or invalid token - please log in again",
|
||||
);
|
||||
|
||||
document.cookie =
|
||||
"jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
|
||||
|
||||
import("sonner").then(({ toast }) => {
|
||||
toast.warning("Session expired - please log in again");
|
||||
toast.warning("Session expired. Please log in again.");
|
||||
});
|
||||
|
||||
setTimeout(() => window.location.reload(), 100);
|
||||
setTimeout(() => window.location.reload(), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -792,6 +803,7 @@ export async function createSSHHost(hostData: SSHHostData): Promise<SSHHost> {
|
||||
: JSON.stringify(hostData.statsConfig)
|
||||
: null,
|
||||
terminalConfig: hostData.terminalConfig || null,
|
||||
forceKeyboardInteractive: Boolean(hostData.forceKeyboardInteractive),
|
||||
};
|
||||
|
||||
if (!submitData.enableTunnel) {
|
||||
@@ -854,6 +866,7 @@ export async function updateSSHHost(
|
||||
: JSON.stringify(hostData.statsConfig)
|
||||
: null,
|
||||
terminalConfig: hostData.terminalConfig || null,
|
||||
forceKeyboardInteractive: Boolean(hostData.forceKeyboardInteractive),
|
||||
};
|
||||
|
||||
if (!submitData.enableTunnel) {
|
||||
@@ -1164,6 +1177,7 @@ export async function connectSSH(
|
||||
authType?: string;
|
||||
credentialId?: number;
|
||||
userId?: string;
|
||||
forceKeyboardInteractive?: boolean;
|
||||
},
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user