Dev 2.0 #23

Merged
LukeGus merged 44 commits from dev-2.0 into main 2025-03-16 19:17:56 +00:00
13 changed files with 802 additions and 136 deletions
Showing only changes of commit e2e35e6130 - Show all commits

View File

@@ -1,8 +1,8 @@
import { useState, useEffect, useRef } from "react";
import { NewTerminal } from "./Terminal.jsx";
import { User } from "./User.jsx";
import AddHostModal from "./AddHostModal.jsx";
import LoginUserModal from "./LoginUserModal.jsx";
import AddHostModal from "./modals/AddHostModal.jsx";
import LoginUserModal from "./modals/LoginUserModal.jsx";
import { Button } from "@mui/joy";
import { CssVarsProvider } from "@mui/joy";
import theme from "./theme";
@@ -12,9 +12,10 @@ import { Debounce } from './Utils';
import TermixIcon from "./images/termix_icon.png";
import RocketIcon from './images/launchpad_rocket.png';
import ProfileIcon from './images/profile_icon.png';
import CreateUserModal from "./CreateUserModal.jsx";
import ProfileModal from "./ProfileModal.jsx";
import ErrorModal from "./ErrorModal.jsx";
import CreateUserModal from "./modals/CreateUserModal.jsx";
import ProfileModal from "./modals/ProfileModal.jsx";
import ErrorModal from "./modals/ErrorModal.jsx";
import EditHostModal from "./modals/EditHostModal.jsx";
function App() {
const [isAddHostHidden, setIsAddHostHidden] = useState(true);
@@ -36,6 +37,15 @@ function App() {
authMethod: "Select Auth",
rememberHost: false,
});
const [editHostForm, setEditHostForm] = useState({
name: "",
ip: "",
user: "",
password: "",
port: 22,
authMethod: "Select Auth",
rememberHost: true,
});
const [loginUserForm, setLoginUserForm] = useState({
username: "",
password: "",
@@ -46,6 +56,8 @@ function App() {
});
const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false);
const [splitTabIds, setSplitTabIds] = useState([]);
const [isEditHostHidden, setIsEditHostHidden] = useState(true);
const [currentHostConfig, setCurrentHostConfig] = useState(null);
useEffect(() => {
const handleKeyDown = (e) => {
@@ -183,6 +195,23 @@ function App() {
}
}
const createFolder = (folderName) => {
if (userRef.current) {
userRef.current.createFolder({
folderName,
});
}
}
const moveHostToFolder = (folderName, hostConfig) => {
if (userRef.current) {
userRef.current.moveHostToFolder({
folderName,
hostConfig,
});
}
}
const handleLoginUser = ({ username, password, sessionToken, onSuccess, onFailure }) => {
if (userRef.current) {
if (sessionToken) {
@@ -241,6 +270,43 @@ function App() {
}
}
const deleteHost = (hostConfig) => {
if (userRef.current) {
userRef.current.deleteHost({
hostConfig,
});
}
}
const updateEditHostForm = (hostConfig) => {
if (hostConfig) {
setCurrentHostConfig(hostConfig);
setIsEditHostHidden(false);
} else {
console.error("hostConfig is null");
}
};
const handleEditHost = () => {
if (editHostForm.ip && editHostForm.user && ((editHostForm.authMethod === 'password' && editHostForm.password) || (editHostForm.authMethod === 'rsaKey' && editHostForm.rsaKey)) && editHostForm.port && editHostForm.authMethod !== 'Select Auth') {
const user = getUser();
editHostForm.rememberHost = true;
if (user && currentHostConfig) {
userRef.current.editExistingHost({
userId: user.id,
oldHostConfig: currentHostConfig,
newHostConfig: editHostForm,
});
setIsEditHostHidden(true);
} else {
console.error("User or currentHostConfig is null");
}
} else {
alert("Please fill out all fields.");
}
};
const closeTab = (id) => {
const newTerminals = terminals.filter((t) => t.id !== id);
setTerminals(newTerminals);
@@ -392,6 +458,14 @@ function App() {
handleAddHost={handleAddHost}
setIsAddHostHidden={setIsAddHostHidden}
/>
<EditHostModal
isHidden={isEditHostHidden}
form={editHostForm}
setForm={setEditHostForm}
handleEditHost={handleEditHost}
setIsEditHostHidden={setIsEditHostHidden}
hostConfig={currentHostConfig}
/>
<CreateUserModal
isHidden={isCreateUserHidden}
form={createUserForm}
@@ -417,6 +491,14 @@ function App() {
onClose={() => setIsLaunchpadOpen(false)}
getHosts={getHosts}
connectToHost={connectToHostWithConfig}
isAddHostHidden={isAddHostHidden}
setIsAddHostHidden={setIsAddHostHidden}
isEditHostHidden={isEditHostHidden}
isErrorHidden={isErrorHidden}
deleteHost={deleteHost}
editHost={updateEditHostForm}
createFolder={createFolder}
moveHostToFolder={moveHostToFolder}
/>
)}

View File

@@ -1,115 +0,0 @@
import PropTypes from "prop-types";
import { useState, useEffect, useRef } from "react";
import { Button } from "@mui/joy";
function HostViewer({ getHosts, connectToHost }) {
const [hosts, setHosts] = useState([]);
const [initialLoadComplete, setInitialLoadComplete] = useState(false);
const isMounted = useRef(true);
useEffect(() => {
isMounted.current = true;
async function fetchInitialHosts() {
try {
const savedHosts = await getHosts();
if (isMounted.current) {
setHosts(savedHosts || []);
setInitialLoadComplete(true);
}
} catch (error) {
console.error("Initial host fetch failed:", error);
if (isMounted.current) {
setHosts([]);
setInitialLoadComplete(true);
}
}
}
// Immediate first fetch
fetchInitialHosts();
// Periodic updates
const intervalId = setInterval(async () => {
try {
const savedHosts = await getHosts();
if (isMounted.current) {
setHosts(savedHosts || []);
}
} catch (error) {
console.error("Periodic host update failed:", error);
}
}, 2000);
return () => {
isMounted.current = false;
clearInterval(intervalId);
};
}, [getHosts]);
return (
<div className="h-full w-full p-4 text-white flex flex-col">
<div className="flex items-center mb-2 w-full">
<h2 className="text-lg font-bold">Saved Hosts</h2>
</div>
<div className="flex-grow overflow-auto">
{!initialLoadComplete ? (
<div className="flex flex-col gap-2 w-full">
<div className="flex justify-between items-center bg-neutral-800 p-3 rounded-lg shadow-md border border-neutral-700 animate-pulse">
<div>
<div className="h-5 bg-gray-600 rounded w-32 mb-2"></div>
<div className="h-4 bg-gray-600 rounded w-24"></div>
</div>
<div className="h-8 w-24 bg-gray-600 rounded"></div>
</div>
</div>
) : hosts.length > 0 ? (
<div className="flex flex-col gap-2 w-full">
{hosts.map((hostWrapper, index) => {
const hostConfig = hostWrapper.hostConfig || {};
const formattedHostConfig = {
name: hostConfig.name || "Unknown Host Name",
ip: hostConfig.ip || "Unknown IP",
user: hostConfig.user || "Unknown User",
password: hostConfig.password || undefined,
rsaKey: hostConfig.rsaKey || undefined,
port: hostConfig.port ? String(hostConfig.port) : "22",
};
const displayName = hostConfig.name ? hostConfig.name : hostConfig.ip;
return (
<div key={index} className="flex justify-between items-center bg-neutral-800 p-3 rounded-lg shadow-md border border-neutral-700 w-full">
<div>
<p className="font-semibold">{displayName}</p>
<p className="text-sm text-gray-400">
{hostConfig.user ? `${hostConfig.user}@${hostConfig.ip}` : hostConfig.ip}:{hostConfig.port}
</p>
</div>
<Button
onClick={() => {
connectToHost(formattedHostConfig);
}}
sx={{ backgroundColor: "#4CAF50", "&:hover": { backgroundColor: "#45A049" } }}
>
Connect
</Button>
</div>
);
})}
</div>
) : (
<p className="text-gray-500">Hosts are loading...</p>
)}
</div>
</div>
);
}
HostViewer.propTypes = {
getHosts: PropTypes.func.isRequired,
connectToHost: PropTypes.func.isRequired,
};
export default HostViewer;

View File

@@ -1,17 +1,40 @@
import PropTypes from 'prop-types';
import { useEffect, useRef } from 'react';
import { useEffect, useRef, useState } from 'react';
import { CssVarsProvider } from '@mui/joy/styles';
import { Button } from '@mui/joy';
import HostViewerIcon from './images/host_viewer_icon.png';
import theme from './theme';
// Apps
import HostViewer from './Apps/HostViewer';
import HostViewer from './apps/HostViewer';
function Launchpad({ onClose, getHosts, connectToHost }) {
function Launchpad({
onClose,
getHosts,
connectToHost,
isAddHostHidden,
setIsAddHostHidden,
isEditHostHidden,
isErrorHidden,
deleteHost,
editHost,
createFolder,
moveHostToFolder,
}) {
const launchpadRef = useRef(null);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [activeApp, setActiveApp] = useState('hostViewer');
useEffect(() => {
const handleClickOutside = (event) => {
if (launchpadRef.current && !launchpadRef.current.contains(event.target)) {
// Close the launchpad when neither form is visible and no error is showing
if (
launchpadRef.current &&
!launchpadRef.current.contains(event.target) &&
isAddHostHidden && // Only close if addHost form is hidden
isEditHostHidden && // Only close if editHost form is hidden
isErrorHidden // Only close if error is hidden
) {
onClose();
}
};
@@ -21,7 +44,12 @@ function Launchpad({ onClose, getHosts, connectToHost }) {
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [onClose]);
}, [onClose, isAddHostHidden, isEditHostHidden, isErrorHidden]);
const handleEditHostClick = () => {
setIsAddHostHidden(false); // Open the form for editing
setActiveApp('hostViewer'); // Set active app to HostViewer
};
return (
<CssVarsProvider theme={theme}>
@@ -47,16 +75,112 @@ function Launchpad({ onClose, getHosts, connectToHost }) {
height: "75%",
backgroundColor: theme.palette.general.tertiary,
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "8px",
boxShadow: "0 4px 10px rgba(0, 0, 0, 0.3)",
border: `1px solid ${theme.palette.general.secondary}`,
color: theme.palette.text.primary,
padding: 3,
padding: 0,
}}
>
<HostViewer getHosts={getHosts} connectToHost={connectToHost} />
{/* Sidebar */}
<div
style={{
width: sidebarOpen ? "200px" : "60px",
backgroundColor: theme.palette.general.disabled,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "flex-start",
padding: "10px 5px",
transition: "width 0.3s ease",
overflow: "hidden",
borderRight: `1px solid ${theme.palette.general.secondary}`,
borderTopLeftRadius: "8px",
borderBottomLeftRadius: "8px",
}}
>
{/* Sidebar Toggle Button */}
<Button
onClick={() => setSidebarOpen(!sidebarOpen)}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.dark,
},
}}
style={{
width: sidebarOpen ? "175px" : "40px",
height: "40px",
borderRadius: "8px",
display: "flex",
alignItems: "center",
justifyContent: "center",
marginBottom: "10px",
transition: "width 0.3s ease",
}}
>
{sidebarOpen ? "←" : "→"}
</Button>
{/* HostViewer Button */}
<Button
onClick={() => setActiveApp('hostViewer')}
sx={{
backgroundColor: activeApp === 'hostViewer'
? theme.palette.general.tertiary
: theme.palette.general.primary,
'&:hover': {
backgroundColor: activeApp === 'hostViewer'
? theme.palette.general.tertiary
: theme.palette.general.dark,
},
}}
style={{
width: sidebarOpen ? "175px" : "40px",
height: "40px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "8px",
paddingLeft: sidebarOpen ? "15px" : "0",
transition: "width 0.3s ease",
}}
>
{sidebarOpen ? (
"Hosts"
) : (
<img
src={HostViewerIcon}
alt="Host Viewer"
width={24}
height={24}
style={{
objectFit: "contain",
position: "absolute",
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
}}
/>
)}
</Button>
</div>
{/* Main Content */}
<div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }}>
{activeApp === 'hostViewer' && (
<HostViewer
getHosts={getHosts}
connectToHost={connectToHost}
setIsAddHostHidden={setIsAddHostHidden}
deleteHost={deleteHost}
editHost={editHost} // Pass editHost here
createFolder={createFolder}
moveHostToFolder={moveHostToFolder}
onEditHostClick={handleEditHostClick} // Pass the handler to the form
/>
)}
</div>
</div>
</div>
</CssVarsProvider>
@@ -65,8 +189,16 @@ function Launchpad({ onClose, getHosts, connectToHost }) {
Launchpad.propTypes = {
onClose: PropTypes.func.isRequired,
connectToHost: PropTypes.func.isRequired,
getHosts: PropTypes.func.isRequired,
connectToHost: PropTypes.func.isRequired,
isAddHostHidden: PropTypes.bool.isRequired,
setIsAddHostHidden: PropTypes.func.isRequired,
isEditHostHidden: PropTypes.bool.isRequired,
isErrorHidden: PropTypes.bool.isRequired,
deleteHost: PropTypes.func.isRequired,
editHost: PropTypes.func.isRequired,
createFolder: PropTypes.func.isRequired,
moveHostToFolder: PropTypes.func.isRequired,
};
export default Launchpad;

View File

@@ -133,7 +133,11 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
});
socketRef.current.once("hostsFound", (data) => {
resolve(data);
if (data && Array.isArray(data)) {
resolve(data);
} else {
reject("Invalid data received.");
}
});
socketRef.current.once("error", (error) => {
@@ -149,6 +153,68 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
});
};
const deleteHost = (hostConfig) => {
if (currentUser.current?.id && socketRef.current) {
socketRef.current.emit("deleteHost", {
userId: currentUser.current.id,
hostConfig: hostConfig,
});
socketRef.current.once("error", (error) => {
onFailure(error);
});
} else {
onFailure("No user is currently logged in.");
}
}
const editExistingHost = ({ userId, oldHostConfig, newHostConfig }) => {
if (currentUser.current?.id && socketRef.current) {
socketRef.current.emit("editHost", {
userId: userId,
oldHostConfig: oldHostConfig,
newHostConfig: newHostConfig,
});
socketRef.current.once("error", (error) => {
onFailure(error);
});
} else {
onFailure("No user is currently logged in.");
}
};
const createFolder = (folderName) => {
if (currentUser.current?.id && socketRef.current) {
socketRef.current.emit("createFolder", {
userId: currentUser.current.id,
folderName: folderName,
});
socketRef.current.once("error", (error) => {
onFailure(error);
});
} else {
onFailure("No user is currently logged in.");
}
}
const moveHostToFolder = (folderName, hostConfig) => {
if (currentUser.current?.id && socketRef.current) {
socketRef.current.emit("moveHostToFolder", {
userId: currentUser.current.id,
folderName: folderName,
hostConfig: hostConfig,
});
socketRef.current.once("error", (error) => {
onFailure(error);
});
} else {
onFailure("No user is currently logged in.");
}
}
useImperativeHandle(ref, () => ({
createUser,
loginUser,
@@ -157,6 +223,10 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
saveHost,
getUser,
getAllHosts,
deleteHost,
editExistingHost,
createFolder,
moveHostToFolder,
}));
return <div></div>;

135
src/apps/HostViewer.jsx Normal file
View File

@@ -0,0 +1,135 @@
import PropTypes from "prop-types";
import { useState, useEffect, useRef } from "react";
import { Button } from "@mui/joy";
function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, editHost }) {
const [hosts, setHosts] = useState([]);
const [initialLoadComplete, setInitialLoadComplete] = useState(false);
const isMounted = useRef(true);
useEffect(() => {
isMounted.current = true;
async function fetchInitialHosts() {
try {
const savedHosts = await getHosts();
if (isMounted.current) {
setHosts(savedHosts || []);
setInitialLoadComplete(true);
}
} catch (error) {
console.error("Initial host fetch failed:", error);
if (isMounted.current) {
setHosts([]);
setInitialLoadComplete(true);
}
}
}
fetchInitialHosts();
const intervalId = setInterval(async () => {
try {
const savedHosts = await getHosts();
if (isMounted.current) {
setHosts(savedHosts || []);
}
} catch (error) {
console.error("Periodic host update failed:", error);
}
}, 2000);
return () => {
isMounted.current = false;
clearInterval(intervalId);
};
}, [getHosts]);
return (
<div className="h-full w-full p-4 text-white flex flex-col">
<div className="flex items-center justify-between mb-2 w-full">
<h2 className="text-lg font-bold">Hosts</h2>
<Button
className="text-black"
onClick={() => setIsAddHostHidden(false)}
sx={{
backgroundColor: "#6e6e6e",
"&:hover": { backgroundColor: "#0f0f0f" }
}}
>
Add Host
</Button>
</div>
<div className="flex-grow overflow-auto">
{hosts.length > 0 ? (
<div className="flex flex-col gap-2 w-full">
{hosts.map((hostWrapper, index) => {
const hostConfig = hostWrapper.hostConfig || {};
return (
<div key={index} className="flex justify-between items-center bg-neutral-800 p-3 rounded-lg shadow-md border border-neutral-700 w-full">
<div>
<p className="font-semibold">{hostConfig.name || hostConfig.ip}</p>
<p className="text-sm text-gray-400">
{hostConfig.user ? `${hostConfig.user}@${hostConfig.ip}` : hostConfig.ip}:{hostConfig.port}
</p>
</div>
<div className="flex gap-2">
<Button
className="text-black"
onClick={() => {
connectToHost(hostConfig);
}}
sx={{
backgroundColor: "#6e6e6e",
"&:hover": { backgroundColor: "#0f0f0f" }
}}
>
Connect
</Button>
<Button
className="text-black"
onClick={() => {
deleteHost(hostConfig);
}}
sx={{
backgroundColor: "#6e6e6e",
"&:hover": { backgroundColor: "#0f0f0f" }
}}
>
Delete
</Button>
<Button
className="text-black"
onClick={() => {
editHost(hostConfig);
}}
sx={{
backgroundColor: "#6e6e6e",
"&:hover": { backgroundColor: "#0f0f0f" }
}}
>
Edit
</Button>
</div>
</div>
);
})}
</div>
) : (
<p className="text-gray-300">Hosts are either loading or do not exist...</p>
)}
</div>
</div>
);
}
HostViewer.propTypes = {
getHosts: PropTypes.func.isRequired,
connectToHost: PropTypes.func.isRequired,
setIsAddHostHidden: PropTypes.func.isRequired,
deleteHost: PropTypes.func.isRequired,
editHost: PropTypes.func.isRequired,
};
export default HostViewer;

