From 8209fb5318baa2574c752e3ec7f142948c346958 Mon Sep 17 00:00:00 2001 From: Karmaa Date: Fri, 21 Mar 2025 00:37:21 -0500 Subject: [PATCH] Improve host viewer UI, improve performance of SSH. Prep for release. --- src/App.jsx | 3 + src/apps/Launchpad.jsx | 9 +- src/apps/ssh/HostViewer.jsx | 204 ++++++++++++++++++++++------------- src/backend/ssh.cjs | 29 +---- src/modals/EditHostModal.jsx | 2 +- 5 files changed, 146 insertions(+), 101 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 6d3b3b2b..6cb4befb 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -70,6 +70,7 @@ function App() { const [currentHostConfig, setCurrentHostConfig] = useState(null); const [isLoggingIn, setIsLoggingIn] = useState(true); const [isEditing, setIsEditing] = useState(false); + const [isHostViewerMenuOpen, setIsHostViewerMenuOpen] = useState(null); useEffect(() => { const handleKeyDown = (e) => { @@ -694,6 +695,8 @@ function App() { editHost={handleEditHost} shareHost={(hostId, username) => userRef.current?.shareHost(hostId, username)} userRef={userRef} + isHostViewerMenuOpen={isHostViewerMenuOpen} + setIsHostViewerMenuOpen={setIsHostViewerMenuOpen} /> )} diff --git a/src/apps/Launchpad.jsx b/src/apps/Launchpad.jsx index c334c6b6..bff98468 100644 --- a/src/apps/Launchpad.jsx +++ b/src/apps/Launchpad.jsx @@ -18,6 +18,8 @@ function Launchpad({ editHost, shareHost, userRef, + isHostViewerMenuOpen, + setIsHostViewerMenuOpen, }) { const launchpadRef = useRef(null); const [sidebarOpen, setSidebarOpen] = useState(false); @@ -32,6 +34,7 @@ function Launchpad({ isAddHostHidden && isEditHostHidden && isErrorHidden && + !isHostViewerMenuOpen && !isAnyModalOpen ) { onClose(); @@ -43,7 +46,7 @@ function Launchpad({ return () => { document.removeEventListener("mousedown", handleClickOutside); }; - }, [onClose, isAddHostHidden, isEditHostHidden, isErrorHidden, isAnyModalOpen]); + }, [onClose, isAddHostHidden, isEditHostHidden, isErrorHidden, isHostViewerMenuOpen, isAnyModalOpen]); const handleModalOpen = () => { setIsAnyModalOpen(true); @@ -190,6 +193,8 @@ function Launchpad({ onModalOpen={handleModalOpen} onModalClose={handleModalClose} userRef={userRef} + isMenuOpen={isHostViewerMenuOpen || false} + setIsMenuOpen={setIsHostViewerMenuOpen} /> )} @@ -211,6 +216,8 @@ Launchpad.propTypes = { editHost: PropTypes.func.isRequired, shareHost: PropTypes.func.isRequired, userRef: PropTypes.object.isRequired, + isHostViewerMenuOpen: PropTypes.bool, + setIsHostViewerMenuOpen: PropTypes.func.isRequired, }; export default Launchpad; \ No newline at end of file diff --git a/src/apps/ssh/HostViewer.jsx b/src/apps/ssh/HostViewer.jsx index de84fbea..4d668067 100644 --- a/src/apps/ssh/HostViewer.jsx +++ b/src/apps/ssh/HostViewer.jsx @@ -1,9 +1,22 @@ import PropTypes from "prop-types"; import { useState, useEffect, useRef } from "react"; -import { Button, Input } from "@mui/joy"; +import { Button, Input, Menu, MenuItem, IconButton } from "@mui/joy"; import ShareHostModal from "../../modals/ShareHostModal"; -function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, editHost, openEditPanel, shareHost, onModalOpen, onModalClose, userRef }) { +function HostViewer({ + getHosts, + connectToHost, + setIsAddHostHidden, + deleteHost, + editHost, + openEditPanel, + shareHost, + onModalOpen, + onModalClose, + userRef, + isMenuOpen, + setIsMenuOpen, + }) { const [hosts, setHosts] = useState([]); const [filteredHosts, setFilteredHosts] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -15,6 +28,24 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e const [isDeleting, setIsDeleting] = useState(false); const [isShareModalHidden, setIsShareModalHidden] = useState(true); const [selectedHostForShare, setSelectedHostForShare] = useState(null); + const [selectedHost, setSelectedHost] = useState(null); + const anchorEl = useRef(null); + const menuRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event) => { + if (menuRef.current && !menuRef.current.contains(event.target) && anchorEl.current && !anchorEl.current.contains(event.target)) { + setIsMenuOpen(false); + setSelectedHost(null); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); const fetchHosts = async () => { try { @@ -51,9 +82,9 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e useEffect(() => { const filtered = hosts.filter((hostWrapper) => { const hostConfig = hostWrapper.config || {}; - return hostConfig.name?.toLowerCase().includes(searchTerm.toLowerCase()) || - hostConfig.ip?.toLowerCase().includes(searchTerm.toLowerCase()) || - hostConfig.folder?.toLowerCase().includes(searchTerm.toLowerCase()); + return hostConfig.name?.toLowerCase().includes(searchTerm.toLowerCase()) || + hostConfig.ip?.toLowerCase().includes(searchTerm.toLowerCase()) || + hostConfig.folder?.toLowerCase().includes(searchTerm.toLowerCase()); }); setFilteredHosts(filtered); }, [searchTerm, hosts]); @@ -168,7 +199,7 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e const handleDelete = async (e, hostWrapper) => { e.stopPropagation(); if (isDeleting) return; - + setIsDeleting(true); try { const isOwner = hostWrapper.createdBy?._id === userRef.current?.getUser()?.id; @@ -229,7 +260,8 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
- {isOwner && ( - <> - - - - - )} - {!isOwner && ( - - )} + { + e.stopPropagation(); + setSelectedHost(hostWrapper); + setIsMenuOpen(!isMenuOpen); + anchorEl.current = e.currentTarget; + }} + disabled={isDeleting} + sx={{ + backgroundColor: "#6e6e6e", + "&:hover": { backgroundColor: "#0f0f0f" }, + opacity: isDeleting ? 0.5 : 1, + cursor: isDeleting ? "not-allowed" : "pointer", + borderColor: "#3d3d3d", + borderWidth: "2px", + color: "#fff", + }} + > + ⋮ +
); @@ -352,7 +344,6 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e return ( <> - {/* Render hosts without folders first */}
handleDragOver(e, 'no-folder')} @@ -362,7 +353,6 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e {noFolder.map((host) => renderHostItem(host))}
- {/* Render folders and their hosts */} {sortedFolders.map((folderName) => (
+ { + setIsMenuOpen(false); + setSelectedHost(null); + }} + sx={{ + "& .MuiMenu-list": { + backgroundColor: "#6e6e6e", + color: "white" + } + }} + > + {selectedHost && ( + selectedHost.createdBy?._id === userRef.current?.getUser()?.id ? ( + <> + { + e.stopPropagation(); + setSelectedHostForShare(selectedHost); + setIsShareModalHidden(false); + setIsMenuOpen(false); + }} + > + Share + + { + e.stopPropagation(); + openEditPanel(selectedHost.config); + setIsMenuOpen(false); + }} + > + Edit + + { + e.stopPropagation(); + handleDelete(e, selectedHost); + setIsMenuOpen(false); + }} + disabled={isDeleting} + > + {isDeleting ? "Deleting..." : "Delete"} + + + ) : ( + { + e.stopPropagation(); + handleDelete(e, selectedHost); + setIsMenuOpen(false); + }} + disabled={isDeleting} + > + {isDeleting ? "Removing..." : "Remove Share"} + + ) + )} +
); } @@ -418,6 +470,8 @@ HostViewer.propTypes = { onModalOpen: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired, userRef: PropTypes.object.isRequired, + isMenuOpen: PropTypes.bool.isRequired, + setIsMenuOpen: PropTypes.func.isRequired, }; export default HostViewer; \ No newline at end of file diff --git a/src/backend/ssh.cjs b/src/backend/ssh.cjs index 427e9edb..400db2b1 100644 --- a/src/backend/ssh.cjs +++ b/src/backend/ssh.cjs @@ -32,7 +32,7 @@ io.on("connection", (socket) => { return; } - if (!hostConfig.password && !hostConfig.privateKey) { + if (!hostConfig.password && !hostConfig.sshKey) { logger.error("No authentication provided"); socket.emit("error", "Authentication required"); return; @@ -43,7 +43,6 @@ io.on("connection", (socket) => { port: hostConfig.port, user: hostConfig.user, authType: hostConfig.password ? 'password' : 'key', - keyType: hostConfig.keyType }; logger.info("Connecting with config:", safeHostConfig); @@ -98,29 +97,11 @@ io.on("connection", (socket) => { host: ip, port: port, username: user, - password: password, - sshKey: sshKey ? Buffer.from(sshKey) : undefined, - tryKeyboard: true, + password: password || undefined, + privateKey: sshKey ? Buffer.from(sshKey) : undefined, algorithms: { - kex: [ - 'curve25519-sha256', - 'curve25519-sha256@libssh.org', - 'ecdh-sha2-nistp256', - 'ecdh-sha2-nistp384', - 'ecdh-sha2-nistp521', - 'diffie-hellman-group-exchange-sha256', - 'diffie-hellman-group14-sha256', - 'diffie-hellman-group14-sha1' - ], - serverHostKey: [ - 'ssh-ed25519', - 'ecdsa-sha2-nistp256', - 'ecdsa-sha2-nistp384', - 'ecdsa-sha2-nistp521', - 'rsa-sha2-512', - 'rsa-sha2-256', - 'ssh-rsa' - ] + kex: ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256'], + serverHostKey: ['ssh-ed25519', 'ecdsa-sha2-nistp256'] } }); }); diff --git a/src/modals/EditHostModal.jsx b/src/modals/EditHostModal.jsx index bd0a2e33..25810fe7 100644 --- a/src/modals/EditHostModal.jsx +++ b/src/modals/EditHostModal.jsx @@ -442,7 +442,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo EditHostModal.propTypes = { isHidden: PropTypes.bool.isRequired, - hostConfig: PropTypes.object.isRequired, + hostConfig: PropTypes.object, setIsEditHostHidden: PropTypes.func.isRequired, handleEditHost: PropTypes.func.isRequired };