diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index ed288f3e..37fff37f 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -1100,7 +1100,7 @@ "refreshing": "Aktualisieren...", "serverOffline": "Server offline", "cannotFetchMetrics": "Metriken können nicht vom Offline-Server abgerufen werden", - "load": "Last" + "load": "Last", "available": "Verfügbar", "editLayout": "Layout anpassen", "cancelEdit": "Abbrechen", diff --git a/src/ui/desktop/DesktopApp.tsx b/src/ui/desktop/DesktopApp.tsx index 39aa9540..b7bcb6de 100644 --- a/src/ui/desktop/DesktopApp.tsx +++ b/src/ui/desktop/DesktopApp.tsx @@ -185,6 +185,7 @@ function AppContent() { setIsCommandPaletteOpen(true)} /> )} diff --git a/src/ui/desktop/apps/command-palette/CommandPalette.tsx b/src/ui/desktop/apps/command-palette/CommandPalette.tsx index 0d647735..fd898db0 100644 --- a/src/ui/desktop/apps/command-palette/CommandPalette.tsx +++ b/src/ui/desktop/apps/command-palette/CommandPalette.tsx @@ -3,21 +3,59 @@ import { CommandInput, CommandItem, CommandList, - CommandShortcut, CommandGroup, CommandSeparator, } from "@/components/ui/command.tsx"; -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { cn } from "@/lib/utils"; import { - Calculator, - Calendar, - CreditCard, + Key, + Server, Settings, - Smile, User, + Github, + Terminal, + FolderOpen, + Pencil, + EllipsisVertical, } 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({ isOpen, setIsOpen, @@ -26,59 +64,316 @@ export function CommandPalette({ setIsOpen: (isOpen: boolean) => void; }) { const inputRef = useRef(null); + const { addTab, setCurrentTab, tabs: tabList, updateTab } = useTabs(); + const [recentActivity, setRecentActivity] = useState( + [], + ); + const [hosts, setHosts] = useState([]); useEffect(() => { if (isOpen) { inputRef.current?.focus(); + getRecentActivity(50).then((activity) => { + setRecentActivity(activity.slice(0, 5)); + }); + getSSHHosts().then((allHosts) => { + setHosts(allHosts); + }); } }, [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 (
setIsOpen(false)} > e.stopPropagation()} > - - - - - Calendar + + {recentActivity.length > 0 && ( + <> + + {recentActivity.map((item, index) => ( + handleActivityClick(item)} + > + {item.type === "terminal" ? : } + {item.hostName} + + ))} + + + + )} + + + + Add Host - - - Search Emoji + + + Add Credential - - - Calculator + + + Admin Settings + + + + User Profile + + + + Update Log - - - - Profile - ⌘P + + {hosts.map((host, index) => { + const title = host.name?.trim() + ? host.name + : `${host.username}@${host.ip}:${host.port}`; + return ( + { + if (host.enableTerminal) { + handleHostTerminalClick(host); + } + }} + className="flex items-center justify-between" + > +
+ + {title} +
+
e.stopPropagation()} + > + + + + + + { + e.stopPropagation(); + handleHostServerDetailsClick(host); + }} + className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-dark-hover text-gray-300" + > + + Open Server Details + + { + e.stopPropagation(); + handleHostFileManagerClick(host); + }} + className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-dark-hover text-gray-300" + > + + Open File Manager + + { + e.stopPropagation(); + handleHostEditClick(host); + }} + className="flex items-center gap-2 cursor-pointer px-3 py-2 hover:bg-dark-hover text-gray-300" + > + + Edit + + + +
+
+ ); + })} +
+ + + + + GitHub - - - Billing - ⌘B + + + Support - - - Settings - ⌘S + + + Discord + + + + Donate
diff --git a/src/ui/desktop/apps/dashboard/Dashboard.tsx b/src/ui/desktop/apps/dashboard/Dashboard.tsx index 39742b77..dd6d3826 100644 --- a/src/ui/desktop/apps/dashboard/Dashboard.tsx +++ b/src/ui/desktop/apps/dashboard/Dashboard.tsx @@ -85,7 +85,7 @@ export function Dashboard({ >([]); const [serverStatsLoading, setServerStatsLoading] = useState(true); - const { addTab, setCurrentTab, tabs: tabList } = useTabs(); + const { addTab, setCurrentTab, tabs: tabList, updateTab } = useTabs(); let sidebarState: "expanded" | "collapsed" = "expanded"; try { @@ -264,6 +264,7 @@ export function Dashboard({ 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({ @@ -278,6 +279,7 @@ export function Dashboard({ 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({ @@ -671,7 +673,7 @@ export function Dashboard({ {server.name}

-
+
{t("dashboard.cpu")}:{" "} {server.cpu !== null diff --git a/src/ui/desktop/apps/host-manager/HostManager.tsx b/src/ui/desktop/apps/host-manager/HostManager.tsx index 04da3788..dff7b923 100644 --- a/src/ui/desktop/apps/host-manager/HostManager.tsx +++ b/src/ui/desktop/apps/host-manager/HostManager.tsx @@ -35,28 +35,10 @@ export function HostManager({ const lastProcessedHostIdRef = useRef(undefined); useEffect(() => { - if (ignoreNextHostConfigChangeRef.current) { - ignoreNextHostConfigChangeRef.current = false; - return; + if (initialTab) { + setActiveTab(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]); + }, [initialTab]); const handleEditHost = (host: SSHHost) => { setEditingHost(host); diff --git a/src/ui/desktop/apps/tools/ToolsMenu.tsx b/src/ui/desktop/apps/tools/ToolsMenu.tsx index 5f6bf96e..f4028f1b 100644 --- a/src/ui/desktop/apps/tools/ToolsMenu.tsx +++ b/src/ui/desktop/apps/tools/ToolsMenu.tsx @@ -6,17 +6,19 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu.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"; interface ToolsMenuProps { onOpenSshTools: () => void; onOpenSnippets: () => void; + onOpenCommandPalette: () => void; } export function ToolsMenu({ onOpenSshTools, onOpenSnippets, + onOpenCommandPalette, }: ToolsMenuProps): React.ReactElement { const { t } = useTranslation(); @@ -33,7 +35,7 @@ export function ToolsMenu({ {t("snippets.title")} + + +
+ Command Palette + + LShift LShift + +
+
); diff --git a/src/ui/desktop/navigation/TopNavbar.tsx b/src/ui/desktop/navigation/TopNavbar.tsx index 7a7bb34d..2ad540f8 100644 --- a/src/ui/desktop/navigation/TopNavbar.tsx +++ b/src/ui/desktop/navigation/TopNavbar.tsx @@ -26,11 +26,13 @@ interface TabData { interface TopNavbarProps { isTopbarOpen: boolean; setIsTopbarOpen: (open: boolean) => void; + onOpenCommandPalette: () => void; } export function TopNavbar({ isTopbarOpen, setIsTopbarOpen, + onOpenCommandPalette, }: TopNavbarProps): React.ReactElement { const { state } = useSidebar(); const { @@ -476,6 +478,7 @@ export function TopNavbar({ setToolsSheetOpen(true)} onOpenSnippets={() => setSnippetsSidebarOpen(true)} + onOpenCommandPalette={onOpenCommandPalette} />