View File

@@ -141,6 +141,93 @@ async function getHosts(userId) {
}
}
async function deleteHost(userId, hostConfig) {
try {
const user = await User.findById(userId);
if (user) {
user.sshConnections = user.sshConnections.filter(connection => {
const matches =
connection.name === hostConfig.name &&
connection.ip === hostConfig.ip &&
connection.port === hostConfig.port &&
connection.user === hostConfig.user;
return !matches;
});
await user.save();
return { success: true };
} else {
return { error: 'User not found' };
}
} catch (err) {
return { error: 'Error deleting host: ' + err.message };
}
}
async function editHost(userId, oldHostConfig, newHostConfig) {
try {
const user = await User.findById(userId);
if (user) {
user.sshConnections = user.sshConnections.map(connection => {
const matches =
connection.hostConfig.name === oldHostConfig.name &&
connection.hostConfig.ip === oldHostConfig.ip &&
connection.hostConfig.port === oldHostConfig.port &&
connection.hostConfig.user === oldHostConfig.user;
if (matches) {
return { hostConfig: newHostConfig };
} else {
return connection;
}
});
await user.save();
return { success: true };
} else {
return { error: 'User not found' };
}
} catch (err) {
return { error: 'Error editing host: ' + err.message };
}
}
async function createFolder(userId, folderName) {
try {
const user = await User.findById(userId);
if (user) {
user.sshConnections.push({ folderName, connections: [] });
await user.save();
return { success: true };
} else {
return { error: 'User not found' };
}
} catch (err) {
return { error: 'Error creating folder: ' + err.message };
}
}
async function moveHostToFolder(userId, hostConfig, folderName) {
try {
const user = await User.findById(userId);
if (user) {
const folder = user.sshConnections.find(folder => folder.folderName === folderName);
if (folder) {
folder.connections.push(hostConfig);
await user.save();
return { success: true };
} else {
return { error: 'Folder not found' };
}
} else {
return { error: 'User not found' };
}
} catch (err) {
return { error: 'Error moving host to folder: ' + err.message };
}
}
dbNamespace.on("connection", (socket) => {
console.log("New socket connection established on");
@@ -202,6 +289,50 @@ dbNamespace.on("connection", (socket) => {
socket.emit(result.error ? "error" : "hostsFound", result);
console.log(result.error || `Hosts found`);
});
socket.on("deleteHost", async (data) => {
const { userId, hostConfig } = data;
if (!userId || !hostConfig) {
socket.emit("error", "User ID and host config are required");
return;
}
const result = await deleteHost(userId, hostConfig);
socket.emit(result.error ? "error" : "hostDeleted", result);
console.log(result.error || `Host deleted`);
});
socket.on("editHost", async (data) => {
const { userId, oldHostConfig, newHostConfig } = data;
if (!userId || !oldHostConfig || !newHostConfig) {
socket.emit("error", "User ID, old host config, and new host config are required");
return;
}
const result = await editHost(userId, oldHostConfig, newHostConfig);
socket.emit(result.error ? "error" : "hostEdited", result);
console.log(result.error || `Host edited`);
});
socket.on("createFolder", async (data) => {
const { userId, folderName } = data;
if (!userId || !folderName) {
socket.emit("error", "User ID and folder name are required");
return;
}
const result = await createFolder(userId, folderName);
socket.emit(result.error ? "error" : "folderCreated", result);
console.log(result.error || `Folder created`);
});
socket.on("moveHostToFolder", async (data) => {
const { userId, hostConfig, folderName } = data;
if (!userId || !hostConfig || !folderName) {
socket.emit("error", "User ID, host config, and folder name are required");
return;
}
const result = await moveHostToFolder(userId, hostConfig, folderName);
socket.emit(result.error ? "error" : "hostMoved", result);
console.log(result.error || `Host moved to folder`);
});
});
server.listen(8082, '0.0.0.0', async () => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -14,7 +14,7 @@ import {
Option,
Checkbox
} from '@mui/joy';
import theme from './theme';
import theme from '/src/theme';
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
const handleFileChange = (e) => {

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles';
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
import theme from './theme';
import theme from '/src/theme';
import { useEffect } from 'react';
const CreateUserModal = ({ isHidden, form, setForm, handleCreateUser, setIsCreateUserHidden, setIsLoginUserHidden }) => {

View File

@@ -0,0 +1,231 @@
import PropTypes from 'prop-types';
import { useEffect } from 'react';
import { CssVarsProvider } from '@mui/joy/styles';
import {
Modal,
Button,
FormControl,
FormLabel,
Input,
Stack,
DialogTitle,
DialogContent,
ModalDialog,
Select,
Option,
Checkbox
} from '@mui/joy';
import theme from '/src/theme';
const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostHidden, hostConfig }) => {
const handleFileChange = (e) => {
const file = e.target.files[0];
if (file) {
if (file.name.endsWith('.rsa') || file.name.endsWith('.key') || file.name.endsWith('.pem') || file.name.endsWith('.der') || file.name.endsWith('.p8') || file.name.endsWith('.ssh')) {
const reader = new FileReader();
reader.onload = (event) => {
setForm({ ...form, rsaKey: event.target.result });
};
reader.readAsText(file);
} else {
alert("Please upload a valid RSA private key file.");
}
}
};
const isFormValid = () => {
if (form.authMethod === 'Select Auth') return false;
if (!form.ip || !form.user || !form.port) return false;
if (form.authMethod === 'rsaKey' && !form.rsaKey) return false;
if (form.authMethod === 'password' && !form.password) return false;
return true;
};
useEffect(() => {
if (hostConfig) {
setForm({
name: hostConfig.name || '',
ip: hostConfig.ip || '',
user: hostConfig.user || '',
password: hostConfig.password || '',
rsaKey: hostConfig.rsaKey || '',
port: Number(hostConfig.port) || 22,
authMethod: hostConfig.password ? 'password' : 'rsaKey',
rememberHost: hostConfig.rememberHost || false,
});
}
}, [hostConfig, setForm]);
return (
<CssVarsProvider theme={theme}>
<Modal open={!isHidden} onClose={() => setIsEditHostHidden(true)}>
<ModalDialog
layout="center"
sx={{
backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
width: "auto",
maxWidth: "90vw",
minWidth: "fit-content",
overflow: "hidden",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<DialogTitle>Edit Host</DialogTitle>
<DialogContent>
<form
onSubmit={(event) => {
event.preventDefault();
if (isFormValid()) handleEditHost();
}}
>
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
<FormControl>
<FormLabel>Host Name</FormLabel>
<Input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
}}
/>
</FormControl>
<FormControl error={!form.ip}>
<FormLabel>Host IP</FormLabel>
<Input
value={form.ip}
onChange={(e) => setForm({ ...form, ip: e.target.value })}
required
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
}}
/>
</FormControl>
<FormControl error={!form.user}>
<FormLabel>Host User</FormLabel>
<Input
value={form.user}
onChange={(e) => setForm({ ...form, user: e.target.value })}
required
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
}}
/>
</FormControl>
<FormControl error={!form.authMethod || form.authMethod === 'Select Auth'}>
<FormLabel>Authentication Method</FormLabel>
<Select
value={form.authMethod || 'Select Auth'}
onChange={(e, newValue) => setForm({ ...form, authMethod: newValue })}
required
sx={{
backgroundColor: !form.authMethod || form.authMethod === 'Select Auth' ? theme.palette.general.tertiary : theme.palette.general.primary,
color: theme.palette.text.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
}}
>
<Option value="Select Auth" disabled>
Select Auth
</Option>
<Option value="password">Password</Option>
<Option value="rsaKey">RSA Key</Option>
</Select>
</FormControl>
{form.authMethod === 'password' && (
<FormControl error={!form.password}>
<FormLabel>Host Password</FormLabel>
<Input
type="password"
value={form.password}
onChange={(e) => setForm({ ...form, password: e.target.value })}
required
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
}}
/>
</FormControl>
)}
{form.authMethod === 'rsaKey' && (
<FormControl error={!form.rsaKey}>
<FormLabel>RSA Key</FormLabel>
<Input
type="file"
onChange={handleFileChange}
required
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
padding: 1,
textAlign: 'center',
width: '100%',
minWidth: 'auto',
minHeight: 'auto',
}}
/>
</FormControl>
)}
<FormControl error={form.port < 1 || form.port > 65535}>
<FormLabel>Host Port</FormLabel>
<Input
value={form.port}
onChange={(e) => setForm({ ...form, port: e.target.value })}
min={1}
max={65535}
required
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
}}
/>
</FormControl>
<Button
type="submit"
disabled={!isFormValid()}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
}}
>
Edit Host
</Button>
</Stack>
</form>
</DialogContent>
</ModalDialog>
</Modal>
</CssVarsProvider>
);
};
EditHostModal.propTypes = {
isHidden: PropTypes.bool.isRequired,
form: PropTypes.shape({
name: PropTypes.string,
ip: PropTypes.string.isRequired,
user: PropTypes.string.isRequired,
password: PropTypes.string,
rsaKey: PropTypes.string,
port: PropTypes.number.isRequired,
authMethod: PropTypes.string.isRequired,
rememberHost: PropTypes.bool,
}).isRequired,
setForm: PropTypes.func.isRequired,
handleEditHost: PropTypes.func.isRequired,
setIsEditHostHidden: PropTypes.func.isRequired,
hostConfig: PropTypes.object,
};
export default EditHostModal;

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles';
import { Modal, Button, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
import theme from './theme';
import theme from '/src/theme';
const ErrorModal = ({ isHidden, errorMessage, setIsErrorHidden }) => {
return (

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles';
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
import theme from './theme';
import theme from '/src/theme';
import {useEffect} from 'react';
const LoginUserModal = ({ isHidden, form, setForm, handleLoginUser, setIsLoginUserHidden, setIsCreateUserHidden }) => {

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles';
import {Modal, Button, DialogTitle, DialogContent, ModalDialog, Stack } from '@mui/joy';
import theme from './theme';
import theme from '/src/theme';
const ProfileModal = ({ isHidden, getUser, handleDeleteUser, handleLogoutUser, setIsProfileHidden }) => {
const handleDelete = () => {