* Added user system with database features. This is fairly experimental and does not include dockerfile to automatically generate a mongodb. This should be in future commits along with ability to save hosts branching off this database feature. * Updated README, fixed a few bugs with user creation, and added docker support to run MongoDB (needs testing) * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in installing MongoDB * Changes to Dockerfile to fix error in connecting to sockets * Update README.md * Changes to connection system to support docker * Changes to connection system to support docker * Changes to connection system to support docker * Changes to connection system to support docker * Save hosts to tabs (very early version, not that many issues not just not very feature rich and has a poor UI that will be improved with more features) * 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.) * Better encryption for everything, new session login, rewrote a lot of database code changing its storage methods. Prepared for release of 2.0. * Updated database connection method. * Updated Profile modal to show username text more clearly * Updated Profile modal to show username text more clearly * Fixed control v pasting formating. Reorganized location of scripts. Visbile password and confirm password. Guest login. OpenSSH key authentication. Optional to remember password. Serach for host viewer. * Waits for user to be able to log in. Improved UI for profile, edit and add host, and added organizational features to the host app (Folders, search, etc.) * Updated various names for rsa keys to public keys, fixes ssh not connecting, better timing for editing host. * Added ability to share hosts. Fixed up overall UI errors in console and cleaned up code for release. * Fix GitHub build errors * Attempt #1 to auto compile MongoDB into the build to exclude it from the compose. * Attempt #2 to auto compile MongoDB into the build to exclude it from the compose. * Attempt #3 to auto compile MongoDB into the build to exclude it from the compose. * Attempt #3 to auto compile MongoDB into the build to exclude it from the compose.
396 lines
20 KiB
JavaScript
396 lines
20 KiB
JavaScript
import PropTypes from 'prop-types';
|
|
import { useEffect, useState } from 'react';
|
|
import { CssVarsProvider } from '@mui/joy/styles';
|
|
import {
|
|
Modal,
|
|
Button,
|
|
FormControl,
|
|
FormLabel,
|
|
Input,
|
|
Stack,
|
|
DialogTitle,
|
|
DialogContent,
|
|
ModalDialog,
|
|
Select,
|
|
Option,
|
|
IconButton,
|
|
Checkbox,
|
|
Tabs,
|
|
TabList,
|
|
Tab,
|
|
TabPanel
|
|
} from '@mui/joy';
|
|
import theme from '/src/theme';
|
|
import Visibility from '@mui/icons-material/Visibility';
|
|
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
|
|
|
const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostHidden, hostConfig }) => {
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const [activeTab, setActiveTab] = useState(0);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!isHidden && hostConfig) {
|
|
setForm({
|
|
name: hostConfig.name || '',
|
|
folder: hostConfig.folder || '',
|
|
ip: hostConfig.ip || '',
|
|
user: hostConfig.user || '',
|
|
password: hostConfig.password || '',
|
|
rsaKey: hostConfig.rsaKey || '',
|
|
port: hostConfig.port || 22,
|
|
authMethod: hostConfig.password ? 'password' : hostConfig.rsaKey ? 'rsaKey' : 'Select Auth',
|
|
rememberHost: true,
|
|
storePassword: !!(hostConfig.password || hostConfig.rsaKey),
|
|
});
|
|
}
|
|
}, [isHidden, hostConfig]);
|
|
|
|
const handleFileChange = (e) => {
|
|
const file = e.target.files[0];
|
|
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') || file.name.endsWith('.pub')) {
|
|
const reader = new FileReader();
|
|
reader.onload = (evt) => {
|
|
setForm((prev) => ({ ...prev, rsaKey: evt.target.result }));
|
|
};
|
|
reader.readAsText(file);
|
|
} else {
|
|
alert('Please upload a valid RSA private key file.');
|
|
}
|
|
};
|
|
|
|
const handleAuthChange = (newMethod) => {
|
|
setForm((prev) => ({
|
|
...prev,
|
|
authMethod: newMethod
|
|
}));
|
|
};
|
|
|
|
const handleStorePasswordChange = (checked) => {
|
|
setForm((prev) => ({
|
|
...prev,
|
|
storePassword: Boolean(checked),
|
|
password: checked ? prev.password : "",
|
|
rsaKey: checked ? prev.rsaKey : "",
|
|
authMethod: checked ? prev.authMethod : "Select Auth"
|
|
}));
|
|
};
|
|
|
|
const isFormValid = () => {
|
|
const { ip, user, port, authMethod, password, rsaKey, storePassword } = form;
|
|
if (!ip?.trim() || !user?.trim() || !port) return false;
|
|
const portNum = Number(port);
|
|
if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false;
|
|
|
|
if (Boolean(storePassword) && authMethod === 'password' && !password?.trim()) return false;
|
|
if (Boolean(storePassword) && authMethod === 'rsaKey' && !rsaKey && !hostConfig?.rsaKey) return false;
|
|
if (Boolean(storePassword) && authMethod === 'Select Auth') return false;
|
|
|
|
return true;
|
|
};
|
|
|
|
const handleSubmit = async (event) => {
|
|
event.preventDefault();
|
|
if (isLoading) return;
|
|
|
|
setIsLoading(true);
|
|
try {
|
|
await handleEditHost(hostConfig, {
|
|
name: form.name || form.ip,
|
|
folder: form.folder,
|
|
ip: form.ip,
|
|
user: form.user,
|
|
password: form.authMethod === 'password' ? form.password : undefined,
|
|
rsaKey: form.authMethod === 'rsaKey' ? form.rsaKey : undefined,
|
|
port: String(form.port),
|
|
});
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<CssVarsProvider theme={theme}>
|
|
<Modal
|
|
open={!isHidden}
|
|
onClose={() => !isLoading && setIsEditHostHidden(true)}
|
|
sx={{
|
|
position: 'fixed',
|
|
inset: 0,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
backdropFilter: 'blur(5px)',
|
|
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
|
}}
|
|
>
|
|
<ModalDialog
|
|
layout="center"
|
|
variant="outlined"
|
|
sx={{
|
|
backgroundColor: theme.palette.general.tertiary,
|
|
borderColor: theme.palette.general.secondary,
|
|
color: theme.palette.text.primary,
|
|
padding: 3,
|
|
borderRadius: 10,
|
|
maxWidth: '500px',
|
|
width: '100%',
|
|
maxHeight: '80vh',
|
|
overflow: 'auto',
|
|
boxSizing: 'border-box',
|
|
mx: 2,
|
|
}}
|
|
>
|
|
<DialogTitle sx={{ mb: 2 }}>Edit Host</DialogTitle>
|
|
<DialogContent>
|
|
<form onSubmit={handleSubmit}>
|
|
<Tabs
|
|
value={activeTab}
|
|
onChange={(e, val) => setActiveTab(val)}
|
|
sx={{
|
|
backgroundColor: theme.palette.general.disabled,
|
|
borderRadius: '8px',
|
|
padding: '8px',
|
|
marginBottom: '16px',
|
|
width: '100%',
|
|
}}
|
|
>
|
|
<TabList
|
|
sx={{
|
|
width: '100%',
|
|
gap: 0,
|
|
mb: 2,
|
|
'& button': {
|
|
flex: 1,
|
|
bgcolor: 'transparent',
|
|
color: theme.palette.text.secondary,
|
|
'&:hover': {
|
|
bgcolor: 'rgba(255, 255, 255, 0.1)',
|
|
},
|
|
'&.Mui-selected': {
|
|
bgcolor: theme.palette.general.primary,
|
|
color: theme.palette.text.primary,
|
|
'&:hover': {
|
|
bgcolor: theme.palette.general.primary,
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
>
|
|
<Tab>Basic Info</Tab>
|
|
<Tab>Connection</Tab>
|
|
<Tab>Authentication</Tab>
|
|
</TabList>
|
|
|
|
<TabPanel value={0}>
|
|
<Stack spacing={2}>
|
|
<FormControl>
|
|
<FormLabel>Host Name</FormLabel>
|
|
<Input
|
|
value={form.name}
|
|
onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
|
|
sx={{
|
|
backgroundColor: theme.palette.general.primary,
|
|
color: theme.palette.text.primary
|
|
}}
|
|
/>
|
|
</FormControl>
|
|
|
|
<FormControl>
|
|
<FormLabel>Folder</FormLabel>
|
|
<Input
|
|
value={form.folder}
|
|
onChange={(e) => setForm((prev) => ({ ...prev, folder: e.target.value }))}
|
|
sx={{
|
|
backgroundColor: theme.palette.general.primary,
|
|
color: theme.palette.text.primary
|
|
}}
|
|
/>
|
|
</FormControl>
|
|
</Stack>
|
|
</TabPanel>
|
|
|
|
<TabPanel value={1}>
|
|
<Stack spacing={2}>
|
|
<FormControl error={!form.ip}>
|
|
<FormLabel>Host IP</FormLabel>
|
|
<Input
|
|
value={form.ip}
|
|
onChange={(e) => setForm((prev) => ({ ...prev, ip: e.target.value }))}
|
|
sx={{
|
|
backgroundColor: theme.palette.general.primary,
|
|
color: theme.palette.text.primary
|
|
}}
|
|
/>
|
|
</FormControl>
|
|
|
|
<FormControl error={form.port < 1 || form.port > 65535}>
|
|
<FormLabel>Host Port</FormLabel>
|
|
<Input
|
|
type="number"
|
|
value={form.port}
|
|
onChange={(e) => setForm((prev) => ({ ...prev, port: e.target.value }))}
|
|
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((prev) => ({ ...prev, user: e.target.value }))}
|
|
sx={{
|
|
backgroundColor: theme.palette.general.primary,
|
|
color: theme.palette.text.primary
|
|
}}
|
|
/>
|
|
</FormControl>
|
|
</Stack>
|
|
</TabPanel>
|
|
|
|
<TabPanel value={2}>
|
|
<Stack spacing={2}>
|
|
<FormControl>
|
|
<FormLabel>Store Password</FormLabel>
|
|
<Checkbox
|
|
checked={form.storePassword}
|
|
onChange={(e) => handleStorePasswordChange(e.target.checked)}
|
|
sx={{
|
|
color: theme.palette.text.primary,
|
|
'&.Mui-checked': {
|
|
color: theme.palette.text.primary
|
|
}
|
|
}}
|
|
/>
|
|
</FormControl>
|
|
|
|
{form.storePassword && (
|
|
<FormControl error={form.authMethod === 'Select Auth'}>
|
|
<FormLabel>Authentication Method</FormLabel>
|
|
<Select
|
|
value={form.authMethod}
|
|
onChange={(e, val) => handleAuthChange(val)}
|
|
sx={{
|
|
backgroundColor: theme.palette.general.primary,
|
|
color: theme.palette.text.primary,
|
|
}}
|
|
>
|
|
<Option value="Select Auth" disabled>Select Auth</Option>
|
|
<Option value="password">Password</Option>
|
|
<Option value="rsaKey">Public Key</Option>
|
|
</Select>
|
|
</FormControl>
|
|
)}
|
|
|
|
{form.authMethod === 'password' && form.storePassword && (
|
|
<FormControl error={!form.password}>
|
|
<FormLabel>Password</FormLabel>
|
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
<Input
|
|
type={showPassword ? 'text' : 'password'}
|
|
value={form.password}
|
|
onChange={(e) => setForm((prev) => ({ ...prev, password: e.target.value }))}
|
|
sx={{
|
|
backgroundColor: theme.palette.general.primary,
|
|
color: theme.palette.text.primary,
|
|
flex: 1
|
|
}}
|
|
/>
|
|
<IconButton
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
sx={{
|
|
color: theme.palette.text.primary,
|
|
marginLeft: 1
|
|
}}
|
|
>
|
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
|
</IconButton>
|
|
</div>
|
|
</FormControl>
|
|
)}
|
|
|
|
{form.authMethod === 'rsaKey' && form.storePassword && (
|
|
<FormControl error={!form.rsaKey && !hostConfig?.rsaKey}>
|
|
<FormLabel>Public Key</FormLabel>
|
|
<Button
|
|
component="label"
|
|
sx={{
|
|
backgroundColor: theme.palette.general.primary,
|
|
color: theme.palette.text.primary,
|
|
width: '100%',
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
height: '40px',
|
|
'&:hover': {
|
|
backgroundColor: theme.palette.general.disabled,
|
|
},
|
|
}}
|
|
>
|
|
{form.rsaKey ? 'Change Public Key File' : 'Upload Public Key File'}
|
|
<Input
|
|
type="file"
|
|
onChange={handleFileChange}
|
|
sx={{ display: 'none' }}
|
|
/>
|
|
</Button>
|
|
{hostConfig?.rsaKey && !form.rsaKey && (
|
|
<FormLabel
|
|
sx={{
|
|
color: theme.palette.text.secondary,
|
|
fontSize: '0.875rem',
|
|
mt: 1,
|
|
display: 'block',
|
|
textAlign: 'center'
|
|
}}
|
|
>
|
|
Existing key detected. Upload to replace.
|
|
</FormLabel>
|
|
)}
|
|
</FormControl>
|
|
)}
|
|
</Stack>
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
<Button
|
|
type="submit"
|
|
disabled={!isFormValid() || isLoading}
|
|
sx={{
|
|
backgroundColor: theme.palette.general.primary,
|
|
color: theme.palette.text.primary,
|
|
'&:hover': {
|
|
backgroundColor: theme.palette.general.disabled
|
|
},
|
|
'&:disabled': {
|
|
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
color: 'rgba(255, 255, 255, 0.3)',
|
|
},
|
|
marginTop: 3,
|
|
width: '100%',
|
|
height: '40px',
|
|
}}
|
|
>
|
|
{isLoading ? "Saving..." : "Save Changes"}
|
|
</Button>
|
|
</form>
|
|
</DialogContent>
|
|
</ModalDialog>
|
|
</Modal>
|
|
</CssVarsProvider>
|
|
);
|
|
};
|
|
|
|
EditHostModal.propTypes = {
|
|
isHidden: PropTypes.bool.isRequired,
|
|
form: PropTypes.object.isRequired,
|
|
setForm: PropTypes.func.isRequired,
|
|
handleEditHost: PropTypes.func.isRequired,
|
|
setIsEditHostHidden: PropTypes.func.isRequired,
|
|
hostConfig: PropTypes.object
|
|
};
|
|
|
|
export default EditHostModal; |