Improve host viewer UI, improve performance of SSH. Prep for release.
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -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;
|
||||
@@ -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
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
className="text-black"
|
||||
variant="outlined"
|
||||
className="text-white"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!hostWrapper.config || !hostWrapper.config.ip || !hostWrapper.config.user) {
|
||||
@@ -242,76 +274,36 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
|
||||
backgroundColor: "#6e6e6e",
|
||||
"&:hover": { backgroundColor: "#0f0f0f" },
|
||||
opacity: isDeleting ? 0.5 : 1,
|
||||
cursor: isDeleting ? "not-allowed" : "pointer"
|
||||
cursor: isDeleting ? "not-allowed" : "pointer",
|
||||
borderColor: "#3d3d3d",
|
||||
borderWidth: "2px",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
{isOwner && (
|
||||
<>
|
||||
<Button
|
||||
className="text-black"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedHostForShare(hostWrapper);
|
||||
setIsShareModalHidden(false);
|
||||
}}
|
||||
disabled={isDeleting}
|
||||
sx={{
|
||||
backgroundColor: "#6e6e6e",
|
||||
"&:hover": { backgroundColor: "#0f0f0f" },
|
||||
opacity: isDeleting ? 0.5 : 1,
|
||||
cursor: isDeleting ? "not-allowed" : "pointer"
|
||||
}}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
<Button
|
||||
className="text-black"
|
||||
onClick={(e) => handleDelete(e, hostWrapper)}
|
||||
disabled={isDeleting}
|
||||
sx={{
|
||||
backgroundColor: "#6e6e6e",
|
||||
"&:hover": { backgroundColor: "#0f0f0f" },
|
||||
opacity: isDeleting ? 0.5 : 1,
|
||||
cursor: isDeleting ? "not-allowed" : "pointer"
|
||||
}}
|
||||
>
|
||||
{isDeleting ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
<Button
|
||||
className="text-black"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openEditPanel(hostConfig);
|
||||
}}
|
||||
disabled={isDeleting}
|
||||
sx={{
|
||||
backgroundColor: "#6e6e6e",
|
||||
"&:hover": { backgroundColor: "#0f0f0f" },
|
||||
opacity: isDeleting ? 0.5 : 1,
|
||||
cursor: isDeleting ? "not-allowed" : "pointer"
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{!isOwner && (
|
||||
<Button
|
||||
className="text-black"
|
||||
onClick={(e) => handleDelete(e, hostWrapper)}
|
||||
disabled={isDeleting}
|
||||
sx={{
|
||||
backgroundColor: "#6e6e6e",
|
||||
"&:hover": { backgroundColor: "#0f0f0f" },
|
||||
opacity: isDeleting ? 0.5 : 1,
|
||||
cursor: isDeleting ? "not-allowed" : "pointer"
|
||||
}}
|
||||
>
|
||||
{isDeleting ? "Removing..." : "Remove Share"}
|
||||
</Button>
|
||||
)}
|
||||
<IconButton
|
||||
variant="outlined"
|
||||
className="text-white"
|
||||
onClick={(e) => {
|
||||
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",
|
||||
}}
|
||||
>
|
||||
⋮
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -352,7 +344,6 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Render hosts without folders first */}
|
||||
<div
|
||||
className={`flex flex-col gap-2 p-2 rounded-lg transition-colors ${isDraggingOver === 'no-folder' ? 'bg-neutral-700' : ''}`}
|
||||
onDragOver={(e) => handleDragOver(e, 'no-folder')}
|
||||
@@ -362,7 +353,6 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
|
||||
{noFolder.map((host) => renderHostItem(host))}
|
||||
</div>
|
||||
|
||||
{/* Render folders and their hosts */}
|
||||
{sortedFolders.map((folderName) => (
|
||||
<div key={folderName} className="mb-2">
|
||||
<div
|
||||
@@ -403,6 +393,68 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
|
||||
handleShare={handleShare}
|
||||
hostConfig={selectedHostForShare}
|
||||
/>
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
anchorEl={anchorEl.current}
|
||||
open={isMenuOpen}
|
||||
onClose={() => {
|
||||
setIsMenuOpen(false);
|
||||
setSelectedHost(null);
|
||||
}}
|
||||
sx={{
|
||||
"& .MuiMenu-list": {
|
||||
backgroundColor: "#6e6e6e",
|
||||
color: "white"
|
||||
}
|
||||
}}
|
||||
>
|
||||
{selectedHost && (
|
||||
selectedHost.createdBy?._id === userRef.current?.getUser()?.id ? (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedHostForShare(selectedHost);
|
||||
setIsShareModalHidden(false);
|
||||
setIsMenuOpen(false);
|
||||
}}
|
||||
>
|
||||
Share
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openEditPanel(selectedHost.config);
|
||||
setIsMenuOpen(false);
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDelete(e, selectedHost);
|
||||
setIsMenuOpen(false);
|
||||
}}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? "Deleting..." : "Delete"}
|
||||
</MenuItem>
|
||||
</>
|
||||
) : (
|
||||
<MenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDelete(e, selectedHost);
|
||||
setIsMenuOpen(false);
|
||||
}}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? "Removing..." : "Remove Share"}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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']
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user