Revamped login system, fixed no auth modal errors, changed many UI's.

This commit is contained in:
Karmaa
2025-03-20 20:41:45 -05:00
parent b5af67bdd6
commit 28081ad6b2
13 changed files with 458 additions and 773 deletions

7
package-lock.json generated
View File

@@ -29,6 +29,7 @@
"express": "^4.21.2",
"is-stream": "^4.0.1",
"make-dir": "^5.0.0",
"mitt": "^3.0.1",
"mongoose": "^8.12.1",
"node-ssh": "^13.2.0",
"prop-types": "^15.8.1",
@@ -6341,6 +6342,12 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"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": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",

View File

@@ -31,6 +31,7 @@
"express": "^4.21.2",
"is-stream": "^4.0.1",
"make-dir": "^5.0.0",
"mitt": "^3.0.1",
"mongoose": "^8.12.1",
"node-ssh": "^13.2.0",
"prop-types": "^15.8.1",

View File

@@ -16,6 +16,7 @@ import ProfileModal from "./modals/ProfileModal.jsx";
import ErrorModal from "./modals/ErrorModal.jsx";
import EditHostModal from "./modals/EditHostModal.jsx";
import NoAuthenticationModal from "./modals/NoAuthenticationModal.jsx";
import eventBus from "./other/eventBus.jsx";
function App() {
const [isAddHostHidden, setIsAddHostHidden] = useState(true);
@@ -33,6 +34,7 @@ function App() {
ip: "",
user: "",
password: "",
sshKey: "",
port: 22,
authMethod: "Select Auth",
rememberHost: true,
@@ -44,6 +46,7 @@ function App() {
ip: "",
user: "",
password: "",
sshKey: "",
port: 22,
authMethod: "Select Auth",
rememberHost: true,
@@ -51,9 +54,16 @@ function App() {
});
const [isNoAuthHidden, setIsNoAuthHidden] = useState(true);
const [authForm, setAuthForm] = useState({
username: "",
password: "",
username: '',
password: '',
confirmPassword: ''
});
const [noAuthenticationForm, setNoAuthenticationForm] = useState({
authMethod: 'Select Auth',
password: '',
sshKey: '',
keyType: '',
})
const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false);
const [splitTabIds, setSplitTabIds] = useState([]);
const [isEditHostHidden, setIsEditHostHidden] = useState(true);
@@ -137,7 +147,7 @@ function App() {
let loginAttempts = 0;
const maxAttempts = 50;
let attemptLoginInterval;
const loginTimeout = setTimeout(() => {
if (isComponentMounted) {
clearInterval(attemptLoginInterval);
@@ -153,11 +163,11 @@ function App() {
const attemptLogin = () => {
if (!isComponentMounted || isLoginInProgress) return;
if (loginAttempts >= maxAttempts || userRef.current?.getUser()) {
clearTimeout(loginTimeout);
clearInterval(attemptLoginInterval);
if (!userRef.current?.getUser()) {
localStorage.removeItem('sessionToken');
setIsAuthModalHidden(false);
@@ -212,12 +222,13 @@ function App() {
}, []);
const handleAddHost = () => {
if (!addHostForm.ip?.trim() || !addHostForm.user?.trim() || !addHostForm.port) {
alert("Please fill out all required fields (IP, User, Port).");
return;
}
if (addHostForm.ip && addHostForm.user && addHostForm.port) {
if (!addHostForm.rememberHost) {
connectToHost();
setIsAddHostHidden(true);
return;
}
if (addHostForm.rememberHost) {
if (addHostForm.authMethod === 'Select Auth') {
alert("Please select an authentication method.");
return;
@@ -226,42 +237,33 @@ function App() {
setIsNoAuthHidden(false);
return;
}
if (addHostForm.authMethod === 'key' && !addHostForm.privateKey) {
if (addHostForm.authMethod === 'sshKey' && !addHostForm.sshKey) {
setIsNoAuthHidden(false);
return;
}
}
connectToHost();
if (addHostForm.rememberHost) {
connectToHost();
if (!addHostForm.storePassword) {
addHostForm.password = '';
addHostForm.privateKey = '';
}
handleSaveHost();
setIsAddHostHidden(true);
} else {
alert("Please fill out all required fields (IP, User, Port).");
}
setIsAddHostHidden(true);
};
const connectToHost = () => {
const hostConfig = {
name: addHostForm.name || '',
folder: addHostForm.folder || '',
ip: addHostForm.ip.trim(),
user: addHostForm.user.trim(),
ip: addHostForm.ip,
user: addHostForm.user,
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 = {
id: nextId,
title: hostConfig.name || hostConfig.ip,
@@ -271,21 +273,9 @@ function App() {
setTerminals([...terminals, newTerminal]);
setActiveTab(nextId);
setNextId(nextId + 1);
setAddHostForm({
name: "",
folder: "",
ip: "",
user: "",
password: "",
privateKey: "",
keyType: "",
passphrase: "",
port: 22,
authMethod: "Select Auth",
rememberHost: true,
storePassword: true
});
};
setIsAddHostHidden(true);
setAddHostForm({ name: "", folder: "", ip: "", user: "", password: "", sshKey: "", port: 22, authMethod: "Select Auth", rememberHost: true, storePassword: true });
}
const handleAuthSubmit = (form) => {
const updatedTerminals = terminals.map((terminal) => {
@@ -295,7 +285,7 @@ function App() {
hostConfig: {
...terminal.hostConfig,
password: form.password,
rsaKey: form.rsaKey
sshKey: form.sshKey
}
};
}
@@ -321,7 +311,7 @@ function App() {
user: hostConfig.user.trim(),
port: hostConfig.port || '22',
password: hostConfig.password?.trim(),
rsaKey: hostConfig.rsaKey?.trim(),
sshKey: hostConfig.sshKey?.trim(),
};
const newTerminal = {
@@ -337,74 +327,71 @@ function App() {
}
const handleSaveHost = () => {
const hostConfig = {
let hostConfig = {
name: addHostForm.name || addHostForm.ip,
folder: addHostForm.folder,
ip: addHostForm.ip.trim(),
user: addHostForm.user.trim(),
ip: addHostForm.ip,
user: addHostForm.user,
password: addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
sshKey: addHostForm.authMethod === 'sshKey' ? addHostForm.sshKey : undefined,
port: String(addHostForm.port),
};
if (addHostForm.storePassword) {
if (addHostForm.authMethod === 'password') {
hostConfig.password = addHostForm.password;
} else if (addHostForm.authMethod === 'key') {
hostConfig.privateKey = addHostForm.privateKey;
hostConfig.keyType = addHostForm.keyType;
hostConfig.passphrase = addHostForm.passphrase;
}
}
if (userRef.current) {
userRef.current.saveHost({
hostConfig,
});
}
};
}
const handleLoginUser = ({ username, password, onSuccess, onFailure }) => {
const handleLoginUser = ({ username, password, sessionToken, onSuccess, onFailure }) => {
if (userRef.current) {
userRef.current.loginUser({
username,
password,
onSuccess: () => {
setIsAuthModalHidden(true);
if (onSuccess) onSuccess();
},
onFailure: (error) => {
setIsAuthModalHidden(false);
if (onFailure) onFailure(error);
},
});
}
};
const handleGuestLogin = async ({ onSuccess, onFailure }) => {
if (userRef.current) {
try {
await userRef.current.loginAsGuest();
setIsAuthModalHidden(true);
if (onSuccess) onSuccess();
} catch (error) {
setIsAuthModalHidden(false);
if (onFailure) onFailure(error);
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({
username,
password,
onSuccess: () => {
setIsAuthModalHidden(true);
setIsLoggingIn(false);
if (onSuccess) onSuccess();
},
onFailure: (error) => {
setIsAuthModalHidden(false);
setIsLoggingIn(false);
if (onFailure) onFailure(error);
},
});
}
}
};
const handleGuestLogin = () => {
if (userRef.current) {
userRef.current.loginAsGuest();
}
}
const handleCreateUser = ({ username, password, onSuccess, onFailure }) => {
if (userRef.current) {
userRef.current.createUser({
username,
password,
onSuccess: () => {
setIsAuthModalHidden(true);
if (onSuccess) onSuccess();
},
onFailure: (error) => {
setIsAuthModalHidden(false);
if (onFailure) onFailure(error);
},
onSuccess,
onFailure,
});
}
};
@@ -459,7 +446,7 @@ function App() {
if (newConfig) {
if (isEditing) return;
setIsEditing(true);
try {
await userRef.current.editHost({
oldHostConfig: oldConfig,
@@ -476,6 +463,7 @@ function App() {
updateEditHostForm(oldConfig);
} catch (error) {
console.error('Edit failed:', error);
setErrorMessage(`Edit failed: ${error}`);
setIsErrorHidden(false);
setIsEditing(false);
@@ -661,10 +649,10 @@ function App() {
)}
<NoAuthenticationModal
isHidden={isNoAuthHidden}
setIsHidden={setIsNoAuthHidden}
form={authForm}
setForm={setAuthForm}
onAuthenticate={handleAuthSubmit}
form={noAuthenticationForm}
setForm={setNoAuthenticationForm}
setIsNoAuthHidden={setIsNoAuthHidden}
handleAuthSubmit={handleAuthSubmit}
/>
</div>
@@ -684,7 +672,7 @@ function App() {
setForm={setEditHostForm}
handleEditHost={handleEditHost}
setIsEditHostHidden={setIsEditHostHidden}
hostConfig={currentHostConfig || {}}
hostConfig={currentHostConfig}
/>
<ProfileModal
isHidden={isProfileHidden}
@@ -722,9 +710,9 @@ function App() {
form={authForm}
setForm={setAuthForm}
handleLoginUser={handleLoginUser}
handleGuestLogin={handleGuestLogin}
handleCreateUser={handleCreateUser}
setIsLoginUserHidden={setIsAuthModalHidden}
handleGuestLogin={handleGuestLogin}
setIsAuthModalHidden={setIsAuthModalHidden}
/>
{/* User component */}
@@ -737,8 +725,8 @@ function App() {
}}
onCreateSuccess={() => {
setIsAuthModalHidden(true);
handleLoginUser({
username: authForm.username,
handleLoginUser({
username: authForm.username,
password: authForm.password,
onSuccess: () => {
setIsAuthModalHidden(true);
@@ -759,6 +747,7 @@ function App() {
setErrorMessage(`Action failed: ${error}`);
setIsErrorHidden(false);
setIsLoggingIn(false);
eventBus.emit('failedLoginUser');
}}
/>
</div>

View File

@@ -76,7 +76,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
socket.on("error", (err) => {
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;
setIsNoAuthHidden(false);
}
@@ -88,7 +88,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
resizeTerminal();
const { cols, rows } = terminalInstance.current;
if (!hostConfig.password?.trim() && !hostConfig.rsaKey?.trim()) {
if (!hostConfig.password?.trim() && !hostConfig.sshKey?.trim()) {
setIsNoAuthHidden(false);
return;
}
@@ -98,7 +98,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
user: hostConfig.user,
port: Number(hostConfig.port) || 22,
password: hostConfig.password?.trim(),
rsaKey: hostConfig.rsaKey?.trim()
sshKey: hostConfig.sshKey?.trim()
};
socket.emit("connectToHost", cols, rows, sshConfig);
@@ -172,7 +172,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
let authModalShown = false;
socket.on("noAuthRequired", () => {
if (!hostConfig.password?.trim() && !hostConfig.rsaKey?.trim() && !authModalShown) {
if (!hostConfig.password?.trim() && !hostConfig.sshKey?.trim() && !authModalShown) {
authModalShown = true;
setIsNoAuthHidden(false);
}
@@ -235,7 +235,7 @@ NewTerminal.propTypes = {
ip: PropTypes.string.isRequired,
user: PropTypes.string.isRequired,
password: PropTypes.string,
rsaKey: PropTypes.string,
sshKey: PropTypes.string,
port: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
}).isRequired,
isVisible: PropTypes.bool.isRequired,

View File

@@ -186,7 +186,7 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
user: host.config.user || '',
port: host.config.port || '22',
password: host.config.password || '',
rsaKey: host.config.rsaKey || '',
sshKey: host.config.sshKey || '',
} : {}
})).filter(host => host.config && host.config.ip && host.config.user);
} else {

View File

@@ -185,7 +185,7 @@ io.of('/database.io').on('connection', (socket) => {
user: hostConfig.user.trim(),
port: hostConfig.port || 22,
password: hostConfig.password?.trim() || undefined,
rsaKey: hostConfig.rsaKey?.trim() || undefined
sshKey: hostConfig.sshKey?.trim() || undefined,
};
const finalName = cleanConfig.name || cleanConfig.ip;
@@ -415,7 +415,7 @@ io.of('/database.io').on('connection', (socket) => {
user: newHostConfig.user.trim(),
port: newHostConfig.port || 22,
password: newHostConfig.password?.trim() || undefined,
rsaKey: newHostConfig.rsaKey?.trim() || undefined
sshKey: newHostConfig.sshKey?.trim() || undefined,
};
const encryptedConfig = encryptData(cleanConfig, userId, sessionToken);

View File

@@ -47,7 +47,7 @@ io.on("connection", (socket) => {
};
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();
conn
@@ -99,8 +99,7 @@ io.on("connection", (socket) => {
port: port,
username: user,
password: password,
privateKey: privateKey ? Buffer.from(privateKey) : undefined,
passphrase: passphrase,
sshKey: sshKey ? Buffer.from(sshKey) : undefined,
tryKeyboard: true,
algorithms: {
kex: [

View File

@@ -24,7 +24,6 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff';
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
const [showPassword, setShowPassword] = useState(false);
const [showPassphrase, setShowPassphrase] = useState(false);
const [activeTab, setActiveTab] = useState(0);
const handleFileChange = (e) => {
@@ -39,7 +38,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
'.ppk': 'PPK'
};
const isValidKeyFile = Object.keys(supportedKeyTypes).some(ext =>
const isValidKeyFile = Object.keys(supportedKeyTypes).some(ext =>
file.name.toLowerCase().includes(ext) || file.name.endsWith('.pub')
);
@@ -48,8 +47,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
reader.onload = (event) => {
const keyContent = event.target.result;
let keyType = 'UNKNOWN';
// Detect key type from content
if (keyContent.includes('BEGIN RSA PRIVATE KEY') || keyContent.includes('BEGIN RSA PUBLIC KEY')) {
keyType = 'RSA';
} else if (keyContent.includes('BEGIN OPENSSH PRIVATE KEY') && keyContent.includes('ssh-ed25519')) {
@@ -60,11 +58,11 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
keyType = 'DSA';
}
setForm(prev => ({
...prev,
privateKey: keyContent,
setForm(prev => ({
...prev,
sshKey: keyContent,
keyType: keyType,
authMethod: 'key'
authMethod: 'sshKey'
}));
};
reader.readAsText(file);
@@ -78,30 +76,25 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
...prev,
authMethod: newMethod,
password: "",
privateKey: "",
sshKey: "",
keyType: "",
passphrase: ""
}));
};
const isFormValid = () => {
const { ip, user, port, authMethod, password, privateKey } = form;
// Basic validation for required fields
const { ip, user, port, authMethod, password, sshKey } = form;
if (!ip?.trim() || !user?.trim() || !port) return false;
// Port validation
const portNum = Number(port);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false;
// If not remembering host, only basic fields are required
if (!form.rememberHost) return true;
// Auth method validation only if remembering host
if (form.rememberHost) {
if (authMethod === 'Select Auth') 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;
@@ -114,6 +107,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
return;
}
handleAddHost();
setActiveTab(0);
};
return (
@@ -144,10 +138,10 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
mx: 2,
}}
>
<Tabs
value={activeTab}
<Tabs
value={activeTab}
onChange={(e, val) => setActiveTab(val)}
sx={{
sx={{
width: '100%',
mb: 0,
backgroundColor: theme.palette.general.tertiary,
@@ -211,8 +205,8 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
<FormLabel>Remember Host</FormLabel>
<Checkbox
checked={Boolean(form.rememberHost)}
onChange={(e) => setForm({
...form,
onChange={(e) => setForm({
...form,
rememberHost: e.target.checked,
})}
sx={{
@@ -284,7 +278,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
>
<Option value="Select Auth" disabled>Select Auth</Option>
<Option value="password">Password</Option>
<Option value="key">SSH Key</Option>
<Option value="sshKey">SSH Key</Option>
</Select>
</FormControl>
@@ -315,9 +309,9 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
</FormControl>
)}
{form.authMethod === 'key' && (
{form.authMethod === 'sshKey' && (
<Stack spacing={2}>
<FormControl error={!form.privateKey}>
<FormControl error={!form.sshKey}>
<FormLabel>SSH Key</FormLabel>
<Button
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
type="file"
onChange={handleFileChange}
@@ -342,32 +336,6 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
/>
</Button>
</FormControl>
{form.privateKey && (
<FormControl>
<FormLabel>Key Passphrase (optional)</FormLabel>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
type={showPassphrase ? "text" : "password"}
value={form.passphrase || ''}
onChange={(e) => setForm(prev => ({ ...prev, passphrase: e.target.value }))}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
flex: 1
}}
/>
<IconButton
onClick={() => setShowPassphrase(!showPassphrase)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1
}}
>
{showPassphrase ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl>
)}
</Stack>
)}

View File

@@ -1,159 +1,267 @@
import PropTypes from 'prop-types';
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 { useEffect, useState } from 'react';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
import eventBus from '/src/other/eventBus';
const AuthModal = ({ isHidden, form, setForm, handleLoginUser, handleGuestLogin, handleCreateUser, setIsLoginUserHidden }) => {
const [showPassword, setShowPassword] = useState(false);
const [confirmPassword, setConfirmPassword] = useState('');
const AuthModal = ({
isHidden,
form,
setForm,
handleLoginUser,
handleCreateUser,
handleGuestLogin,
setIsAuthModalHidden
}) => {
const [activeTab, setActiveTab] = useState(0);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const isLoginFormValid = () => {
return form.username?.trim() && form.password?.trim();
useEffect(() => {
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 = () => {
return form.username?.trim() && form.password?.trim() && confirmPassword?.trim() && form.password === confirmPassword;
};
const handleLogin = () => {
if (!isLoginFormValid()) {
alert("Please fill out all fields");
return;
}
const handleLogin = async () => {
setIsLoading(true);
handleLoginUser({
username: form.username.trim(),
password: form.password.trim(),
onSuccess: () => {
setIsLoading(false);
setIsLoginUserHidden(true);
},
onFailure: (error) => {
setIsLoading(false);
alert(error);
}
});
try {
await handleLoginUser({
...form,
onSuccess: () => {
setIsLoading(false);
setIsAuthModalHidden(true);
},
onFailure: () => setIsLoading(false),
});
} catch (error) {
setIsLoading(false);
}
};
const handleCreate = () => {
if (!isCreateFormValid()) {
alert("Please fill out all fields and ensure passwords match");
return;
}
const handleCreate = async () => {
setIsLoading(true);
handleCreateUser({
username: form.username.trim(),
password: form.password.trim(),
onSuccess: () => {
setIsLoading(false);
setIsLoginUserHidden(true);
},
onFailure: (error) => {
setIsLoading(false);
alert(error);
}
});
try {
await handleCreateUser({
...form,
onSuccess: () => {
setIsLoading(false);
setActiveTab(0);
setIsAuthModalHidden(true);
},
onFailure: () => setIsLoading(false),
});
} 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(() => {
if (isHidden) {
setForm({ username: '', password: '' });
setConfirmPassword('');
setIsLoading(false);
setActiveTab(0);
}
if (isHidden) resetForm();
}, [isHidden]);
const isLoginValid = !!form.username && !!form.password;
const isCreateValid = isLoginValid && form.password === form.confirmPassword;
return (
<CssVarsProvider theme={theme}>
<Modal open={!isHidden} onClose={() => setIsLoginUserHidden(true)}>
<ModalDialog layout="center" sx={{ backgroundColor: theme.palette.general.tertiary }}>
<Tabs value={activeTab} onChange={(e, val) => setActiveTab(val)}>
<TabList>
<Tab>Login</Tab>
<Tab>Create Account</Tab>
<Modal open={!isHidden} onClose={() => setIsAuthModalHidden(true)}>
<ModalDialog
layout="center"
variant="outlined"
sx={{
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>
<div style={{ padding: '24px', backgroundColor: theme.palette.general.tertiary }}>
<TabPanel value={0}>
<Stack spacing={2}>
<DialogContent sx={{ padding: 3, backgroundColor: theme.palette.general.tertiary }}>
<TabPanel value={0} sx={{ p: 0 }}>
<Stack spacing={2} component="form" onSubmit={(e) => { e.preventDefault(); handleLogin(); }}>
<FormControl>
<FormLabel>Username</FormLabel>
<Input
value={form.username}
onChange={(event) => setForm({ ...form, username: event.target.value })}
disabled={isLoading}
value={form.username}
onChange={(e) => setForm({ ...form, username: e.target.value })}
sx={inputStyle}
/>
</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 })}
disabled={isLoading}
onChange={(e) => setForm({ ...form, password: e.target.value })}
sx={{ ...inputStyle, flex: 1 }}
/>
<IconButton onClick={() => setShowPassword(!showPassword)}>
<IconButton
disabled={isLoading}
onClick={() => setShowPassword(!showPassword)}
sx={iconButtonStyle}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl>
<Button onClick={handleLogin} disabled={!isLoginFormValid() || isLoading}>
<Button
type="submit"
disabled={!isLoginValid || isLoading}
sx={buttonStyle}
>
{isLoading ? "Logging in..." : "Login"}
</Button>
<Button onClick={handleGuestLogin} disabled={isLoading}>
{isLoading ? "Logging in as guest..." : "Login as Guest"}
<Button
disabled={isLoading}
onClick={handleGuest}
sx={buttonStyle}
>
{isLoading ? "Logging in..." : "Continue as Guest"}
</Button>
</Stack>
</TabPanel>
<TabPanel value={1}>
<Stack spacing={2}>
<TabPanel value={1} sx={{ p: 0 }}>
<Stack spacing={2} component="form" onSubmit={(e) => { e.preventDefault(); handleCreate(); }}>
<FormControl>
<FormLabel>Username</FormLabel>
<Input
value={form.username}
onChange={(event) => setForm({ ...form, username: event.target.value })}
disabled={isLoading}
value={form.username}
onChange={(e) => setForm({ ...form, username: e.target.value })}
sx={inputStyle}
/>
</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 })}
disabled={isLoading}
onChange={(e) => setForm({ ...form, password: e.target.value })}
sx={{ ...inputStyle, flex: 1 }}
/>
<IconButton onClick={() => setShowPassword(!showPassword)}>
<IconButton
disabled={isLoading}
onClick={() => setShowPassword(!showPassword)}
sx={iconButtonStyle}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl>
<FormControl>
<FormLabel>Confirm Password</FormLabel>
<Input
type={showPassword ? 'text' : 'password'}
value={confirmPassword}
onChange={(event) => setConfirmPassword(event.target.value)}
disabled={isLoading}
/>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
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>
<Button onClick={handleCreate} disabled={!isCreateFormValid() || isLoading}>
{isLoading ? "Creating account..." : "Create Account"}
<Button
type="submit"
disabled={!isCreateValid || isLoading}
sx={buttonStyle}
>
{isLoading ? "Creating..." : "Create Account"}
</Button>
</Stack>
</TabPanel>
</div>
</DialogContent>
</Tabs>
</ModalDialog>
</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 = {
isHidden: PropTypes.bool.isRequired,
form: PropTypes.object.isRequired,
setForm: PropTypes.func.isRequired,
handleLoginUser: PropTypes.func.isRequired,
handleGuestLogin: PropTypes.func.isRequired,
handleCreateUser: PropTypes.func.isRequired,
setIsLoginUserHidden: PropTypes.func.isRequired,
handleGuestLogin: PropTypes.func.isRequired,
setIsAuthModalHidden: PropTypes.func.isRequired,
};
export default AuthModal;

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;

View File

@@ -8,8 +8,6 @@ import {
FormLabel,
Input,
Stack,
DialogTitle,
DialogContent,
ModalDialog,
Select,
Option,
@@ -32,15 +30,13 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
user: hostConfig?.user || '',
port: hostConfig?.port || '',
password: '',
privateKey: hostConfig?.privateKey || '',
sshKey: hostConfig?.sshKey || '',
keyType: hostConfig?.keyType || '',
passphrase: '',
authMethod: hostConfig?.authMethod || 'Select Auth',
storePassword: true,
rememberHost: true
});
const [showPassword, setShowPassword] = useState(false);
const [showPassphrase, setShowPassphrase] = useState(false);
const [activeTab, setActiveTab] = useState(0);
const [isLoading, setIsLoading] = useState(false);
@@ -52,13 +48,12 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
ip: hostConfig.ip || '',
user: hostConfig.user || '',
password: hostConfig.password || '',
privateKey: hostConfig.privateKey || '',
sshKey: hostConfig.sshKey || '',
keyType: hostConfig.keyType || '',
passphrase: hostConfig.passphrase || '',
port: hostConfig.port || 22,
authMethod: hostConfig.password ? 'password' : hostConfig.privateKey ? 'key' : 'Select Auth',
authMethod: hostConfig.password ? 'password' : hostConfig.sshKey ? 'key' : 'Select Auth',
rememberHost: true,
storePassword: !!(hostConfig.password || hostConfig.privateKey),
storePassword: !!(hostConfig.password || hostConfig.sshKey),
});
}
}, [isHidden, hostConfig]);
@@ -84,8 +79,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
reader.onload = (evt) => {
const keyContent = evt.target.result;
let keyType = 'UNKNOWN';
// Detect key type from content
if (keyContent.includes('BEGIN RSA PRIVATE KEY') || keyContent.includes('BEGIN RSA PUBLIC KEY')) {
keyType = 'RSA';
} else if (keyContent.includes('BEGIN OPENSSH PRIVATE KEY') && keyContent.includes('ssh-ed25519')) {
@@ -98,7 +92,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
setForm((prev) => ({
...prev,
privateKey: keyContent,
sshKey: keyContent,
keyType: keyType,
authMethod: 'key'
}));
@@ -121,27 +115,23 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
...prev,
storePassword: Boolean(checked),
password: checked ? prev.password : "",
privateKey: checked ? prev.privateKey : "",
passphrase: checked ? prev.passphrase : "",
sshKey: checked ? prev.sshKey : "",
authMethod: checked ? prev.authMethod : "Select Auth"
}));
};
const isFormValid = () => {
const { ip, user, port, authMethod, password, privateKey } = form;
// Basic validation for required fields
const { ip, user, port, authMethod, password, sshKey } = form;
if (!ip?.trim() || !user?.trim() || !port) return false;
// Port validation
const portNum = Number(port);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false;
// Auth method validation only if storing password
if (form.storePassword) {
if (authMethod === 'Select Auth') return false;
if (authMethod === 'password' && !password?.trim()) return false;
if (authMethod === 'key' && !privateKey?.trim()) return false;
if (authMethod === 'sshKey' && !sshKey?.trim()) return false;
}
return true;
@@ -150,7 +140,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
const handleSubmit = async (event) => {
event.preventDefault();
if (isLoading) return;
setIsLoading(true);
try {
const newConfig = {
@@ -165,9 +155,8 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
if (form.authMethod === 'password') {
newConfig.password = form.password;
} else if (form.authMethod === 'key') {
newConfig.privateKey = form.privateKey;
newConfig.sshKey = form.sshKey;
newConfig.keyType = form.keyType;
newConfig.passphrase = form.passphrase;
}
}
@@ -378,7 +367,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
{form.authMethod === 'key' && (
<Stack spacing={2}>
<FormControl error={form.storePassword && !form.privateKey}>
<FormControl error={form.storePassword && !form.sshKey}>
<FormLabel>SSH Key</FormLabel>
<Button
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
type="file"
onChange={handleFileChange}
sx={{ display: 'none' }}
/>
</Button>
{hostConfig?.privateKey && !form.privateKey && (
{hostConfig?.sshKey && !form.sshKey && (
<FormLabel
sx={{
color: theme.palette.text.secondary,
@@ -416,32 +405,6 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
</FormLabel>
)}
</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>
)}
</>
@@ -452,7 +415,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
<Button
onClick={handleSubmit}
disabled={isLoading}
disabled={isLoading || !isFormValid()}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,

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;

View File

@@ -15,31 +15,44 @@ import {
Option,
} from '@mui/joy';
import theme from '/src/theme';
import { useState, useEffect } from 'react';
import {useEffect, useState} from 'react';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
const [form, setForm] = useState({
authMethod: 'Select Auth',
password: '',
privateKey: '',
keyType: '',
passphrase: ''
});
const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, handleAuthSubmit }) => {
const [showPassword, setShowPassword] = useState(false);
const [showPassphrase, setShowPassphrase] = useState(false);
useEffect(() => {
if (!form.authMethod) {
setForm(prev => ({
...prev,
authMethod: 'Select Auth',
password: '',
sshKey: '',
keyType: '',
}));
}
}, []);
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) => {
e.preventDefault();
onAuthenticate({
authMethod: form.authMethod,
password: form.password,
privateKey: form.privateKey,
keyType: form.keyType,
passphrase: form.passphrase
});
setIsHidden(true);
if(isFormValid()) {
handleAuthSubmit(form);
setForm (prev => ({
...prev,
authMethod: 'Select Auth',
password: '',
sshKey: '',
keyType: '',
}))
}
};
const handleFileChange = (e) => {
@@ -77,9 +90,9 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
setForm({
...form,
privateKey: keyContent,
sshKey: keyContent,
keyType: keyType,
authMethod: 'key'
authMethod: 'sshKey'
});
};
reader.readAsText(file);
@@ -92,12 +105,31 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
<CssVarsProvider theme={theme}>
<Modal
open={!isHidden}
onClose={() => setIsHidden(true)}
onClose={(e, reason) => {
if (reason !== 'backdropClick') {
setIsNoAuthHidden(true);
}
}}
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<ModalDialog
layout="center"
sx={{
backgroundColor: theme.palette.general.secondary,
backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
maxWidth: '500px',
width: '100%',
maxHeight: '80vh',
overflow: 'auto',
boxSizing: 'border-box',
mx: 2,
}}
>
<DialogTitle sx={{ mb: 2 }}>Authentication Required</DialogTitle>
@@ -107,14 +139,13 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
<FormControl error={!form.authMethod || form.authMethod === 'Select Auth'}>
<FormLabel>Authentication Method</FormLabel>
<Select
value={form.authMethod}
value={form.authMethod || 'Select Auth'}
onChange={(e, val) => setForm(prev => ({
...prev,
authMethod: val,
password: '',
privateKey: '',
sshKey: '',
keyType: '',
passphrase: ''
}))}
sx={{
backgroundColor: theme.palette.general.primary,
@@ -123,29 +154,43 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
>
<Option value="Select Auth" disabled>Select Auth</Option>
<Option value="password">Password</Option>
<Option value="key">SSH Key</Option>
<Option value="sshKey">SSH Key</Option >
</Select>
</FormControl>
{form.authMethod === 'password' && (
<FormControl error={!form.password}>
<FormLabel>Password</FormLabel>
<Input
type={showPassword ? "text" : "password"}
value={form.password}
onChange={(e) => setForm({ ...form, password: e.target.value })}
endDecorator={
<IconButton onClick={() => setShowPassword(!showPassword)}>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
}
/>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input
type={showPassword ? "text" : "password"}
value={form.password || ''}
onChange={(e) => setForm({...form, password: e.target.value})}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
flex: 1
}}
/>
<IconButton
onClick={() => setShowPassword(!showPassword)}
sx={{
color: theme.palette.text.primary,
marginLeft: 1,
'&:disabled': {
opacity: 0.5,
},
}}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</div>
</FormControl>
)}
{form.authMethod === 'key' && (
{form.authMethod === 'sshKey' && (
<Stack spacing={2}>
<FormControl error={!form.privateKey}>
<FormControl error={!form.sshKey}>
<FormLabel>SSH Key</FormLabel>
<Button
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
type="file"
onChange={handleFileChange}
@@ -170,29 +215,12 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
/>
</Button>
</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>
)}
<Button
type="submit"
disabled={!form.authMethod || form.authMethod === 'Select Auth' ||
(form.authMethod === 'password' && !form.password) ||
(form.authMethod === 'key' && !form.privateKey)}
disabled={!isFormValid()}
sx={{
backgroundColor: theme.palette.general.primary,
color: theme.palette.text.primary,
@@ -203,6 +231,8 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
color: 'rgba(255, 255, 255, 0.3)',
},
marginTop: 2,
height: '40px',
}}
>
Connect
@@ -218,8 +248,10 @@ const NoAuthenticationModal = ({ isHidden, setIsHidden, onAuthenticate }) => {
NoAuthenticationModal.propTypes = {
isHidden: PropTypes.bool.isRequired,
setIsHidden: PropTypes.func.isRequired,
onAuthenticate: PropTypes.func.isRequired,
form: PropTypes.object.isRequired,
setForm: PropTypes.func.isRequired,
setIsNoAuthHidden: PropTypes.func.isRequired,
handleAuthSubmit: PropTypes.func.isRequired,
};
export default NoAuthenticationModal;