Dev 0.2.1 #30

Merged
LukeGus merged 47 commits from dev-0.2.1 into main 2025-03-24 03:17:56 +00:00
13 changed files with 458 additions and 773 deletions
Showing only changes of commit 28081ad6b2 - Show all commits
+7
View File
@@ -29,6 +29,7 @@
"express": "^4.21.2", "express": "^4.21.2",
"is-stream": "^4.0.1", "is-stream": "^4.0.1",
"make-dir": "^5.0.0", "make-dir": "^5.0.0",
"mitt": "^3.0.1",
"mongoose": "^8.12.1", "mongoose": "^8.12.1",
"node-ssh": "^13.2.0", "node-ssh": "^13.2.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
@@ -6341,6 +6342,12 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/mkdirp": { "node_modules/mkdirp": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+1
View File
@@ -31,6 +31,7 @@
"express": "^4.21.2", "express": "^4.21.2",
"is-stream": "^4.0.1", "is-stream": "^4.0.1",
"make-dir": "^5.0.0", "make-dir": "^5.0.0",
"mitt": "^3.0.1",
"mongoose": "^8.12.1", "mongoose": "^8.12.1",
"node-ssh": "^13.2.0", "node-ssh": "^13.2.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
+68 -79
View File
@@ -16,6 +16,7 @@ import ProfileModal from "./modals/ProfileModal.jsx";
import ErrorModal from "./modals/ErrorModal.jsx"; import ErrorModal from "./modals/ErrorModal.jsx";
import EditHostModal from "./modals/EditHostModal.jsx"; import EditHostModal from "./modals/EditHostModal.jsx";
import NoAuthenticationModal from "./modals/NoAuthenticationModal.jsx"; import NoAuthenticationModal from "./modals/NoAuthenticationModal.jsx";
import eventBus from "./other/eventBus.jsx";
function App() { function App() {
const [isAddHostHidden, setIsAddHostHidden] = useState(true); const [isAddHostHidden, setIsAddHostHidden] = useState(true);
@@ -33,6 +34,7 @@ function App() {
ip: "", ip: "",
user: "", user: "",
password: "", password: "",
sshKey: "",
port: 22, port: 22,
authMethod: "Select Auth", authMethod: "Select Auth",
rememberHost: true, rememberHost: true,
@@ -44,6 +46,7 @@ function App() {
ip: "", ip: "",
user: "", user: "",
password: "", password: "",
sshKey: "",
port: 22, port: 22,
authMethod: "Select Auth", authMethod: "Select Auth",
rememberHost: true, rememberHost: true,
@@ -51,9 +54,16 @@ function App() {
}); });
const [isNoAuthHidden, setIsNoAuthHidden] = useState(true); const [isNoAuthHidden, setIsNoAuthHidden] = useState(true);
const [authForm, setAuthForm] = useState({ const [authForm, setAuthForm] = useState({
username: "", username: '',
password: "", password: '',
confirmPassword: ''
}); });
const [noAuthenticationForm, setNoAuthenticationForm] = useState({
authMethod: 'Select Auth',
password: '',
sshKey: '',
keyType: '',
})
const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false); const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false);
const [splitTabIds, setSplitTabIds] = useState([]); const [splitTabIds, setSplitTabIds] = useState([]);
const [isEditHostHidden, setIsEditHostHidden] = useState(true); const [isEditHostHidden, setIsEditHostHidden] = useState(true);
@@ -212,12 +222,13 @@ function App() {
}, []); }, []);
const handleAddHost = () => { const handleAddHost = () => {
if (!addHostForm.ip?.trim() || !addHostForm.user?.trim() || !addHostForm.port) { if (addHostForm.ip && addHostForm.user && addHostForm.port) {
alert("Please fill out all required fields (IP, User, Port)."); if (!addHostForm.rememberHost) {
connectToHost();
setIsAddHostHidden(true);
return; return;
} }
if (addHostForm.rememberHost) {
if (addHostForm.authMethod === 'Select Auth') { if (addHostForm.authMethod === 'Select Auth') {
alert("Please select an authentication method."); alert("Please select an authentication method.");
return; return;
@@ -226,42 +237,33 @@ function App() {
setIsNoAuthHidden(false); setIsNoAuthHidden(false);
return; return;
} }
if (addHostForm.authMethod === 'key' && !addHostForm.privateKey) { if (addHostForm.authMethod === 'sshKey' && !addHostForm.sshKey) {
setIsNoAuthHidden(false); setIsNoAuthHidden(false);
return; return;
} }
}
connectToHost(); connectToHost();
if (addHostForm.rememberHost) {
if (!addHostForm.storePassword) { if (!addHostForm.storePassword) {
addHostForm.password = ''; addHostForm.password = '';
addHostForm.privateKey = '';
} }
handleSaveHost(); handleSaveHost();
}
setIsAddHostHidden(true); setIsAddHostHidden(true);
} else {
alert("Please fill out all required fields (IP, User, Port).");
}
}; };
const connectToHost = () => { const connectToHost = () => {
const hostConfig = { const hostConfig = {
name: addHostForm.name || '', name: addHostForm.name || '',
folder: addHostForm.folder || '', folder: addHostForm.folder || '',
ip: addHostForm.ip.trim(), ip: addHostForm.ip,
user: addHostForm.user.trim(), user: addHostForm.user,
port: String(addHostForm.port), port: String(addHostForm.port),
password: addHostForm.rememberHost && addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
sshKey: addHostForm.rememberHost && addHostForm.authMethod === 'sshKey' ? addHostForm.sshKey : 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 = { const newTerminal = {
id: nextId, id: nextId,
title: hostConfig.name || hostConfig.ip, title: hostConfig.name || hostConfig.ip,
@@ -271,21 +273,9 @@ function App() {
setTerminals([...terminals, newTerminal]); setTerminals([...terminals, newTerminal]);
setActiveTab(nextId); setActiveTab(nextId);
setNextId(nextId + 1); setNextId(nextId + 1);
setAddHostForm({ setIsAddHostHidden(true);
name: "", setAddHostForm({ name: "", folder: "", ip: "", user: "", password: "", sshKey: "", port: 22, authMethod: "Select Auth", rememberHost: true, storePassword: true });
folder: "", }
ip: "",
user: "",
password: "",
privateKey: "",
keyType: "",
passphrase: "",
port: 22,
authMethod: "Select Auth",
rememberHost: true,
storePassword: true
});
};
const handleAuthSubmit = (form) => { const handleAuthSubmit = (form) => {
const updatedTerminals = terminals.map((terminal) => { const updatedTerminals = terminals.map((terminal) => {
@@ -295,7 +285,7 @@ function App() {
hostConfig: { hostConfig: {
...terminal.hostConfig, ...terminal.hostConfig,
password: form.password, password: form.password,
rsaKey: form.rsaKey sshKey: form.sshKey
} }
}; };
} }
@@ -321,7 +311,7 @@ function App() {
user: hostConfig.user.trim(), user: hostConfig.user.trim(),
port: hostConfig.port || '22', port: hostConfig.port || '22',
password: hostConfig.password?.trim(), password: hostConfig.password?.trim(),
rsaKey: hostConfig.rsaKey?.trim(), sshKey: hostConfig.sshKey?.trim(),
}; };
const newTerminal = { const newTerminal = {
@@ -337,74 +327,71 @@ function App() {
} }
const handleSaveHost = () => { const handleSaveHost = () => {
const hostConfig = { let hostConfig = {
name: addHostForm.name || addHostForm.ip, name: addHostForm.name || addHostForm.ip,
folder: addHostForm.folder, folder: addHostForm.folder,
ip: addHostForm.ip.trim(), ip: addHostForm.ip,
user: addHostForm.user.trim(), user: addHostForm.user,
password: addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
sshKey: addHostForm.authMethod === 'sshKey' ? addHostForm.sshKey : undefined,
port: String(addHostForm.port), 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) { if (userRef.current) {
userRef.current.saveHost({ userRef.current.saveHost({
hostConfig, hostConfig,
}); });
} }
}; }
const handleLoginUser = ({ username, password, onSuccess, onFailure }) => { const handleLoginUser = ({ username, password, sessionToken, onSuccess, onFailure }) => {
if (userRef.current) { if (userRef.current) {
if (sessionToken) {
userRef.current.loginUser({
sessionToken,
onSuccess: () => {
setIsAuthModalHidden(true);
setIsLoggingIn(false);
if (onSuccess) onSuccess();
},
onFailure: (error) => {
localStorage.removeItem('sessionToken');
setIsAuthModalHidden(false);
setIsLoggingIn(false);
if (onFailure) onFailure(error);
},
});
} else {
userRef.current.loginUser({ userRef.current.loginUser({
username, username,
password, password,
onSuccess: () => { onSuccess: () => {
setIsAuthModalHidden(true); setIsAuthModalHidden(true);
setIsLoggingIn(false);
if (onSuccess) onSuccess(); if (onSuccess) onSuccess();
}, },
onFailure: (error) => { onFailure: (error) => {
setIsAuthModalHidden(false); setIsAuthModalHidden(false);
setIsLoggingIn(false);
if (onFailure) onFailure(error); if (onFailure) onFailure(error);
}, },
}); });
} }
}
}; };
const handleGuestLogin = async ({ onSuccess, onFailure }) => { const handleGuestLogin = () => {
if (userRef.current) { if (userRef.current) {
try { userRef.current.loginAsGuest();
await userRef.current.loginAsGuest();
setIsAuthModalHidden(true);
if (onSuccess) onSuccess();
} catch (error) {
setIsAuthModalHidden(false);
if (onFailure) onFailure(error);
} }
} }
};
const handleCreateUser = ({ username, password, onSuccess, onFailure }) => { const handleCreateUser = ({ username, password, onSuccess, onFailure }) => {
if (userRef.current) { if (userRef.current) {
userRef.current.createUser({ userRef.current.createUser({
username, username,
password, password,
onSuccess: () => { onSuccess,
setIsAuthModalHidden(true); onFailure,
if (onSuccess) onSuccess();
},
onFailure: (error) => {
setIsAuthModalHidden(false);
if (onFailure) onFailure(error);
},
}); });
} }
}; };
@@ -476,6 +463,7 @@ function App() {
updateEditHostForm(oldConfig); updateEditHostForm(oldConfig);
} catch (error) { } catch (error) {
console.error('Edit failed:', error);
setErrorMessage(`Edit failed: ${error}`); setErrorMessage(`Edit failed: ${error}`);
setIsErrorHidden(false); setIsErrorHidden(false);
setIsEditing(false); setIsEditing(false);
@@ -661,10 +649,10 @@ function App() {
)} )}
<NoAuthenticationModal <NoAuthenticationModal
isHidden={isNoAuthHidden} isHidden={isNoAuthHidden}
setIsHidden={setIsNoAuthHidden} form={noAuthenticationForm}
form={authForm} setForm={setNoAuthenticationForm}
setForm={setAuthForm} setIsNoAuthHidden={setIsNoAuthHidden}
onAuthenticate={handleAuthSubmit} handleAuthSubmit={handleAuthSubmit}
/> />
</div> </div>
@@ -684,7 +672,7 @@ function App() {
setForm={setEditHostForm} setForm={setEditHostForm}
handleEditHost={handleEditHost} handleEditHost={handleEditHost}
setIsEditHostHidden={setIsEditHostHidden} setIsEditHostHidden={setIsEditHostHidden}
hostConfig={currentHostConfig || {}} hostConfig={currentHostConfig}
/> />
<ProfileModal <ProfileModal
isHidden={isProfileHidden} isHidden={isProfileHidden}
@@ -722,9 +710,9 @@ function App() {
form={authForm} form={authForm}
setForm={setAuthForm} setForm={setAuthForm}
handleLoginUser={handleLoginUser} handleLoginUser={handleLoginUser}
handleGuestLogin={handleGuestLogin}
handleCreateUser={handleCreateUser} handleCreateUser={handleCreateUser}
setIsLoginUserHidden={setIsAuthModalHidden} handleGuestLogin={handleGuestLogin}
setIsAuthModalHidden={setIsAuthModalHidden}
/> />
{/* User component */} {/* User component */}
@@ -759,6 +747,7 @@ function App() {
setErrorMessage(`Action failed: ${error}`); setErrorMessage(`Action failed: ${error}`);
setIsErrorHidden(false); setIsErrorHidden(false);
setIsLoggingIn(false); setIsLoggingIn(false);
eventBus.emit('failedLoginUser');
}} }}
/> />
</div> </div>
+5 -5
View File
@@ -76,7 +76,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
socket.on("error", (err) => { socket.on("error", (err) => {
const isAuthError = err.toLowerCase().includes("authentication") || err.toLowerCase().includes("auth"); const isAuthError = err.toLowerCase().includes("authentication") || err.toLowerCase().includes("auth");
if (isAuthError && !hostConfig.password?.trim() && !hostConfig.rsaKey?.trim() && !authModalShown) { if (isAuthError && !hostConfig.password?.trim() && !hostConfig.sshKey?.trim() && !authModalShown) {
authModalShown = true; authModalShown = true;
setIsNoAuthHidden(false); setIsNoAuthHidden(false);
} }
@@ -88,7 +88,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
resizeTerminal(); resizeTerminal();
const { cols, rows } = terminalInstance.current; const { cols, rows } = terminalInstance.current;
if (!hostConfig.password?.trim() && !hostConfig.rsaKey?.trim()) { if (!hostConfig.password?.trim() && !hostConfig.sshKey?.trim()) {
setIsNoAuthHidden(false); setIsNoAuthHidden(false);
return; return;
} }
@@ -98,7 +98,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
user: hostConfig.user, user: hostConfig.user,
port: Number(hostConfig.port) || 22, port: Number(hostConfig.port) || 22,
password: hostConfig.password?.trim(), password: hostConfig.password?.trim(),
rsaKey: hostConfig.rsaKey?.trim() sshKey: hostConfig.sshKey?.trim()
}; };
socket.emit("connectToHost", cols, rows, sshConfig); socket.emit("connectToHost", cols, rows, sshConfig);
@@ -172,7 +172,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
let authModalShown = false; let authModalShown = false;
socket.on("noAuthRequired", () => { socket.on("noAuthRequired", () => {
if (!hostConfig.password?.trim() && !hostConfig.rsaKey?.trim() && !authModalShown) { if (!hostConfig.password?.trim() && !hostConfig.sshKey?.trim() && !authModalShown) {
authModalShown = true; authModalShown = true;
setIsNoAuthHidden(false); setIsNoAuthHidden(false);
} }
@@ -235,7 +235,7 @@ NewTerminal.propTypes = {
ip: PropTypes.string.isRequired, ip: PropTypes.string.isRequired,
user: PropTypes.string.isRequired, user: PropTypes.string.isRequired,
password: PropTypes.string, password: PropTypes.string,
rsaKey: PropTypes.string, sshKey: PropTypes.string,
port: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, port: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
}).isRequired, }).isRequired,
isVisible: PropTypes.bool.isRequired, isVisible: PropTypes.bool.isRequired,
+1 -1
View File
@@ -186,7 +186,7 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
user: host.config.user || '', user: host.config.user || '',
port: host.config.port || '22', port: host.config.port || '22',
password: host.config.password || '', password: host.config.password || '',
rsaKey: host.config.rsaKey || '', sshKey: host.config.sshKey || '',
} : {} } : {}
})).filter(host => host.config && host.config.ip && host.config.user); })).filter(host => host.config && host.config.ip && host.config.user);
} else { } else {
+2 -2
View File
@@ -185,7 +185,7 @@ io.of('/database.io').on('connection', (socket) => {
user: hostConfig.user.trim(), user: hostConfig.user.trim(),
port: hostConfig.port || 22, port: hostConfig.port || 22,
password: hostConfig.password?.trim() || undefined, password: hostConfig.password?.trim() || undefined,
rsaKey: hostConfig.rsaKey?.trim() || undefined sshKey: hostConfig.sshKey?.trim() || undefined,
}; };
const finalName = cleanConfig.name || cleanConfig.ip; const finalName = cleanConfig.name || cleanConfig.ip;
@@ -415,7 +415,7 @@ io.of('/database.io').on('connection', (socket) => {
user: newHostConfig.user.trim(), user: newHostConfig.user.trim(),
port: newHostConfig.port || 22, port: newHostConfig.port || 22,
password: newHostConfig.password?.trim() || undefined, password: newHostConfig.password?.trim() || undefined,
rsaKey: newHostConfig.rsaKey?.trim() || undefined sshKey: newHostConfig.sshKey?.trim() || undefined,
}; };
const encryptedConfig = encryptData(cleanConfig, userId, sessionToken); const encryptedConfig = encryptData(cleanConfig, userId, sessionToken);
+2 -3
View File
@@ -47,7 +47,7 @@ io.on("connection", (socket) => {
}; };
logger.info("Connecting with config:", safeHostConfig); logger.info("Connecting with config:", safeHostConfig);
const { ip, port, user, password, privateKey, passphrase } = hostConfig; const { ip, port, user, password, sshKey, } = hostConfig;
const conn = new SSHClient(); const conn = new SSHClient();
conn conn
@@ -99,8 +99,7 @@ io.on("connection", (socket) => {
port: port, port: port,
username: user, username: user,
password: password, password: password,
privateKey: privateKey ? Buffer.from(privateKey) : undefined, sshKey: sshKey ? Buffer.from(sshKey) : undefined,
passphrase: passphrase,
tryKeyboard: true, tryKeyboard: true,
algorithms: { algorithms: {
kex: [ kex: [
+10 -42
View File
@@ -24,7 +24,6 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff';
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => { const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showPassphrase, setShowPassphrase] = useState(false);
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const handleFileChange = (e) => { const handleFileChange = (e) => {
@@ -49,7 +48,6 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
const keyContent = event.target.result; const keyContent = event.target.result;
let keyType = 'UNKNOWN'; let keyType = 'UNKNOWN';
// Detect key type from content
if (keyContent.includes('BEGIN RSA PRIVATE KEY') || keyContent.includes('BEGIN RSA PUBLIC KEY')) { if (keyContent.includes('BEGIN RSA PRIVATE KEY') || keyContent.includes('BEGIN RSA PUBLIC KEY')) {
keyType = 'RSA'; keyType = 'RSA';
} else if (keyContent.includes('BEGIN OPENSSH PRIVATE KEY') && keyContent.includes('ssh-ed25519')) { } else if (keyContent.includes('BEGIN OPENSSH PRIVATE KEY') && keyContent.includes('ssh-ed25519')) {
@@ -62,9 +60,9 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
setForm(prev => ({ setForm(prev => ({
...prev, ...prev,
privateKey: keyContent, sshKey: keyContent,
keyType: keyType, keyType: keyType,
authMethod: 'key' authMethod: 'sshKey'
})); }));
}; };
reader.readAsText(file); reader.readAsText(file);
@@ -78,30 +76,25 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
...prev, ...prev,
authMethod: newMethod, authMethod: newMethod,
password: "", password: "",
privateKey: "", sshKey: "",
keyType: "", keyType: "",
passphrase: ""
})); }));
}; };
const isFormValid = () => { const isFormValid = () => {
const { ip, user, port, authMethod, password, privateKey } = form; const { ip, user, port, authMethod, password, sshKey } = form;
// Basic validation for required fields
if (!ip?.trim() || !user?.trim() || !port) return false; if (!ip?.trim() || !user?.trim() || !port) return false;
// Port validation
const portNum = Number(port); const portNum = Number(port);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false; if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false;
// If not remembering host, only basic fields are required
if (!form.rememberHost) return true; if (!form.rememberHost) return true;
// Auth method validation only if remembering host
if (form.rememberHost) { if (form.rememberHost) {
if (authMethod === 'Select Auth') return false; if (authMethod === 'Select Auth') return false;
if (authMethod === 'password' && !password?.trim()) return false; if (authMethod === 'password' && !password?.trim()) return false;
if (authMethod === 'key' && !privateKey?.trim()) return false; if (authMethod === 'sshKey' && !sshKey?.trim()) return false;
} }
return true; return true;
@@ -114,6 +107,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
return; return;
} }
handleAddHost(); handleAddHost();
setActiveTab(0);
}; };
return ( return (
@@ -284,7 +278,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
> >
<Option value="Select Auth" disabled>Select Auth</Option> <Option value="Select Auth" disabled>Select Auth</Option>
<Option value="password">Password</Option> <Option value="password">Password</Option>
<Option value="key">SSH Key</Option> <Option value="sshKey">SSH Key</Option>
</Select> </Select>
</FormControl> </FormControl>
@@ -315,9 +309,9 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
</FormControl> </FormControl>
)} )}
{form.authMethod === 'key' && ( {form.authMethod === 'sshKey' && (
<Stack spacing={2}> <Stack spacing={2}>
<FormControl error={!form.privateKey}> <FormControl error={!form.sshKey}>
<FormLabel>SSH Key</FormLabel> <FormLabel>SSH Key</FormLabel>
<Button <Button
component="label" component="label"
@@ -334,7 +328,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
}, },
}} }}
> >
{form.privateKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'} {form.sshKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'}
<Input <Input
type="file" type="file"
onChange={handleFileChange} onChange={handleFileChange}
@@ -342,32 +336,6 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
/> />
</Button> </Button>
</FormControl> </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>
)} )}
+208 -76
View File
@@ -1,159 +1,267 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles'; import { CssVarsProvider } from '@mui/joy/styles';
import { Modal, Button, FormControl, FormLabel, Input, Stack, ModalDialog, IconButton, Tabs, TabList, Tab, TabPanel } from '@mui/joy'; import {
Modal,
Button,
FormControl,
FormLabel,
Input,
Stack,
DialogContent,
ModalDialog,
IconButton,
Tabs,
TabList,
Tab,
TabPanel
} from '@mui/joy';
import theme from '/src/theme'; import theme from '/src/theme';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Visibility from '@mui/icons-material/Visibility'; import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff'; import VisibilityOff from '@mui/icons-material/VisibilityOff';
import eventBus from '/src/other/eventBus';
const AuthModal = ({ isHidden, form, setForm, handleLoginUser, handleGuestLogin, handleCreateUser, setIsLoginUserHidden }) => { const AuthModal = ({
const [showPassword, setShowPassword] = useState(false); isHidden,
const [confirmPassword, setConfirmPassword] = useState(''); form,
setForm,
handleLoginUser,
handleCreateUser,
handleGuestLogin,
setIsAuthModalHidden
}) => {
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const isLoginFormValid = () => { useEffect(() => {
return form.username?.trim() && form.password?.trim(); const loginErrorHandler = () => setIsLoading(false);
eventBus.on('failedLoginUser', loginErrorHandler);
return () => eventBus.off('failedLoginUser', loginErrorHandler);
}, []);
const resetForm = () => {
setForm({ username: '', password: '' });
setShowPassword(false);
setShowConfirmPassword(false);
setIsLoading(false);
}; };
const isCreateFormValid = () => { const handleLogin = async () => {
return form.username?.trim() && form.password?.trim() && confirmPassword?.trim() && form.password === confirmPassword;
};
const handleLogin = () => {
if (!isLoginFormValid()) {
alert("Please fill out all fields");
return;
}
setIsLoading(true); setIsLoading(true);
handleLoginUser({ try {
username: form.username.trim(), await handleLoginUser({
password: form.password.trim(), ...form,
onSuccess: () => { onSuccess: () => {
setIsLoading(false); setIsLoading(false);
setIsLoginUserHidden(true); setIsAuthModalHidden(true);
}, },
onFailure: (error) => { onFailure: () => setIsLoading(false),
setIsLoading(false);
alert(error);
}
}); });
} catch (error) {
setIsLoading(false);
}
}; };
const handleCreate = () => { const handleCreate = async () => {
if (!isCreateFormValid()) {
alert("Please fill out all fields and ensure passwords match");
return;
}
setIsLoading(true); setIsLoading(true);
handleCreateUser({ try {
username: form.username.trim(), await handleCreateUser({
password: form.password.trim(), ...form,
onSuccess: () => { onSuccess: () => {
setIsLoading(false); setIsLoading(false);
setIsLoginUserHidden(true); setActiveTab(0);
setIsAuthModalHidden(true);
}, },
onFailure: (error) => { onFailure: () => setIsLoading(false),
setIsLoading(false);
alert(error);
}
}); });
} catch (error) {
setIsLoading(false);
}
};
const handleGuest = async () => {
setIsLoading(true);
try {
await handleGuestLogin({
onSuccess: () => {
setIsLoading(false);
setIsAuthModalHidden(true);
},
onFailure: () => setIsLoading(false)
});
} catch (error) {
setIsLoading(false);
}
}; };
useEffect(() => { useEffect(() => {
if (isHidden) { if (isHidden) resetForm();
setForm({ username: '', password: '' });
setConfirmPassword('');
setIsLoading(false);
setActiveTab(0);
}
}, [isHidden]); }, [isHidden]);
const isLoginValid = !!form.username && !!form.password;
const isCreateValid = isLoginValid && form.password === form.confirmPassword;
return ( return (
<CssVarsProvider theme={theme}> <CssVarsProvider theme={theme}>
<Modal open={!isHidden} onClose={() => setIsLoginUserHidden(true)}> <Modal open={!isHidden} onClose={() => setIsAuthModalHidden(true)}>
<ModalDialog layout="center" sx={{ backgroundColor: theme.palette.general.tertiary }}> <ModalDialog
<Tabs value={activeTab} onChange={(e, val) => setActiveTab(val)}> layout="center"
<TabList> variant="outlined"
<Tab>Login</Tab> sx={{
<Tab>Create Account</Tab> backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary,
padding: 0,
borderRadius: 10,
maxWidth: '400px',
width: '100%',
overflow: 'hidden',
}}
>
<Tabs
value={activeTab}
onChange={(e, val) => setActiveTab(val)}
sx={{
width: '100%',
backgroundColor: theme.palette.general.tertiary,
}}
>
<TabList
sx={{
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 sx={{ flex: 1 }}>Login</Tab>
<Tab sx={{ flex: 1 }}>Create</Tab>
</TabList> </TabList>
<div style={{ padding: '24px', backgroundColor: theme.palette.general.tertiary }}> <DialogContent sx={{ padding: 3, backgroundColor: theme.palette.general.tertiary }}>
<TabPanel value={0}> <TabPanel value={0} sx={{ p: 0 }}>
<Stack spacing={2}> <Stack spacing={2} component="form" onSubmit={(e) => { e.preventDefault(); handleLogin(); }}>
<FormControl> <FormControl>
<FormLabel>Username</FormLabel> <FormLabel>Username</FormLabel>
<Input <Input
value={form.username}
onChange={(event) => setForm({ ...form, username: event.target.value })}
disabled={isLoading} disabled={isLoading}
value={form.username}
onChange={(e) => setForm({ ...form, username: e.target.value })}
sx={inputStyle}
/> />
</FormControl> </FormControl>
<FormControl> <FormControl>
<FormLabel>Password</FormLabel> <FormLabel>Password</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
<Input <Input
disabled={isLoading}
type={showPassword ? 'text' : 'password'} type={showPassword ? 'text' : 'password'}
value={form.password} value={form.password}
onChange={(event) => setForm({ ...form, password: event.target.value })} onChange={(e) => setForm({ ...form, password: e.target.value })}
disabled={isLoading} sx={{ ...inputStyle, flex: 1 }}
/> />
<IconButton onClick={() => setShowPassword(!showPassword)}> <IconButton
disabled={isLoading}
onClick={() => setShowPassword(!showPassword)}
sx={iconButtonStyle}
>
{showPassword ? <VisibilityOff /> : <Visibility />} {showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton> </IconButton>
</div> </div>
</FormControl> </FormControl>
<Button onClick={handleLogin} disabled={!isLoginFormValid() || isLoading}> <Button
type="submit"
disabled={!isLoginValid || isLoading}
sx={buttonStyle}
>
{isLoading ? "Logging in..." : "Login"} {isLoading ? "Logging in..." : "Login"}
</Button> </Button>
<Button onClick={handleGuestLogin} disabled={isLoading}> <Button
{isLoading ? "Logging in as guest..." : "Login as Guest"} disabled={isLoading}
onClick={handleGuest}
sx={buttonStyle}
>
{isLoading ? "Logging in..." : "Continue as Guest"}
</Button> </Button>
</Stack> </Stack>
</TabPanel> </TabPanel>
<TabPanel value={1}> <TabPanel value={1} sx={{ p: 0 }}>
<Stack spacing={2}> <Stack spacing={2} component="form" onSubmit={(e) => { e.preventDefault(); handleCreate(); }}>
<FormControl> <FormControl>
<FormLabel>Username</FormLabel> <FormLabel>Username</FormLabel>
<Input <Input
value={form.username}
onChange={(event) => setForm({ ...form, username: event.target.value })}
disabled={isLoading} disabled={isLoading}
value={form.username}
onChange={(e) => setForm({ ...form, username: e.target.value })}
sx={inputStyle}
/> />
</FormControl> </FormControl>
<FormControl> <FormControl>
<FormLabel>Password</FormLabel> <FormLabel>Password</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
<Input <Input
disabled={isLoading}
type={showPassword ? 'text' : 'password'} type={showPassword ? 'text' : 'password'}
value={form.password} value={form.password}
onChange={(event) => setForm({ ...form, password: event.target.value })} onChange={(e) => setForm({ ...form, password: e.target.value })}
disabled={isLoading} sx={{ ...inputStyle, flex: 1 }}
/> />
<IconButton onClick={() => setShowPassword(!showPassword)}> <IconButton
disabled={isLoading}
onClick={() => setShowPassword(!showPassword)}
sx={iconButtonStyle}
>
{showPassword ? <VisibilityOff /> : <Visibility />} {showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton> </IconButton>
</div> </div>
</FormControl> </FormControl>
<FormControl> <FormControl>
<FormLabel>Confirm Password</FormLabel> <FormLabel>Confirm Password</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input <Input
type={showPassword ? 'text' : 'password'}
value={confirmPassword}
onChange={(event) => setConfirmPassword(event.target.value)}
disabled={isLoading} disabled={isLoading}
type={showConfirmPassword ? 'text' : 'password'}
value={form.confirmPassword || ''}
onChange={(e) => setForm({ ...form, confirmPassword: e.target.value })}
sx={{ ...inputStyle, flex: 1 }}
/> />
<IconButton
disabled={isLoading}
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
sx={iconButtonStyle}
>
{showConfirmPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl> </FormControl>
<Button onClick={handleCreate} disabled={!isCreateFormValid() || isLoading}> <Button
{isLoading ? "Creating account..." : "Create Account"} type="submit"
disabled={!isCreateValid || isLoading}
sx={buttonStyle}
>
{isLoading ? "Creating..." : "Create Account"}
</Button> </Button>
</Stack> </Stack>
</TabPanel> </TabPanel>
</div> </DialogContent>
</Tabs> </Tabs>
</ModalDialog> </ModalDialog>
</Modal> </Modal>
@@ -161,14 +269,38 @@ const AuthModal = ({ isHidden, form, setForm, handleLoginUser, handleGuestLogin,
); );
}; };
const inputStyle = {
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
};
const iconButtonStyle = {
color: theme.palette.text.primary,
marginLeft: 1,
'&:disabled': { opacity: 0.5 },
};
const buttonStyle = {
backgroundColor: theme.palette.general.primary,
'&:hover': { backgroundColor: theme.palette.general.disabled },
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
};
AuthModal.propTypes = { AuthModal.propTypes = {
isHidden: PropTypes.bool.isRequired, isHidden: PropTypes.bool.isRequired,
form: PropTypes.object.isRequired, form: PropTypes.object.isRequired,
setForm: PropTypes.func.isRequired, setForm: PropTypes.func.isRequired,
handleLoginUser: PropTypes.func.isRequired, handleLoginUser: PropTypes.func.isRequired,
handleGuestLogin: PropTypes.func.isRequired,
handleCreateUser: PropTypes.func.isRequired, handleCreateUser: PropTypes.func.isRequired,
setIsLoginUserHidden: PropTypes.func.isRequired, handleGuestLogin: PropTypes.func.isRequired,
setIsAuthModalHidden: PropTypes.func.isRequired,
}; };
export default AuthModal; export default AuthModal;
-207
View File
@@ -1,207 +0,0 @@
import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles';
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog, IconButton } from '@mui/joy';
import theme from '/src/theme';
import { useEffect, useState } from 'react';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
const CreateUserModal = ({ isHidden, form, setForm, handleCreateUser, setIsCreateUserHidden, setIsLoginUserHidden }) => {
const [confirmPassword, setConfirmPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const isFormValid = () => {
if (!form.username || !form.password || form.password !== confirmPassword) return false;
return true;
};
const handleCreate = async () => {
setIsLoading(true);
try {
await handleCreateUser({
...form,
onSuccess: () => setIsLoading(false),
onFailure: () => setIsLoading(false)
});
} catch (error) {
setIsLoading(false);
}
};
useEffect(() => {
if (isHidden) {
setForm({ username: '', password: '' });
setConfirmPassword('');
setIsLoading(false);
}
}, [isHidden]);
return (
<CssVarsProvider theme={theme}>
<Modal open={!isHidden} onClose={() => {}}>
<ModalDialog
layout="center"
sx={{
backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
width: "auto",
maxWidth: "90vw",
minWidth: "fit-content",
overflow: "hidden",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<DialogTitle>Create</DialogTitle>
<DialogContent>
<form
onSubmit={(event) => {
event.preventDefault();
if (isFormValid() && !isLoading) handleCreate();
}}
>
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
<FormControl>
<FormLabel>Username</FormLabel>
<Input
disabled={isLoading}
value={form.username}
onChange={(event) => setForm({ ...form, username: event.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
}}
/>
</FormControl>
<FormControl>
<FormLabel>Password</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
disabled={isLoading}
type={showPassword ? 'text' : 'password'}
value={form.password}
onChange={(event) => setForm({ ...form, password: event.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
flex: 1,
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
}}
/>
<IconButton
disabled={isLoading}
onClick={() => setShowPassword(!showPassword)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1,
'&:disabled': {
opacity: 0.5,
},
}}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl>
<FormControl>
<FormLabel>Confirm Password</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
disabled={isLoading}
type={showConfirmPassword ? 'text' : 'password'}
value={confirmPassword}
onChange={(event) => setConfirmPassword(event.target.value)}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
flex: 1,
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
}}
/>
<IconButton
disabled={isLoading}
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1,
'&:disabled': {
opacity: 0.5,
},
}}
>
{showConfirmPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl>
<Button
type="submit"
disabled={!isFormValid() || isLoading}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
}}
>
{isLoading ? "Creating user..." : "Create"}
</Button>
<Button
disabled={isLoading}
onClick={() => {
setForm({ username: '', password: '' });
setConfirmPassword('');
setIsCreateUserHidden(true);
setIsLoginUserHidden(false);
}}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
}}
>
Back
</Button>
</Stack>
</form>
</DialogContent>
</ModalDialog>
</Modal>
</CssVarsProvider>
);
};
CreateUserModal.propTypes = {
isHidden: PropTypes.bool.isRequired,
form: PropTypes.object.isRequired,
setForm: PropTypes.func.isRequired,
handleCreateUser: PropTypes.func.isRequired,
setIsCreateUserHidden: PropTypes.func.isRequired,
setIsLoginUserHidden: PropTypes.func.isRequired,
};
export default CreateUserModal;
+13 -50
View File
@@ -8,8 +8,6 @@ import {
FormLabel, FormLabel,
Input, Input,
Stack, Stack,
DialogTitle,
DialogContent,
ModalDialog, ModalDialog,
Select, Select,
Option, Option,
@@ -32,15 +30,13 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
user: hostConfig?.user || '', user: hostConfig?.user || '',
port: hostConfig?.port || '', port: hostConfig?.port || '',
password: '', password: '',
privateKey: hostConfig?.privateKey || '', sshKey: hostConfig?.sshKey || '',
keyType: hostConfig?.keyType || '', keyType: hostConfig?.keyType || '',
passphrase: '',
authMethod: hostConfig?.authMethod || 'Select Auth', authMethod: hostConfig?.authMethod || 'Select Auth',
storePassword: true, storePassword: true,
rememberHost: true rememberHost: true
}); });
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showPassphrase, setShowPassphrase] = useState(false);
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@@ -52,13 +48,12 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
ip: hostConfig.ip || '', ip: hostConfig.ip || '',
user: hostConfig.user || '', user: hostConfig.user || '',
password: hostConfig.password || '', password: hostConfig.password || '',
privateKey: hostConfig.privateKey || '', sshKey: hostConfig.sshKey || '',
keyType: hostConfig.keyType || '', keyType: hostConfig.keyType || '',
passphrase: hostConfig.passphrase || '',
port: hostConfig.port || 22, port: hostConfig.port || 22,
authMethod: hostConfig.password ? 'password' : hostConfig.privateKey ? 'key' : 'Select Auth', authMethod: hostConfig.password ? 'password' : hostConfig.sshKey ? 'key' : 'Select Auth',
rememberHost: true, rememberHost: true,
storePassword: !!(hostConfig.password || hostConfig.privateKey), storePassword: !!(hostConfig.password || hostConfig.sshKey),
}); });
} }
}, [isHidden, hostConfig]); }, [isHidden, hostConfig]);
@@ -85,7 +80,6 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
const keyContent = evt.target.result; const keyContent = evt.target.result;
let keyType = 'UNKNOWN'; let keyType = 'UNKNOWN';
// Detect key type from content
if (keyContent.includes('BEGIN RSA PRIVATE KEY') || keyContent.includes('BEGIN RSA PUBLIC KEY')) { if (keyContent.includes('BEGIN RSA PRIVATE KEY') || keyContent.includes('BEGIN RSA PUBLIC KEY')) {
keyType = 'RSA'; keyType = 'RSA';
} else if (keyContent.includes('BEGIN OPENSSH PRIVATE KEY') && keyContent.includes('ssh-ed25519')) { } else if (keyContent.includes('BEGIN OPENSSH PRIVATE KEY') && keyContent.includes('ssh-ed25519')) {
@@ -98,7 +92,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
setForm((prev) => ({ setForm((prev) => ({
...prev, ...prev,
privateKey: keyContent, sshKey: keyContent,
keyType: keyType, keyType: keyType,
authMethod: 'key' authMethod: 'key'
})); }));
@@ -121,27 +115,23 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
...prev, ...prev,
storePassword: Boolean(checked), storePassword: Boolean(checked),
password: checked ? prev.password : "", password: checked ? prev.password : "",
privateKey: checked ? prev.privateKey : "", sshKey: checked ? prev.sshKey : "",
passphrase: checked ? prev.passphrase : "",
authMethod: checked ? prev.authMethod : "Select Auth" authMethod: checked ? prev.authMethod : "Select Auth"
})); }));
}; };
const isFormValid = () => { const isFormValid = () => {
const { ip, user, port, authMethod, password, privateKey } = form; const { ip, user, port, authMethod, password, sshKey } = form;
// Basic validation for required fields
if (!ip?.trim() || !user?.trim() || !port) return false; if (!ip?.trim() || !user?.trim() || !port) return false;
// Port validation
const portNum = Number(port); const portNum = Number(port);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false; if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false;
// Auth method validation only if storing password
if (form.storePassword) { if (form.storePassword) {
if (authMethod === 'Select Auth') return false; if (authMethod === 'Select Auth') return false;
if (authMethod === 'password' && !password?.trim()) return false; if (authMethod === 'password' && !password?.trim()) return false;
if (authMethod === 'key' && !privateKey?.trim()) return false; if (authMethod === 'sshKey' && !sshKey?.trim()) return false;
} }
return true; return true;
@@ -165,9 +155,8 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
if (form.authMethod === 'password') { if (form.authMethod === 'password') {
newConfig.password = form.password; newConfig.password = form.password;
} else if (form.authMethod === 'key') { } else if (form.authMethod === 'key') {
newConfig.privateKey = form.privateKey; newConfig.sshKey = form.sshKey;
newConfig.keyType = form.keyType; newConfig.keyType = form.keyType;
newConfig.passphrase = form.passphrase;
} }
} }
@@ -378,7 +367,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
{form.authMethod === 'key' && ( {form.authMethod === 'key' && (
<Stack spacing={2}> <Stack spacing={2}>
<FormControl error={form.storePassword && !form.privateKey}> <FormControl error={form.storePassword && !form.sshKey}>
<FormLabel>SSH Key</FormLabel> <FormLabel>SSH Key</FormLabel>
<Button <Button
component="label" component="label"
@@ -395,14 +384,14 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
}, },
}} }}
> >
{form.privateKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'} {form.sshKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'}
<Input <Input
type="file" type="file"
onChange={handleFileChange} onChange={handleFileChange}
sx={{ display: 'none' }} sx={{ display: 'none' }}
/> />
</Button> </Button>
{hostConfig?.privateKey && !form.privateKey && ( {hostConfig?.sshKey && !form.sshKey && (
<FormLabel <FormLabel
sx={{ sx={{
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
@@ -416,32 +405,6 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
</FormLabel> </FormLabel>
)} )}
</FormControl> </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>
)} )}
</> </>
@@ -452,7 +415,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
<Button <Button
onClick={handleSubmit} onClick={handleSubmit}
disabled={isLoading} disabled={isLoading || !isFormValid()}
sx={{ sx={{
backgroundColor: theme.palette.general.primary, backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary, color: theme.palette.text.primary,
-199
View File
@@ -1,199 +0,0 @@
import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles';
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog, IconButton } from '@mui/joy';
import theme from '/src/theme';
import { useEffect, useState } from 'react';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
const LoginUserModal = ({ isHidden, form, setForm, handleLoginUser, handleGuestLogin, setIsLoginUserHidden, setIsCreateUserHidden }) => {
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const isFormValid = () => {
if (!form.username || !form.password) return false;
return true;
};
const handleLogin = async () => {
setIsLoading(true);
try {
await handleLoginUser({
...form,
onSuccess: () => setIsLoading(false),
onFailure: () => setIsLoading(false)
});
} catch (error) {
setIsLoading(false);
}
};
const handleGuest = async () => {
setIsLoading(true);
try {
await handleGuestLogin({
onSuccess: () => setIsLoading(false),
onFailure: () => setIsLoading(false)
});
} catch (error) {
setIsLoading(false);
}
};
useEffect(() => {
if (isHidden) {
setForm({ username: '', password: '' });
setIsLoading(false);
}
}, [isHidden]);
return (
<CssVarsProvider theme={theme}>
<Modal open={!isHidden} onClose={() => {}}>
<ModalDialog
layout="center"
sx={{
backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
width: "auto",
maxWidth: "90vw",
minWidth: "fit-content",
overflow: "hidden",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<DialogTitle>Login</DialogTitle>
<DialogContent>
<form
onSubmit={(event) => {
event.preventDefault();
if (isFormValid() && !isLoading) handleLogin();
}}
>
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
<FormControl>
<FormLabel>Username</FormLabel>
<Input
disabled={isLoading}
value={form.username}
onChange={(event) => setForm({ ...form, username: event.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
}}
/>
</FormControl>
<FormControl>
<FormLabel>Password</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
disabled={isLoading}
type={showPassword ? 'text' : 'password'}
value={form.password}
onChange={(event) => setForm({ ...form, password: event.target.value })}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
flex: 1,
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
}}
/>
<IconButton
disabled={isLoading}
onClick={() => setShowPassword(!showPassword)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1,
'&:disabled': {
opacity: 0.5,
},
}}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl>
<Button
type="submit"
disabled={!isFormValid() || isLoading}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
}}
>
{isLoading ? "Logging in..." : "Login"}
</Button>
<Button
disabled={isLoading}
onClick={() => {
setForm({ username: '', password: '' });
setIsCreateUserHidden(false);
setIsLoginUserHidden(true);
}}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
}}
>
Create User
</Button>
<Button
disabled={isLoading}
onClick={handleGuest}
sx={{
backgroundColor: theme.palette.general.primary,
'&:hover': {
backgroundColor: theme.palette.general.disabled,
},
'&:disabled': {
opacity: 0.5,
backgroundColor: theme.palette.general.primary,
},
}}
>
{isLoading ? "Logging in as guest..." : "Login as Guest"}
</Button>
</Stack>
</form>
</DialogContent>
</ModalDialog>
</Modal>
</CssVarsProvider>
);
};
LoginUserModal.propTypes = {
isHidden: PropTypes.bool.isRequired,
form: PropTypes.object.isRequired,
setForm: PropTypes.func.isRequired,
handleLoginUser: PropTypes.func.isRequired,
handleGuestLogin: PropTypes.func.isRequired,
setIsLoginUserHidden: PropTypes.func.isRequired,
setIsCreateUserHidden: PropTypes.func.isRequired,
};
export default LoginUserModal;
+85 -53
View File
@@ -15,31 +15,44 @@ import {
Option, Option,
} from '@mui/joy'; } from '@mui/joy';
import theme from '/src/theme'; import theme from '/src/theme';
import { useState, useEffect } from 'react'; import {useEffect, useState} from 'react';
import Visibility from '@mui/icons-material/Visibility'; import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff'; import VisibilityOff from '@mui/icons-material/VisibilityOff';
const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => { const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, handleAuthSubmit }) => {
const [form, setForm] = useState({ const [showPassword, setShowPassword] = useState(false);
useEffect(() => {
if (!form.authMethod) {
setForm(prev => ({
...prev,
authMethod: 'Select Auth', authMethod: 'Select Auth',
password: '', password: '',
privateKey: '', sshKey: '',
keyType: '', keyType: '',
passphrase: '' }));
}); }
const [showPassword, setShowPassword] = useState(false); }, []);
const [showPassphrase, setShowPassphrase] = useState(false);
const isFormValid = () => {
if (!form.authMethod || form.authMethod === 'Select Auth') return false;
if (form.authMethod === 'sshKey' && !form.sshKey) return false;
if (form.authMethod === 'password' && !form.password) return false;
return true;
};
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
onAuthenticate({ if(isFormValid()) {
authMethod: form.authMethod, handleAuthSubmit(form);
password: form.password, setForm (prev => ({
privateKey: form.privateKey, ...prev,
keyType: form.keyType, authMethod: 'Select Auth',
passphrase: form.passphrase password: '',
}); sshKey: '',
setIsHidden(true); keyType: '',
}))
}
}; };
const handleFileChange = (e) => { const handleFileChange = (e) => {
@@ -77,9 +90,9 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
setForm({ setForm({
...form, ...form,
privateKey: keyContent, sshKey: keyContent,
keyType: keyType, keyType: keyType,
authMethod: 'key' authMethod: 'sshKey'
}); });
}; };
reader.readAsText(file); reader.readAsText(file);
@@ -92,12 +105,31 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
<CssVarsProvider theme={theme}> <CssVarsProvider theme={theme}>
<Modal <Modal
open={!isHidden} open={!isHidden}
onClose={() => setIsHidden(true)} onClose={(e, reason) => {
if (reason !== 'backdropClick') {
setIsNoAuthHidden(true);
}
}}
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
> >
<ModalDialog <ModalDialog
layout="center"
sx={{ sx={{
backgroundColor: theme.palette.general.secondary, backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary, color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
maxWidth: '500px',
width: '100%',
maxHeight: '80vh',
overflow: 'auto',
boxSizing: 'border-box',
mx: 2,
}} }}
> >
<DialogTitle sx={{ mb: 2 }}>Authentication Required</DialogTitle> <DialogTitle sx={{ mb: 2 }}>Authentication Required</DialogTitle>
@@ -107,14 +139,13 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
<FormControl error={!form.authMethod || form.authMethod === 'Select Auth'}> <FormControl error={!form.authMethod || form.authMethod === 'Select Auth'}>
<FormLabel>Authentication Method</FormLabel> <FormLabel>Authentication Method</FormLabel>
<Select <Select
value={form.authMethod} value={form.authMethod || 'Select Auth'}
onChange={(e, val) => setForm(prev => ({ onChange={(e, val) => setForm(prev => ({
...prev, ...prev,
authMethod: val, authMethod: val,
password: '', password: '',
privateKey: '', sshKey: '',
keyType: '', keyType: '',
passphrase: ''
}))} }))}
sx={{ sx={{
backgroundColor: theme.palette.general.primary, backgroundColor: theme.palette.general.primary,
@@ -123,29 +154,43 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
> >
<Option value="Select Auth" disabled>Select Auth</Option> <Option value="Select Auth" disabled>Select Auth</Option>
<Option value="password">Password</Option> <Option value="password">Password</Option>
<Option value="key">SSH Key</Option> <Option value="sshKey">SSH Key</Option >
</Select> </Select>
</FormControl> </FormControl>
{form.authMethod === 'password' && ( {form.authMethod === 'password' && (
<FormControl error={!form.password}> <FormControl error={!form.password}>
<FormLabel>Password</FormLabel> <FormLabel>Password</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input <Input
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
value={form.password} value={form.password || ''}
onChange={(e) => setForm({ ...form, password: e.target.value })} onChange={(e) => setForm({...form, password: e.target.value})}
endDecorator={ sx={{
<IconButton onClick={() => setShowPassword(!showPassword)}> backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
flex: 1
}}
/>
<IconButton
onClick={() => setShowPassword(!showPassword)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1,
'&:disabled': {
opacity: 0.5,
},
}}
>
{showPassword ? <VisibilityOff /> : <Visibility />} {showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton> </IconButton>
} </div>
/>
</FormControl> </FormControl>
)} )}
{form.authMethod === 'key' && ( {form.authMethod === 'sshKey' && (
<Stack spacing={2}> <Stack spacing={2}>
<FormControl error={!form.privateKey}> <FormControl error={!form.sshKey}>
<FormLabel>SSH Key</FormLabel> <FormLabel>SSH Key</FormLabel>
<Button <Button
component="label" component="label"
@@ -162,7 +207,7 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
}, },
}} }}
> >
{form.privateKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'} {form.sshKey ? `Change ${form.keyType || 'SSH'} Key File` : 'Upload SSH Key File'}
<Input <Input
type="file" type="file"
onChange={handleFileChange} onChange={handleFileChange}
@@ -170,29 +215,12 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
/> />
</Button> </Button>
</FormControl> </FormControl>
{form.privateKey && (
<FormControl>
<FormLabel>Key Passphrase (optional)</FormLabel>
<Input
type={showPassphrase ? "text" : "password"}
value={form.passphrase || ''}
onChange={(e) => setForm(prev => ({ ...prev, passphrase: e.target.value }))}
endDecorator={
<IconButton onClick={() => setShowPassphrase(!showPassphrase)}>
{showPassphrase ? <VisibilityOff /> : <Visibility />}
</IconButton>
}
/>
</FormControl>
)}
</Stack> </Stack>
)} )}
<Button <Button
type="submit" type="submit"
disabled={!form.authMethod || form.authMethod === 'Select Auth' || disabled={!isFormValid()}
(form.authMethod === 'password' && !form.password) ||
(form.authMethod === 'key' && !form.privateKey)}
sx={{ sx={{
backgroundColor: theme.palette.general.primary, backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary, color: theme.palette.text.primary,
@@ -203,6 +231,8 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
backgroundColor: 'rgba(255, 255, 255, 0.1)', backgroundColor: 'rgba(255, 255, 255, 0.1)',
color: 'rgba(255, 255, 255, 0.3)', color: 'rgba(255, 255, 255, 0.3)',
}, },
marginTop: 2,
height: '40px',
}} }}
> >
Connect Connect
@@ -218,8 +248,10 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
NoAuthenticationModal.propTypes = { NoAuthenticationModal.propTypes = {
isHidden: PropTypes.bool.isRequired, isHidden: PropTypes.bool.isRequired,
setIsHidden: PropTypes.func.isRequired, form: PropTypes.object.isRequired,
onAuthenticate: PropTypes.func.isRequired, setForm: PropTypes.func.isRequired,
setIsNoAuthHidden: PropTypes.func.isRequired,
handleAuthSubmit: PropTypes.func.isRequired,
}; };
export default NoAuthenticationModal; export default NoAuthenticationModal;