feat: Finalize command palette
This commit is contained in:
@@ -1100,7 +1100,7 @@
|
|||||||
"refreshing": "Aktualisieren...",
|
"refreshing": "Aktualisieren...",
|
||||||
"serverOffline": "Server offline",
|
"serverOffline": "Server offline",
|
||||||
"cannotFetchMetrics": "Metriken können nicht vom Offline-Server abgerufen werden",
|
"cannotFetchMetrics": "Metriken können nicht vom Offline-Server abgerufen werden",
|
||||||
"load": "Last"
|
"load": "Last",
|
||||||
"available": "Verfügbar",
|
"available": "Verfügbar",
|
||||||
"editLayout": "Layout anpassen",
|
"editLayout": "Layout anpassen",
|
||||||
"cancelEdit": "Abbrechen",
|
"cancelEdit": "Abbrechen",
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ function AppContent() {
|
|||||||
<TopNavbar
|
<TopNavbar
|
||||||
isTopbarOpen={isTopbarOpen}
|
isTopbarOpen={isTopbarOpen}
|
||||||
setIsTopbarOpen={setIsTopbarOpen}
|
setIsTopbarOpen={setIsTopbarOpen}
|
||||||
|
onOpenCommandPalette={() => setIsCommandPaletteOpen(true)}
|
||||||
/>
|
/>
|
||||||
</LeftSidebar>
|
</LeftSidebar>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,21 +3,59 @@ import {
|
|||||||
CommandInput,
|
CommandInput,
|
||||||
CommandItem,
|
CommandItem,
|
||||||
CommandList,
|
CommandList,
|
||||||
CommandShortcut,
|
|
||||||
CommandGroup,
|
CommandGroup,
|
||||||
CommandSeparator,
|
CommandSeparator,
|
||||||
} from "@/components/ui/command.tsx";
|
} from "@/components/ui/command.tsx";
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
Calculator,
|
Key,
|
||||||
Calendar,
|
Server,
|
||||||
CreditCard,
|
|
||||||
Settings,
|
Settings,
|
||||||
Smile,
|
|
||||||
User,
|
User,
|
||||||
|
Github,
|
||||||
|
Terminal,
|
||||||
|
FolderOpen,
|
||||||
|
Pencil,
|
||||||
|
EllipsisVertical,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { CommandEmpty } from "cmdk";
|
import { BiMoney, BiSupport } from "react-icons/bi";
|
||||||
|
import { BsDiscord } from "react-icons/bs";
|
||||||
|
import { GrUpdate } from "react-icons/gr";
|
||||||
|
import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext.tsx";
|
||||||
|
import { getRecentActivity, getSSHHosts } from "@/ui/main-axios.ts";
|
||||||
|
import type { RecentActivityItem } from "@/ui/main-axios.ts";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
|
|
||||||
|
interface SSHHost {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
ip: string;
|
||||||
|
port: number;
|
||||||
|
username: string;
|
||||||
|
folder: string;
|
||||||
|
tags: string[];
|
||||||
|
pin: boolean;
|
||||||
|
authType: string;
|
||||||
|
password?: string;
|
||||||
|
key?: string;
|
||||||
|
keyPassword?: string;
|
||||||
|
keyType?: string;
|
||||||
|
enableTerminal: boolean;
|
||||||
|
enableTunnel: boolean;
|
||||||
|
enableFileManager: boolean;
|
||||||
|
defaultPath: string;
|
||||||
|
tunnelConnections: unknown[];
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
export function CommandPalette({
|
export function CommandPalette({
|
||||||
isOpen,
|
isOpen,
|
||||||
setIsOpen,
|
setIsOpen,
|
||||||
@@ -26,59 +64,316 @@ export function CommandPalette({
|
|||||||
setIsOpen: (isOpen: boolean) => void;
|
setIsOpen: (isOpen: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const { addTab, setCurrentTab, tabs: tabList, updateTab } = useTabs();
|
||||||
|
const [recentActivity, setRecentActivity] = useState<RecentActivityItem[]>(
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const [hosts, setHosts] = useState<SSHHost[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
|
getRecentActivity(50).then((activity) => {
|
||||||
|
setRecentActivity(activity.slice(0, 5));
|
||||||
|
});
|
||||||
|
getSSHHosts().then((allHosts) => {
|
||||||
|
setHosts(allHosts);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const handleAddHost = () => {
|
||||||
|
const sshManagerTab = tabList.find((t) => t.type === "ssh_manager");
|
||||||
|
if (sshManagerTab) {
|
||||||
|
updateTab(sshManagerTab.id, { initialTab: "add_host" });
|
||||||
|
setCurrentTab(sshManagerTab.id);
|
||||||
|
} else {
|
||||||
|
const id = addTab({
|
||||||
|
type: "ssh_manager",
|
||||||
|
title: "Host Manager",
|
||||||
|
initialTab: "add_host",
|
||||||
|
});
|
||||||
|
setCurrentTab(id);
|
||||||
|
}
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddCredential = () => {
|
||||||
|
const sshManagerTab = tabList.find((t) => t.type === "ssh_manager");
|
||||||
|
if (sshManagerTab) {
|
||||||
|
updateTab(sshManagerTab.id, { initialTab: "add_credential" });
|
||||||
|
setCurrentTab(sshManagerTab.id);
|
||||||
|
} else {
|
||||||
|
const id = addTab({
|
||||||
|
type: "ssh_manager",
|
||||||
|
title: "Host Manager",
|
||||||
|
initialTab: "add_credential",
|
||||||
|
});
|
||||||
|
setCurrentTab(id);
|
||||||
|
}
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenAdminSettings = () => {
|
||||||
|
const adminTab = tabList.find((t) => t.type === "admin");
|
||||||
|
if (adminTab) {
|
||||||
|
setCurrentTab(adminTab.id);
|
||||||
|
} else {
|
||||||
|
const id = addTab({ type: "admin", title: "Admin Settings" });
|
||||||
|
setCurrentTab(id);
|
||||||
|
}
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenUserProfile = () => {
|
||||||
|
const userProfileTab = tabList.find((t) => t.type === "user_profile");
|
||||||
|
if (userProfileTab) {
|
||||||
|
setCurrentTab(userProfileTab.id);
|
||||||
|
} else {
|
||||||
|
const id = addTab({ type: "user_profile", title: "User Profile" });
|
||||||
|
setCurrentTab(id);
|
||||||
|
}
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenUpdateLog = () => {
|
||||||
|
window.open("https://github.com/Termix-SSH/Termix/releases", "_blank");
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGitHub = () => {
|
||||||
|
window.open("https://github.com/Termix-SSH/Termix", "_blank");
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSupport = () => {
|
||||||
|
window.open("https://github.com/Termix-SSH/Support/issues/new", "_blank");
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDiscord = () => {
|
||||||
|
window.open("https://discord.com/invite/jVQGdvHDrf", "_blank");
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDonate = () => {
|
||||||
|
window.open("https://github.com/sponsors/LukeGus", "_blank");
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleActivityClick = (item: RecentActivityItem) => {
|
||||||
|
getSSHHosts().then((hosts) => {
|
||||||
|
const host = hosts.find((h: { id: number }) => h.id === item.hostId);
|
||||||
|
if (!host) return;
|
||||||
|
|
||||||
|
if (item.type === "terminal") {
|
||||||
|
addTab({
|
||||||
|
type: "terminal",
|
||||||
|
title: item.hostName,
|
||||||
|
hostConfig: host,
|
||||||
|
});
|
||||||
|
} else if (item.type === "file_manager") {
|
||||||
|
addTab({
|
||||||
|
type: "file_manager",
|
||||||
|
title: item.hostName,
|
||||||
|
hostConfig: host,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHostTerminalClick = (host: SSHHost) => {
|
||||||
|
const title = host.name?.trim()
|
||||||
|
? host.name
|
||||||
|
: `${host.username}@${host.ip}:${host.port}`;
|
||||||
|
addTab({ type: "terminal", title, hostConfig: host });
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHostFileManagerClick = (host: SSHHost) => {
|
||||||
|
const title = host.name?.trim()
|
||||||
|
? host.name
|
||||||
|
: `${host.username}@${host.ip}:${host.port}`;
|
||||||
|
addTab({ type: "file_manager", title, hostConfig: host });
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHostServerDetailsClick = (host: SSHHost) => {
|
||||||
|
const title = host.name?.trim()
|
||||||
|
? host.name
|
||||||
|
: `${host.username}@${host.ip}:${host.port}`;
|
||||||
|
addTab({ type: "server", title, hostConfig: host });
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHostEditClick = (host: SSHHost) => {
|
||||||
|
const title = host.name?.trim()
|
||||||
|
? host.name
|
||||||
|
: `${host.username}@${host.ip}:${host.port}`;
|
||||||
|
addTab({
|
||||||
|
type: "ssh_manager",
|
||||||
|
title: "Host Manager",
|
||||||
|
hostConfig: host,
|
||||||
|
initialTab: "add_host",
|
||||||
|
});
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-50 flex items-center justify-center bg-black/20 backdrop-blur-sm",
|
"fixed inset-0 z-50 flex items-center justify-center bg-black/30",
|
||||||
!isOpen && "hidden",
|
!isOpen && "hidden",
|
||||||
)}
|
)}
|
||||||
onClick={() => setIsOpen(false)}
|
onClick={() => setIsOpen(false)}
|
||||||
>
|
>
|
||||||
<Command
|
<Command
|
||||||
className="w-3/4 max-w-2xl max-h-[60vh] rounded-lg border-2 border-dark-border shadow-md"
|
className="w-3/4 max-w-2xl max-h-[60vh] rounded-lg border-2 border-dark-border shadow-md flex flex-col"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<CommandInput
|
<CommandInput
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
placeholder="Search for hosts or quick actions..."
|
placeholder="Search for hosts or quick actions..."
|
||||||
/>
|
/>
|
||||||
<CommandList>
|
<CommandList
|
||||||
<CommandGroup heading="Suggestions">
|
key={recentActivity.length}
|
||||||
<CommandItem>
|
className="w-full h-auto flex-grow overflow-y-auto"
|
||||||
<Calendar />
|
style={{ maxHeight: "inherit" }}
|
||||||
<span>Calendar</span>
|
>
|
||||||
|
{recentActivity.length > 0 && (
|
||||||
|
<>
|
||||||
|
<CommandGroup heading="Recent Activity">
|
||||||
|
{recentActivity.map((item, index) => (
|
||||||
|
<CommandItem
|
||||||
|
key={`recent-activity-${index}-${item.type}-${item.hostId}-${item.timestamp}`}
|
||||||
|
value={`recent-activity-${index}-${item.hostName}-${item.type}`}
|
||||||
|
onSelect={() => handleActivityClick(item)}
|
||||||
|
>
|
||||||
|
{item.type === "terminal" ? <Terminal /> : <FolderOpen />}
|
||||||
|
<span>{item.hostName}</span>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
<CommandSeparator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<CommandGroup heading="Navigation">
|
||||||
|
<CommandItem onSelect={handleAddHost}>
|
||||||
|
<Server />
|
||||||
|
<span>Add Host</span>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem>
|
<CommandItem onSelect={handleAddCredential}>
|
||||||
<Smile />
|
<Key />
|
||||||
<span>Search Emoji</span>
|
<span>Add Credential</span>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem disabled>
|
<CommandItem onSelect={handleOpenAdminSettings}>
|
||||||
<Calculator />
|
<Settings />
|
||||||
<span>Calculator</span>
|
<span>Admin Settings</span>
|
||||||
|
</CommandItem>
|
||||||
|
<CommandItem onSelect={handleOpenUserProfile}>
|
||||||
|
<User />
|
||||||
|
<span>User Profile</span>
|
||||||
|
</CommandItem>
|
||||||
|
<CommandItem onSelect={handleOpenUpdateLog}>
|
||||||
|
<GrUpdate />
|
||||||
|
<span>Update Log</span>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
<CommandSeparator />
|
<CommandSeparator />
|
||||||
<CommandGroup heading="Settings">
|
<CommandGroup heading="Hosts">
|
||||||
<CommandItem>
|
{hosts.map((host, index) => {
|
||||||
<User />
|
const title = host.name?.trim()
|
||||||
<span>Profile</span>
|
? host.name
|
||||||
<CommandShortcut>⌘P</CommandShortcut>
|
: `${host.username}@${host.ip}:${host.port}`;
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={`host-${index}-${host.id}`}
|
||||||
|
value={`host-${index}-${title}-${host.id}`}
|
||||||
|
onSelect={() => {
|
||||||
|
if (host.enableTerminal) {
|
||||||
|
handleHostTerminalClick(host);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Server className="h-4 w-4" />
|
||||||
|
<span>{title}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<DropdownMenu modal={false}>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="!px-2 h-7 border-1 border-dark-border"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<EllipsisVertical className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
align="end"
|
||||||
|
side="right"
|
||||||
|
className="w-56 bg-dark-bg border-dark-border text-white"
|
||||||
|
>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHostServerDetailsClick(host);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-dark-hover text-gray-300"
|
||||||
|
>
|
||||||
|
<Server className="h-4 w-4" />
|
||||||
|
<span className="flex-1">Open Server Details</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHostFileManagerClick(host);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-dark-hover text-gray-300"
|
||||||
|
>
|
||||||
|
<FolderOpen className="h-4 w-4" />
|
||||||
|
<span className="flex-1">Open File Manager</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHostEditClick(host);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-dark-hover text-gray-300"
|
||||||
|
>
|
||||||
|
<Pencil className="h-4 w-4" />
|
||||||
|
<span className="flex-1">Edit</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
<CommandSeparator />
|
||||||
|
<CommandGroup heading="Links">
|
||||||
|
<CommandItem onSelect={handleGitHub}>
|
||||||
|
<Github />
|
||||||
|
<span>GitHub</span>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem>
|
<CommandItem onSelect={handleSupport}>
|
||||||
<CreditCard />
|
<BiSupport />
|
||||||
<span>Billing</span>
|
<span>Support</span>
|
||||||
<CommandShortcut>⌘B</CommandShortcut>
|
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem>
|
<CommandItem onSelect={handleDiscord}>
|
||||||
<Settings />
|
<BsDiscord />
|
||||||
<span>Settings</span>
|
<span>Discord</span>
|
||||||
<CommandShortcut>⌘S</CommandShortcut>
|
</CommandItem>
|
||||||
|
<CommandItem onSelect={handleDonate}>
|
||||||
|
<BiMoney />
|
||||||
|
<span>Donate</span>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export function Dashboard({
|
|||||||
>([]);
|
>([]);
|
||||||
const [serverStatsLoading, setServerStatsLoading] = useState<boolean>(true);
|
const [serverStatsLoading, setServerStatsLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
const { addTab, setCurrentTab, tabs: tabList } = useTabs();
|
const { addTab, setCurrentTab, tabs: tabList, updateTab } = useTabs();
|
||||||
|
|
||||||
let sidebarState: "expanded" | "collapsed" = "expanded";
|
let sidebarState: "expanded" | "collapsed" = "expanded";
|
||||||
try {
|
try {
|
||||||
@@ -264,6 +264,7 @@ export function Dashboard({
|
|||||||
const handleAddHost = () => {
|
const handleAddHost = () => {
|
||||||
const sshManagerTab = tabList.find((t) => t.type === "ssh_manager");
|
const sshManagerTab = tabList.find((t) => t.type === "ssh_manager");
|
||||||
if (sshManagerTab) {
|
if (sshManagerTab) {
|
||||||
|
updateTab(sshManagerTab.id, { initialTab: "add_host" });
|
||||||
setCurrentTab(sshManagerTab.id);
|
setCurrentTab(sshManagerTab.id);
|
||||||
} else {
|
} else {
|
||||||
const id = addTab({
|
const id = addTab({
|
||||||
@@ -278,6 +279,7 @@ export function Dashboard({
|
|||||||
const handleAddCredential = () => {
|
const handleAddCredential = () => {
|
||||||
const sshManagerTab = tabList.find((t) => t.type === "ssh_manager");
|
const sshManagerTab = tabList.find((t) => t.type === "ssh_manager");
|
||||||
if (sshManagerTab) {
|
if (sshManagerTab) {
|
||||||
|
updateTab(sshManagerTab.id, { initialTab: "add_credential" });
|
||||||
setCurrentTab(sshManagerTab.id);
|
setCurrentTab(sshManagerTab.id);
|
||||||
} else {
|
} else {
|
||||||
const id = addTab({
|
const id = addTab({
|
||||||
@@ -671,7 +673,7 @@ export function Dashboard({
|
|||||||
{server.name}
|
{server.name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-between text-xs text-muted-foreground">
|
<div className="flex flex-row justify-start gap-4 text-xs text-muted-foreground">
|
||||||
<span>
|
<span>
|
||||||
{t("dashboard.cpu")}:{" "}
|
{t("dashboard.cpu")}:{" "}
|
||||||
{server.cpu !== null
|
{server.cpu !== null
|
||||||
|
|||||||
@@ -35,28 +35,10 @@ export function HostManager({
|
|||||||
const lastProcessedHostIdRef = useRef<number | undefined>(undefined);
|
const lastProcessedHostIdRef = useRef<number | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ignoreNextHostConfigChangeRef.current) {
|
if (initialTab) {
|
||||||
ignoreNextHostConfigChangeRef.current = false;
|
setActiveTab(initialTab);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}, [initialTab]);
|
||||||
if (hostConfig && initialTab === "add_host") {
|
|
||||||
const currentHostId = hostConfig.id;
|
|
||||||
|
|
||||||
if (currentHostId !== lastProcessedHostIdRef.current) {
|
|
||||||
setEditingHost(hostConfig);
|
|
||||||
setActiveTab("add_host");
|
|
||||||
lastProcessedHostIdRef.current = currentHostId;
|
|
||||||
} else if (
|
|
||||||
activeTab === "host_viewer" ||
|
|
||||||
activeTab === "credentials" ||
|
|
||||||
activeTab === "add_credential"
|
|
||||||
) {
|
|
||||||
setEditingHost(hostConfig);
|
|
||||||
setActiveTab("add_host");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [hostConfig, initialTab]);
|
|
||||||
|
|
||||||
const handleEditHost = (host: SSHHost) => {
|
const handleEditHost = (host: SSHHost) => {
|
||||||
setEditingHost(host);
|
setEditingHost(host);
|
||||||
|
|||||||
@@ -6,17 +6,19 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu.tsx";
|
} from "@/components/ui/dropdown-menu.tsx";
|
||||||
import { Button } from "@/components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { Hammer, Wrench, FileText } from "lucide-react";
|
import { Hammer, Wrench, FileText, Command } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface ToolsMenuProps {
|
interface ToolsMenuProps {
|
||||||
onOpenSshTools: () => void;
|
onOpenSshTools: () => void;
|
||||||
onOpenSnippets: () => void;
|
onOpenSnippets: () => void;
|
||||||
|
onOpenCommandPalette: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ToolsMenu({
|
export function ToolsMenu({
|
||||||
onOpenSshTools,
|
onOpenSshTools,
|
||||||
onOpenSnippets,
|
onOpenSnippets,
|
||||||
|
onOpenCommandPalette,
|
||||||
}: ToolsMenuProps): React.ReactElement {
|
}: ToolsMenuProps): React.ReactElement {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -33,7 +35,7 @@ export function ToolsMenu({
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
align="end"
|
align="end"
|
||||||
className="w-56 bg-dark-bg border-dark-border text-white"
|
className="w-70 bg-dark-bg border-dark-border text-white"
|
||||||
>
|
>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={onOpenSshTools}
|
onClick={onOpenSshTools}
|
||||||
@@ -49,6 +51,18 @@ export function ToolsMenu({
|
|||||||
<FileText className="h-4 w-4" />
|
<FileText className="h-4 w-4" />
|
||||||
<span className="flex-1">{t("snippets.title")}</span>
|
<span className="flex-1">{t("snippets.title")}</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={onOpenCommandPalette}
|
||||||
|
className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-dark-hover text-gray-300"
|
||||||
|
>
|
||||||
|
<Command className="h-4 w-4" />
|
||||||
|
<div className="flex items-center justify-between flex-1">
|
||||||
|
<span>Command Palette</span>
|
||||||
|
<kbd className="ml-2 px-1.5 py-0.5 text-xs font-semibold bg-dark-bg-darker border border-dark-border rounded">
|
||||||
|
LShift LShift
|
||||||
|
</kbd>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ interface TabData {
|
|||||||
interface TopNavbarProps {
|
interface TopNavbarProps {
|
||||||
isTopbarOpen: boolean;
|
isTopbarOpen: boolean;
|
||||||
setIsTopbarOpen: (open: boolean) => void;
|
setIsTopbarOpen: (open: boolean) => void;
|
||||||
|
onOpenCommandPalette: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TopNavbar({
|
export function TopNavbar({
|
||||||
isTopbarOpen,
|
isTopbarOpen,
|
||||||
setIsTopbarOpen,
|
setIsTopbarOpen,
|
||||||
|
onOpenCommandPalette,
|
||||||
}: TopNavbarProps): React.ReactElement {
|
}: TopNavbarProps): React.ReactElement {
|
||||||
const { state } = useSidebar();
|
const { state } = useSidebar();
|
||||||
const {
|
const {
|
||||||
@@ -476,6 +478,7 @@ export function TopNavbar({
|
|||||||
<ToolsMenu
|
<ToolsMenu
|
||||||
onOpenSshTools={() => setToolsSheetOpen(true)}
|
onOpenSshTools={() => setToolsSheetOpen(true)}
|
||||||
onOpenSnippets={() => setSnippetsSidebarOpen(true)}
|
onOpenSnippets={() => setSnippetsSidebarOpen(true)}
|
||||||
|
onOpenCommandPalette={onOpenCommandPalette}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user