diff --git a/package-lock.json b/package-lock.json
index 7c67313c..7615c7d0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@fontsource/inter": "^5.1.1",
+ "@mui/icons-material": "^6.4.7",
"@mui/joy": "^5.0.0-beta.51",
"@tailwindcss/vite": "^4.0.8",
"@tiptap/extension-link": "^2.11.5",
@@ -1321,6 +1322,32 @@
"url": "https://opencollective.com/mui-org"
}
},
+ "node_modules/@mui/icons-material": {
+ "version": "6.4.7",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.7.tgz",
+ "integrity": "sha512-Rk8cs9ufQoLBw582Rdqq7fnSXXZTqhYRbpe1Y5SAz9lJKZP3CIdrj0PfG8HJLGw1hrsHFN/rkkm70IDzhJsG1g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@mui/material": "^6.4.7",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@mui/joy": {
"version": "5.0.0-beta.51",
"resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.51.tgz",
@@ -1362,6 +1389,209 @@
}
}
},
+ "node_modules/@mui/material": {
+ "version": "6.4.7",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.7.tgz",
+ "integrity": "sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mui/core-downloads-tracker": "^6.4.7",
+ "@mui/system": "^6.4.7",
+ "@mui/types": "^7.2.21",
+ "@mui/utils": "^6.4.6",
+ "@popperjs/core": "^2.11.8",
+ "@types/react-transition-group": "^4.4.12",
+ "clsx": "^2.1.1",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.0.0",
+ "react-transition-group": "^4.4.5"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@mui/material-pigment-css": "^6.4.7",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@mui/material-pigment-css": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material/node_modules/@mui/core-downloads-tracker": {
+ "version": "6.4.7",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.7.tgz",
+ "integrity": "sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==",
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ }
+ },
+ "node_modules/@mui/material/node_modules/@mui/private-theming": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.6.tgz",
+ "integrity": "sha512-T5FxdPzCELuOrhpA2g4Pi6241HAxRwZudzAuL9vBvniuB5YU82HCmrARw32AuCiyTfWzbrYGGpZ4zyeqqp9RvQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mui/utils": "^6.4.6",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material/node_modules/@mui/styled-engine": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.6.tgz",
+ "integrity": "sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@emotion/cache": "^11.13.5",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/sheet": "^1.4.0",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.4.1",
+ "@emotion/styled": "^11.3.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material/node_modules/@mui/system": {
+ "version": "6.4.7",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.7.tgz",
+ "integrity": "sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mui/private-theming": "^6.4.6",
+ "@mui/styled-engine": "^6.4.6",
+ "@mui/types": "^7.2.21",
+ "@mui/utils": "^6.4.6",
+ "clsx": "^2.1.1",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material/node_modules/@mui/utils": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.6.tgz",
+ "integrity": "sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mui/types": "^7.2.21",
+ "@types/prop-types": "^15.7.14",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.0.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material/node_modules/react-is": {
+ "version": "19.0.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz",
+ "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/@mui/private-theming": {
"version": "5.16.14",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.14.tgz",
@@ -2583,7 +2813,6 @@
"version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -2600,6 +2829,16 @@
"@types/react": "^18.0.0"
}
},
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
diff --git a/package.json b/package.json
index 4b432af5..0ab6013a 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@fontsource/inter": "^5.1.1",
+ "@mui/icons-material": "^6.4.7",
"@mui/joy": "^5.0.0-beta.51",
"@tailwindcss/vite": "^4.0.8",
"@tiptap/extension-link": "^2.11.5",
diff --git a/src/App.jsx b/src/App.jsx
index 7dc8af8d..a2992c0c 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,14 +1,14 @@
import { useState, useEffect, useRef } from "react";
-import { NewTerminal } from "./Terminal.jsx";
-import { User } from "./User.jsx";
+import { NewTerminal } from "./apps/ssh/Terminal.jsx";
+import { User } from "./apps/user/User.jsx";
import AddHostModal from "./modals/AddHostModal.jsx";
import LoginUserModal from "./modals/LoginUserModal.jsx";
import { Button } from "@mui/joy";
import { CssVarsProvider } from "@mui/joy";
import theme from "./theme";
-import TabList from "./TabList.jsx";
-import Launchpad from "./Launchpad.jsx";
-import { Debounce } from './Utils';
+import TabList from "./ui/TabList.jsx";
+import Launchpad from "./apps/Launchpad.jsx";
+import { Debounce } from './other/Utils.jsx';
import TermixIcon from "./images/termix_icon.png";
import RocketIcon from './images/launchpad_rocket.png';
import ProfileIcon from './images/profile_icon.png';
@@ -16,6 +16,7 @@ import CreateUserModal from "./modals/CreateUserModal.jsx";
import ProfileModal from "./modals/ProfileModal.jsx";
import ErrorModal from "./modals/ErrorModal.jsx";
import EditHostModal from "./modals/EditHostModal.jsx";
+import NoAuthenticationModal from "./modals/NoAuthenticationModal.jsx";
function App() {
const [isAddHostHidden, setIsAddHostHidden] = useState(true);
@@ -36,6 +37,7 @@ function App() {
port: 22,
authMethod: "Select Auth",
rememberHost: false,
+ storePassword: true,
});
const [editHostForm, setEditHostForm] = useState({
name: "",
@@ -45,6 +47,12 @@ function App() {
port: 22,
authMethod: "Select Auth",
rememberHost: true,
+ storePassword: true,
+ });
+ const [isNoAuthHidden, setIsNoAuthHidden] = useState(true);
+ const [authForm, setAuthForm] = useState({
+ password: "",
+ rsaKey: "",
});
const [loginUserForm, setLoginUserForm] = useState({
username: "",
@@ -136,10 +144,19 @@ function App() {
}, []);
const handleAddHost = () => {
- if (addHostForm.ip && addHostForm.user && ((addHostForm.authMethod === 'password' && addHostForm.password) || (addHostForm.authMethod === 'rsaKey' && addHostForm.rsaKey)) && addHostForm.port && addHostForm.authMethod !== 'Select Auth') {
- connectToHost();
- if (addHostForm.rememberHost) {
- handleSaveHost();
+ if (addHostForm.ip && addHostForm.user && addHostForm.port && addHostForm.authMethod !== 'Select Auth') {
+ if (addHostForm.authMethod === 'password' && !addHostForm.password) {
+ setIsNoAuthHidden(false);
+ } else if (addHostForm.authMethod === 'rsaKey' && !addHostForm.rsaKey) {
+ setIsNoAuthHidden(false);
+ } else {
+ connectToHost();
+ if (addHostForm.rememberHost) {
+ if (!addHostForm.storePassword) {
+ addHostForm.password = '';
+ }
+ handleSaveHost();
+ }
}
} else {
alert("Please fill out all fields.");
@@ -166,6 +183,24 @@ function App() {
setAddHostForm({ name: "", ip: "", user: "", password: "", rsaKey: "", port: 22, authMethod: "Select Auth" });
}
+ const handleAuthSubmit = (form) => {
+ const updatedTerminals = terminals.map((terminal) => {
+ if (terminal.id === activeTab) {
+ return {
+ ...terminal,
+ hostConfig: {
+ ...terminal.hostConfig,
+ password: form.password,
+ rsaKey: form.rsaKey
+ }
+ };
+ }
+ return terminal;
+ });
+ setTerminals(updatedTerminals);
+ setIsNoAuthHidden(true);
+ };
+
const connectToHostWithConfig = (hostConfig) => {
const newTerminal = {
id: nextId,
@@ -195,23 +230,6 @@ function App() {
}
}
- const createFolder = (folderName) => {
- if (userRef.current) {
- userRef.current.createFolder({
- folderName,
- });
- }
- }
-
- const moveHostToFolder = (folderName, hostConfig) => {
- if (userRef.current) {
- userRef.current.moveHostToFolder({
- folderName,
- hostConfig,
- });
- }
- }
-
const handleLoginUser = ({ username, password, sessionToken, onSuccess, onFailure }) => {
if (userRef.current) {
if (sessionToken) {
@@ -231,6 +249,12 @@ function App() {
}
};
+ const handleGuestLogin = () => {
+ if (userRef.current) {
+ userRef.current.loginAsGuest();
+ }
+ }
+
const handleCreateUser = ({ username, password, onSuccess, onFailure }) => {
if (userRef.current) {
userRef.current.createUser({
@@ -287,21 +311,31 @@ function App() {
}
};
- const handleEditHost = () => {
- if (editHostForm.ip && editHostForm.user && ((editHostForm.authMethod === 'password' && editHostForm.password) || (editHostForm.authMethod === 'rsaKey' && editHostForm.rsaKey)) && editHostForm.port && editHostForm.authMethod !== 'Select Auth') {
- editHostForm.rememberHost = true;
-
- if (currentHostConfig) {
- userRef.current.editHost({
- oldHostConfig: currentHostConfig,
- newHostConfig: editHostForm,
- });
- setIsEditHostHidden(true);
- } else {
- alert("Host not found");
+ const handleEditHost = async () => {
+ 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 = '';
}
- } else {
- alert("Please fill out all fields.");
+
+ 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);
+ } catch (error) {
+ alert('Edit failed: ' + error);
}
};
@@ -439,12 +473,20 @@ function App() {
key={terminal.id}
hostConfig={terminal.hostConfig}
isVisible={activeTab === terminal.id || splitTabIds.includes(terminal.id)}
+ setIsNoAuthHidden={setIsNoAuthHidden}
ref={(ref) => {
terminal.terminalRef = ref;
}}
/>
))}
+