Dev 2.0 #23
+87
-5
@@ -1,8 +1,8 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { NewTerminal } from "./Terminal.jsx";
|
import { NewTerminal } from "./Terminal.jsx";
|
||||||
import { User } from "./User.jsx";
|
import { User } from "./User.jsx";
|
||||||
import AddHostModal from "./AddHostModal.jsx";
|
import AddHostModal from "./modals/AddHostModal.jsx";
|
||||||
import LoginUserModal from "./LoginUserModal.jsx";
|
import LoginUserModal from "./modals/LoginUserModal.jsx";
|
||||||
import { Button } from "@mui/joy";
|
import { Button } from "@mui/joy";
|
||||||
import { CssVarsProvider } from "@mui/joy";
|
import { CssVarsProvider } from "@mui/joy";
|
||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
@@ -12,9 +12,10 @@ import { Debounce } from './Utils';
|
|||||||
import TermixIcon from "./images/termix_icon.png";
|
import TermixIcon from "./images/termix_icon.png";
|
||||||
import RocketIcon from './images/launchpad_rocket.png';
|
import RocketIcon from './images/launchpad_rocket.png';
|
||||||
import ProfileIcon from './images/profile_icon.png';
|
import ProfileIcon from './images/profile_icon.png';
|
||||||
import CreateUserModal from "./CreateUserModal.jsx";
|
import CreateUserModal from "./modals/CreateUserModal.jsx";
|
||||||
import ProfileModal from "./ProfileModal.jsx";
|
import ProfileModal from "./modals/ProfileModal.jsx";
|
||||||
import ErrorModal from "./ErrorModal.jsx";
|
import ErrorModal from "./modals/ErrorModal.jsx";
|
||||||
|
import EditHostModal from "./modals/EditHostModal.jsx";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isAddHostHidden, setIsAddHostHidden] = useState(true);
|
const [isAddHostHidden, setIsAddHostHidden] = useState(true);
|
||||||
@@ -36,6 +37,15 @@ function App() {
|
|||||||
authMethod: "Select Auth",
|
authMethod: "Select Auth",
|
||||||
rememberHost: false,
|
rememberHost: false,
|
||||||
});
|
});
|
||||||
|
const [editHostForm, setEditHostForm] = useState({
|
||||||
|
name: "",
|
||||||
|
ip: "",
|
||||||
|
user: "",
|
||||||
|
password: "",
|
||||||
|
port: 22,
|
||||||
|
authMethod: "Select Auth",
|
||||||
|
rememberHost: true,
|
||||||
|
});
|
||||||
const [loginUserForm, setLoginUserForm] = useState({
|
const [loginUserForm, setLoginUserForm] = useState({
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
@@ -46,6 +56,8 @@ function App() {
|
|||||||
});
|
});
|
||||||
const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false);
|
const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false);
|
||||||
const [splitTabIds, setSplitTabIds] = useState([]);
|
const [splitTabIds, setSplitTabIds] = useState([]);
|
||||||
|
const [isEditHostHidden, setIsEditHostHidden] = useState(true);
|
||||||
|
const [currentHostConfig, setCurrentHostConfig] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e) => {
|
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 }) => {
|
const handleLoginUser = ({ username, password, sessionToken, onSuccess, onFailure }) => {
|
||||||
if (userRef.current) {
|
if (userRef.current) {
|
||||||
if (sessionToken) {
|
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 closeTab = (id) => {
|
||||||
const newTerminals = terminals.filter((t) => t.id !== id);
|
const newTerminals = terminals.filter((t) => t.id !== id);
|
||||||
setTerminals(newTerminals);
|
setTerminals(newTerminals);
|
||||||
@@ -392,6 +458,14 @@ function App() {
|
|||||||
handleAddHost={handleAddHost}
|
handleAddHost={handleAddHost}
|
||||||
setIsAddHostHidden={setIsAddHostHidden}
|
setIsAddHostHidden={setIsAddHostHidden}
|
||||||
/>
|
/>
|
||||||
|
<EditHostModal
|
||||||
|
isHidden={isEditHostHidden}
|
||||||
|
form={editHostForm}
|
||||||
|
setForm={setEditHostForm}
|
||||||
|
handleEditHost={handleEditHost}
|
||||||
|
setIsEditHostHidden={setIsEditHostHidden}
|
||||||
|
hostConfig={currentHostConfig}
|
||||||
|
/>
|
||||||
<CreateUserModal
|
<CreateUserModal
|
||||||
isHidden={isCreateUserHidden}
|
isHidden={isCreateUserHidden}
|
||||||
form={createUserForm}
|
form={createUserForm}
|
||||||
@@ -417,6 +491,14 @@ function App() {
|
|||||||
onClose={() => setIsLaunchpadOpen(false)}
|
onClose={() => setIsLaunchpadOpen(false)}
|
||||||
getHosts={getHosts}
|
getHosts={getHosts}
|
||||||
connectToHost={connectToHostWithConfig}
|
connectToHost={connectToHostWithConfig}
|
||||||
|
isAddHostHidden={isAddHostHidden}
|
||||||
|
setIsAddHostHidden={setIsAddHostHidden}
|
||||||
|
isEditHostHidden={isEditHostHidden}
|
||||||
|
isErrorHidden={isErrorHidden}
|
||||||
|
deleteHost={deleteHost}
|
||||||
|
editHost={updateEditHostForm}
|
||||||
|
createFolder={createFolder}
|
||||||
|
moveHostToFolder={moveHostToFolder}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
+142
-10
@@ -1,17 +1,40 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { CssVarsProvider } from '@mui/joy/styles';
|
import { CssVarsProvider } from '@mui/joy/styles';
|
||||||
|
import { Button } from '@mui/joy';
|
||||||
|
import HostViewerIcon from './images/host_viewer_icon.png';
|
||||||
import theme from './theme';
|
import theme from './theme';
|
||||||
|
|
||||||
// Apps
|
// 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 launchpadRef = useRef(null);
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
|
const [activeApp, setActiveApp] = useState('hostViewer');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event) => {
|
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();
|
onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -21,7 +44,12 @@ function Launchpad({ onClose, getHosts, connectToHost }) {
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("mousedown", handleClickOutside);
|
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 (
|
return (
|
||||||
<CssVarsProvider theme={theme}>
|
<CssVarsProvider theme={theme}>
|
||||||
@@ -47,16 +75,112 @@ function Launchpad({ onClose, getHosts, connectToHost }) {
|
|||||||
height: "75%",
|
height: "75%",
|
||||||
backgroundColor: theme.palette.general.tertiary,
|
backgroundColor: theme.palette.general.tertiary,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
boxShadow: "0 4px 10px rgba(0, 0, 0, 0.3)",
|
boxShadow: "0 4px 10px rgba(0, 0, 0, 0.3)",
|
||||||
border: `1px solid ${theme.palette.general.secondary}`,
|
border: `1px solid ${theme.palette.general.secondary}`,
|
||||||
color: theme.palette.text.primary,
|
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>
|
||||||
</div>
|
</div>
|
||||||
</CssVarsProvider>
|
</CssVarsProvider>
|
||||||
@@ -65,8 +189,16 @@ function Launchpad({ onClose, getHosts, connectToHost }) {
|
|||||||
|
|
||||||
Launchpad.propTypes = {
|
Launchpad.propTypes = {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
connectToHost: PropTypes.func.isRequired,
|
|
||||||
getHosts: 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;
|
export default Launchpad;
|
||||||
+71
-1
@@ -133,7 +133,11 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
|
|||||||
});
|
});
|
||||||
|
|
||||||
socketRef.current.once("hostsFound", (data) => {
|
socketRef.current.once("hostsFound", (data) => {
|
||||||
resolve(data);
|
if (data && Array.isArray(data)) {
|
||||||
|
resolve(data);
|
||||||
|
} else {
|
||||||
|
reject("Invalid data received.");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socketRef.current.once("error", (error) => {
|
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, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
createUser,
|
createUser,
|
||||||
loginUser,
|
loginUser,
|
||||||
@@ -157,6 +223,10 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
|
|||||||
saveHost,
|
saveHost,
|
||||||
getUser,
|
getUser,
|
||||||
getAllHosts,
|
getAllHosts,
|
||||||
|
deleteHost,
|
||||||
|
editExistingHost,
|
||||||
|
createFolder,
|
||||||
|
moveHostToFolder,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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) => {
|
dbNamespace.on("connection", (socket) => {
|
||||||
console.log("New socket connection established on");
|
console.log("New socket connection established on");
|
||||||
|
|
||||||
@@ -202,6 +289,50 @@ dbNamespace.on("connection", (socket) => {
|
|||||||
socket.emit(result.error ? "error" : "hostsFound", result);
|
socket.emit(result.error ? "error" : "hostsFound", result);
|
||||||
console.log(result.error || `Hosts found`);
|
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 () => {
|
server.listen(8082, '0.0.0.0', async () => {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -14,7 +14,7 @@ import {
|
|||||||
Option,
|
Option,
|
||||||
Checkbox
|
Checkbox
|
||||||
} from '@mui/joy';
|
} from '@mui/joy';
|
||||||
import theme from './theme';
|
import theme from '/src/theme';
|
||||||
|
|
||||||
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
|
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
|
||||||
const handleFileChange = (e) => {
|
const handleFileChange = (e) => {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { CssVarsProvider } from '@mui/joy/styles';
|
import { CssVarsProvider } from '@mui/joy/styles';
|
||||||
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
|
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';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
const CreateUserModal = ({ isHidden, form, setForm, handleCreateUser, setIsCreateUserHidden, setIsLoginUserHidden }) => {
|
const CreateUserModal = ({ isHidden, form, setForm, handleCreateUser, setIsCreateUserHidden, setIsLoginUserHidden }) => {
|
||||||
@@ -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;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { CssVarsProvider } from '@mui/joy/styles';
|
import { CssVarsProvider } from '@mui/joy/styles';
|
||||||
import { Modal, Button, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
|
import { Modal, Button, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
|
||||||
import theme from './theme';
|
import theme from '/src/theme';
|
||||||
|
|
||||||
const ErrorModal = ({ isHidden, errorMessage, setIsErrorHidden }) => {
|
const ErrorModal = ({ isHidden, errorMessage, setIsErrorHidden }) => {
|
||||||
return (
|
return (
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { CssVarsProvider } from '@mui/joy/styles';
|
import { CssVarsProvider } from '@mui/joy/styles';
|
||||||
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
|
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';
|
import {useEffect} from 'react';
|
||||||
|
|
||||||
const LoginUserModal = ({ isHidden, form, setForm, handleLoginUser, setIsLoginUserHidden, setIsCreateUserHidden }) => {
|
const LoginUserModal = ({ isHidden, form, setForm, handleLoginUser, setIsLoginUserHidden, setIsCreateUserHidden }) => {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { CssVarsProvider } from '@mui/joy/styles';
|
import { CssVarsProvider } from '@mui/joy/styles';
|
||||||
import {Modal, Button, DialogTitle, DialogContent, ModalDialog, Stack } from '@mui/joy';
|
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 ProfileModal = ({ isHidden, getUser, handleDeleteUser, handleLogoutUser, setIsProfileHidden }) => {
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
Reference in New Issue
Block a user