Updated various names for rsa keys to public keys, fixes ssh not connecting, better timing for editing host.

This commit is contained in:
Karmaa
2025-03-16 02:28:20 -05:00
parent fda8c7ce4b
commit bec8b67303
9 changed files with 372 additions and 164 deletions

View File

@@ -69,6 +69,7 @@ function App() {
const [isEditHostHidden, setIsEditHostHidden] = useState(true);
const [currentHostConfig, setCurrentHostConfig] = useState(null);
const [isLoggingIn, setIsLoggingIn] = useState(true);
const [isEditing, setIsEditing] = useState(false);
useEffect(() => {
const handleKeyDown = (e) => {
@@ -221,38 +222,56 @@ function App() {
}, []);
const handleAddHost = () => {
if (addHostForm.ip && addHostForm.user && addHostForm.port && addHostForm.authMethod !== 'Select Auth') {
if (addHostForm.ip && addHostForm.user && addHostForm.port) {
// If not remembering the host, just connect without auth validation
if (!addHostForm.rememberHost) {
connectToHost();
setIsAddHostHidden(true);
return;
}
// If remembering the host, validate auth method
if (addHostForm.authMethod === 'Select Auth') {
alert("Please select an authentication method.");
return;
}
if (addHostForm.authMethod === 'password' && !addHostForm.password) {
setIsNoAuthHidden(false);
} else if (addHostForm.authMethod === 'rsaKey' && !addHostForm.rsaKey) {
setIsNoAuthHidden(false);
} else {
connectToHost();
if (addHostForm.rememberHost) {
if (!addHostForm.storePassword) {
addHostForm.password = '';
}
handleSaveHost();
}
return;
}
if (addHostForm.authMethod === 'rsaKey' && !addHostForm.rsaKey) {
setIsNoAuthHidden(false);
return;
}
// Connect and save if all validation passes
connectToHost();
if (!addHostForm.storePassword) {
addHostForm.password = '';
}
handleSaveHost();
setIsAddHostHidden(true);
} else {
alert("Please fill out all fields.");
alert("Please fill out all required fields (IP, User, Port).");
}
};
const connectToHost = () => {
// Create a clean host config object
const hostConfig = {
name: addHostForm.name || '',
folder: addHostForm.folder || '',
ip: addHostForm.ip,
user: addHostForm.user,
port: String(addHostForm.port),
password: addHostForm.rememberHost && addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
rsaKey: addHostForm.rememberHost && addHostForm.authMethod === 'rsaKey' ? addHostForm.rsaKey : undefined,
};
const newTerminal = {
id: nextId,
title: addHostForm.name || addHostForm.ip,
hostConfig: {
name: addHostForm.name,
folder: addHostForm.folder,
ip: addHostForm.ip,
user: addHostForm.user,
password: addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
rsaKey: addHostForm.authMethod === 'rsaKey' ? addHostForm.rsaKey : undefined,
port: String(addHostForm.port),
},
title: hostConfig.name || hostConfig.ip,
hostConfig,
terminalRef: null,
};
setTerminals([...terminals, newTerminal]);
@@ -281,10 +300,31 @@ function App() {
};
const connectToHostWithConfig = (hostConfig) => {
// Ensure we have a valid host config
if (!hostConfig || typeof hostConfig !== 'object') {
return;
}
// Ensure all required fields are present
if (!hostConfig.ip || !hostConfig.user) {
return;
}
// Create a clean host config object with all required fields
const cleanHostConfig = {
name: hostConfig.name || '',
folder: hostConfig.folder || '',
ip: hostConfig.ip.trim(),
user: hostConfig.user.trim(),
port: hostConfig.port || '22',
password: hostConfig.password?.trim(),
rsaKey: hostConfig.rsaKey?.trim(),
};
const newTerminal = {
id: nextId,
title: hostConfig.name || hostConfig.ip,
hostConfig: hostConfig,
title: cleanHostConfig.name || cleanHostConfig.ip,
hostConfig: cleanHostConfig,
terminalRef: null,
};
setTerminals([...terminals, newTerminal]);
@@ -411,10 +451,21 @@ function App() {
const handleEditHost = async (oldConfig, newConfig = null) => {
try {
if (newConfig) {
await userRef.current.editHost({
oldHostConfig: oldConfig,
newHostConfig: newConfig,
});
if (isEditing) return;
setIsEditing(true);
try {
await userRef.current.editHost({
oldHostConfig: oldConfig,
newHostConfig: newConfig,
});
// Keep the modal visible during the delay
await new Promise(resolve => setTimeout(resolve, 3000));
} finally {
setIsEditing(false);
setIsEditHostHidden(true);
}
return;
}
@@ -423,6 +474,7 @@ function App() {
console.error('Edit failed:', error);
setErrorMessage(`Edit failed: ${error}`);
setIsErrorHidden(false);
setIsEditing(false);
}
};

View File

@@ -161,7 +161,15 @@ function Launchpad({
{activeApp === 'hostViewer' && (
<HostViewer
getHosts={getHosts}
connectToHost={connectToHost}
connectToHost={(hostConfig) => {
if (!hostConfig || typeof hostConfig !== 'object') {
return;
}
if (!hostConfig.ip || !hostConfig.user) {
return;
}
connectToHost(hostConfig);
}}
setIsAddHostHidden={setIsAddHostHidden}
deleteHost={deleteHost}
editHost={editHost}

View File

@@ -11,6 +11,7 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
const [draggedHost, setDraggedHost] = useState(null);
const [isDraggingOver, setIsDraggingOver] = useState(null);
const isMounted = useRef(true);
const [isDeleting, setIsDeleting] = useState(false);
const fetchHosts = async () => {
try {
@@ -153,6 +154,22 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
setDraggedHost(null);
};
const handleDelete = async (e, hostWrapper) => {
e.stopPropagation();
if (isDeleting) return;
setIsDeleting(true);
try {
await deleteHost({ _id: hostWrapper._id });
await new Promise(resolve => setTimeout(resolve, 500)); // Add a small delay for UX
await fetchHosts();
} catch (error) {
console.error('Failed to delete host:', error);
} finally {
setIsDeleting(false);
}
};
const renderHostItem = (hostWrapper) => {
const hostConfig = hostWrapper.config || {};
@@ -182,27 +199,33 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
className="text-black"
onClick={(e) => {
e.stopPropagation();
connectToHost(hostConfig);
if (!hostWrapper.config || !hostWrapper.config.ip || !hostWrapper.config.user) {
return;
}
connectToHost(hostWrapper.config);
}}
disabled={isDeleting}
sx={{
backgroundColor: "#6e6e6e",
"&:hover": { backgroundColor: "#0f0f0f" }
"&:hover": { backgroundColor: "#0f0f0f" },
opacity: isDeleting ? 0.5 : 1,
cursor: isDeleting ? "not-allowed" : "pointer"
}}
>
Connect
</Button>
<Button
className="text-black"
onClick={(e) => {
e.stopPropagation();
deleteHost({ ...hostConfig, _id: hostWrapper._id });
}}
onClick={(e) => handleDelete(e, hostWrapper)}
disabled={isDeleting}
sx={{
backgroundColor: "#6e6e6e",
"&:hover": { backgroundColor: "#0f0f0f" }
"&:hover": { backgroundColor: "#0f0f0f" },
opacity: isDeleting ? 0.5 : 1,
cursor: isDeleting ? "not-allowed" : "pointer"
}}
>
Delete
{isDeleting ? "Deleting..." : "Delete"}
</Button>
<Button
className="text-black"
@@ -210,9 +233,12 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
e.stopPropagation();
openEditPanel(hostConfig);
}}
disabled={isDeleting}
sx={{
backgroundColor: "#6e6e6e",
"&:hover": { backgroundColor: "#0f0f0f" }
"&:hover": { backgroundColor: "#0f0f0f" },
opacity: isDeleting ? 0.5 : 1,
cursor: isDeleting ? "not-allowed" : "pointer"
}}
>
Edit

View File

@@ -55,12 +55,6 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
terminalInstance.current.loadAddon(fitAddon.current);
terminalInstance.current.open(terminalRef.current);
setTimeout(() => {
fitAddon.current.fit();
resizeTerminal();
terminalInstance.current.focus();
}, 50);
const socket = io(
window.location.hostname === "localhost"
? "http://localhost:8081"
@@ -72,17 +66,53 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
);
socketRef.current = socket;
socket.on("connect_error", (error) => {
terminalInstance.current.write(`\r\n*** Socket connection error: ${error.message} ***\r\n`);
});
socket.on("connect_timeout", () => {
terminalInstance.current.write(`\r\n*** Socket connection timeout ***\r\n`);
});
socket.on("error", (err) => {
const isAuthError = err.toLowerCase().includes("authentication") || err.toLowerCase().includes("auth");
if (isAuthError && !hostConfig.password?.trim() && !hostConfig.rsaKey?.trim() && !authModalShown) {
authModalShown = true;
setIsNoAuthHidden(false);
}
terminalInstance.current.write(`\r\n*** Error: ${err} ***\r\n`);
});
socket.on("connect", () => {
fitAddon.current.fit();
resizeTerminal();
const { cols, rows } = terminalInstance.current;
if (!hostConfig.password && !hostConfig.rsaKey) {
// Check if we have authentication
if (!hostConfig.password?.trim() && !hostConfig.rsaKey?.trim()) {
setIsNoAuthHidden(false);
} else {
socket.emit("connectToHost", cols, rows, hostConfig);
return;
}
// Only connect if we have authentication
const sshConfig = {
ip: hostConfig.ip,
user: hostConfig.user,
port: Number(hostConfig.port) || 22,
password: hostConfig.password?.trim(),
rsaKey: hostConfig.rsaKey?.trim()
};
socket.emit("connectToHost", cols, rows, sshConfig);
});
// Fit and focus the terminal after it's opened
setTimeout(() => {
fitAddon.current.fit();
resizeTerminal();
terminalInstance.current.focus();
}, 50);
socket.on("data", (data) => {
const decoder = new TextDecoder("utf-8");
terminalInstance.current.write(decoder.decode(new Uint8Array(data)));
@@ -142,17 +172,26 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
}
});
socket.on("noAuthRequired", () => {
setIsNoAuthHidden(false);
});
let authModalShown = false;
socket.on("error", (err) => {
terminalInstance.current.write(`\r\n*** Error: ${err} ***\r\n`);
socket.on("noAuthRequired", () => {
// Only show auth modal if we don't have valid credentials
if (!hostConfig.password?.trim() && !hostConfig.rsaKey?.trim() && !authModalShown) {
authModalShown = true;
setIsNoAuthHidden(false);
}
});
return () => {
terminalInstance.current.dispose();
socket.disconnect();
if (terminalInstance.current) {
terminalInstance.current.dispose();
terminalInstance.current = null;
}
if (socketRef.current) {
socketRef.current.disconnect();
socketRef.current = null;
}
authModalShown = false;
};
}, [hostConfig]);
@@ -201,7 +240,7 @@ NewTerminal.propTypes = {
user: PropTypes.string.isRequired,
password: PropTypes.string,
rsaKey: PropTypes.string,
port: PropTypes.number.isRequired,
port: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
}).isRequired,
isVisible: PropTypes.bool.isRequired,
setIsNoAuthHidden: PropTypes.func.isRequired,

View File

@@ -177,7 +177,18 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
});
if (response?.success) {
return response.hosts;
return response.hosts.map(host => ({
...host,
config: host.config ? {
name: host.config.name || '',
folder: host.config.folder || '',
ip: host.config.ip || '',
user: host.config.user || '',
port: host.config.port || '22',
password: host.config.password || '',
rsaKey: host.config.rsaKey || '',
} : {}
})).filter(host => host.config && host.config.ip && host.config.user);
} else {
throw new Error(response?.error || "Failed to fetch hosts");
}

View File

@@ -26,9 +26,16 @@ io.on("connection", (socket) => {
let stream = null;
socket.on("connectToHost", (cols, rows, hostConfig) => {
if (!hostConfig || !hostConfig.ip || !hostConfig.user || (!hostConfig.password && !hostConfig.rsaKey) || !hostConfig.port) {
logger.error("Invalid hostConfig received:", hostConfig);
socket.emit("noAuthRequired");
if (!hostConfig || !hostConfig.ip || !hostConfig.user || !hostConfig.port) {
logger.error("Invalid hostConfig received - missing required fields:", hostConfig);
socket.emit("error", "Missing required connection details (IP, user, or port)");
return;
}
// Require authentication
if (!hostConfig.password && !hostConfig.rsaKey) {
logger.error("No authentication provided");
socket.emit("error", "Authentication required");
return;
}
@@ -36,11 +43,10 @@ io.on("connection", (socket) => {
ip: hostConfig.ip,
port: hostConfig.port,
user: hostConfig.user,
password: hostConfig.password ? '***REDACTED***' : undefined,
rsaKey: hostConfig.rsaKey ? '***REDACTED***' : undefined,
authType: hostConfig.password ? 'password' : 'public key',
};
logger.info("Received hostConfig:", safeHostConfig);
logger.info("Connecting with config:", safeHostConfig);
const { ip, port, user, password, rsaKey } = hostConfig;
const conn = new SSHClient();
@@ -50,7 +56,7 @@ io.on("connection", (socket) => {
conn.shell({ term: "xterm-256color" }, function (err, newStream) {
if (err) {
logger.error("Error:", err.message);
logger.error("Shell error:", err.message);
socket.emit("error", err.message);
return;
}

View File

@@ -31,32 +31,58 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
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') || file.name.endsWith('.pub')) {
if (file.name.endsWith('.key') || file.name.endsWith('.pem') || file.name.endsWith('.pub')) {
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.");
alert("Please upload a valid public key file.");
}
}
};
const handleAuthChange = (newMethod) => {
setForm((prev) => ({
...prev,
authMethod: newMethod,
password: "",
rsaKey: ""
}));
};
const isFormValid = () => {
if (form.authMethod === 'Select Auth') return false;
// Basic validation for required fields
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;
const portNum = Number(form.port);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false;
// Only validate auth method if rememberHost is true
if (form.rememberHost) {
if (form.authMethod === 'Select Auth') return false;
if (form.authMethod === 'rsaKey' && !form.rsaKey) return false;
if (form.authMethod === 'password' && !form.password) return false;
}
return true;
};
const handleSubmit = (event) => {
event.preventDefault();
if (isFormValid()) {
handleAddHost();
// If not remembering the host, only send basic connection info
if (!form.rememberHost) {
handleAddHost();
} else {
// Only include auth details if remembering the host
handleAddHost();
}
// Reset form after successful submission
setForm({
name: '',
folder: '',
ip: '',
user: '',
password: '',
@@ -66,6 +92,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
rememberHost: false,
storePassword: true,
});
setIsAddHostHidden(true);
}
};
@@ -212,7 +239,17 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
<FormLabel>Remember Host</FormLabel>
<Checkbox
checked={form.rememberHost}
onChange={(e) => setForm({ ...form, rememberHost: e.target.checked })}
onChange={(e) => setForm({
...form,
rememberHost: e.target.checked,
// Reset auth fields if unchecking remember host
...((!e.target.checked) && {
authMethod: 'Select Auth',
password: '',
rsaKey: '',
storePassword: true
})
})}
sx={{
color: theme.palette.text.primary,
'&.Mui-checked': {
@@ -239,44 +276,38 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
<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
value={form.authMethod}
onChange={(e, val) => handleAuthChange(val)}
sx={{
backgroundColor: !form.authMethod || form.authMethod === 'Select Auth' ? theme.palette.general.tertiary : theme.palette.general.primary,
backgroundColor: 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="Select Auth" disabled>Select Auth</Option>
<Option value="password">Password</Option>
<Option value="rsaKey">RSA Key</Option>
<Option value="rsaKey">Public Key</Option>
</Select>
</FormControl>
{form.authMethod === 'password' && (
<FormControl error={!form.password}>
<FormLabel>Host Password</FormLabel>
<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 })}
required
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
flex: 1,
flex: 1
}}
/>
<IconButton
onClick={() => setShowPassword(!showPassword)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1,
marginLeft: 1
}}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
@@ -284,9 +315,10 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
</div>
</FormControl>
)}
{form.authMethod === 'rsaKey' && (
<FormControl error={!form.rsaKey}>
<FormLabel>RSA Key</FormLabel>
<FormLabel>Public Key</FormLabel>
<Button
component="label"
sx={{
@@ -302,11 +334,10 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
},
}}
>
{form.rsaKey ? 'Change RSA Key File' : 'Upload RSA Key File'}
{form.rsaKey ? 'Change Public Key File' : 'Upload Public Key File'}
<Input
type="file"
onChange={handleFileChange}
required
sx={{ display: 'none' }}
/>
</Button>

View File

@@ -27,22 +27,24 @@ 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 (hostConfig && !isHidden) {
if (!isHidden && hostConfig) {
setForm({
name: hostConfig.name || "",
folder: hostConfig.folder || "",
ip: hostConfig.ip || "",
user: hostConfig.user || "",
password: hostConfig.password || "",
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",
authMethod: hostConfig.password ? 'password' : hostConfig.rsaKey ? 'rsaKey' : 'Select Auth',
rememberHost: true,
storePassword: true,
});
}
}, [hostConfig, isHidden]);
}, [isHidden, hostConfig]);
const handleFileChange = (e) => {
const file = e.target.files[0];
@@ -68,7 +70,9 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
setForm((prev) => ({
...prev,
storePassword: Boolean(checked),
authMethod: checked ? 'password' : 'Select Auth'
password: checked ? prev.password : "",
rsaKey: checked ? prev.rsaKey : "",
authMethod: checked ? prev.authMethod : "Select Auth"
}));
};
@@ -85,22 +89,23 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
return true;
};
const handleSave = async (e) => {
e.preventDefault();
const handleSubmit = async (event) => {
event.preventDefault();
if (isLoading) return;
setIsLoading(true);
try {
const newConfig = {
...form,
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),
};
if (form.authMethod === 'rsaKey' || !form.storePassword) {
newConfig.password = '';
}
await handleEditHost(hostConfig, newConfig);
setIsEditHostHidden(true);
} catch (error) {
console.error('Failed to save:', error);
});
} finally {
setIsLoading(false);
}
};
@@ -108,15 +113,20 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
<CssVarsProvider theme={theme}>
<Modal
open={!isHidden}
onClose={() => setIsEditHostHidden(true)}
onClose={() => !isLoading && setIsEditHostHidden(true)}
sx={{
position: 'fixed',
inset: 0,
display: 'flex',
justifyContent: 'center',
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,
@@ -133,7 +143,7 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
>
<DialogTitle sx={{ mb: 2 }}>Edit Host</DialogTitle>
<DialogContent>
<form onSubmit={handleSave}>
<form onSubmit={handleSubmit}>
<Tabs
value={activeTab}
onChange={(e, val) => setActiveTab(val)}
@@ -270,7 +280,7 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
>
<Option value="Select Auth" disabled>Select Auth</Option>
<Option value="password">Password</Option>
<Option value="rsaKey">RSA Key</Option>
<Option value="rsaKey">Public Key</Option>
</Select>
</FormControl>
)}
@@ -304,7 +314,7 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
{form.authMethod === 'rsaKey' && form.storePassword && (
<FormControl error={!form.rsaKey && !hostConfig?.rsaKey}>
<FormLabel>RSA Key</FormLabel>
<FormLabel>Public Key</FormLabel>
<Button
component="label"
sx={{
@@ -320,7 +330,7 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
},
}}
>
{form.rsaKey ? 'Change RSA Key File' : 'Upload RSA Key File'}
{form.rsaKey ? 'Change Public Key File' : 'Upload Public Key File'}
<Input
type="file"
onChange={handleFileChange}
@@ -348,7 +358,7 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
<Button
type="submit"
disabled={!isFormValid()}
disabled={!isFormValid() || isLoading}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
@@ -364,7 +374,7 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
height: '40px',
}}
>
Save Changes
{isLoading ? "Saving..." : "Save Changes"}
</Button>
</form>
</DialogContent>

View File

@@ -15,15 +15,25 @@ import {
Option,
} from '@mui/joy';
import theme from '/src/theme';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, handleAuthSubmit }) => {
const [showPassword, setShowPassword] = useState(false);
// Initialize form with default values if not provided
useEffect(() => {
if (!form.authMethod) {
setForm(prev => ({
...prev,
authMethod: 'Select Auth'
}));
}
}, []);
const isFormValid = () => {
if (form.authMethod === 'Select Auth') return false;
if (!form.authMethod || form.authMethod === 'Select Auth') return false;
if (form.authMethod === 'rsaKey' && !form.rsaKey) return false;
if (form.authMethod === 'password' && !form.password) return false;
return true;
@@ -46,7 +56,11 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
setIsNoAuthHidden(true);
}
}}
disableBackdropClic
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<ModalDialog
layout="center"
@@ -56,58 +70,53 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
width: "auto",
maxWidth: "90vw",
minWidth: "fit-content",
overflow: "hidden",
display: "flex",
flexDirection: "column",
alignItems: "center",
maxWidth: '500px',
width: '100%',
maxHeight: '80vh',
overflow: 'auto',
boxSizing: 'border-box',
mx: 2,
}}
>
<DialogTitle>Authentication Required</DialogTitle>
<DialogTitle sx={{ mb: 2 }}>Authentication Required</DialogTitle>
<DialogContent>
<form onSubmit={handleSubmit}>
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
<Stack spacing={2}>
<FormControl error={!form.authMethod || form.authMethod === 'Select Auth'}>
<FormLabel sx={{ color: theme.palette.text.primary }}>Authentication Method</FormLabel>
<FormLabel>Authentication Method</FormLabel>
<Select
value={form.authMethod || 'Select Auth'}
onChange={(e, newValue) => setForm({ ...form, authMethod: newValue })}
required
onChange={(e, val) => setForm(prev => ({ ...prev, authMethod: val, password: '', rsaKey: '' }))}
sx={{
backgroundColor: !form.authMethod || form.authMethod === 'Select Auth' ? theme.palette.general.tertiary : theme.palette.general.primary,
backgroundColor: 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>
<Option value="rsaKey">Public Key</Option>
</Select>
</FormControl>
{form.authMethod === 'password' && (
<FormControl error={!form.password}>
<FormLabel sx={{ color: theme.palette.text.primary }}>Password</FormLabel>
<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 })}
required
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,
flex: 1
}}
/>
<IconButton
onClick={() => setShowPassword(!showPassword)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1,
marginLeft: 1
}}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
@@ -115,34 +124,44 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
</div>
</FormControl>
)}
{form.authMethod === 'rsaKey' && (
<FormControl error={!form.rsaKey}>
<FormLabel sx={{ color: theme.palette.text.primary }}>RSA Key</FormLabel>
<Input
type="file"
onChange={(e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
setForm({ ...form, rsaKey: event.target.result });
};
reader.readAsText(file);
}
}}
required
<FormLabel>Public Key</FormLabel>
<Button
component="label"
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
padding: 1,
textAlign: 'center',
width: '100%',
minWidth: 'auto',
minHeight: 'auto',
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={(e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
setForm({ ...form, rsaKey: event.target.result });
};
reader.readAsText(file);
}
}}
sx={{ display: 'none' }}
/>
</Button>
</FormControl>
)}
<Button
type="submit"
disabled={!isFormValid()}
@@ -152,6 +171,12 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
'&:disabled': {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
color: 'rgba(255, 255, 255, 0.3)',
},
marginTop: 2,
height: '40px',
}}
>
Connect