Updated various names for rsa keys to public keys, fixes ssh not connecting, better timing for editing host.
This commit is contained in:
108
src/App.jsx
108
src/App.jsx
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user