diff --git a/src/App.jsx b/src/App.jsx
index a2992c0c..ee72eee8 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -31,6 +31,7 @@ function App() {
const [nextId, setNextId] = useState(1);
const [addHostForm, setAddHostForm] = useState({
name: "",
+ folder: "",
ip: "",
user: "",
password: "",
@@ -41,6 +42,7 @@ function App() {
});
const [editHostForm, setEditHostForm] = useState({
name: "",
+ folder: "",
ip: "",
user: "",
password: "",
@@ -66,6 +68,7 @@ function App() {
const [splitTabIds, setSplitTabIds] = useState([]);
const [isEditHostHidden, setIsEditHostHidden] = useState(true);
const [currentHostConfig, setCurrentHostConfig] = useState(null);
+ const [isLoggingIn, setIsLoggingIn] = useState(true);
useEffect(() => {
const handleKeyDown = (e) => {
@@ -124,23 +127,97 @@ function App() {
useEffect(() => {
const sessionToken = localStorage.getItem('sessionToken');
- if (sessionToken) {
- setTimeout(() => {
- handleLoginUser({
+ let isComponentMounted = true;
+ let isLoginInProgress = false;
+
+ if (userRef.current?.getUser()) {
+ setIsLoggingIn(false);
+ setIsLoginUserHidden(true);
+ return;
+ }
+
+ if (!sessionToken) {
+ setIsLoggingIn(false);
+ setIsLoginUserHidden(false);
+ return;
+ }
+
+ setIsLoggingIn(true);
+ let loginAttempts = 0;
+ const maxAttempts = 50;
+ let attemptLoginInterval;
+
+ const loginTimeout = setTimeout(() => {
+ if (isComponentMounted) {
+ clearInterval(attemptLoginInterval);
+ if (!userRef.current?.getUser()) {
+ localStorage.removeItem('sessionToken');
+ setIsLoginUserHidden(false);
+ setIsLoggingIn(false);
+ setErrorMessage('Login timed out. Please try again.');
+ setIsErrorHidden(false);
+ }
+ }
+ }, 10000);
+
+ const attemptLogin = () => {
+ if (!isComponentMounted || isLoginInProgress) return;
+
+ if (loginAttempts >= maxAttempts || userRef.current?.getUser()) {
+ clearTimeout(loginTimeout);
+ clearInterval(attemptLoginInterval);
+
+ if (!userRef.current?.getUser()) {
+ localStorage.removeItem('sessionToken');
+ setIsLoginUserHidden(false);
+ setIsLoggingIn(false);
+ setErrorMessage('Login timed out. Please try again.');
+ setIsErrorHidden(false);
+ }
+ return;
+ }
+
+ if (userRef.current) {
+ isLoginInProgress = true;
+ userRef.current.loginUser({
sessionToken,
onSuccess: () => {
- setIsLoginUserHidden(true);
+ if (isComponentMounted) {
+ clearTimeout(loginTimeout);
+ clearInterval(attemptLoginInterval);
+ setIsLoginUserHidden(true);
+ setIsLoggingIn(false);
+ setIsErrorHidden(true);
+ }
+ isLoginInProgress = false;
},
onFailure: (error) => {
- setErrorMessage(`Auto-login failed: ${error}`);
- setIsErrorHidden(false);
- setIsLoginUserHidden(false);
+ if (isComponentMounted) {
+ if (!userRef.current?.getUser()) {
+ clearTimeout(loginTimeout);
+ clearInterval(attemptLoginInterval);
+ localStorage.removeItem('sessionToken');
+ setErrorMessage(`Auto-login failed: ${error}`);
+ setIsErrorHidden(false);
+ setIsLoginUserHidden(false);
+ setIsLoggingIn(false);
+ }
+ }
+ isLoginInProgress = false;
},
});
- }, 500);
- } else {
- setIsLoginUserHidden(false);
- }
+ }
+ loginAttempts++;
+ };
+
+ attemptLoginInterval = setInterval(attemptLogin, 100);
+ attemptLogin();
+
+ return () => {
+ isComponentMounted = false;
+ clearTimeout(loginTimeout);
+ clearInterval(attemptLoginInterval);
+ };
}, []);
const handleAddHost = () => {
@@ -168,6 +245,8 @@ function App() {
id: nextId,
title: addHostForm.name || addHostForm.ip,
hostConfig: {
+ name: addHostForm.name,
+ folder: addHostForm.folder,
ip: addHostForm.ip,
user: addHostForm.user,
password: addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
@@ -180,7 +259,7 @@ function App() {
setActiveTab(nextId);
setNextId(nextId + 1);
setIsAddHostHidden(true);
- setAddHostForm({ name: "", ip: "", user: "", password: "", rsaKey: "", port: 22, authMethod: "Select Auth" });
+ setAddHostForm({ name: "", folder: "", ip: "", user: "", password: "", rsaKey: "", port: 22, authMethod: "Select Auth", rememberHost: false, storePassword: true });
}
const handleAuthSubmit = (form) => {
@@ -217,6 +296,7 @@ function App() {
const handleSaveHost = () => {
let hostConfig = {
name: addHostForm.name || addHostForm.ip,
+ folder: addHostForm.folder,
ip: addHostForm.ip,
user: addHostForm.user,
password: addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
@@ -235,15 +315,32 @@ function App() {
if (sessionToken) {
userRef.current.loginUser({
sessionToken,
- onSuccess,
- onFailure,
+ onSuccess: () => {
+ setIsLoginUserHidden(true);
+ setIsLoggingIn(false);
+ if (onSuccess) onSuccess();
+ },
+ onFailure: (error) => {
+ localStorage.removeItem('sessionToken');
+ setIsLoginUserHidden(false);
+ setIsLoggingIn(false);
+ if (onFailure) onFailure(error);
+ },
});
} else {
userRef.current.loginUser({
username,
password,
- onSuccess,
- onFailure,
+ onSuccess: () => {
+ setIsLoginUserHidden(true);
+ setIsLoggingIn(false);
+ if (onSuccess) onSuccess();
+ },
+ onFailure: (error) => {
+ setIsLoginUserHidden(false);
+ setIsLoggingIn(false);
+ if (onFailure) onFailure(error);
+ },
});
}
}
@@ -264,7 +361,7 @@ function App() {
onFailure,
});
}
- }
+ };
const handleDeleteUser = ({ onSuccess, onFailure }) => {
if (userRef.current) {
@@ -311,31 +408,21 @@ function App() {
}
};
- const handleEditHost = async () => {
+ const handleEditHost = async (oldConfig, newConfig = null) => {
try {
- // Only clear the password if switching to RSA or storePassword is false
- if (editHostForm.authMethod === 'rsaKey') {
- editHostForm.password = '';
- } else if (!editHostForm.storePassword) {
- editHostForm.password = '';
+ if (newConfig) {
+ await userRef.current.editHost({
+ oldHostConfig: oldConfig,
+ newHostConfig: newConfig,
+ });
+ return;
}
- await userRef.current.editHost({
- oldHostConfig: currentHostConfig,
- newHostConfig: editHostForm,
- });
-
- // Refresh the updated config
- const refreshedHosts = await userRef.current.getAllHosts();
- const updated = refreshedHosts.find(
- (h) => h.config.ip === editHostForm.ip && h.config.user === editHostForm.user
- );
- if (updated) {
- setCurrentHostConfig(updated.config);
- }
- setIsEditHostHidden(true);
+ updateEditHostForm(oldConfig);
} catch (error) {
- alert('Edit failed: ' + error);
+ console.error('Edit failed:', error);
+ setErrorMessage(`Edit failed: ${error}`);
+ setIsErrorHidden(false);
}
};
@@ -398,88 +485,124 @@ function App() {
- {/* Launchpad Button */}
-
+ {/* Action Buttons */}
+
+ {/* Launchpad Button */}
+
- {/* Add Host Button */}
-
+ {/* Add Host Button */}
+
- {/* Profile Button */}
-
+ {/* Profile Button */}
+
+
{/* Terminal Views */}
- {terminals.map((terminal) => (
-
-
(
+ {
- terminal.terminalRef = ref;
+ className={`bg-neutral-800 rounded-lg overflow-hidden shadow-xl border-5 border-neutral-700 ${
+ splitTabIds.includes(terminal.id) || activeTab === terminal.id ? "block" : "hidden"
+ } flex-1`}
+ style={{
+ order: splitTabIds.includes(terminal.id)
+ ? splitTabIds.indexOf(terminal.id)
+ : 0,
}}
- />
+ >
+ {
+ terminal.terminalRef = ref;
+ }}
+ />
+
+ ))
+ ) : (
+
+
+
Welcome to Termix
+
{isLoggingIn ? "Checking login status..." : "Please login to start managing your SSH connections"}
+
- ))}
+ )}
-
- {/* Modals */}
-
-
-
-
-
- {isLaunchpadOpen && (
- setIsLaunchpadOpen(false)}
- getHosts={getHosts}
- connectToHost={connectToHostWithConfig}
- isAddHostHidden={isAddHostHidden}
- setIsAddHostHidden={setIsAddHostHidden}
- isEditHostHidden={isEditHostHidden}
- isErrorHidden={isErrorHidden}
- deleteHost={deleteHost}
- editHost={updateEditHostForm}
+ {/* Modals */}
+ {userRef.current?.getUser() && (
+ <>
+
+
+
+ {isLaunchpadOpen && (
+ setIsLaunchpadOpen(false)}
+ getHosts={getHosts}
+ connectToHost={connectToHostWithConfig}
+ isAddHostHidden={isAddHostHidden}
+ setIsAddHostHidden={setIsAddHostHidden}
+ isEditHostHidden={isEditHostHidden}
+ isErrorHidden={isErrorHidden}
+ deleteHost={deleteHost}
+ editHost={handleEditHost}
+ />
+ )}
+ >
+ )}
+
+
- )}
-
+
- {/* User component */}
- setIsLoginUserHidden(true)}
- onCreateSuccess={() => {
- setIsCreateUserHidden(true);
- handleLoginUser({ username: createUserForm.username, password: createUserForm.password })}
- }
- onDeleteSuccess={() => {
- setIsProfileHidden(true);
- window.location.reload();
- }}
- onFailure={(error) => {
- setErrorMessage(`Action failed: ${error}`);
- setIsErrorHidden(false);
- }}
- />
+
+
+ {/* User component */}
+ {
+ setIsLoginUserHidden(true);
+ setIsLoggingIn(false);
+ setIsErrorHidden(true);
+ }}
+ onCreateSuccess={() => {
+ setIsCreateUserHidden(true);
+ handleLoginUser({
+ username: createUserForm.username,
+ password: createUserForm.password,
+ onSuccess: () => {
+ setIsLoginUserHidden(true);
+ setIsLoggingIn(false);
+ setIsErrorHidden(true);
+ },
+ onFailure: (error) => {
+ setErrorMessage(`Login failed: ${error}`);
+ setIsErrorHidden(false);
+ }
+ });
+ }}
+ onDeleteSuccess={() => {
+ setIsProfileHidden(true);
+ window.location.reload();
+ }}
+ onFailure={(error) => {
+ setErrorMessage(`Action failed: ${error}`);
+ setIsErrorHidden(false);
+ setIsLoggingIn(false);
+ }}
+ />
+
);
diff --git a/src/apps/Launchpad.jsx b/src/apps/Launchpad.jsx
index 00413bbf..03b2774f 100644
--- a/src/apps/Launchpad.jsx
+++ b/src/apps/Launchpad.jsx
@@ -4,20 +4,19 @@ import { CssVarsProvider } from '@mui/joy/styles';
import { Button } from '@mui/joy';
import HostViewerIcon from '../images/host_viewer_icon.png';
import theme from '../theme.js';
-
-// Apps
import HostViewer from './ssh/HostViewer.jsx';
-function Launchpad({onClose,
- getHosts,
- connectToHost,
- isAddHostHidden,
- setIsAddHostHidden,
- isEditHostHidden,
- isErrorHidden,
- deleteHost,
- editHost,
- }) {
+function Launchpad({
+ onClose,
+ getHosts,
+ connectToHost,
+ isAddHostHidden,
+ setIsAddHostHidden,
+ isEditHostHidden,
+ isErrorHidden,
+ deleteHost,
+ editHost,
+}) {
const launchpadRef = useRef(null);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [activeApp, setActiveApp] = useState('hostViewer');
@@ -42,11 +41,6 @@ function Launchpad({onClose,
};
}, [onClose, isAddHostHidden, isEditHostHidden, isErrorHidden]);
- const handleEditHostClick = () => {
- setIsAddHostHidden(false);
- setActiveApp('hostViewer');
- };
-
return (
{/* Main Content */}
-
+
{activeApp === 'hostViewer' && (
)}
diff --git a/src/apps/ssh/HostViewer.jsx b/src/apps/ssh/HostViewer.jsx
index 6a2bf683..7efb1ec5 100644
--- a/src/apps/ssh/HostViewer.jsx
+++ b/src/apps/ssh/HostViewer.jsx
@@ -2,11 +2,14 @@ import PropTypes from "prop-types";
import { useState, useEffect, useRef } from "react";
import { Button, Input } from "@mui/joy";
-function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, editHost }) {
+function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, editHost, openEditPanel }) {
const [hosts, setHosts] = useState([]);
const [filteredHosts, setFilteredHosts] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState("");
+ const [collapsedFolders, setCollapsedFolders] = useState(new Set());
+ const [draggedHost, setDraggedHost] = useState(null);
+ const [isDraggingOver, setIsDraggingOver] = useState(null);
const isMounted = useRef(true);
const fetchHosts = async () => {
@@ -44,11 +47,181 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
useEffect(() => {
const filtered = hosts.filter((hostWrapper) => {
const hostConfig = hostWrapper.config || {};
- return hostConfig.name?.toLowerCase().includes(searchTerm.toLowerCase()) || hostConfig.ip?.toLowerCase().includes(searchTerm.toLowerCase());
+ return hostConfig.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ hostConfig.ip?.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ hostConfig.folder?.toLowerCase().includes(searchTerm.toLowerCase());
});
setFilteredHosts(filtered);
}, [searchTerm, hosts]);
+ const toggleFolder = (folderName) => {
+ setCollapsedFolders(prev => {
+ const newSet = new Set(prev);
+ if (newSet.has(folderName)) {
+ newSet.delete(folderName);
+ } else {
+ newSet.add(folderName);
+ }
+ return newSet;
+ });
+ };
+
+ const groupHostsByFolder = (hosts) => {
+ const grouped = {};
+ const noFolder = [];
+
+ const sortedHosts = [...hosts].sort((a, b) => {
+ const nameA = (a.config?.name || a.config?.ip || '').toLowerCase();
+ const nameB = (b.config?.name || b.config?.ip || '').toLowerCase();
+ return nameA.localeCompare(nameB);
+ });
+
+ sortedHosts.forEach(host => {
+ const folder = host.config?.folder;
+ if (folder) {
+ if (!grouped[folder]) {
+ grouped[folder] = [];
+ }
+ grouped[folder].push(host);
+ } else {
+ noFolder.push(host);
+ }
+ });
+
+ const sortedFolders = Object.keys(grouped).sort((a, b) => a.localeCompare(b));
+
+ return { grouped, sortedFolders, noFolder };
+ };
+
+ const handleDragStart = (e, host) => {
+ setDraggedHost(host);
+ e.dataTransfer.setData('text/plain', '');
+ };
+
+ const handleDragOver = (e, folderName) => {
+ e.preventDefault();
+ setIsDraggingOver(folderName);
+ };
+
+ const handleDragLeave = () => {
+ setIsDraggingOver(null);
+ };
+
+ const handleDrop = async (e, targetFolder) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setIsDraggingOver(null);
+
+ if (!draggedHost) return;
+
+ if (draggedHost.config.folder === targetFolder) return;
+
+ const newConfig = {
+ ...draggedHost.config,
+ folder: targetFolder
+ };
+
+ try {
+ await editHost(draggedHost.config, newConfig);
+ await fetchHosts();
+ } catch (error) {
+ console.error('Failed to update folder:', error);
+ }
+
+ setDraggedHost(null);
+ };
+
+ const handleDropOnNoFolder = async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setIsDraggingOver(null);
+
+ if (!draggedHost || !draggedHost.config.folder) return;
+
+ const newConfig = {
+ ...draggedHost.config,
+ folder: null
+ };
+
+ try {
+ await editHost(draggedHost.config, newConfig);
+ await fetchHosts();
+ } catch (error) {
+ console.error('Failed to remove from folder:', error);
+ }
+
+ setDraggedHost(null);
+ };
+
+ const renderHostItem = (hostWrapper) => {
+ const hostConfig = hostWrapper.config || {};
+
+ if (!hostConfig) {
+ return null;
+ }
+
+ return (
+
handleDragStart(e, hostWrapper)}
+ onDragEnd={() => setDraggedHost(null)}
+ >
+
+
⋮⋮
+
+
{hostConfig.name || hostConfig.ip}
+
+ {hostConfig.user ? `${hostConfig.user}@${hostConfig.ip}` : `${hostConfig.ip}:${hostConfig.port}`}
+
+
+
+
+
+
+
+
+
+ );
+ };
+
return (
@@ -79,60 +252,51 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
Loading hosts...
) : filteredHosts.length > 0 ? (
- {filteredHosts.map((hostWrapper, index) => {
- const hostConfig = hostWrapper.config || {};
-
- if (!hostConfig) {
- return null;
- }
+ {(() => {
+ const { grouped, sortedFolders, noFolder } = groupHostsByFolder(filteredHosts);
return (
-
-
-
{hostConfig.name || hostConfig.ip}
-
- {hostConfig.user ? `${hostConfig.user}@${hostConfig.ip}` : `${hostConfig.ip}:${hostConfig.port}`}
-
+ <>
+ {/* Render hosts without folders first */}
+
handleDragOver(e, 'no-folder')}
+ onDragLeave={handleDragLeave}
+ onDrop={handleDropOnNoFolder}
+ >
+ {noFolder.map((host) => renderHostItem(host))}
-
-
-
-
-
-
+
+ {/* Render folders and their hosts */}
+ {sortedFolders.map((folderName) => (
+
+
toggleFolder(folderName)}
+ onDragOver={(e) => handleDragOver(e, folderName)}
+ onDragLeave={handleDragLeave}
+ onDrop={(e) => handleDrop(e, folderName)}
+ >
+
+ ▼
+
+ {folderName}
+
+ ({grouped[folderName].length})
+
+
+ {!collapsedFolders.has(folderName) && (
+
+ {grouped[folderName].map((host) => renderHostItem(host))}
+
+ )}
+
+ ))}
+ >
);
- })}
+ })()}
) : (
No hosts available...
@@ -148,6 +312,7 @@ HostViewer.propTypes = {
setIsAddHostHidden: PropTypes.func.isRequired,
deleteHost: PropTypes.func.isRequired,
editHost: PropTypes.func.isRequired,
+ openEditPanel: PropTypes.func.isRequired,
};
export default HostViewer;
\ No newline at end of file
diff --git a/src/backend/database.cjs b/src/backend/database.cjs
index f5bc9c87..8d2f7167 100644
--- a/src/backend/database.cjs
+++ b/src/backend/database.cjs
@@ -28,7 +28,8 @@ const hostSchema = new mongoose.Schema({
name: { type: String, required: true },
config: { type: String, required: true },
users: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
- createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
+ createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
+ folder: { type: String, default: null }
});
const User = mongoose.model('User', userSchema);
@@ -178,7 +179,8 @@ io.of('/database.io').on('connection', (socket) => {
}
const cleanConfig = {
- name: hostConfig.name.trim(),
+ name: hostConfig.name?.trim(),
+ folder: hostConfig.folder?.trim() || null,
ip: hostConfig.ip.trim(),
user: hostConfig.user.trim(),
port: hostConfig.port || 22,
@@ -208,7 +210,8 @@ io.of('/database.io').on('connection', (socket) => {
name: finalName,
config: encryptedConfig,
users: [userId],
- createdBy: userId
+ createdBy: userId,
+ folder: cleanConfig.folder
});
logger.info(`Host created successfully: ${finalName}`);
@@ -360,10 +363,11 @@ io.of('/database.io').on('connection', (socket) => {
}
const cleanConfig = {
+ name: newHostConfig.name?.trim(),
+ folder: newHostConfig.folder?.trim() || null,
ip: newHostConfig.ip.trim(),
user: newHostConfig.user.trim(),
port: newHostConfig.port || 22,
- name: newHostConfig.name.trim(),
password: newHostConfig.password?.trim() || undefined,
rsaKey: newHostConfig.rsaKey?.trim() || undefined
};
@@ -375,6 +379,7 @@ io.of('/database.io').on('connection', (socket) => {
}
host.config = encryptedConfig;
+ host.folder = cleanConfig.folder;
await host.save();
logger.info(`Host edited successfully`);
diff --git a/src/modals/AddHostModal.jsx b/src/modals/AddHostModal.jsx
index 046c8b1d..71955c51 100644
--- a/src/modals/AddHostModal.jsx
+++ b/src/modals/AddHostModal.jsx
@@ -13,7 +13,11 @@ import {
Select,
Option,
Checkbox,
- IconButton
+ IconButton,
+ Tabs,
+ TabList,
+ Tab,
+ TabPanel
} from '@mui/joy';
import theme from '/src/theme';
import { useState } from 'react';
@@ -22,6 +26,7 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff';
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
const [showPassword, setShowPassword] = useState(false);
+ const [activeTab, setActiveTab] = useState(0);
const handleFileChange = (e) => {
const file = e.target.files[0];
@@ -68,7 +73,6 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
setIsAddHostHidden(true)}
sx={{
- overflow: 'hidden',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
@@ -82,172 +86,258 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
- maxWidth: '400px',
+ maxWidth: '500px',
width: '100%',
- overflow: 'hidden',
+ maxHeight: '80vh',
+ overflow: 'auto',
boxSizing: 'border-box',
mx: 2,
}}
>
- Add Host
+ Add Host
@@ -260,6 +350,7 @@ AddHostModal.propTypes = {
isHidden: PropTypes.bool.isRequired,
form: PropTypes.shape({
name: PropTypes.string,
+ folder: PropTypes.string,
ip: PropTypes.string.isRequired,
user: PropTypes.string.isRequired,
password: PropTypes.string,
diff --git a/src/modals/EditHostModal.jsx b/src/modals/EditHostModal.jsx
index ab0096ff..411dbe98 100644
--- a/src/modals/EditHostModal.jsx
+++ b/src/modals/EditHostModal.jsx
@@ -14,7 +14,11 @@ import {
Select,
Option,
IconButton,
- Checkbox
+ Checkbox,
+ Tabs,
+ TabList,
+ Tab,
+ TabPanel
} from '@mui/joy';
import theme from '/src/theme';
import Visibility from '@mui/icons-material/Visibility';
@@ -22,25 +26,23 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff';
const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostHidden, hostConfig }) => {
const [showPassword, setShowPassword] = useState(false);
+ const [activeTab, setActiveTab] = useState(0);
useEffect(() => {
- if (hostConfig) {
- const storePassword = hostConfig.password || hostConfig.rsaKey;
-
+ if (hostConfig && !isHidden) {
setForm({
- ...form,
- name: hostConfig.name || '',
- ip: hostConfig.ip || '',
- user: hostConfig.user || '',
- password: storePassword && hostConfig.password ? hostConfig.password : '',
- rsaKey: '',
- port: Number(hostConfig.port) || 22,
- authMethod: hostConfig.rsaKey ? 'rsaKey' : (storePassword ? 'password' : 'Select Auth'),
- rememberHost: hostConfig.rememberHost || true,
- storePassword: storePassword ?? false
+ name: hostConfig.name || "",
+ folder: hostConfig.folder || "",
+ ip: hostConfig.ip || "",
+ user: hostConfig.user || "",
+ password: hostConfig.password || "",
+ port: hostConfig.port || 22,
+ authMethod: hostConfig.password ? "password" : hostConfig.rsaKey ? "rsaKey" : "Select Auth",
+ rememberHost: true,
+ storePassword: true,
});
}
- }, [hostConfig, setForm]);
+ }, [hostConfig, isHidden]);
const handleFileChange = (e) => {
const file = e.target.files[0];
@@ -65,7 +67,7 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
const handleStorePasswordChange = (checked) => {
setForm((prev) => ({
...prev,
- storePassword: checked,
+ storePassword: Boolean(checked),
authMethod: checked ? 'password' : 'Select Auth'
}));
};
@@ -76,35 +78,42 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
const portNum = Number(port);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) return false;
- if (storePassword && authMethod === 'password' && !password.trim()) return false;
- if (storePassword && authMethod === 'rsaKey' && !rsaKey && !hostConfig?.rsaKey) return false;
- if (storePassword && authMethod === 'Select Auth') return false;
+ if (Boolean(storePassword) && authMethod === 'password' && !password?.trim()) return false;
+ if (Boolean(storePassword) && authMethod === 'rsaKey' && !rsaKey && !hostConfig?.rsaKey) return false;
+ if (Boolean(storePassword) && authMethod === 'Select Auth') return false;
return true;
};
- const handleSubmit = (e) => {
+ const handleSave = async (e) => {
e.preventDefault();
- if (isFormValid()) {
- const { authMethod, password, rsaKey, storePassword, ...rest } = form;
- handleEditHost({
- ...rest,
- authMethod,
- password: authMethod === 'password' && storePassword ? password : '',
- rsaKey: authMethod === 'rsaKey' ? rsaKey : ''
- });
+ try {
+ const newConfig = {
+ ...form,
+ port: String(form.port),
+ };
+
+ if (form.authMethod === 'rsaKey' || !form.storePassword) {
+ newConfig.password = '';
+ }
+
+ await handleEditHost(hostConfig, newConfig);
+ setIsEditHostHidden(true);
+ } catch (error) {
+ console.error('Failed to save:', error);
}
};
return (
- setIsEditHostHidden(true)}
- sx={{
- overflowX: 'hidden',
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- }}
+ setIsEditHostHidden(true)}
+ sx={{
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ }}
>
- Edit Host
+ Edit Host
-
diff --git a/src/modals/ProfileModal.jsx b/src/modals/ProfileModal.jsx
index 38e59239..d572fb26 100644
--- a/src/modals/ProfileModal.jsx
+++ b/src/modals/ProfileModal.jsx
@@ -1,101 +1,83 @@
import PropTypes from 'prop-types';
-import { CssVarsProvider } from '@mui/joy/styles';
-import { Modal, Button, DialogTitle, DialogContent, ModalDialog, Stack } from '@mui/joy';
-import theme from '/src/theme';
-
-const ProfileModal = ({ isHidden, getUser, handleDeleteUser, handleLogoutUser, setIsProfileHidden }) => {
- const handleDelete = () => {
- handleDeleteUser({
- onSuccess: () => {
- window.location.reload();
- }
- });
- };
-
- const handleLogout = () => {
- handleLogoutUser({
- onSuccess: () => {
- window.location.reload();
- }
- });
- }
-
- const getUserName = () => {
- const user = getUser();
- return user ? user.username : '';
- }
+import { Modal, Typography, Button } from "@mui/joy";
+import LogoutIcon from "@mui/icons-material/Logout";
+import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import theme from "../theme";
+export default function ProfileModal({
+ isHidden,
+ getUser,
+ handleDeleteUser,
+ handleLogoutUser,
+ setIsProfileHidden,
+}) {
return (
-
- setIsProfileHidden(true)}>
-
- setIsProfileHidden(true)}
+ sx={{
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+ }}
+ >
+
+
+ }
sx={{
- marginBottom: 1.5,
- backgroundColor: theme.palette.general.primary,
- color: theme.palette.text.primary,
- padding: 1,
- borderRadius: 10,
- width: "100%",
- textAlign: "center",
- display: "flex",
- justifyContent: "center",
- alignItems: "center",
+ backgroundColor: theme.palette.general.tertiary,
+ color: "white",
+ "&:hover": {
+ backgroundColor: theme.palette.general.secondary,
+ },
+ height: "40px",
+ border: `1px solid ${theme.palette.general.secondary}`,
}}
>
- User: {getUserName()}
-
-
-
-
-
-
-
-
-
-
+ Logout
+
+
+
+
+
+
);
-};
+}
ProfileModal.propTypes = {
isHidden: PropTypes.bool.isRequired,
@@ -103,6 +85,4 @@ ProfileModal.propTypes = {
handleDeleteUser: PropTypes.func.isRequired,
handleLogoutUser: PropTypes.func.isRequired,
setIsProfileHidden: PropTypes.func.isRequired,
-};
-
-export default ProfileModal;
\ No newline at end of file
+};
\ No newline at end of file