feat: added sidebar management and improved some host manager UI/UX
This commit is contained in:
@@ -585,6 +585,32 @@ const migrateSchema = () => {
|
|||||||
addColumnIfNotExists("ssh_data", "socks5_password", "TEXT");
|
addColumnIfNotExists("ssh_data", "socks5_password", "TEXT");
|
||||||
addColumnIfNotExists("ssh_data", "socks5_proxy_chain", "TEXT");
|
addColumnIfNotExists("ssh_data", "socks5_proxy_chain", "TEXT");
|
||||||
|
|
||||||
|
addColumnIfNotExists(
|
||||||
|
"ssh_data",
|
||||||
|
"show_terminal_in_sidebar",
|
||||||
|
"INTEGER NOT NULL DEFAULT 1",
|
||||||
|
);
|
||||||
|
addColumnIfNotExists(
|
||||||
|
"ssh_data",
|
||||||
|
"show_file_manager_in_sidebar",
|
||||||
|
"INTEGER NOT NULL DEFAULT 0",
|
||||||
|
);
|
||||||
|
addColumnIfNotExists(
|
||||||
|
"ssh_data",
|
||||||
|
"show_tunnel_in_sidebar",
|
||||||
|
"INTEGER NOT NULL DEFAULT 0",
|
||||||
|
);
|
||||||
|
addColumnIfNotExists(
|
||||||
|
"ssh_data",
|
||||||
|
"show_docker_in_sidebar",
|
||||||
|
"INTEGER NOT NULL DEFAULT 0",
|
||||||
|
);
|
||||||
|
addColumnIfNotExists(
|
||||||
|
"ssh_data",
|
||||||
|
"show_server_stats_in_sidebar",
|
||||||
|
"INTEGER NOT NULL DEFAULT 0",
|
||||||
|
);
|
||||||
|
|
||||||
addColumnIfNotExists("ssh_credentials", "private_key", "TEXT");
|
addColumnIfNotExists("ssh_credentials", "private_key", "TEXT");
|
||||||
addColumnIfNotExists("ssh_credentials", "public_key", "TEXT");
|
addColumnIfNotExists("ssh_credentials", "public_key", "TEXT");
|
||||||
addColumnIfNotExists("ssh_credentials", "detected_key_type", "TEXT");
|
addColumnIfNotExists("ssh_credentials", "detected_key_type", "TEXT");
|
||||||
|
|||||||
@@ -90,6 +90,21 @@ export const sshData = sqliteTable("ssh_data", {
|
|||||||
enableDocker: integer("enable_docker", { mode: "boolean" })
|
enableDocker: integer("enable_docker", { mode: "boolean" })
|
||||||
.notNull()
|
.notNull()
|
||||||
.default(false),
|
.default(false),
|
||||||
|
showTerminalInSidebar: integer("show_terminal_in_sidebar", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(true),
|
||||||
|
showFileManagerInSidebar: integer("show_file_manager_in_sidebar", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
showTunnelInSidebar: integer("show_tunnel_in_sidebar", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
showDockerInSidebar: integer("show_docker_in_sidebar", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
showServerStatsInSidebar: integer("show_server_stats_in_sidebar", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
defaultPath: text("default_path"),
|
defaultPath: text("default_path"),
|
||||||
statsConfig: text("stats_config"),
|
statsConfig: text("stats_config"),
|
||||||
terminalConfig: text("terminal_config"),
|
terminalConfig: text("terminal_config"),
|
||||||
|
|||||||
@@ -139,6 +139,11 @@ router.get("/db/host/internal", async (req: Request, res: Response) => {
|
|||||||
pin: !!host.pin,
|
pin: !!host.pin,
|
||||||
enableTerminal: !!host.enableTerminal,
|
enableTerminal: !!host.enableTerminal,
|
||||||
enableFileManager: !!host.enableFileManager,
|
enableFileManager: !!host.enableFileManager,
|
||||||
|
showTerminalInSidebar: !!host.showTerminalInSidebar,
|
||||||
|
showFileManagerInSidebar: !!host.showFileManagerInSidebar,
|
||||||
|
showTunnelInSidebar: !!host.showTunnelInSidebar,
|
||||||
|
showDockerInSidebar: !!host.showDockerInSidebar,
|
||||||
|
showServerStatsInSidebar: !!host.showServerStatsInSidebar,
|
||||||
tags: ["autostart"],
|
tags: ["autostart"],
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -213,6 +218,11 @@ router.get("/db/host/internal/all", async (req: Request, res: Response) => {
|
|||||||
pin: !!host.pin,
|
pin: !!host.pin,
|
||||||
enableTerminal: !!host.enableTerminal,
|
enableTerminal: !!host.enableTerminal,
|
||||||
enableFileManager: !!host.enableFileManager,
|
enableFileManager: !!host.enableFileManager,
|
||||||
|
showTerminalInSidebar: !!host.showTerminalInSidebar,
|
||||||
|
showFileManagerInSidebar: !!host.showFileManagerInSidebar,
|
||||||
|
showTunnelInSidebar: !!host.showTunnelInSidebar,
|
||||||
|
showDockerInSidebar: !!host.showDockerInSidebar,
|
||||||
|
showServerStatsInSidebar: !!host.showServerStatsInSidebar,
|
||||||
defaultPath: host.defaultPath,
|
defaultPath: host.defaultPath,
|
||||||
createdAt: host.createdAt,
|
createdAt: host.createdAt,
|
||||||
updatedAt: host.updatedAt,
|
updatedAt: host.updatedAt,
|
||||||
@@ -298,6 +308,11 @@ router.post(
|
|||||||
enableTunnel,
|
enableTunnel,
|
||||||
enableFileManager,
|
enableFileManager,
|
||||||
enableDocker,
|
enableDocker,
|
||||||
|
showTerminalInSidebar,
|
||||||
|
showFileManagerInSidebar,
|
||||||
|
showTunnelInSidebar,
|
||||||
|
showDockerInSidebar,
|
||||||
|
showServerStatsInSidebar,
|
||||||
defaultPath,
|
defaultPath,
|
||||||
tunnelConnections,
|
tunnelConnections,
|
||||||
jumpHosts,
|
jumpHosts,
|
||||||
@@ -354,6 +369,11 @@ router.post(
|
|||||||
: null,
|
: null,
|
||||||
enableFileManager: enableFileManager ? 1 : 0,
|
enableFileManager: enableFileManager ? 1 : 0,
|
||||||
enableDocker: enableDocker ? 1 : 0,
|
enableDocker: enableDocker ? 1 : 0,
|
||||||
|
showTerminalInSidebar: showTerminalInSidebar ? 1 : 0,
|
||||||
|
showFileManagerInSidebar: showFileManagerInSidebar ? 1 : 0,
|
||||||
|
showTunnelInSidebar: showTunnelInSidebar ? 1 : 0,
|
||||||
|
showDockerInSidebar: showDockerInSidebar ? 1 : 0,
|
||||||
|
showServerStatsInSidebar: showServerStatsInSidebar ? 1 : 0,
|
||||||
defaultPath: defaultPath || null,
|
defaultPath: defaultPath || null,
|
||||||
statsConfig: statsConfig ? JSON.stringify(statsConfig) : null,
|
statsConfig: statsConfig ? JSON.stringify(statsConfig) : null,
|
||||||
terminalConfig: terminalConfig ? JSON.stringify(terminalConfig) : null,
|
terminalConfig: terminalConfig ? JSON.stringify(terminalConfig) : null,
|
||||||
@@ -426,6 +446,11 @@ router.post(
|
|||||||
: [],
|
: [],
|
||||||
enableFileManager: !!createdHost.enableFileManager,
|
enableFileManager: !!createdHost.enableFileManager,
|
||||||
enableDocker: !!createdHost.enableDocker,
|
enableDocker: !!createdHost.enableDocker,
|
||||||
|
showTerminalInSidebar: !!createdHost.showTerminalInSidebar,
|
||||||
|
showFileManagerInSidebar: !!createdHost.showFileManagerInSidebar,
|
||||||
|
showTunnelInSidebar: !!createdHost.showTunnelInSidebar,
|
||||||
|
showDockerInSidebar: !!createdHost.showDockerInSidebar,
|
||||||
|
showServerStatsInSidebar: !!createdHost.showServerStatsInSidebar,
|
||||||
statsConfig: createdHost.statsConfig
|
statsConfig: createdHost.statsConfig
|
||||||
? JSON.parse(createdHost.statsConfig as string)
|
? JSON.parse(createdHost.statsConfig as string)
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -569,6 +594,11 @@ router.put(
|
|||||||
enableTunnel,
|
enableTunnel,
|
||||||
enableFileManager,
|
enableFileManager,
|
||||||
enableDocker,
|
enableDocker,
|
||||||
|
showTerminalInSidebar,
|
||||||
|
showFileManagerInSidebar,
|
||||||
|
showTunnelInSidebar,
|
||||||
|
showDockerInSidebar,
|
||||||
|
showServerStatsInSidebar,
|
||||||
defaultPath,
|
defaultPath,
|
||||||
tunnelConnections,
|
tunnelConnections,
|
||||||
jumpHosts,
|
jumpHosts,
|
||||||
@@ -626,6 +656,11 @@ router.put(
|
|||||||
: null,
|
: null,
|
||||||
enableFileManager: enableFileManager ? 1 : 0,
|
enableFileManager: enableFileManager ? 1 : 0,
|
||||||
enableDocker: enableDocker ? 1 : 0,
|
enableDocker: enableDocker ? 1 : 0,
|
||||||
|
showTerminalInSidebar: showTerminalInSidebar ? 1 : 0,
|
||||||
|
showFileManagerInSidebar: showFileManagerInSidebar ? 1 : 0,
|
||||||
|
showTunnelInSidebar: showTunnelInSidebar ? 1 : 0,
|
||||||
|
showDockerInSidebar: showDockerInSidebar ? 1 : 0,
|
||||||
|
showServerStatsInSidebar: showServerStatsInSidebar ? 1 : 0,
|
||||||
defaultPath: defaultPath || null,
|
defaultPath: defaultPath || null,
|
||||||
statsConfig: statsConfig ? JSON.stringify(statsConfig) : null,
|
statsConfig: statsConfig ? JSON.stringify(statsConfig) : null,
|
||||||
terminalConfig: terminalConfig ? JSON.stringify(terminalConfig) : null,
|
terminalConfig: terminalConfig ? JSON.stringify(terminalConfig) : null,
|
||||||
@@ -793,6 +828,11 @@ router.put(
|
|||||||
: [],
|
: [],
|
||||||
enableFileManager: !!updatedHost.enableFileManager,
|
enableFileManager: !!updatedHost.enableFileManager,
|
||||||
enableDocker: !!updatedHost.enableDocker,
|
enableDocker: !!updatedHost.enableDocker,
|
||||||
|
showTerminalInSidebar: !!updatedHost.showTerminalInSidebar,
|
||||||
|
showFileManagerInSidebar: !!updatedHost.showFileManagerInSidebar,
|
||||||
|
showTunnelInSidebar: !!updatedHost.showTunnelInSidebar,
|
||||||
|
showDockerInSidebar: !!updatedHost.showDockerInSidebar,
|
||||||
|
showServerStatsInSidebar: !!updatedHost.showServerStatsInSidebar,
|
||||||
statsConfig: updatedHost.statsConfig
|
statsConfig: updatedHost.statsConfig
|
||||||
? JSON.parse(updatedHost.statsConfig as string)
|
? JSON.parse(updatedHost.statsConfig as string)
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -928,6 +968,11 @@ router.get(
|
|||||||
quickActions: sshData.quickActions,
|
quickActions: sshData.quickActions,
|
||||||
notes: sshData.notes,
|
notes: sshData.notes,
|
||||||
enableDocker: sshData.enableDocker,
|
enableDocker: sshData.enableDocker,
|
||||||
|
showTerminalInSidebar: sshData.showTerminalInSidebar,
|
||||||
|
showFileManagerInSidebar: sshData.showFileManagerInSidebar,
|
||||||
|
showTunnelInSidebar: sshData.showTunnelInSidebar,
|
||||||
|
showDockerInSidebar: sshData.showDockerInSidebar,
|
||||||
|
showServerStatsInSidebar: sshData.showServerStatsInSidebar,
|
||||||
useSocks5: sshData.useSocks5,
|
useSocks5: sshData.useSocks5,
|
||||||
socks5Host: sshData.socks5Host,
|
socks5Host: sshData.socks5Host,
|
||||||
socks5Port: sshData.socks5Port,
|
socks5Port: sshData.socks5Port,
|
||||||
@@ -1017,6 +1062,11 @@ router.get(
|
|||||||
: [],
|
: [],
|
||||||
enableFileManager: !!row.enableFileManager,
|
enableFileManager: !!row.enableFileManager,
|
||||||
enableDocker: !!row.enableDocker,
|
enableDocker: !!row.enableDocker,
|
||||||
|
showTerminalInSidebar: !!row.showTerminalInSidebar,
|
||||||
|
showFileManagerInSidebar: !!row.showFileManagerInSidebar,
|
||||||
|
showTunnelInSidebar: !!row.showTunnelInSidebar,
|
||||||
|
showDockerInSidebar: !!row.showDockerInSidebar,
|
||||||
|
showServerStatsInSidebar: !!row.showServerStatsInSidebar,
|
||||||
statsConfig: row.statsConfig
|
statsConfig: row.statsConfig
|
||||||
? JSON.parse(row.statsConfig as string)
|
? JSON.parse(row.statsConfig as string)
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -1123,6 +1173,11 @@ router.get(
|
|||||||
jumpHosts: host.jumpHosts ? JSON.parse(host.jumpHosts) : [],
|
jumpHosts: host.jumpHosts ? JSON.parse(host.jumpHosts) : [],
|
||||||
quickActions: host.quickActions ? JSON.parse(host.quickActions) : [],
|
quickActions: host.quickActions ? JSON.parse(host.quickActions) : [],
|
||||||
enableFileManager: !!host.enableFileManager,
|
enableFileManager: !!host.enableFileManager,
|
||||||
|
showTerminalInSidebar: !!host.showTerminalInSidebar,
|
||||||
|
showFileManagerInSidebar: !!host.showFileManagerInSidebar,
|
||||||
|
showTunnelInSidebar: !!host.showTunnelInSidebar,
|
||||||
|
showDockerInSidebar: !!host.showDockerInSidebar,
|
||||||
|
showServerStatsInSidebar: !!host.showServerStatsInSidebar,
|
||||||
statsConfig: host.statsConfig
|
statsConfig: host.statsConfig
|
||||||
? JSON.parse(host.statsConfig)
|
? JSON.parse(host.statsConfig)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function TooltipTrigger({
|
|||||||
|
|
||||||
function TooltipContent({
|
function TooltipContent({
|
||||||
className,
|
className,
|
||||||
sideOffset = 0,
|
sideOffset = 4,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||||
@@ -46,7 +46,7 @@ function TooltipContent({
|
|||||||
data-slot="tooltip-content"
|
data-slot="tooltip-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
"bg-elevated text-foreground border border-edge-medium shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1108,6 +1108,19 @@
|
|||||||
"quickActionName": "Action name",
|
"quickActionName": "Action name",
|
||||||
"noSnippetFound": "No snippet found",
|
"noSnippetFound": "No snippet found",
|
||||||
"quickActionsOrder": "Quick action buttons will appear in the order listed above on the Server Stats page",
|
"quickActionsOrder": "Quick action buttons will appear in the order listed above on the Server Stats page",
|
||||||
|
"sidebarCustomization": "Sidebar Button Customization",
|
||||||
|
"sidebarCustomizationDesc": "Choose which actions appear as quick buttons in the sidebar. Actions not shown as buttons will appear in the dropdown menu.",
|
||||||
|
"showTerminalInSidebar": "Show Terminal Button",
|
||||||
|
"showTerminalInSidebarDesc": "Display terminal as a quick button in the sidebar",
|
||||||
|
"showFileManagerInSidebar": "Show File Manager Button",
|
||||||
|
"showFileManagerInSidebarDesc": "Display file manager as a quick button in the sidebar",
|
||||||
|
"showTunnelInSidebar": "Show Tunnel Button",
|
||||||
|
"showTunnelInSidebarDesc": "Display tunnel management as a quick button in the sidebar",
|
||||||
|
"showDockerInSidebar": "Show Docker Button",
|
||||||
|
"showDockerInSidebarDesc": "Display docker management as a quick button in the sidebar",
|
||||||
|
"showServerStatsInSidebar": "Show Server Stats Button",
|
||||||
|
"showServerStatsInSidebarDesc": "Display server statistics as a quick button in the sidebar",
|
||||||
|
"atLeastOneActionRequired": "At least one enabled action must be shown in the sidebar",
|
||||||
"advancedAuthSettings": "Advanced Authentication Settings",
|
"advancedAuthSettings": "Advanced Authentication Settings",
|
||||||
"sudoPasswordAutoFill": "Sudo Password Auto-Fill",
|
"sudoPasswordAutoFill": "Sudo Password Auto-Fill",
|
||||||
"sudoPasswordAutoFillDesc": "Automatically offer to insert SSH password when sudo prompts for password",
|
"sudoPasswordAutoFillDesc": "Automatically offer to insert SSH password when sudo prompts for password",
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ export interface SSHHost {
|
|||||||
enableTunnel: boolean;
|
enableTunnel: boolean;
|
||||||
enableFileManager: boolean;
|
enableFileManager: boolean;
|
||||||
enableDocker: boolean;
|
enableDocker: boolean;
|
||||||
|
showTerminalInSidebar: boolean;
|
||||||
|
showFileManagerInSidebar: boolean;
|
||||||
|
showTunnelInSidebar: boolean;
|
||||||
|
showDockerInSidebar: boolean;
|
||||||
|
showServerStatsInSidebar: boolean;
|
||||||
defaultPath: string;
|
defaultPath: string;
|
||||||
tunnelConnections: TunnelConnection[];
|
tunnelConnections: TunnelConnection[];
|
||||||
jumpHosts?: JumpHost[];
|
jumpHosts?: JumpHost[];
|
||||||
@@ -102,6 +107,11 @@ export interface SSHHostData {
|
|||||||
enableTunnel?: boolean;
|
enableTunnel?: boolean;
|
||||||
enableFileManager?: boolean;
|
enableFileManager?: boolean;
|
||||||
enableDocker?: boolean;
|
enableDocker?: boolean;
|
||||||
|
showTerminalInSidebar?: boolean;
|
||||||
|
showFileManagerInSidebar?: boolean;
|
||||||
|
showTunnelInSidebar?: boolean;
|
||||||
|
showDockerInSidebar?: boolean;
|
||||||
|
showServerStatsInSidebar?: boolean;
|
||||||
defaultPath?: string;
|
defaultPath?: string;
|
||||||
forceKeyboardInteractive?: boolean;
|
forceKeyboardInteractive?: boolean;
|
||||||
tunnelConnections?: TunnelConnection[];
|
tunnelConnections?: TunnelConnection[];
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Button } from "@/components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
|
import { ButtonGroup } from "@/components/ui/button-group.tsx";
|
||||||
|
|
||||||
interface SSHHost {
|
interface SSHHost {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -364,19 +365,90 @@ export function CommandPalette({
|
|||||||
}}
|
}}
|
||||||
className="flex items-center justify-between"
|
className="flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||||
<Server className="h-4 w-4" />
|
<Server className="h-4 w-4 flex-shrink-0" />
|
||||||
<span>{title}</span>
|
<span className="truncate">{title}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<ButtonGroup
|
||||||
className="flex items-center gap-1"
|
className="flex-shrink-0"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
|
{host.enableTerminal &&
|
||||||
|
(host.showTerminalInSidebar ?? true) && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="!px-2 h-7 border-1 border-edge"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHostTerminalClick(host);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Terminal className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{host.enableFileManager &&
|
||||||
|
(host.showFileManagerInSidebar ?? false) && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="!px-2 h-7 border-1 border-edge"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHostFileManagerClick(host);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FolderOpen className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{host.enableTunnel &&
|
||||||
|
hasTunnelConnections &&
|
||||||
|
(host.showTunnelInSidebar ?? false) && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="!px-2 h-7 border-1 border-edge"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHostTunnelClick(host);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowDownUp className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{host.enableDocker &&
|
||||||
|
(host.showDockerInSidebar ?? false) && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="!px-2 h-7 border-1 border-edge"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHostDockerClick(host);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Container className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shouldShowMetrics &&
|
||||||
|
(host.showServerStatsInSidebar ?? false) && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="!px-2 h-7 border-1 border-edge"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHostServerDetailsClick(host);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Server className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<DropdownMenu modal={false}>
|
<DropdownMenu modal={false}>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="!px-2 h-7 border-1 border-edge"
|
className="!px-2 h-7 border-1 border-edge rounded-l-none border-l-0"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<EllipsisVertical className="h-3 w-3" />
|
<EllipsisVertical className="h-3 w-3" />
|
||||||
@@ -387,62 +459,82 @@ export function CommandPalette({
|
|||||||
side="right"
|
side="right"
|
||||||
className="w-56 bg-canvas border-edge text-foreground"
|
className="w-56 bg-canvas border-edge text-foreground"
|
||||||
>
|
>
|
||||||
{shouldShowMetrics && (
|
{host.enableTerminal &&
|
||||||
<DropdownMenuItem
|
!(host.showTerminalInSidebar ?? true) && (
|
||||||
onClick={(e) => {
|
<DropdownMenuItem
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
handleHostServerDetailsClick(host);
|
e.stopPropagation();
|
||||||
}}
|
handleHostTerminalClick(host);
|
||||||
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
}}
|
||||||
>
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
||||||
<Server className="h-4 w-4" />
|
>
|
||||||
<span className="flex-1">
|
<Terminal className="h-4 w-4" />
|
||||||
{t("hosts.openServerStats")}
|
<span className="flex-1">
|
||||||
</span>
|
{t("hosts.openTerminal")}
|
||||||
</DropdownMenuItem>
|
</span>
|
||||||
)}
|
</DropdownMenuItem>
|
||||||
{host.enableFileManager && (
|
)}
|
||||||
<DropdownMenuItem
|
{shouldShowMetrics &&
|
||||||
onClick={(e) => {
|
!(host.showServerStatsInSidebar ?? false) && (
|
||||||
e.stopPropagation();
|
<DropdownMenuItem
|
||||||
handleHostFileManagerClick(host);
|
onClick={(e) => {
|
||||||
}}
|
e.stopPropagation();
|
||||||
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
handleHostServerDetailsClick(host);
|
||||||
>
|
}}
|
||||||
<FolderOpen className="h-4 w-4" />
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
||||||
<span className="flex-1">
|
>
|
||||||
{t("hosts.openFileManager")}
|
<Server className="h-4 w-4" />
|
||||||
</span>
|
<span className="flex-1">
|
||||||
</DropdownMenuItem>
|
{t("hosts.openServerStats")}
|
||||||
)}
|
</span>
|
||||||
{host.enableTunnel && hasTunnelConnections && (
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
)}
|
||||||
onClick={(e) => {
|
{host.enableFileManager &&
|
||||||
e.stopPropagation();
|
!(host.showFileManagerInSidebar ?? false) && (
|
||||||
handleHostTunnelClick(host);
|
<DropdownMenuItem
|
||||||
}}
|
onClick={(e) => {
|
||||||
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
e.stopPropagation();
|
||||||
>
|
handleHostFileManagerClick(host);
|
||||||
<ArrowDownUp className="h-4 w-4" />
|
}}
|
||||||
<span className="flex-1">
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
||||||
{t("hosts.openTunnels")}
|
>
|
||||||
</span>
|
<FolderOpen className="h-4 w-4" />
|
||||||
</DropdownMenuItem>
|
<span className="flex-1">
|
||||||
)}
|
{t("hosts.openFileManager")}
|
||||||
{host.enableDocker && (
|
</span>
|
||||||
<DropdownMenuItem
|
</DropdownMenuItem>
|
||||||
onClick={(e) => {
|
)}
|
||||||
e.stopPropagation();
|
{host.enableTunnel &&
|
||||||
handleHostDockerClick(host);
|
hasTunnelConnections &&
|
||||||
}}
|
!(host.showTunnelInSidebar ?? false) && (
|
||||||
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
<DropdownMenuItem
|
||||||
>
|
onClick={(e) => {
|
||||||
<Container className="h-4 w-4" />
|
e.stopPropagation();
|
||||||
<span className="flex-1">
|
handleHostTunnelClick(host);
|
||||||
{t("hosts.openDocker")}
|
}}
|
||||||
</span>
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
||||||
</DropdownMenuItem>
|
>
|
||||||
)}
|
<ArrowDownUp className="h-4 w-4" />
|
||||||
|
<span className="flex-1">
|
||||||
|
{t("hosts.openTunnels")}
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
{host.enableDocker &&
|
||||||
|
!(host.showDockerInSidebar ?? false) && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHostDockerClick(host);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
||||||
|
>
|
||||||
|
<Container className="h-4 w-4" />
|
||||||
|
<span className="flex-1">
|
||||||
|
{t("hosts.openDocker")}
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -455,7 +547,7 @@ export function CommandPalette({
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</ButtonGroup>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -514,7 +514,6 @@ export function CredentialEditor({
|
|||||||
}
|
}
|
||||||
backgroundColor="var(--bg-base)"
|
backgroundColor="var(--bg-base)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit, handleFormError)}
|
onSubmit={form.handleSubmit(onSubmit, handleFormError)}
|
||||||
|
|||||||
@@ -178,17 +178,6 @@ export function CredentialSelector({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 gap-2.5">
|
<div className="grid grid-cols-1 gap-2.5">
|
||||||
{value && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="w-full justify-start text-left rounded-lg px-3 py-2 text-destructive hover:bg-destructive/10 transition-colors duration-200"
|
|
||||||
onClick={handleClear}
|
|
||||||
>
|
|
||||||
{t("common.clear")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{filteredCredentials.map((credential) => (
|
{filteredCredentials.map((credential) => (
|
||||||
<Button
|
<Button
|
||||||
key={credential.id}
|
key={credential.id}
|
||||||
|
|||||||
@@ -758,15 +758,6 @@ export function CredentialsManager({
|
|||||||
)}
|
)}
|
||||||
{credential.authType}
|
{credential.authType}
|
||||||
</Badge>
|
</Badge>
|
||||||
{credential.authType === "key" &&
|
|
||||||
credential.keyType && (
|
|
||||||
<Badge
|
|
||||||
variant="outline"
|
|
||||||
className="text-xs px-1 py-0"
|
|
||||||
>
|
|
||||||
{credential.keyType}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ export function HostManager({
|
|||||||
const handleTabChange = (value: string) => {
|
const handleTabChange = (value: string) => {
|
||||||
if (activeTab === "add_host" && value !== "add_host") {
|
if (activeTab === "add_host" && value !== "add_host") {
|
||||||
setEditingHost(null);
|
setEditingHost(null);
|
||||||
|
lastProcessedHostIdRef.current = undefined;
|
||||||
}
|
}
|
||||||
if (activeTab === "add_credential" && value !== "add_credential") {
|
if (activeTab === "add_credential" && value !== "add_credential") {
|
||||||
setEditingCredential(null);
|
setEditingCredential(null);
|
||||||
|
|||||||
@@ -418,6 +418,11 @@ export function HostManagerEditor({
|
|||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
enableDocker: z.boolean().default(false),
|
enableDocker: z.boolean().default(false),
|
||||||
|
showTerminalInSidebar: z.boolean().default(true),
|
||||||
|
showFileManagerInSidebar: z.boolean().default(false),
|
||||||
|
showTunnelInSidebar: z.boolean().default(false),
|
||||||
|
showDockerInSidebar: z.boolean().default(false),
|
||||||
|
showServerStatsInSidebar: z.boolean().default(false),
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
if (data.authType === "none") {
|
if (data.authType === "none") {
|
||||||
@@ -475,6 +480,21 @@ export function HostManagerEditor({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasAtLeastOneSidebarAction =
|
||||||
|
(data.enableTerminal && data.showTerminalInSidebar) ||
|
||||||
|
(data.enableFileManager && data.showFileManagerInSidebar) ||
|
||||||
|
(data.enableTunnel && data.showTunnelInSidebar) ||
|
||||||
|
(data.enableDocker && data.showDockerInSidebar) ||
|
||||||
|
data.showServerStatsInSidebar;
|
||||||
|
|
||||||
|
if (!hasAtLeastOneSidebarAction) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: t("hosts.atLeastOneActionRequired"),
|
||||||
|
path: ["showTerminalInSidebar"],
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormData = z.infer<typeof formSchema>;
|
type FormData = z.infer<typeof formSchema>;
|
||||||
@@ -500,6 +520,11 @@ export function HostManagerEditor({
|
|||||||
enableTerminal: true,
|
enableTerminal: true,
|
||||||
enableTunnel: true,
|
enableTunnel: true,
|
||||||
enableFileManager: true,
|
enableFileManager: true,
|
||||||
|
showTerminalInSidebar: true,
|
||||||
|
showFileManagerInSidebar: false,
|
||||||
|
showTunnelInSidebar: false,
|
||||||
|
showDockerInSidebar: false,
|
||||||
|
showServerStatsInSidebar: false,
|
||||||
defaultPath: "/",
|
defaultPath: "/",
|
||||||
tunnelConnections: [],
|
tunnelConnections: [],
|
||||||
jumpHosts: [],
|
jumpHosts: [],
|
||||||
@@ -671,6 +696,11 @@ export function HostManagerEditor({
|
|||||||
? cleanedHost.socks5ProxyChain
|
? cleanedHost.socks5ProxyChain
|
||||||
: [],
|
: [],
|
||||||
enableDocker: Boolean(cleanedHost.enableDocker),
|
enableDocker: Boolean(cleanedHost.enableDocker),
|
||||||
|
showTerminalInSidebar: cleanedHost.showTerminalInSidebar ?? true,
|
||||||
|
showFileManagerInSidebar: cleanedHost.showFileManagerInSidebar ?? false,
|
||||||
|
showTunnelInSidebar: cleanedHost.showTunnelInSidebar ?? false,
|
||||||
|
showDockerInSidebar: cleanedHost.showDockerInSidebar ?? false,
|
||||||
|
showServerStatsInSidebar: cleanedHost.showServerStatsInSidebar ?? false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -731,6 +761,11 @@ export function HostManagerEditor({
|
|||||||
terminalConfig: DEFAULT_TERMINAL_CONFIG,
|
terminalConfig: DEFAULT_TERMINAL_CONFIG,
|
||||||
forceKeyboardInteractive: false,
|
forceKeyboardInteractive: false,
|
||||||
enableDocker: false,
|
enableDocker: false,
|
||||||
|
showTerminalInSidebar: true,
|
||||||
|
showFileManagerInSidebar: false,
|
||||||
|
showTunnelInSidebar: false,
|
||||||
|
showDockerInSidebar: false,
|
||||||
|
showServerStatsInSidebar: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
form.reset(defaultFormData as FormData);
|
form.reset(defaultFormData as FormData);
|
||||||
@@ -1073,7 +1108,6 @@ export function HostManagerEditor({
|
|||||||
}
|
}
|
||||||
backgroundColor="var(--bg-base)"
|
backgroundColor="var(--bg-base)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit, handleFormError)}
|
onSubmit={form.handleSubmit(onSubmit, handleFormError)}
|
||||||
|
|||||||
@@ -868,10 +868,7 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
|
|||||||
{importing ? t("hosts.importing") : t("hosts.importJson")}
|
{importing ? t("hosts.importing") : t("hosts.importJson")}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent
|
<TooltipContent side="bottom" className="max-w-sm">
|
||||||
side="bottom"
|
|
||||||
className="max-w-sm bg-popover text-popover-foreground border border-border shadow-lg"
|
|
||||||
>
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="font-semibold text-sm">
|
<p className="font-semibold text-sm">
|
||||||
{t("hosts.importJsonTitle")}
|
{t("hosts.importJsonTitle")}
|
||||||
@@ -952,10 +949,7 @@ export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
|
|||||||
{importing ? t("hosts.importing") : t("hosts.importJson")}
|
{importing ? t("hosts.importing") : t("hosts.importJson")}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent
|
<TooltipContent side="bottom" className="max-w-sm">
|
||||||
side="bottom"
|
|
||||||
className="max-w-sm bg-popover text-popover-foreground border border-border shadow-lg"
|
|
||||||
>
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="font-semibold text-sm">
|
<p className="font-semibold text-sm">
|
||||||
{t("hosts.importJsonTitle")}
|
{t("hosts.importJsonTitle")}
|
||||||
|
|||||||
@@ -611,6 +611,132 @@ export function HostGeneralTab({
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
<Separator className="my-6" />
|
<Separator className="my-6" />
|
||||||
<Accordion type="multiple" className="w-full">
|
<Accordion type="multiple" className="w-full">
|
||||||
|
<AccordionItem value="sidebar-customization">
|
||||||
|
<AccordionTrigger>{t("hosts.sidebarCustomization")}</AccordionTrigger>
|
||||||
|
<AccordionContent className="space-y-4 pt-4">
|
||||||
|
<Alert>
|
||||||
|
<AlertDescription>
|
||||||
|
{t("hosts.sidebarCustomizationDesc")}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
{form.watch("enableTerminal") && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="showTerminalInSidebar"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.showTerminalInSidebar")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.showTerminalInSidebarDesc")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{form.watch("enableFileManager") && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="showFileManagerInSidebar"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>
|
||||||
|
{t("hosts.showFileManagerInSidebar")}
|
||||||
|
</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.showFileManagerInSidebarDesc")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{form.watch("enableTunnel") && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="showTunnelInSidebar"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.showTunnelInSidebar")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.showTunnelInSidebarDesc")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{form.watch("enableDocker") && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="showDockerInSidebar"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.showDockerInSidebar")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.showDockerInSidebarDesc")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="showServerStatsInSidebar"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>{t("hosts.showServerStatsInSidebar")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("hosts.showServerStatsInSidebarDesc")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="advanced-auth">
|
<AccordionItem value="advanced-auth">
|
||||||
<AccordionTrigger>{t("hosts.advancedAuthSettings")}</AccordionTrigger>
|
<AccordionTrigger>{t("hosts.advancedAuthSettings")}</AccordionTrigger>
|
||||||
<AccordionContent className="space-y-4 pt-4">
|
<AccordionContent className="space-y-4 pt-4">
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function SimpleLoader({
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute inset-0 flex items-center justify-center z-50",
|
"absolute inset-0 flex items-center justify-center z-[100]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: backgroundColor || "var(--bg-base)" }}
|
style={{ backgroundColor: backgroundColor || "var(--bg-base)" }}
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export function Host({ host: initialHost }: HostProps): React.ReactElement {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ButtonGroup className="flex-shrink-0">
|
<ButtonGroup className="flex-shrink-0">
|
||||||
{host.enableTerminal && (
|
{host.enableTerminal && (host.showTerminalInSidebar ?? true) && (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="!px-2 border-1 border-edge"
|
className="!px-2 border-1 border-edge"
|
||||||
@@ -165,13 +165,62 @@ export function Host({ host: initialHost }: HostProps): React.ReactElement {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{host.enableFileManager &&
|
||||||
|
(host.showFileManagerInSidebar ?? false) && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="!px-2 border-1 border-edge"
|
||||||
|
onClick={() =>
|
||||||
|
addTab({ type: "file_manager", title, hostConfig: host })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FolderOpen />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{host.enableTunnel &&
|
||||||
|
hasTunnelConnections &&
|
||||||
|
(host.showTunnelInSidebar ?? false) && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="!px-2 border-1 border-edge"
|
||||||
|
onClick={() =>
|
||||||
|
addTab({ type: "tunnel", title, hostConfig: host })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ArrowDownUp />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{host.enableDocker && (host.showDockerInSidebar ?? false) && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="!px-2 border-1 border-edge"
|
||||||
|
onClick={() =>
|
||||||
|
addTab({ type: "docker", title, hostConfig: host })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Container />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shouldShowMetrics && (host.showServerStatsInSidebar ?? false) && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="!px-2 border-1 border-edge"
|
||||||
|
onClick={() =>
|
||||||
|
addTab({ type: "server_stats", title, hostConfig: host })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Server />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<DropdownMenu modal={false}>
|
<DropdownMenu modal={false}>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={`!px-2 border-1 border-edge ${
|
className="!px-2 border-1 border-edge rounded-l-none border-l-0"
|
||||||
host.enableTerminal ? "rounded-tl-none rounded-bl-none" : ""
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<EllipsisVertical />
|
<EllipsisVertical />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -182,40 +231,53 @@ export function Host({ host: initialHost }: HostProps): React.ReactElement {
|
|||||||
side="right"
|
side="right"
|
||||||
className="w-56 bg-canvas border-edge text-foreground"
|
className="w-56 bg-canvas border-edge text-foreground"
|
||||||
>
|
>
|
||||||
{shouldShowMetrics && (
|
{host.enableTerminal && !(host.showTerminalInSidebar ?? true) && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() =>
|
onClick={handleTerminalClick}
|
||||||
addTab({ type: "server_stats", title, hostConfig: host })
|
|
||||||
}
|
|
||||||
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
||||||
>
|
>
|
||||||
<Server className="h-4 w-4" />
|
<Terminal className="h-4 w-4" />
|
||||||
<span className="flex-1">{t("hosts.openServerStats")}</span>
|
<span className="flex-1">{t("hosts.openTerminal")}</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{host.enableFileManager && (
|
{shouldShowMetrics &&
|
||||||
<DropdownMenuItem
|
!(host.showServerStatsInSidebar ?? false) && (
|
||||||
onClick={() =>
|
<DropdownMenuItem
|
||||||
addTab({ type: "file_manager", title, hostConfig: host })
|
onClick={() =>
|
||||||
}
|
addTab({ type: "server_stats", title, hostConfig: host })
|
||||||
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
}
|
||||||
>
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
||||||
<FolderOpen className="h-4 w-4" />
|
>
|
||||||
<span className="flex-1">{t("hosts.openFileManager")}</span>
|
<Server className="h-4 w-4" />
|
||||||
</DropdownMenuItem>
|
<span className="flex-1">{t("hosts.openServerStats")}</span>
|
||||||
)}
|
</DropdownMenuItem>
|
||||||
{host.enableTunnel && hasTunnelConnections && (
|
)}
|
||||||
<DropdownMenuItem
|
{host.enableFileManager &&
|
||||||
onClick={() =>
|
!(host.showFileManagerInSidebar ?? false) && (
|
||||||
addTab({ type: "tunnel", title, hostConfig: host })
|
<DropdownMenuItem
|
||||||
}
|
onClick={() =>
|
||||||
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
addTab({ type: "file_manager", title, hostConfig: host })
|
||||||
>
|
}
|
||||||
<ArrowDownUp className="h-4 w-4" />
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
||||||
<span className="flex-1">{t("hosts.openTunnels")}</span>
|
>
|
||||||
</DropdownMenuItem>
|
<FolderOpen className="h-4 w-4" />
|
||||||
)}
|
<span className="flex-1">{t("hosts.openFileManager")}</span>
|
||||||
{host.enableDocker && (
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
{host.enableTunnel &&
|
||||||
|
hasTunnelConnections &&
|
||||||
|
!(host.showTunnelInSidebar ?? false) && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() =>
|
||||||
|
addTab({ type: "tunnel", title, hostConfig: host })
|
||||||
|
}
|
||||||
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-hover text-foreground-secondary"
|
||||||
|
>
|
||||||
|
<ArrowDownUp className="h-4 w-4" />
|
||||||
|
<span className="flex-1">{t("hosts.openTunnels")}</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
{host.enableDocker && !(host.showDockerInSidebar ?? false) && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addTab({ type: "docker", title, hostConfig: host })
|
addTab({ type: "docker", title, hostConfig: host })
|
||||||
|
|||||||
@@ -959,6 +959,11 @@ export async function createSSHHost(hostData: SSHHostData): Promise<SSHHost> {
|
|||||||
enableTunnel: Boolean(hostData.enableTunnel),
|
enableTunnel: Boolean(hostData.enableTunnel),
|
||||||
enableFileManager: Boolean(hostData.enableFileManager),
|
enableFileManager: Boolean(hostData.enableFileManager),
|
||||||
enableDocker: Boolean(hostData.enableDocker),
|
enableDocker: Boolean(hostData.enableDocker),
|
||||||
|
showTerminalInSidebar: Boolean(hostData.showTerminalInSidebar),
|
||||||
|
showFileManagerInSidebar: Boolean(hostData.showFileManagerInSidebar),
|
||||||
|
showTunnelInSidebar: Boolean(hostData.showTunnelInSidebar),
|
||||||
|
showDockerInSidebar: Boolean(hostData.showDockerInSidebar),
|
||||||
|
showServerStatsInSidebar: Boolean(hostData.showServerStatsInSidebar),
|
||||||
defaultPath: hostData.defaultPath || "/",
|
defaultPath: hostData.defaultPath || "/",
|
||||||
tunnelConnections: hostData.tunnelConnections || [],
|
tunnelConnections: hostData.tunnelConnections || [],
|
||||||
jumpHosts: hostData.jumpHosts || [],
|
jumpHosts: hostData.jumpHosts || [],
|
||||||
@@ -1033,6 +1038,11 @@ export async function updateSSHHost(
|
|||||||
enableTunnel: Boolean(hostData.enableTunnel),
|
enableTunnel: Boolean(hostData.enableTunnel),
|
||||||
enableFileManager: Boolean(hostData.enableFileManager),
|
enableFileManager: Boolean(hostData.enableFileManager),
|
||||||
enableDocker: Boolean(hostData.enableDocker),
|
enableDocker: Boolean(hostData.enableDocker),
|
||||||
|
showTerminalInSidebar: Boolean(hostData.showTerminalInSidebar),
|
||||||
|
showFileManagerInSidebar: Boolean(hostData.showFileManagerInSidebar),
|
||||||
|
showTunnelInSidebar: Boolean(hostData.showTunnelInSidebar),
|
||||||
|
showDockerInSidebar: Boolean(hostData.showDockerInSidebar),
|
||||||
|
showServerStatsInSidebar: Boolean(hostData.showServerStatsInSidebar),
|
||||||
defaultPath: hostData.defaultPath || "/",
|
defaultPath: hostData.defaultPath || "/",
|
||||||
tunnelConnections: hostData.tunnelConnections || [],
|
tunnelConnections: hostData.tunnelConnections || [],
|
||||||
jumpHosts: hostData.jumpHosts || [],
|
jumpHosts: hostData.jumpHosts || [],
|
||||||
|
|||||||
Reference in New Issue
Block a user