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) => (
+
);
}
@@ -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
};