Improved and fixed editing and adding host UI

This commit is contained in:
Karmaa
2025-03-17 23:38:43 -05:00
parent d71352045c
commit 0a50d5c85c
4 changed files with 597 additions and 623 deletions

View File

@@ -222,13 +222,12 @@ function App() {
}, []);
const handleAddHost = () => {
if (addHostForm.ip && addHostForm.user && addHostForm.port) {
if (!addHostForm.rememberHost) {
connectToHost();
setIsAddHostHidden(true);
return;
}
if (!addHostForm.ip?.trim() || !addHostForm.user?.trim() || !addHostForm.port) {
alert("Please fill out all required fields (IP, User, Port).");
return;
}
if (addHostForm.rememberHost) {
if (addHostForm.authMethod === 'Select Auth') {
alert("Please select an authentication method.");
return;
@@ -237,33 +236,42 @@ function App() {
setIsNoAuthHidden(false);
return;
}
if (addHostForm.authMethod === 'rsaKey' && !addHostForm.rsaKey) {
if (addHostForm.authMethod === 'key' && !addHostForm.privateKey) {
setIsNoAuthHidden(false);
return;
}
}
connectToHost();
connectToHost();
if (addHostForm.rememberHost) {
if (!addHostForm.storePassword) {
addHostForm.password = '';
addHostForm.privateKey = '';
}
handleSaveHost();
setIsAddHostHidden(true);
} else {
alert("Please fill out all required fields (IP, User, Port).");
}
setIsAddHostHidden(true);
};
const connectToHost = () => {
const hostConfig = {
name: addHostForm.name || '',
folder: addHostForm.folder || '',
ip: addHostForm.ip,
user: addHostForm.user,
ip: addHostForm.ip.trim(),
user: addHostForm.user.trim(),
port: String(addHostForm.port),
password: addHostForm.rememberHost && addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
rsaKey: addHostForm.rememberHost && addHostForm.authMethod === 'rsaKey' ? addHostForm.rsaKey : undefined,
};
if (addHostForm.rememberHost && addHostForm.storePassword) {
if (addHostForm.authMethod === 'password') {
hostConfig.password = addHostForm.password;
} else if (addHostForm.authMethod === 'key') {
hostConfig.privateKey = addHostForm.privateKey;
hostConfig.keyType = addHostForm.keyType;
hostConfig.passphrase = addHostForm.passphrase;
}
}
const newTerminal = {
id: nextId,
title: hostConfig.name || hostConfig.ip,
@@ -273,9 +281,21 @@ function App() {
setTerminals([...terminals, newTerminal]);
setActiveTab(nextId);
setNextId(nextId + 1);
setIsAddHostHidden(true);
setAddHostForm({ name: "", folder: "", ip: "", user: "", password: "", rsaKey: "", port: 22, authMethod: "Select Auth", rememberHost: false, storePassword: true });
}
setAddHostForm({
name: "",
folder: "",
ip: "",
user: "",
password: "",
privateKey: "",
keyType: "",
passphrase: "",
port: 22,
authMethod: "Select Auth",
rememberHost: true,
storePassword: true
});
};
const handleAuthSubmit = (form) => {
const updatedTerminals = terminals.map((terminal) => {
@@ -327,21 +347,30 @@ function App() {
}
const handleSaveHost = () => {
let hostConfig = {
const hostConfig = {
name: addHostForm.name || addHostForm.ip,
folder: addHostForm.folder,
ip: addHostForm.ip,
user: addHostForm.user,
password: addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
rsaKey: addHostForm.authMethod === 'rsaKey' ? addHostForm.rsaKey : undefined,
ip: addHostForm.ip.trim(),
user: addHostForm.user.trim(),
port: String(addHostForm.port),
};
if (addHostForm.storePassword) {
if (addHostForm.authMethod === 'password') {
hostConfig.password = addHostForm.password;
} else if (addHostForm.authMethod === 'key') {
hostConfig.privateKey = addHostForm.privateKey;
hostConfig.keyType = addHostForm.keyType;
hostConfig.passphrase = addHostForm.passphrase;
}
}
if (userRef.current) {
userRef.current.saveHost({
hostConfig,
});
}
}
};
const handleLoginUser = ({ username, password, sessionToken, onSuccess, onFailure }) => {
if (userRef.current) {
@@ -472,7 +501,6 @@ function App() {
updateEditHostForm(oldConfig);
} catch (error) {
console.error('Edit failed:', error);
setErrorMessage(`Edit failed: ${error}`);
setIsErrorHidden(false);
setIsEditing(false);
@@ -658,10 +686,10 @@ function App() {
)}
<NoAuthenticationModal
isHidden={isNoAuthHidden}
setIsHidden={setIsNoAuthHidden}
form={authForm}
setForm={setAuthForm}
setIsNoAuthHidden={setIsNoAuthHidden}
handleAuthSubmit={handleAuthSubmit}
onAuthenticate={handleAuthSubmit}
/>
</div>
@@ -681,7 +709,7 @@ function App() {
setForm={setEditHostForm}
handleEditHost={handleEditHost}
setIsEditHostHidden={setIsEditHostHidden}
hostConfig={currentHostConfig}
hostConfig={currentHostConfig || {}}
/>
<ProfileModal
isHidden={isProfileHidden}

View File

@@ -222,7 +222,6 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
if (!currentUser.current) return onFailure("Not authenticated");
try {
console.log('Editing host with configs:', { oldHostConfig, newHostConfig });
const response = await new Promise((resolve) => {
socketRef.current.emit("editHost", {
userId: currentUser.current.id,

View File

@@ -24,20 +24,7 @@ import { useState } from 'react';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
const AddHostModal = ({ isHidden, setIsAddHostHidden, handleAddHost }) => {
const [form, setForm] = useState({
name: '',
folder: '',
ip: '',
user: '',
port: 22,
password: '',
privateKey: '',
keyType: '',
passphrase: '',
authMethod: 'Select Auth',
rememberHost: true
});
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
const [showPassword, setShowPassword] = useState(false);
const [showPassphrase, setShowPassphrase] = useState(false);
const [activeTab, setActiveTab] = useState(0);
@@ -100,14 +87,23 @@ const AddHostModal = ({ isHidden, setIsAddHostHidden, handleAddHost }) => {
};
const isFormValid = () => {
if (!form.ip || !form.user || !form.port) return false;
const portNum = Number(form.port);
const { ip, user, port, authMethod, password, privateKey } = form;
// Basic validation for required fields
if (!ip?.trim() || !user?.trim() || !port) return false;
// Port validation
const portNum = Number(port);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false;
// If not remembering host, only basic fields are required
if (!form.rememberHost) return true;
// Auth method validation only if remembering host
if (form.rememberHost) {
if (form.authMethod === 'Select Auth') return false;
if (form.authMethod === 'key' && !form.privateKey) return false;
if (form.authMethod === 'password' && !form.password) return false;
if (authMethod === 'Select Auth') return false;
if (authMethod === 'password' && !password?.trim()) return false;
if (authMethod === 'key' && !privateKey?.trim()) return false;
}
return true;
@@ -115,28 +111,11 @@ const AddHostModal = ({ isHidden, setIsAddHostHidden, handleAddHost }) => {
const handleSubmit = (event) => {
event.preventDefault();
if (isFormValid()) {
if (!form.rememberHost) {
handleAddHost();
} else {
handleAddHost();
}
setForm({
name: '',
folder: '',
ip: '',
user: '',
password: '',
privateKey: '',
keyType: '',
port: 22,
authMethod: 'Select Auth',
rememberHost: false,
storePassword: true,
});
setIsAddHostHidden(true);
if (!form.ip?.trim() || !form.user?.trim() || !form.port) {
alert("Please fill out all required fields (IP, User, Port).");
return;
}
handleAddHost();
};
return (
@@ -146,15 +125,18 @@ const AddHostModal = ({ isHidden, setIsAddHostHidden, handleAddHost }) => {
display: 'flex',
justifyContent: 'center',
alignItems: '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,
padding: 0,
borderRadius: 10,
maxWidth: '500px',
width: '100%',
@@ -164,137 +146,239 @@ const AddHostModal = ({ isHidden, setIsAddHostHidden, handleAddHost }) => {
mx: 2,
}}
>
<DialogTitle sx={{ mb: 2 }}>Add 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,
},
},
<Tabs
value={activeTab}
onChange={(e, val) => setActiveTab(val)}
sx={{
width: '100%',
mb: 0,
backgroundColor: theme.palette.general.tertiary,
}}
>
<TabList
sx={{
width: '100%',
gap: 0,
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
backgroundColor: theme.palette.general.primary,
'& button': {
flex: 1,
bgcolor: 'transparent',
color: theme.palette.text.secondary,
'&:hover': {
bgcolor: theme.palette.general.disabled,
},
'&.Mui-selected': {
bgcolor: theme.palette.general.tertiary,
color: theme.palette.text.primary,
'&:hover': {
bgcolor: theme.palette.general.tertiary,
},
}}
>
<Tab>Basic Info</Tab>
<Tab>Connection</Tab>
<Tab>Authentication</Tab>
</TabList>
},
},
}}
>
<Tab sx={{ flex: 1 }}>Basic Info</Tab>
<Tab sx={{ flex: 1 }}>Connection</Tab>
<Tab sx={{ flex: 1 }}>Authentication</Tab>
</TabList>
<TabPanel value={0}>
<Stack spacing={2}>
<FormControl>
<FormLabel>Host Name</FormLabel>
<Input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
<div style={{ padding: '24px', backgroundColor: theme.palette.general.tertiary }}>
<TabPanel value={0}>
<Stack spacing={2}>
<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>
<FormLabel>Folder</FormLabel>
<Input
value={form.folder || ''}
onChange={(e) => setForm({ ...form, folder: e.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
}}
/>
</FormControl>
<FormControl>
<FormLabel>Remember Host</FormLabel>
<Checkbox
checked={Boolean(form.rememberHost)}
onChange={(e) => setForm({
...form,
rememberHost: e.target.checked,
})}
sx={{
color: theme.palette.text.primary,
'&.Mui-checked': {
color: theme.palette.text.primary,
}}
/>
</FormControl>
<FormControl>
<FormLabel>Folder</FormLabel>
<Input
value={form.folder || ''}
onChange={(e) => setForm({ ...form, folder: e.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
}}
/>
</FormControl>
</Stack>
</TabPanel>
},
}}
/>
</FormControl>
</Stack>
</TabPanel>
<TabPanel value={1}>
<Stack spacing={2}>
<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.port < 1 || form.port > 65535}>
<FormLabel>Host Port</FormLabel>
<Input
type="number"
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>
</Stack>
</TabPanel>
<TabPanel value={1}>
<Stack spacing={2}>
<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.port < 1 || form.port > 65535}>
<FormLabel>Host Port</FormLabel>
<Input
type="number"
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>
</Stack>
</TabPanel>
<TabPanel value={2}>
<Stack spacing={2}>
<TabPanel value={2}>
<Stack spacing={2}>
<FormControl error={!form.authMethod || 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="key">SSH Key</Option>
</Select>
</FormControl>
{form.authMethod === 'password' && (
<FormControl error={!form.password}>
<FormLabel>Password</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
type={showPassword ? 'text' : 'password'}
value={form.password}
onChange={(e) => setForm({ ...form, 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 === 'key' && (
<Stack spacing={2}>
<FormControl error={!form.privateKey}>
<FormLabel>SSH 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.privateKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'}
<Input
type="file"
onChange={handleFileChange}
sx={{ display: 'none' }}
/>
</Button>
</FormControl>
{form.privateKey && (
<FormControl>
<FormLabel>Key Passphrase (optional)</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
type={showPassphrase ? "text" : "password"}
value={form.passphrase || ''}
onChange={(e) => setForm(prev => ({ ...prev, passphrase: e.target.value }))}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
flex: 1
}}
/>
<IconButton
onClick={() => setShowPassphrase(!showPassphrase)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1
}}
>
{showPassphrase ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl>
)}
</Stack>
)}
{form.rememberHost && (
<FormControl>
<FormLabel>Remember Host</FormLabel>
<FormLabel>Store Password</FormLabel>
<Checkbox
checked={form.rememberHost}
onChange={(e) => setForm({
...form,
rememberHost: e.target.checked,
...((!e.target.checked) && {
authMethod: 'Select Auth',
password: '',
privateKey: '',
keyType: '',
passphrase: '',
storePassword: true
})
})}
checked={Boolean(form.storePassword)}
onChange={(e) => setForm({ ...form, storePassword: e.target.checked })}
sx={{
color: theme.palette.text.primary,
'&.Mui-checked': {
@@ -303,147 +387,31 @@ const AddHostModal = ({ isHidden, setIsAddHostHidden, handleAddHost }) => {
}}
/>
</FormControl>
{form.rememberHost && (
<>
<FormControl>
<FormLabel>Store Password</FormLabel>
<Checkbox
checked={form.storePassword}
onChange={(e) => setForm({ ...form, storePassword: e.target.checked })}
sx={{
color: theme.palette.text.primary,
'&.Mui-checked': {
color: theme.palette.text.primary,
},
}}
/>
</FormControl>
<FormControl error={!form.authMethod || 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="key">SSH Key</Option>
</Select>
</FormControl>
)}
</Stack>
</TabPanel>
</div>
{form.authMethod === 'password' && (
<FormControl error={!form.password}>
<FormLabel>Password</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
type={showPassword ? 'text' : 'password'}
value={form.password}
onChange={(e) => setForm({ ...form, 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 === 'key' && form.rememberHost && (
<Stack spacing={2}>
<FormControl error={!form.privateKey}>
<FormLabel>SSH 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: 'rgba(0, 0, 0, 0.3)',
},
}}
>
{form.privateKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'}
<Input
type="file"
onChange={handleFileChange}
sx={{ display: 'none' }}
/>
</Button>
</FormControl>
{form.privateKey && (
<FormControl>
<FormLabel>Key Passphrase (optional)</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
type={showPassphrase ? "text" : "password"}
value={form.passphrase || ''}
onChange={(e) => setForm(prev => ({ ...prev, passphrase: e.target.value }))}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
flex: 1
}}
/>
<IconButton
onClick={() => setShowPassphrase(!showPassphrase)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1
}}
>
{showPassphrase ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl>
)}
</Stack>
)}
</>
)}
</Stack>
</TabPanel>
</Tabs>
<Button
type="submit"
disabled={!isFormValid()}
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',
}}
>
Add Host
</Button>
</form>
</DialogContent>
<Button
onClick={handleSubmit}
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: 1,
width: '100%',
height: '40px',
}}
>
Add Host
</Button>
</Tabs>
</ModalDialog>
</Modal>
</CssVarsProvider>
@@ -452,8 +420,10 @@ const AddHostModal = ({ isHidden, setIsAddHostHidden, handleAddHost }) => {
AddHostModal.propTypes = {
isHidden: PropTypes.bool.isRequired,
setIsAddHostHidden: PropTypes.func.isRequired,
form: PropTypes.object.isRequired,
setForm: PropTypes.func.isRequired,
handleAddHost: PropTypes.func.isRequired,
setIsAddHostHidden: PropTypes.func.isRequired,
};
export default AddHostModal;

View File

@@ -36,7 +36,8 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
keyType: hostConfig?.keyType || '',
passphrase: '',
authMethod: hostConfig?.authMethod || 'Select Auth',
storePassword: true
storePassword: true,
rememberHost: true
});
const [showPassword, setShowPassword] = useState(false);
const [showPassphrase, setShowPassphrase] = useState(false);
@@ -51,11 +52,13 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
ip: hostConfig.ip || '',
user: hostConfig.user || '',
password: hostConfig.password || '',
rsaKey: hostConfig.rsaKey || '',
privateKey: hostConfig.privateKey || '',
keyType: hostConfig.keyType || '',
passphrase: hostConfig.passphrase || '',
port: hostConfig.port || 22,
authMethod: hostConfig.password ? 'password' : hostConfig.rsaKey ? 'rsaKey' : 'Select Auth',
authMethod: hostConfig.password ? 'password' : hostConfig.privateKey ? 'key' : 'Select Auth',
rememberHost: true,
storePassword: !!(hostConfig.password || hostConfig.rsaKey),
storePassword: !!(hostConfig.password || hostConfig.privateKey),
});
}
}, [isHidden, hostConfig]);
@@ -118,20 +121,28 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
...prev,
storePassword: Boolean(checked),
password: checked ? prev.password : "",
rsaKey: checked ? prev.rsaKey : "",
privateKey: checked ? prev.privateKey : "",
passphrase: checked ? prev.passphrase : "",
authMethod: checked ? prev.authMethod : "Select Auth"
}));
};
const isFormValid = () => {
const { ip, user, port, authMethod, password, rsaKey, storePassword } = form;
const { ip, user, port, authMethod, password, privateKey } = form;
// Basic validation for required fields
if (!ip?.trim() || !user?.trim() || !port) return false;
// Port validation
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;
// Auth method validation only if storing password
if (form.storePassword) {
if (authMethod === 'Select Auth') return false;
if (authMethod === 'password' && !password?.trim()) return false;
if (authMethod === 'key' && !privateKey?.trim()) return false;
}
return true;
};
@@ -142,15 +153,25 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
setIsLoading(true);
try {
await handleEditHost(hostConfig, {
const newConfig = {
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),
});
};
if (form.storePassword) {
if (form.authMethod === 'password') {
newConfig.password = form.password;
} else if (form.authMethod === 'key') {
newConfig.privateKey = form.privateKey;
newConfig.keyType = form.keyType;
newConfig.passphrase = form.passphrase;
}
}
await handleEditHost(hostConfig, newConfig);
} finally {
setIsLoading(false);
}
@@ -162,11 +183,9 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
open={!isHidden}
onClose={() => !isLoading && setIsEditHostHidden(true)}
sx={{
position: 'fixed',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
alignItems: 'center',
backdropFilter: 'blur(5px)',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
}}
@@ -178,7 +197,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary,
padding: 3,
padding: 0,
borderRadius: 10,
maxWidth: '500px',
width: '100%',
@@ -188,134 +207,133 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
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,
},
},
<Tabs
value={activeTab}
onChange={(e, val) => setActiveTab(val)}
sx={{
width: '100%',
mb: 0,
backgroundColor: theme.palette.general.tertiary,
}}
>
<TabList
sx={{
width: '100%',
gap: 0,
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
backgroundColor: theme.palette.general.primary,
'& button': {
flex: 1,
bgcolor: 'transparent',
color: theme.palette.text.secondary,
'&:hover': {
bgcolor: theme.palette.general.disabled,
},
'&.Mui-selected': {
bgcolor: theme.palette.general.tertiary,
color: theme.palette.text.primary,
'&:hover': {
bgcolor: theme.palette.general.tertiary,
},
}}
>
<Tab>Basic Info</Tab>
<Tab>Connection</Tab>
<Tab>Authentication</Tab>
</TabList>
},
},
}}
>
<Tab sx={{ flex: 1 }}>Basic Info</Tab>
<Tab sx={{ flex: 1 }}>Connection</Tab>
<Tab sx={{ flex: 1 }}>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>
<div style={{ padding: '24px', backgroundColor: theme.palette.general.tertiary }}>
<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>
<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>
<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.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>
<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={{
<TabPanel value={2}>
<Stack spacing={2}>
<FormControl>
<FormLabel>Store Password</FormLabel>
<Checkbox
checked={Boolean(form.storePassword)}
onChange={(e) => handleStorePasswordChange(e.target.checked)}
sx={{
color: theme.palette.text.primary,
'&.Mui-checked': {
color: theme.palette.text.primary,
'&.Mui-checked': {
color: theme.palette.text.primary
}
}}
/>
</FormControl>
},
}}
/>
</FormControl>
{form.storePassword && (
<FormControl error={form.authMethod === 'Select Auth'}>
{form.storePassword && (
<>
<FormControl error={form.storePassword && (!form.authMethod || form.authMethod === 'Select Auth')}>
<FormLabel>Authentication Method</FormLabel>
<Select
value={form.authMethod}
@@ -330,170 +348,129 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
<Option value="key">SSH 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>
)}
{form.authMethod === 'key' && form.storePassword && (
<Stack spacing={2}>
<FormControl error={!form.privateKey && !hostConfig?.privateKey}>
<FormLabel>SSH 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: 'rgba(0, 0, 0, 0.3)',
},
}}
>
{form.privateKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'}
{form.authMethod === 'password' && (
<FormControl error={form.storePassword && !form.password}>
<FormLabel>Password</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
type="file"
onChange={handleFileChange}
sx={{ display: 'none' }}
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
}}
/>
</Button>
{hostConfig?.privateKey && !form.privateKey && (
<FormLabel
sx={{
color: theme.palette.text.secondary,
fontSize: '0.875rem',
mt: 1,
display: 'block',
textAlign: 'center'
<IconButton
onClick={() => setShowPassword(!showPassword)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1
}}
>
Existing {hostConfig.keyType || 'SSH'} key detected. Upload to replace.
</FormLabel>
)}
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl>
{form.privateKey && (
<FormControl>
<FormLabel>Key Passphrase (optional)</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
)}
{form.authMethod === 'key' && (
<Stack spacing={2}>
<FormControl error={form.storePassword && !form.privateKey}>
<FormLabel>SSH 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.privateKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'}
<Input
type={showPassphrase ? "text" : "password"}
value={form.passphrase || ''}
onChange={(e) => setForm(prev => ({ ...prev, passphrase: e.target.value }))}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
flex: 1
}}
type="file"
onChange={handleFileChange}
sx={{ display: 'none' }}
/>
<IconButton
onClick={() => setShowPassphrase(!showPassphrase)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1
</Button>
{hostConfig?.privateKey && !form.privateKey && (
<FormLabel
sx={{
color: theme.palette.text.secondary,
fontSize: '0.875rem',
mt: 1,
display: 'block',
textAlign: 'center'
}}
>
{showPassphrase ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
Existing {hostConfig.keyType || 'SSH'} key detected. Upload to replace.
</FormLabel>
)}
</FormControl>
)}
</Stack>
)}
</Stack>
</TabPanel>
</Tabs>
{form.privateKey && (
<FormControl>
<FormLabel>Key Passphrase (optional)</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
type={showPassphrase ? "text" : "password"}
value={form.passphrase || ''}
onChange={(e) => setForm(prev => ({ ...prev, passphrase: e.target.value }))}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
flex: 1
}}
/>
<IconButton
onClick={() => setShowPassphrase(!showPassphrase)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1
}}
>
{showPassphrase ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl>
)}
</Stack>
)}
</>
)}
</Stack>
</TabPanel>
</div>
<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>
<Button
onClick={handleSubmit}
disabled={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: 1,
width: '100%',
height: '40px',
}}
>
{isLoading ? "Saving changes..." : "Save changes"}
</Button>
</Tabs>
</ModalDialog>
</Modal>
</CssVarsProvider>