From cfaa04e42c72d5de4cdac04937c2df289cb409b4 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Sat, 19 Jul 2025 02:27:20 -0500 Subject: [PATCH] Added tools (run multi cmds), fixed UI scrolling, added SSH algo's and key types. --- public/favicon.ico | Bin 4286 -> 226046 bytes public/icon.svg | 80 +++ .../Config Editor/ConfigEditorSidebar.tsx | 6 +- src/apps/Homepage/HomepageSidebar.tsx | 6 +- src/apps/SSH Tunnel/SSHTunnelSidebar.tsx | 6 +- src/apps/SSH/SSH.tsx | 8 + src/apps/SSH/SSHSidebar.tsx | 466 ++++++++++++++++-- src/apps/SSH/SSHTerminal.tsx | 5 + src/apps/Template/TemplateSidebar.tsx | 6 +- src/apps/Tools/ToolsSidebar.tsx | 6 +- src/backend/db/db/index.ts | 2 + src/backend/db/db/schema.ts | 4 +- src/backend/db/routes/ssh.ts | 16 +- src/backend/ssh/ssh.ts | 61 ++- src/components/ui/select.tsx | 183 +++++++ 15 files changed, 795 insertions(+), 60 deletions(-) create mode 100644 public/icon.svg create mode 100644 src/components/ui/select.tsx diff --git a/public/favicon.ico b/public/favicon.ico index 9dfe8173ff4bc41901559aa383551647a5148801..862e8505c2f40247d01dee7bd57563c18868cc4e 100644 GIT binary patch literal 226046 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 U_ers_9NNP+7+`^Emv`9z0KJ2if&c&j diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 00000000..4a51272d --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,80 @@ + + + + + + + + + + >_ + + + + + + + + + diff --git a/src/apps/Config Editor/ConfigEditorSidebar.tsx b/src/apps/Config Editor/ConfigEditorSidebar.tsx index c3cec7fd..71303884 100644 --- a/src/apps/Config Editor/ConfigEditorSidebar.tsx +++ b/src/apps/Config Editor/ConfigEditorSidebar.tsx @@ -21,6 +21,7 @@ import { import { Separator, } from "@/components/ui/separator.tsx" +import Icon from "../../../public/icon.svg"; interface SidebarProps { onSelectView: (view: string) => void; @@ -32,8 +33,9 @@ export function ConfigEditorSidebar({ onSelectView }: SidebarProps): React.React - - Termix / Config Editor + + Icon + - Termix / Config diff --git a/src/apps/Homepage/HomepageSidebar.tsx b/src/apps/Homepage/HomepageSidebar.tsx index 36deb0f9..81fcdc88 100644 --- a/src/apps/Homepage/HomepageSidebar.tsx +++ b/src/apps/Homepage/HomepageSidebar.tsx @@ -16,6 +16,7 @@ import { SidebarMenuButton, SidebarMenuItem, SidebarProvider, } from "@/components/ui/sidebar.tsx" +import Icon from "/public/icon.svg"; import { Separator, @@ -85,8 +86,9 @@ export function HomepageSidebar({ onSelectView, disabled, isAdmin, username }: S - - Termix + + Icon + - Termix diff --git a/src/apps/SSH Tunnel/SSHTunnelSidebar.tsx b/src/apps/SSH Tunnel/SSHTunnelSidebar.tsx index 7f8c2dc8..86a7f792 100644 --- a/src/apps/SSH Tunnel/SSHTunnelSidebar.tsx +++ b/src/apps/SSH Tunnel/SSHTunnelSidebar.tsx @@ -21,6 +21,7 @@ import { import { Separator, } from "@/components/ui/separator.tsx" +import Icon from "../../../public/icon.svg"; interface SidebarProps { onSelectView: (view: string) => void; @@ -32,8 +33,9 @@ export function SSHTunnelSidebar({ onSelectView }: SidebarProps): React.ReactEle - - Termix / SSH Tunnel + + Icon + - Termix / SSH Tunnel diff --git a/src/apps/SSH/SSH.tsx b/src/apps/SSH/SSH.tsx index fac17ad8..8029ae58 100644 --- a/src/apps/SSH/SSH.tsx +++ b/src/apps/SSH/SSH.tsx @@ -431,6 +431,14 @@ export function SSH({ onSelectView }: ConfigEditorProps): React.ReactElement { onSelectView={onSelectView} onAddHostSubmit={onAddHostSubmit} onHostConnect={onHostConnect} + allTabs={allTabs} + runCommandOnTabs={(tabIds: number[], command: string) => { + allTabs.forEach(tab => { + if (tabIds.includes(tab.id) && tab.terminalRef?.current?.sendInput) { + tab.terminalRef.current.sendInput(command); + } + }); + }} /> {/* Main area: fills the rest */} diff --git a/src/apps/SSH/SSHSidebar.tsx b/src/apps/SSH/SSHSidebar.tsx index cade8ce9..3aeb58fc 100644 --- a/src/apps/SSH/SSHSidebar.tsx +++ b/src/apps/SSH/SSHSidebar.tsx @@ -4,7 +4,8 @@ import { useForm, Controller } from "react-hook-form"; import { CornerDownLeft, Plus, - MoreVertical + MoreVertical, + Hammer } from "lucide-react" import { @@ -58,11 +59,15 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import Icon from "../../../public/icon.svg"; +import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from "@/components/ui/select"; interface SidebarProps { onSelectView: (view: string) => void; onAddHostSubmit: (data: any) => void; onHostConnect: (hostConfig: any) => void; + allTabs: { id: number; title: string; terminalRef: React.RefObject }[]; + runCommandOnTabs: (tabIds: number[], command: string) => void; } interface AuthPromptFormData { @@ -70,6 +75,8 @@ interface AuthPromptFormData { authMethod: string; sshKeyFile: File | null; sshKeyContent?: string; + keyPassword?: string; + keyType?: string; } interface AddHostFormData { @@ -84,11 +91,13 @@ interface AddHostFormData { authMethod: string; sshKeyFile: File | null; sshKeyContent?: string; + keyPassword?: string; + keyType?: string; saveAuthMethod: boolean; isPinned: boolean; } -export function SSHSidebar({ onSelectView, onAddHostSubmit, onHostConnect }: SidebarProps): React.ReactElement { +export function SSHSidebar({ onSelectView, onAddHostSubmit, onHostConnect, allTabs, runCommandOnTabs }: SidebarProps): React.ReactElement { const addHostForm = useForm({ defaultValues: { name: '', @@ -228,6 +237,8 @@ export function SSHSidebar({ onSelectView, onAddHostSubmit, onHostConnect }: Sid password: data.password, authMethod: data.authMethod, key: sshKeyContent, + keyPassword: data.keyPassword, + keyType: data.keyType === 'auto' ? '' : data.keyType, saveAuthMethod: data.saveAuthMethod, isPinned: data.isPinned }, @@ -381,6 +392,8 @@ export function SSHSidebar({ onSelectView, onAddHostSubmit, onHostConnect }: Sid tagsInput: '', sshKeyFile: null, sshKeyContent: editHostData.key || '', + keyPassword: editHostData.keyPassword || '', + keyType: editHostData.keyType || '', }); } }, [editHostData]); @@ -423,9 +436,11 @@ export function SSHSidebar({ onSelectView, onAddHostSubmit, onHostConnect }: Sid ip: data.ip, port: data.port, username: data.username, - password: data.password, + password: data.password, // always send authMethod: data.authMethod, - key: sshKeyContent, + key: sshKeyContent, // always send + keyPassword: data.keyPassword, // always send + keyType: data.keyType, // always send saveAuthMethod: data.saveAuthMethod, isPinned: data.isPinned }, @@ -486,6 +501,8 @@ export function SSHSidebar({ onSelectView, onAddHostSubmit, onHostConnect }: Sid ...authPromptHost, password: data.authMethod === 'password' ? data.password : undefined, key: data.authMethod === 'key' ? sshKeyContent : undefined, + keyPassword: data.authMethod === 'key' ? data.keyPassword : undefined, + keyType: data.authMethod === 'key' ? (data.keyType === 'auto' ? undefined : data.keyType) : undefined, authMethod: data.authMethod, }; @@ -507,17 +524,125 @@ export function SSHSidebar({ onSelectView, onAddHostSubmit, onHostConnect }: Sid } }, [authPromptOpen, authPromptForm]); + // Key type options + const keyTypeOptions = [ + { value: 'auto', label: 'Auto-detect' }, + { value: 'ssh-rsa', label: 'RSA' }, + { value: 'ssh-ed25519', label: 'ED25519' }, + { value: 'ecdsa-sha2-nistp256', label: 'ECDSA NIST P-256' }, + { value: 'ecdsa-sha2-nistp384', label: 'ECDSA NIST P-384' }, + { value: 'ecdsa-sha2-nistp521', label: 'ECDSA NIST P-521' }, + { value: 'ssh-dss', label: 'DSA' }, + { value: 'ssh-rsa-sha2-256', label: 'RSA SHA2-256' }, + { value: 'ssh-rsa-sha2-512', label: 'RSA SHA2-512' }, + ]; + + const [keyTypeDropdownOpen, setKeyTypeDropdownOpen] = useState(false); + const [editKeyTypeDropdownOpen, setEditKeyTypeDropdownOpen] = useState(false); + const keyTypeDropdownRef = React.useRef(null); + const editKeyTypeDropdownRef = React.useRef(null); + const keyTypeButtonRef = React.useRef(null); + const editKeyTypeButtonRef = React.useRef(null); + + // Close dropdown on outside click (add form) + React.useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if ( + keyTypeDropdownRef.current && + !keyTypeDropdownRef.current.contains(event.target as Node) && + keyTypeButtonRef.current && + !keyTypeButtonRef.current.contains(event.target as Node) + ) { + setKeyTypeDropdownOpen(false); + } + } + if (keyTypeDropdownOpen) { + document.addEventListener('mousedown', handleClickOutside); + } else { + document.removeEventListener('mousedown', handleClickOutside); + } + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [keyTypeDropdownOpen]); + // Close dropdown on outside click (edit form) + React.useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if ( + editKeyTypeDropdownRef.current && + !editKeyTypeDropdownRef.current.contains(event.target as Node) && + editKeyTypeButtonRef.current && + !editKeyTypeButtonRef.current.contains(event.target as Node) + ) { + setEditKeyTypeDropdownOpen(false); + } + } + if (editKeyTypeDropdownOpen) { + document.addEventListener('mousedown', handleClickOutside); + } else { + document.removeEventListener('mousedown', handleClickOutside); + } + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [editKeyTypeDropdownOpen]); + + const [keyTypeDropdownOpenAuth, setKeyTypeDropdownOpenAuth] = useState(false); + const keyTypeDropdownAuthRef = React.useRef(null); + const keyTypeButtonAuthRef = React.useRef(null); + + // Close dropdown on outside click (auth prompt) + React.useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if ( + keyTypeDropdownAuthRef.current && + !keyTypeDropdownAuthRef.current.contains(event.target as Node) && + keyTypeButtonAuthRef.current && + !keyTypeButtonAuthRef.current.contains(event.target as Node) + ) { + setKeyTypeDropdownOpenAuth(false); + } + } + if (keyTypeDropdownOpenAuth) { + document.addEventListener('mousedown', handleClickOutside); + } else { + document.removeEventListener('mousedown', handleClickOutside); + } + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [keyTypeDropdownOpenAuth]); + + // Tools Sheet State + const [toolsSheetOpen, setToolsSheetOpen] = useState(false); + const [toolsCommand, setToolsCommand] = useState(""); + const [selectedTabIds, setSelectedTabIds] = useState([]); + const handleTabToggle = (tabId: number) => { + setSelectedTabIds(prev => prev.includes(tabId) ? prev.filter(id => id !== tabId) : [...prev, tabId]); + }; + // --- Fix: Run Command logic --- + const handleRunCommand = () => { + if (selectedTabIds.length && toolsCommand.trim()) { + // Ensure command ends with newline + let cmd = toolsCommand; + if (!cmd.endsWith("\n")) cmd += "\n"; + runCommandOnTabs(selectedTabIds, cmd); + setToolsCommand(""); // Clear after run + } + }; + return ( - - - - - Termix / SSH + + + + + Icon + - Termix / SSH - - + + - - - - )} - /> - + + ( + + SSH Private Key + +
+ { + const file = e.target.files?.[0]; + field.onChange(file || null); + }} + className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" + /> + +
+
+
+ )} + /> + ( + + Key Password (if protected) + + + + + + )} + /> + ( + + Key Type + +
+ + {keyTypeDropdownOpen && ( +
+
+ {keyTypeOptions.map(opt => ( + + ))} +
+
+ )} +
+
+ +
+ )} + /> +
)} /> @@ -865,10 +1054,10 @@ export function SSHSidebar({ onSelectView, onAddHostSubmit, onHostConnect }: Sid
- -
+ +
{/* Search bar */} -
+
setSearch(e.target.value)} @@ -890,7 +1079,7 @@ export function SSHSidebar({ onSelectView, onAddHostSubmit, onHostConnect }: Sid
)}
- + 0 ? sortedFolders : undefined}> {sortedFolders.map((folder, idx) => ( @@ -921,6 +1110,65 @@ export function SSHSidebar({ onSelectView, onAddHostSubmit, onHostConnect }: Sid + {/* Tools Button at the very bottom */} +
+ + + + + + + Tools + +
+ + + Run multiwindow commands + +