From e2e35e613009ef0f682dc668b2c860c12e4445ae Mon Sep 17 00:00:00 2001 From: Karmaa Date: Thu, 13 Mar 2025 22:42:18 -0500 Subject: [PATCH] Updated launchpad UI to be expandable in the future. Updated UI for the hosts to be able to easily configure them. They stil need organizational system (folders, etc.) --- src/App.jsx | 92 ++++++++++- src/Apps/HostViewer.jsx | 115 ------------- src/Launchpad.jsx | 152 ++++++++++++++++-- src/User.jsx | 72 ++++++++- src/apps/HostViewer.jsx | 135 ++++++++++++++++ src/backend/database.cjs | 131 +++++++++++++++ src/images/host_viewer_icon.png | Bin 0 -> 12937 bytes src/{ => modals}/AddHostModal.jsx | 2 +- src/{ => modals}/CreateUserModal.jsx | 2 +- src/modals/EditHostModal.jsx | 231 +++++++++++++++++++++++++++ src/{ => modals}/ErrorModal.jsx | 2 +- src/{ => modals}/LoginUserModal.jsx | 2 +- src/{ => modals}/ProfileModal.jsx | 2 +- 13 files changed, 802 insertions(+), 136 deletions(-) delete mode 100644 src/Apps/HostViewer.jsx create mode 100644 src/apps/HostViewer.jsx create mode 100644 src/images/host_viewer_icon.png rename src/{ => modals}/AddHostModal.jsx (99%) rename src/{ => modals}/CreateUserModal.jsx (99%) create mode 100644 src/modals/EditHostModal.jsx rename src/{ => modals}/ErrorModal.jsx (98%) rename src/{ => modals}/LoginUserModal.jsx (99%) rename src/{ => modals}/ProfileModal.jsx (99%) diff --git a/src/App.jsx b/src/App.jsx index 5f8bc445..2a1c665e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -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} /> + setIsLaunchpadOpen(false)} getHosts={getHosts} connectToHost={connectToHostWithConfig} + isAddHostHidden={isAddHostHidden} + setIsAddHostHidden={setIsAddHostHidden} + isEditHostHidden={isEditHostHidden} + isErrorHidden={isErrorHidden} + deleteHost={deleteHost} + editHost={updateEditHostForm} + createFolder={createFolder} + moveHostToFolder={moveHostToFolder} /> )} diff --git a/src/Apps/HostViewer.jsx b/src/Apps/HostViewer.jsx deleted file mode 100644 index bce47eb0..00000000 --- a/src/Apps/HostViewer.jsx +++ /dev/null @@ -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 ( -
-
-

Saved Hosts

-
-
- {!initialLoadComplete ? ( -
-
-
-
-
-
-
-
-
- ) : hosts.length > 0 ? ( -
- {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 ( -
-
-

{displayName}

-

- {hostConfig.user ? `${hostConfig.user}@${hostConfig.ip}` : hostConfig.ip}:{hostConfig.port} -

-
- -
- ); - })} -
- ) : ( -

Hosts are loading...

- )} -
-
- ); -} - -HostViewer.propTypes = { - getHosts: PropTypes.func.isRequired, - connectToHost: PropTypes.func.isRequired, -}; - -export default HostViewer; \ No newline at end of file diff --git a/src/Launchpad.jsx b/src/Launchpad.jsx index 7aa6e391..76408d4e 100644 --- a/src/Launchpad.jsx +++ b/src/Launchpad.jsx @@ -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 ( @@ -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, }} > - + {/* Sidebar */} +
+ {/* Sidebar Toggle Button */} + + + {/* HostViewer Button */} + +
+ + {/* Main Content */} +
+ {activeApp === 'hostViewer' && ( + + )} +
@@ -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; \ No newline at end of file diff --git a/src/User.jsx b/src/User.jsx index 0c217213..9dbcca2a 100644 --- a/src/User.jsx +++ b/src/User.jsx @@ -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
; diff --git a/src/apps/HostViewer.jsx b/src/apps/HostViewer.jsx new file mode 100644 index 00000000..8bae1e6f --- /dev/null +++ b/src/apps/HostViewer.jsx @@ -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 ( +
+
+

Hosts

+ +
+
+ {hosts.length > 0 ? ( +
+ {hosts.map((hostWrapper, index) => { + const hostConfig = hostWrapper.hostConfig || {}; + + return ( +
+
+

{hostConfig.name || hostConfig.ip}

+

+ {hostConfig.user ? `${hostConfig.user}@${hostConfig.ip}` : hostConfig.ip}:{hostConfig.port} +

+
+
+ + + +
+
+ ); + })} +
+ ) : ( +

Hosts are either loading or do not exist...

+ )} +
+
+ ); +} + +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; \ No newline at end of file diff --git a/src/backend/database.cjs b/src/backend/database.cjs index 7c13f0f2..c2c3cf2e 100644 --- a/src/backend/database.cjs +++ b/src/backend/database.cjs @@ -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 () => { diff --git a/src/images/host_viewer_icon.png b/src/images/host_viewer_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..03bc917cac5255fd5f523473bd836baa237f071c GIT binary patch literal 12937 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 { const handleFileChange = (e) => { diff --git a/src/CreateUserModal.jsx b/src/modals/CreateUserModal.jsx similarity index 99% rename from src/CreateUserModal.jsx rename to src/modals/CreateUserModal.jsx index c9e6d797..e0c24e33 100644 --- a/src/CreateUserModal.jsx +++ b/src/modals/CreateUserModal.jsx @@ -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 }) => { diff --git a/src/modals/EditHostModal.jsx b/src/modals/EditHostModal.jsx new file mode 100644 index 00000000..4fefa74c --- /dev/null +++ b/src/modals/EditHostModal.jsx @@ -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 ( + + setIsEditHostHidden(true)}> + + Edit Host + +
{ + event.preventDefault(); + if (isFormValid()) handleEditHost(); + }} + > + + + Host Name + setForm({ ...form, name: e.target.value })} + sx={{ + backgroundColor: theme.palette.general.primary, + color: theme.palette.text.primary, + }} + /> + + + Host IP + setForm({ ...form, ip: e.target.value })} + required + sx={{ + backgroundColor: theme.palette.general.primary, + color: theme.palette.text.primary, + }} + /> + + + Host User + setForm({ ...form, user: e.target.value })} + required + sx={{ + backgroundColor: theme.palette.general.primary, + color: theme.palette.text.primary, + }} + /> + + + Authentication Method + + + {form.authMethod === 'password' && ( + + Host Password + setForm({ ...form, password: e.target.value })} + required + sx={{ + backgroundColor: theme.palette.general.primary, + color: theme.palette.text.primary, + }} + /> + + )} + {form.authMethod === 'rsaKey' && ( + + RSA Key + + + )} + 65535}> + Host Port + setForm({ ...form, port: e.target.value })} + min={1} + max={65535} + required + sx={{ + backgroundColor: theme.palette.general.primary, + color: theme.palette.text.primary, + }} + /> + + + +
+
+
+
+
+ ); +}; + +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; \ No newline at end of file diff --git a/src/ErrorModal.jsx b/src/modals/ErrorModal.jsx similarity index 98% rename from src/ErrorModal.jsx rename to src/modals/ErrorModal.jsx index 272b6675..46590b07 100644 --- a/src/ErrorModal.jsx +++ b/src/modals/ErrorModal.jsx @@ -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 ( diff --git a/src/LoginUserModal.jsx b/src/modals/LoginUserModal.jsx similarity index 99% rename from src/LoginUserModal.jsx rename to src/modals/LoginUserModal.jsx index 44ec49bc..9a0dc90d 100644 --- a/src/LoginUserModal.jsx +++ b/src/modals/LoginUserModal.jsx @@ -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 }) => { diff --git a/src/ProfileModal.jsx b/src/modals/ProfileModal.jsx similarity index 99% rename from src/ProfileModal.jsx rename to src/modals/ProfileModal.jsx index 17806b4f..198ef423 100644 --- a/src/ProfileModal.jsx +++ b/src/modals/ProfileModal.jsx @@ -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 = () => {