Fixed control v pasting formating. Reorganized location of scripts. Visbile password and confirm password. Guest login. OpenSSH key authentication. Optional to remember password. Serach for host viewer.
This commit is contained in:
241
package-lock.json
generated
241
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@fontsource/inter": "^5.1.1",
|
"@fontsource/inter": "^5.1.1",
|
||||||
|
"@mui/icons-material": "^6.4.7",
|
||||||
"@mui/joy": "^5.0.0-beta.51",
|
"@mui/joy": "^5.0.0-beta.51",
|
||||||
"@tailwindcss/vite": "^4.0.8",
|
"@tailwindcss/vite": "^4.0.8",
|
||||||
"@tiptap/extension-link": "^2.11.5",
|
"@tiptap/extension-link": "^2.11.5",
|
||||||
@@ -1321,6 +1322,32 @@
|
|||||||
"url": "https://opencollective.com/mui-org"
|
"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": {
|
"node_modules/@mui/joy": {
|
||||||
"version": "5.0.0-beta.51",
|
"version": "5.0.0-beta.51",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.51.tgz",
|
"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": {
|
"node_modules/@mui/private-theming": {
|
||||||
"version": "5.16.14",
|
"version": "5.16.14",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.14.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.14.tgz",
|
||||||
@@ -2583,7 +2813,6 @@
|
|||||||
"version": "18.3.18",
|
"version": "18.3.18",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
||||||
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
@@ -2600,6 +2829,16 @@
|
|||||||
"@types/react": "^18.0.0"
|
"@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": {
|
"node_modules/@types/use-sync-external-store": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@fontsource/inter": "^5.1.1",
|
"@fontsource/inter": "^5.1.1",
|
||||||
|
"@mui/icons-material": "^6.4.7",
|
||||||
"@mui/joy": "^5.0.0-beta.51",
|
"@mui/joy": "^5.0.0-beta.51",
|
||||||
"@tailwindcss/vite": "^4.0.8",
|
"@tailwindcss/vite": "^4.0.8",
|
||||||
"@tiptap/extension-link": "^2.11.5",
|
"@tiptap/extension-link": "^2.11.5",
|
||||||
|
|||||||
111
src/App.jsx
111
src/App.jsx
@@ -1,14 +1,14 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { NewTerminal } from "./Terminal.jsx";
|
import { NewTerminal } from "./apps/ssh/Terminal.jsx";
|
||||||
import { User } from "./User.jsx";
|
import { User } from "./apps/user/User.jsx";
|
||||||
import AddHostModal from "./modals/AddHostModal.jsx";
|
import AddHostModal from "./modals/AddHostModal.jsx";
|
||||||
import LoginUserModal from "./modals/LoginUserModal.jsx";
|
import LoginUserModal from "./modals/LoginUserModal.jsx";
|
||||||
import { Button } from "@mui/joy";
|
import { Button } from "@mui/joy";
|
||||||
import { CssVarsProvider } from "@mui/joy";
|
import { CssVarsProvider } from "@mui/joy";
|
||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
import TabList from "./TabList.jsx";
|
import TabList from "./ui/TabList.jsx";
|
||||||
import Launchpad from "./Launchpad.jsx";
|
import Launchpad from "./apps/Launchpad.jsx";
|
||||||
import { Debounce } from './Utils';
|
import { Debounce } from './other/Utils.jsx';
|
||||||
import TermixIcon from "./images/termix_icon.png";
|
import TermixIcon from "./images/termix_icon.png";
|
||||||
import RocketIcon from './images/launchpad_rocket.png';
|
import RocketIcon from './images/launchpad_rocket.png';
|
||||||
import ProfileIcon from './images/profile_icon.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 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";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isAddHostHidden, setIsAddHostHidden] = useState(true);
|
const [isAddHostHidden, setIsAddHostHidden] = useState(true);
|
||||||
@@ -36,6 +37,7 @@ function App() {
|
|||||||
port: 22,
|
port: 22,
|
||||||
authMethod: "Select Auth",
|
authMethod: "Select Auth",
|
||||||
rememberHost: false,
|
rememberHost: false,
|
||||||
|
storePassword: true,
|
||||||
});
|
});
|
||||||
const [editHostForm, setEditHostForm] = useState({
|
const [editHostForm, setEditHostForm] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
@@ -45,6 +47,12 @@ function App() {
|
|||||||
port: 22,
|
port: 22,
|
||||||
authMethod: "Select Auth",
|
authMethod: "Select Auth",
|
||||||
rememberHost: true,
|
rememberHost: true,
|
||||||
|
storePassword: true,
|
||||||
|
});
|
||||||
|
const [isNoAuthHidden, setIsNoAuthHidden] = useState(true);
|
||||||
|
const [authForm, setAuthForm] = useState({
|
||||||
|
password: "",
|
||||||
|
rsaKey: "",
|
||||||
});
|
});
|
||||||
const [loginUserForm, setLoginUserForm] = useState({
|
const [loginUserForm, setLoginUserForm] = useState({
|
||||||
username: "",
|
username: "",
|
||||||
@@ -136,11 +144,20 @@ function App() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleAddHost = () => {
|
const handleAddHost = () => {
|
||||||
if (addHostForm.ip && addHostForm.user && ((addHostForm.authMethod === 'password' && addHostForm.password) || (addHostForm.authMethod === 'rsaKey' && addHostForm.rsaKey)) && addHostForm.port && addHostForm.authMethod !== 'Select Auth') {
|
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();
|
connectToHost();
|
||||||
if (addHostForm.rememberHost) {
|
if (addHostForm.rememberHost) {
|
||||||
|
if (!addHostForm.storePassword) {
|
||||||
|
addHostForm.password = '';
|
||||||
|
}
|
||||||
handleSaveHost();
|
handleSaveHost();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
alert("Please fill out all fields.");
|
alert("Please fill out all fields.");
|
||||||
}
|
}
|
||||||
@@ -166,6 +183,24 @@ function App() {
|
|||||||
setAddHostForm({ name: "", ip: "", user: "", password: "", rsaKey: "", port: 22, authMethod: "Select Auth" });
|
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 connectToHostWithConfig = (hostConfig) => {
|
||||||
const newTerminal = {
|
const newTerminal = {
|
||||||
id: nextId,
|
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 }) => {
|
const handleLoginUser = ({ username, password, sessionToken, onSuccess, onFailure }) => {
|
||||||
if (userRef.current) {
|
if (userRef.current) {
|
||||||
if (sessionToken) {
|
if (sessionToken) {
|
||||||
@@ -231,6 +249,12 @@ function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGuestLogin = () => {
|
||||||
|
if (userRef.current) {
|
||||||
|
userRef.current.loginAsGuest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleCreateUser = ({ username, password, onSuccess, onFailure }) => {
|
const handleCreateUser = ({ username, password, onSuccess, onFailure }) => {
|
||||||
if (userRef.current) {
|
if (userRef.current) {
|
||||||
userRef.current.createUser({
|
userRef.current.createUser({
|
||||||
@@ -287,21 +311,31 @@ function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditHost = () => {
|
const handleEditHost = async () => {
|
||||||
if (editHostForm.ip && editHostForm.user && ((editHostForm.authMethod === 'password' && editHostForm.password) || (editHostForm.authMethod === 'rsaKey' && editHostForm.rsaKey)) && editHostForm.port && editHostForm.authMethod !== 'Select Auth') {
|
try {
|
||||||
editHostForm.rememberHost = true;
|
// 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 (currentHostConfig) {
|
await userRef.current.editHost({
|
||||||
userRef.current.editHost({
|
|
||||||
oldHostConfig: currentHostConfig,
|
oldHostConfig: currentHostConfig,
|
||||||
newHostConfig: editHostForm,
|
newHostConfig: editHostForm,
|
||||||
});
|
});
|
||||||
setIsEditHostHidden(true);
|
|
||||||
} else {
|
// Refresh the updated config
|
||||||
alert("Host not found");
|
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);
|
||||||
}
|
}
|
||||||
} else {
|
setIsEditHostHidden(true);
|
||||||
alert("Please fill out all fields.");
|
} catch (error) {
|
||||||
|
alert('Edit failed: ' + error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -439,12 +473,20 @@ function App() {
|
|||||||
key={terminal.id}
|
key={terminal.id}
|
||||||
hostConfig={terminal.hostConfig}
|
hostConfig={terminal.hostConfig}
|
||||||
isVisible={activeTab === terminal.id || splitTabIds.includes(terminal.id)}
|
isVisible={activeTab === terminal.id || splitTabIds.includes(terminal.id)}
|
||||||
|
setIsNoAuthHidden={setIsNoAuthHidden}
|
||||||
ref={(ref) => {
|
ref={(ref) => {
|
||||||
terminal.terminalRef = ref;
|
terminal.terminalRef = ref;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
<NoAuthenticationModal
|
||||||
|
isHidden={isNoAuthHidden}
|
||||||
|
form={authForm}
|
||||||
|
setForm={setAuthForm}
|
||||||
|
setIsNoAuthHidden={setIsNoAuthHidden}
|
||||||
|
handleAuthSubmit={handleAuthSubmit}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -495,8 +537,6 @@ function App() {
|
|||||||
isErrorHidden={isErrorHidden}
|
isErrorHidden={isErrorHidden}
|
||||||
deleteHost={deleteHost}
|
deleteHost={deleteHost}
|
||||||
editHost={updateEditHostForm}
|
editHost={updateEditHostForm}
|
||||||
createFolder={createFolder}
|
|
||||||
moveHostToFolder={moveHostToFolder}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -505,6 +545,7 @@ function App() {
|
|||||||
form={loginUserForm}
|
form={loginUserForm}
|
||||||
setForm={setLoginUserForm}
|
setForm={setLoginUserForm}
|
||||||
handleLoginUser={handleLoginUser}
|
handleLoginUser={handleLoginUser}
|
||||||
|
handleGuestLogin={handleGuestLogin}
|
||||||
setIsLoginUserHidden={setIsLoginUserHidden}
|
setIsLoginUserHidden={setIsLoginUserHidden}
|
||||||
setIsCreateUserHidden={setIsCreateUserHidden}
|
setIsCreateUserHidden={setIsCreateUserHidden}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ import PropTypes from 'prop-types';
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { CssVarsProvider } from '@mui/joy/styles';
|
import { CssVarsProvider } from '@mui/joy/styles';
|
||||||
import { Button } from '@mui/joy';
|
import { Button } from '@mui/joy';
|
||||||
import HostViewerIcon from './images/host_viewer_icon.png';
|
import HostViewerIcon from '../images/host_viewer_icon.png';
|
||||||
import theme from './theme';
|
import theme from '../theme.js';
|
||||||
|
|
||||||
// Apps
|
// Apps
|
||||||
import HostViewer from './apps/HostViewer';
|
import HostViewer from './ssh/HostViewer.jsx';
|
||||||
|
|
||||||
function Launchpad({
|
function Launchpad({onClose,
|
||||||
onClose,
|
|
||||||
getHosts,
|
getHosts,
|
||||||
connectToHost,
|
connectToHost,
|
||||||
isAddHostHidden,
|
isAddHostHidden,
|
||||||
@@ -18,8 +17,6 @@ function Launchpad({
|
|||||||
isErrorHidden,
|
isErrorHidden,
|
||||||
deleteHost,
|
deleteHost,
|
||||||
editHost,
|
editHost,
|
||||||
createFolder,
|
|
||||||
moveHostToFolder,
|
|
||||||
}) {
|
}) {
|
||||||
const launchpadRef = useRef(null);
|
const launchpadRef = useRef(null);
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
@@ -174,8 +171,6 @@ function Launchpad({
|
|||||||
setIsAddHostHidden={setIsAddHostHidden}
|
setIsAddHostHidden={setIsAddHostHidden}
|
||||||
deleteHost={deleteHost}
|
deleteHost={deleteHost}
|
||||||
editHost={editHost}
|
editHost={editHost}
|
||||||
createFolder={createFolder}
|
|
||||||
moveHostToFolder={moveHostToFolder}
|
|
||||||
onEditHostClick={handleEditHostClick}
|
onEditHostClick={handleEditHostClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -196,8 +191,6 @@ Launchpad.propTypes = {
|
|||||||
isErrorHidden: PropTypes.bool.isRequired,
|
isErrorHidden: PropTypes.bool.isRequired,
|
||||||
deleteHost: PropTypes.func.isRequired,
|
deleteHost: PropTypes.func.isRequired,
|
||||||
editHost: PropTypes.func.isRequired,
|
editHost: PropTypes.func.isRequired,
|
||||||
createFolder: PropTypes.func.isRequired,
|
|
||||||
moveHostToFolder: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Launchpad;
|
export default Launchpad;
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { Button } from "@mui/joy";
|
import { Button, Input } from "@mui/joy";
|
||||||
|
|
||||||
function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, editHost }) {
|
function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, editHost }) {
|
||||||
const [hosts, setHosts] = useState([]);
|
const [hosts, setHosts] = useState([]);
|
||||||
|
const [filteredHosts, setFilteredHosts] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const isMounted = useRef(true);
|
const isMounted = useRef(true);
|
||||||
|
|
||||||
const fetchHosts = async () => {
|
const fetchHosts = async () => {
|
||||||
@@ -12,12 +14,14 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
|
|||||||
const savedHosts = await getHosts();
|
const savedHosts = await getHosts();
|
||||||
if (isMounted.current) {
|
if (isMounted.current) {
|
||||||
setHosts(savedHosts || []);
|
setHosts(savedHosts || []);
|
||||||
|
setFilteredHosts(savedHosts || []);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Host fetch failed:", error);
|
console.error("Host fetch failed:", error);
|
||||||
if (isMounted.current) {
|
if (isMounted.current) {
|
||||||
setHosts([]);
|
setHosts([]);
|
||||||
|
setFilteredHosts([]);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,10 +41,28 @@ 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());
|
||||||
|
});
|
||||||
|
setFilteredHosts(filtered);
|
||||||
|
}, [searchTerm, hosts]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full p-4 text-white flex flex-col">
|
<div className="h-full w-full p-4 text-white flex flex-col">
|
||||||
<div className="flex items-center justify-between mb-2 w-full">
|
<div className="flex items-center justify-between mb-2 w-full gap-2">
|
||||||
<h2 className="text-lg font-bold">Hosts</h2>
|
<Input
|
||||||
|
placeholder="Search hosts..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: "#6e6e6e",
|
||||||
|
color: "#fff",
|
||||||
|
"&::placeholder": { color: "#ccc" },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="text-black"
|
className="text-black"
|
||||||
onClick={() => setIsAddHostHidden(false)}
|
onClick={() => setIsAddHostHidden(false)}
|
||||||
@@ -55,9 +77,9 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
|
|||||||
<div className="flex-grow overflow-auto">
|
<div className="flex-grow overflow-auto">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<p className="text-gray-300">Loading hosts...</p>
|
<p className="text-gray-300">Loading hosts...</p>
|
||||||
) : hosts.length > 0 ? (
|
) : filteredHosts.length > 0 ? (
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col gap-2 w-full">
|
||||||
{hosts.map((hostWrapper, index) => {
|
{filteredHosts.map((hostWrapper, index) => {
|
||||||
const hostConfig = hostWrapper.config || {};
|
const hostConfig = hostWrapper.config || {};
|
||||||
|
|
||||||
if (!hostConfig) {
|
if (!hostConfig) {
|
||||||
@@ -4,9 +4,9 @@ import { FitAddon } from "@xterm/addon-fit";
|
|||||||
import "@xterm/xterm/css/xterm.css";
|
import "@xterm/xterm/css/xterm.css";
|
||||||
import io from "socket.io-client";
|
import io from "socket.io-client";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import theme from "./theme";
|
import theme from "../../theme.js";
|
||||||
|
|
||||||
export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
|
export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidden }, ref) => {
|
||||||
const terminalRef = useRef(null);
|
const terminalRef = useRef(null);
|
||||||
const socketRef = useRef(null);
|
const socketRef = useRef(null);
|
||||||
const fitAddon = useRef(new FitAddon());
|
const fitAddon = useRef(new FitAddon());
|
||||||
@@ -76,7 +76,11 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
|
|||||||
fitAddon.current.fit();
|
fitAddon.current.fit();
|
||||||
resizeTerminal();
|
resizeTerminal();
|
||||||
const { cols, rows } = terminalInstance.current;
|
const { cols, rows } = terminalInstance.current;
|
||||||
|
if (!hostConfig.password && !hostConfig.rsaKey) {
|
||||||
|
setIsNoAuthHidden(false);
|
||||||
|
} else {
|
||||||
socket.emit("connectToHost", cols, rows, hostConfig);
|
socket.emit("connectToHost", cols, rows, hostConfig);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("data", (data) => {
|
socket.on("data", (data) => {
|
||||||
@@ -91,23 +95,41 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
terminalInstance.current.attachCustomKeyEventHandler((event) => {
|
terminalInstance.current.attachCustomKeyEventHandler((event) => {
|
||||||
if (isPasting) return;
|
|
||||||
|
|
||||||
isPasting = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
isPasting = false;
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key === "v") {
|
if ((event.ctrlKey || event.metaKey) && event.key === "v") {
|
||||||
|
if (isPasting) return false;
|
||||||
|
isPasting = true;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
navigator.clipboard.readText().then((text) => {
|
navigator.clipboard.readText().then((text) => {
|
||||||
socketRef.current.emit("data", text);
|
text = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
||||||
|
const lines = text.split("\n");
|
||||||
|
|
||||||
|
if (socketRef.current) {
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
const sendLine = () => {
|
||||||
|
if (index < lines.length) {
|
||||||
|
socketRef.current.emit("data", lines[index] + "\r");
|
||||||
|
index++;
|
||||||
|
setTimeout(sendLine, 10);
|
||||||
|
} else {
|
||||||
|
isPasting = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sendLine();
|
||||||
|
} else {
|
||||||
|
isPasting = false;
|
||||||
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error("Failed to read clipboard contents:", err);
|
console.error("Failed to read clipboard contents:", err);
|
||||||
|
isPasting = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -120,6 +142,10 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("noAuthRequired", () => {
|
||||||
|
setIsNoAuthHidden(false);
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("error", (err) => {
|
socket.on("error", (err) => {
|
||||||
terminalInstance.current.write(`\r\n*** Error: ${err} ***\r\n`);
|
terminalInstance.current.write(`\r\n*** Error: ${err} ***\r\n`);
|
||||||
});
|
});
|
||||||
@@ -173,8 +199,10 @@ NewTerminal.propTypes = {
|
|||||||
hostConfig: PropTypes.shape({
|
hostConfig: PropTypes.shape({
|
||||||
ip: PropTypes.string.isRequired,
|
ip: PropTypes.string.isRequired,
|
||||||
user: PropTypes.string.isRequired,
|
user: PropTypes.string.isRequired,
|
||||||
password: PropTypes.string.isRequired,
|
password: PropTypes.string,
|
||||||
port: PropTypes.string.isRequired,
|
rsaKey: PropTypes.string,
|
||||||
|
port: PropTypes.number.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
isVisible: PropTypes.bool.isRequired,
|
isVisible: PropTypes.bool.isRequired,
|
||||||
|
setIsNoAuthHidden: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
@@ -12,12 +12,7 @@ const socket = io(SOCKET_URL, {
|
|||||||
autoConnect: false,
|
autoConnect: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const User = forwardRef(({
|
export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSuccess, onFailure }, ref) => {
|
||||||
onLoginSuccess,
|
|
||||||
onCreateSuccess,
|
|
||||||
onDeleteSuccess,
|
|
||||||
onFailure
|
|
||||||
}, ref) => {
|
|
||||||
const socketRef = useRef(socket);
|
const socketRef = useRef(socket);
|
||||||
const currentUser = useRef(null);
|
const currentUser = useRef(null);
|
||||||
|
|
||||||
@@ -100,6 +95,28 @@ export const User = forwardRef(({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loginAsGuest = async () => {
|
||||||
|
try {
|
||||||
|
const response = await new Promise((resolve) => {
|
||||||
|
socketRef.current.emit("loginAsGuest", resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.success) {
|
||||||
|
currentUser.current = {
|
||||||
|
id: response.user.id,
|
||||||
|
username: response.user.username,
|
||||||
|
sessionToken: response.user.sessionToken,
|
||||||
|
};
|
||||||
|
localStorage.setItem("sessionToken", response.user.sessionToken);
|
||||||
|
onLoginSuccess(response.user);
|
||||||
|
} else {
|
||||||
|
throw new Error(response?.error || "Guest login failed");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
onFailure(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const logoutUser = () => {
|
const logoutUser = () => {
|
||||||
localStorage.removeItem("sessionToken");
|
localStorage.removeItem("sessionToken");
|
||||||
currentUser.current = null;
|
currentUser.current = null;
|
||||||
@@ -236,6 +253,7 @@ export const User = forwardRef(({
|
|||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
createUser,
|
createUser,
|
||||||
loginUser,
|
loginUser,
|
||||||
|
loginAsGuest,
|
||||||
logoutUser,
|
logoutUser,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
saveHost,
|
saveHost,
|
||||||
@@ -35,7 +35,8 @@ const User = mongoose.model('User', userSchema);
|
|||||||
const Host = mongoose.model('Host', hostSchema);
|
const Host = mongoose.model('Host', hostSchema);
|
||||||
|
|
||||||
const getEncryptionKey = (userId, sessionToken) => {
|
const getEncryptionKey = (userId, sessionToken) => {
|
||||||
return crypto.scryptSync(`${userId}-${sessionToken}`, 'salt', 32);
|
const salt = process.env.SALT || 'default_salt';
|
||||||
|
return crypto.scryptSync(`${userId}-${sessionToken}`, salt, 32);
|
||||||
};
|
};
|
||||||
|
|
||||||
const encryptData = (data, userId, sessionToken) => {
|
const encryptData = (data, userId, sessionToken) => {
|
||||||
@@ -130,6 +131,29 @@ io.of('/database.io').on('connection', (socket) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('loginAsGuest', async (callback) => {
|
||||||
|
try {
|
||||||
|
const username = `guest-${crypto.randomBytes(4).toString('hex')}`;
|
||||||
|
const sessionToken = crypto.randomBytes(64).toString('hex');
|
||||||
|
|
||||||
|
const user = await User.create({
|
||||||
|
username,
|
||||||
|
password: await bcrypt.hash(username, 10),
|
||||||
|
sessionToken
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Guest user created: ${username}`);
|
||||||
|
callback({ success: true, user: {
|
||||||
|
id: user._id,
|
||||||
|
username: user.username,
|
||||||
|
sessionToken
|
||||||
|
}});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Guest login error:', error);
|
||||||
|
callback({error: 'Guest login failed'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
socket.on('saveHostConfig', async ({ userId, sessionToken, hostConfig }, callback) => {
|
socket.on('saveHostConfig', async ({ userId, sessionToken, hostConfig }, callback) => {
|
||||||
try {
|
try {
|
||||||
if (!userId || !sessionToken) {
|
if (!userId || !sessionToken) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ io.on("connection", (socket) => {
|
|||||||
socket.on("connectToHost", (cols, rows, hostConfig) => {
|
socket.on("connectToHost", (cols, rows, hostConfig) => {
|
||||||
if (!hostConfig || !hostConfig.ip || !hostConfig.user || (!hostConfig.password && !hostConfig.rsaKey) || !hostConfig.port) {
|
if (!hostConfig || !hostConfig.ip || !hostConfig.user || (!hostConfig.password && !hostConfig.rsaKey) || !hostConfig.port) {
|
||||||
logger.error("Invalid hostConfig received:", hostConfig);
|
logger.error("Invalid hostConfig received:", hostConfig);
|
||||||
|
socket.emit("noAuthRequired");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,17 @@ import {
|
|||||||
ModalDialog,
|
ModalDialog,
|
||||||
Select,
|
Select,
|
||||||
Option,
|
Option,
|
||||||
Checkbox
|
Checkbox,
|
||||||
|
IconButton
|
||||||
} from '@mui/joy';
|
} from '@mui/joy';
|
||||||
import theme from '/src/theme';
|
import theme from '/src/theme';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Visibility from '@mui/icons-material/Visibility';
|
||||||
|
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 handleFileChange = (e) => {
|
const handleFileChange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
@@ -40,9 +46,34 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (isFormValid()) {
|
||||||
|
handleAddHost();
|
||||||
|
setForm({
|
||||||
|
name: '',
|
||||||
|
ip: '',
|
||||||
|
user: '',
|
||||||
|
password: '',
|
||||||
|
rsaKey: '',
|
||||||
|
port: 22,
|
||||||
|
authMethod: 'Select Auth',
|
||||||
|
rememberHost: false,
|
||||||
|
storePassword: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CssVarsProvider theme={theme}>
|
<CssVarsProvider theme={theme}>
|
||||||
<Modal open={!isHidden} onClose={() => setIsAddHostHidden(true)}>
|
<Modal open={!isHidden} onClose={() => setIsAddHostHidden(true)}
|
||||||
|
sx={{
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ModalDialog
|
<ModalDialog
|
||||||
layout="center"
|
layout="center"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -51,26 +82,17 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
padding: 3,
|
padding: 3,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
width: "auto",
|
maxWidth: '400px',
|
||||||
maxWidth: "90vw",
|
width: '100%',
|
||||||
minWidth: "fit-content",
|
overflow: 'hidden',
|
||||||
overflow: "hidden",
|
boxSizing: 'border-box',
|
||||||
display: "flex",
|
mx: 2,
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle>Add Host</DialogTitle>
|
<DialogTitle>Add Host</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<form
|
<form onSubmit={handleSubmit}>
|
||||||
onSubmit={(event) => {
|
<Stack spacing={2} sx={{ width: '100%' }}>
|
||||||
event.preventDefault();
|
|
||||||
if (isFormValid()) {
|
|
||||||
handleAddHost();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>Host Name</FormLabel>
|
<FormLabel>Host Name</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
@@ -130,16 +152,28 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
{form.authMethod === 'password' && (
|
{form.authMethod === 'password' && (
|
||||||
<FormControl error={!form.password}>
|
<FormControl error={!form.password}>
|
||||||
<FormLabel>Host Password</FormLabel>
|
<FormLabel>Host Password</FormLabel>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<Input
|
<Input
|
||||||
type="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 })}
|
||||||
required
|
required
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
|
flex: 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
marginLeft: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
{form.authMethod === 'rsaKey' && (
|
{form.authMethod === 'rsaKey' && (
|
||||||
@@ -155,8 +189,6 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
padding: 1,
|
padding: 1,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
minWidth: 'auto',
|
|
||||||
minHeight: 'auto',
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -188,6 +220,21 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
{form.rememberHost && (
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Store Password</FormLabel>
|
||||||
|
<Checkbox
|
||||||
|
checked={form.storePassword}
|
||||||
|
onChange={(e) => setForm({ ...form, storePassword: e.target.checked })}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
'&.Mui-checked': {
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!isFormValid()}
|
disabled={!isFormValid()}
|
||||||
@@ -220,6 +267,7 @@ AddHostModal.propTypes = {
|
|||||||
port: PropTypes.number.isRequired,
|
port: PropTypes.number.isRequired,
|
||||||
authMethod: PropTypes.string.isRequired,
|
authMethod: PropTypes.string.isRequired,
|
||||||
rememberHost: PropTypes.bool,
|
rememberHost: PropTypes.bool,
|
||||||
|
storePassword: PropTypes.bool,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
setForm: PropTypes.func.isRequired,
|
setForm: PropTypes.func.isRequired,
|
||||||
handleAddHost: PropTypes.func.isRequired,
|
handleAddHost: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
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, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
|
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog, IconButton } from '@mui/joy';
|
||||||
import theme from '/src/theme';
|
import theme from '/src/theme';
|
||||||
import { useEffect } from 'react';
|
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 CreateUserModal = ({ isHidden, form, setForm, handleCreateUser, setIsCreateUserHidden, setIsLoginUserHidden }) => {
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
|
|
||||||
const isFormValid = () => {
|
const isFormValid = () => {
|
||||||
if (!form.username || !form.password) return false;
|
if (!form.username || !form.password || form.password !== confirmPassword) return false;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19,6 +25,7 @@ const CreateUserModal = ({ isHidden, form, setForm, handleCreateUser, setIsCreat
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
setForm({ username: '', password: '' });
|
setForm({ username: '', password: '' });
|
||||||
|
setConfirmPassword('');
|
||||||
}
|
}
|
||||||
}, [isHidden]);
|
}, [isHidden]);
|
||||||
|
|
||||||
@@ -64,15 +71,51 @@ const CreateUserModal = ({ isHidden, form, setForm, handleCreateUser, setIsCreat
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>Password</FormLabel>
|
<FormLabel>Password</FormLabel>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type={showPassword ? 'text' : 'password'}
|
||||||
value={form.password}
|
value={form.password}
|
||||||
onChange={(event) => setForm({ ...form, password: event.target.value })}
|
onChange={(event) => setForm({ ...form, password: event.target.value })}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
|
flex: 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
marginLeft: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Confirm Password</FormLabel>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Input
|
||||||
|
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,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
marginLeft: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showConfirmPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -89,6 +132,7 @@ const CreateUserModal = ({ isHidden, form, setForm, handleCreateUser, setIsCreat
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setForm({ username: '', password: '' });
|
setForm({ username: '', password: '' });
|
||||||
|
setConfirmPassword('');
|
||||||
setIsCreateUserHidden(true);
|
setIsCreateUserHidden(true);
|
||||||
setIsLoginUserHidden(false);
|
setIsLoginUserHidden(false);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { CssVarsProvider } from '@mui/joy/styles';
|
import { CssVarsProvider } from '@mui/joy/styles';
|
||||||
import {
|
import {
|
||||||
Modal,
|
Modal,
|
||||||
@@ -12,57 +12,100 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
Select,
|
Select,
|
||||||
Option
|
Option,
|
||||||
|
IconButton,
|
||||||
|
Checkbox
|
||||||
} from '@mui/joy';
|
} from '@mui/joy';
|
||||||
import theme from '/src/theme';
|
import theme from '/src/theme';
|
||||||
|
import Visibility from '@mui/icons-material/Visibility';
|
||||||
|
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||||
|
|
||||||
const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostHidden, hostConfig }) => {
|
const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostHidden, hostConfig }) => {
|
||||||
const handleFileChange = (e) => {
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
if (file.name.endsWith('.rsa') || file.name.endsWith('.key') || file.name.endsWith('.pem') || file.name.endsWith('.der') || file.name.endsWith('.p8') || file.name.endsWith('.ssh')) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (event) => {
|
|
||||||
setForm({ ...form, rsaKey: event.target.result });
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
} else {
|
|
||||||
alert("Please upload a valid RSA private key file.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isFormValid = () => {
|
|
||||||
if (form.authMethod === 'Select Auth') return false;
|
|
||||||
if (!form.ip || !form.user || !form.port) return false;
|
|
||||||
if (form.authMethod === 'rsaKey' && !form.rsaKey) return false;
|
|
||||||
if (form.authMethod === 'password' && !form.password) return false;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditHostInternal = (form) => {
|
|
||||||
const updatedForm = { ...form, name: form.name || form.ip };
|
|
||||||
handleEditHost(updatedForm);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hostConfig) {
|
if (hostConfig) {
|
||||||
|
const storePassword = hostConfig.password || hostConfig.rsaKey;
|
||||||
|
|
||||||
setForm({
|
setForm({
|
||||||
|
...form,
|
||||||
name: hostConfig.name || '',
|
name: hostConfig.name || '',
|
||||||
ip: hostConfig.ip || '',
|
ip: hostConfig.ip || '',
|
||||||
user: hostConfig.user || '',
|
user: hostConfig.user || '',
|
||||||
password: hostConfig.password || '',
|
password: storePassword && hostConfig.password ? hostConfig.password : '',
|
||||||
rsaKey: hostConfig.rsaKey || '',
|
rsaKey: '',
|
||||||
port: Number(hostConfig.port) || 22,
|
port: Number(hostConfig.port) || 22,
|
||||||
authMethod: hostConfig.password ? 'password' : 'rsaKey',
|
authMethod: hostConfig.rsaKey ? 'rsaKey' : (storePassword ? 'password' : 'Select Auth'),
|
||||||
rememberHost: hostConfig.rememberHost || false,
|
rememberHost: hostConfig.rememberHost || true,
|
||||||
|
storePassword: storePassword ?? false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [hostConfig, setForm]);
|
}, [hostConfig, setForm]);
|
||||||
|
|
||||||
|
const handleFileChange = (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file.name.endsWith('.rsa') || file.name.endsWith('.key') || file.name.endsWith('.pem') || file.name.endsWith('.der') || file.name.endsWith('.p8') || file.name.endsWith('.ssh') || file.name.endsWith('.pub')) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (evt) => {
|
||||||
|
setForm((prev) => ({ ...prev, rsaKey: evt.target.result }));
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
} else {
|
||||||
|
alert('Please upload a valid RSA private key file.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAuthChange = (newMethod) => {
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
authMethod: newMethod
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStorePasswordChange = (checked) => {
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
storePassword: checked,
|
||||||
|
authMethod: checked ? 'password' : 'Select Auth'
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFormValid = () => {
|
||||||
|
const { ip, user, port, authMethod, password, rsaKey, storePassword } = form;
|
||||||
|
if (!ip?.trim() || !user?.trim() || !port) return false;
|
||||||
|
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;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isFormValid()) {
|
||||||
|
const { authMethod, password, rsaKey, storePassword, ...rest } = form;
|
||||||
|
handleEditHost({
|
||||||
|
...rest,
|
||||||
|
authMethod,
|
||||||
|
password: authMethod === 'password' && storePassword ? password : '',
|
||||||
|
rsaKey: authMethod === 'rsaKey' ? rsaKey : ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CssVarsProvider theme={theme}>
|
<CssVarsProvider theme={theme}>
|
||||||
<Modal open={!isHidden} onClose={() => setIsEditHostHidden(true)}>
|
<Modal open={!isHidden} onClose={() => setIsEditHostHidden(true)}
|
||||||
|
sx={{
|
||||||
|
overflowX: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ModalDialog
|
<ModalDialog
|
||||||
layout="center"
|
layout="center"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -71,139 +114,167 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
|
|||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
padding: 3,
|
padding: 3,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
width: "auto",
|
maxWidth: '400px',
|
||||||
maxWidth: "90vw",
|
width: '100%',
|
||||||
minWidth: "fit-content",
|
overflow: 'hidden',
|
||||||
overflow: "hidden",
|
boxSizing: 'border-box',
|
||||||
display: "flex",
|
mx: 2,
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle>Edit Host</DialogTitle>
|
<DialogTitle>Edit Host</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<form
|
<form onSubmit={handleSubmit}>
|
||||||
onSubmit={(event) => {
|
<Stack spacing={2} sx={{ width: '100%', overflow: 'hidden' }}>
|
||||||
event.preventDefault();
|
|
||||||
if (isFormValid()) handleEditHostInternal(form);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>Host Name</FormLabel>
|
<FormLabel>Host Name</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
value={form.name}
|
value={form.name}
|
||||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl error={!form.ip}>
|
<FormControl error={!form.ip}>
|
||||||
<FormLabel>Host IP</FormLabel>
|
<FormLabel>Host IP</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
value={form.ip}
|
value={form.ip}
|
||||||
onChange={(e) => setForm({ ...form, ip: e.target.value })}
|
onChange={(e) => setForm((prev) => ({ ...prev, ip: e.target.value }))}
|
||||||
required
|
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl error={!form.user}>
|
<FormControl error={!form.user}>
|
||||||
<FormLabel>Host User</FormLabel>
|
<FormLabel>Host User</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
value={form.user}
|
value={form.user}
|
||||||
onChange={(e) => setForm({ ...form, user: e.target.value })}
|
onChange={(e) => setForm((prev) => ({ ...prev, user: e.target.value }))}
|
||||||
required
|
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl error={!form.authMethod || form.authMethod === 'Select Auth'}>
|
|
||||||
|
{form.storePassword && form.authMethod !== 'Select Auth' && (
|
||||||
|
<FormControl error={form.authMethod === 'Select Auth'}>
|
||||||
<FormLabel>Authentication Method</FormLabel>
|
<FormLabel>Authentication Method</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
value={form.authMethod || 'Select Auth'}
|
value={form.authMethod}
|
||||||
onChange={(e, newValue) => setForm({ ...form, authMethod: newValue })}
|
onChange={(e, val) => handleAuthChange(val)}
|
||||||
required
|
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: !form.authMethod || form.authMethod === 'Select Auth' ? theme.palette.general.tertiary : theme.palette.general.primary,
|
backgroundColor:
|
||||||
|
form.authMethod === 'Select Auth'
|
||||||
|
? theme.palette.general.tertiary
|
||||||
|
: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: theme.palette.general.disabled,
|
backgroundColor: theme.palette.general.disabled
|
||||||
},
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Option value="Select Auth" disabled>
|
<Option value="Select Auth" disabled>Select Auth</Option>
|
||||||
Select Auth
|
|
||||||
</Option>
|
|
||||||
<Option value="password">Password</Option>
|
<Option value="password">Password</Option>
|
||||||
<Option value="rsaKey">RSA Key</Option>
|
<Option value="rsaKey">RSA Key</Option>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{form.authMethod === 'password' && (
|
)}
|
||||||
|
|
||||||
|
{form.authMethod === 'password' && form.storePassword && (
|
||||||
<FormControl error={!form.password}>
|
<FormControl error={!form.password}>
|
||||||
<FormLabel>Host Password</FormLabel>
|
<FormLabel>Password</FormLabel>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type={showPassword ? 'text' : 'password'}
|
||||||
value={form.password}
|
value={form.password}
|
||||||
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
onChange={(e) =>
|
||||||
required
|
setForm((prev) => ({ ...prev, password: e.target.value }))
|
||||||
|
}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
|
flex: 1
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
marginLeft: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
{form.authMethod === 'rsaKey' && (
|
|
||||||
<FormControl error={!form.rsaKey}>
|
{form.authMethod === 'rsaKey' && form.storePassword && (
|
||||||
|
<FormControl
|
||||||
|
error={!form.rsaKey && !hostConfig?.rsaKey}
|
||||||
|
sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center' }}
|
||||||
|
>
|
||||||
<FormLabel>RSA Key</FormLabel>
|
<FormLabel>RSA Key</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
type="file"
|
type="file"
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
required
|
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
padding: 1,
|
alignItems: 'center'
|
||||||
textAlign: 'center',
|
|
||||||
width: '100%',
|
|
||||||
minWidth: 'auto',
|
|
||||||
minHeight: 'auto',
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{hostConfig?.rsaKey && !form.rsaKey && (
|
||||||
|
<FormLabel sx={{ color: theme.palette.text.secondary }}>
|
||||||
|
Existing key detected. Upload to replace.
|
||||||
|
</FormLabel>
|
||||||
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormControl error={form.port < 1 || form.port > 65535}>
|
<FormControl error={form.port < 1 || form.port > 65535}>
|
||||||
<FormLabel>Host Port</FormLabel>
|
<FormLabel>Host Port</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
value={form.port}
|
value={form.port}
|
||||||
onChange={(e) => setForm({ ...form, port: e.target.value })}
|
onChange={(e) => setForm((prev) => ({ ...prev, port: e.target.value }))}
|
||||||
min={1}
|
|
||||||
max={65535}
|
|
||||||
required
|
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Store Password</FormLabel>
|
||||||
|
<Checkbox
|
||||||
|
checked={form.storePassword}
|
||||||
|
onChange={(e) => handleStorePasswordChange(e.target.checked)}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
'&.Mui-checked': {
|
||||||
|
color: theme.palette.text.primary
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!isFormValid()}
|
disabled={!isFormValid()}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: theme.palette.general.disabled,
|
backgroundColor: theme.palette.general.disabled
|
||||||
},
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit Host
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
@@ -216,20 +287,11 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
|
|||||||
|
|
||||||
EditHostModal.propTypes = {
|
EditHostModal.propTypes = {
|
||||||
isHidden: PropTypes.bool.isRequired,
|
isHidden: PropTypes.bool.isRequired,
|
||||||
form: PropTypes.shape({
|
form: PropTypes.object.isRequired,
|
||||||
name: PropTypes.string,
|
|
||||||
ip: PropTypes.string.isRequired,
|
|
||||||
user: PropTypes.string.isRequired,
|
|
||||||
password: PropTypes.string,
|
|
||||||
rsaKey: PropTypes.string,
|
|
||||||
port: PropTypes.number.isRequired,
|
|
||||||
authMethod: PropTypes.string.isRequired,
|
|
||||||
rememberHost: PropTypes.bool,
|
|
||||||
}).isRequired,
|
|
||||||
setForm: PropTypes.func.isRequired,
|
setForm: PropTypes.func.isRequired,
|
||||||
handleEditHost: PropTypes.func.isRequired,
|
handleEditHost: PropTypes.func.isRequired,
|
||||||
setIsEditHostHidden: PropTypes.func.isRequired,
|
setIsEditHostHidden: PropTypes.func.isRequired,
|
||||||
hostConfig: PropTypes.object,
|
hostConfig: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EditHostModal;
|
export default EditHostModal;
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
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, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
|
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog, IconButton } from '@mui/joy';
|
||||||
import theme from '/src/theme';
|
import theme from '/src/theme';
|
||||||
import {useEffect} from 'react';
|
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 LoginUserModal = ({ isHidden, form, setForm, handleLoginUser, setIsLoginUserHidden, setIsCreateUserHidden }) => {
|
|
||||||
const isFormValid = () => {
|
const isFormValid = () => {
|
||||||
if (!form.username || !form.password) return false;
|
if (!form.username || !form.password) return false;
|
||||||
return true;
|
return true;
|
||||||
@@ -64,15 +68,27 @@ const LoginUserModal = ({ isHidden, form, setForm, handleLoginUser, setIsLoginUs
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>Password</FormLabel>
|
<FormLabel>Password</FormLabel>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type={showPassword ? 'text' : 'password'}
|
||||||
value={form.password}
|
value={form.password}
|
||||||
onChange={(event) => setForm({ ...form, password: event.target.value })}
|
onChange={(event) => setForm({ ...form, password: event.target.value })}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.general.primary,
|
backgroundColor: theme.palette.general.primary,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
|
flex: 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
marginLeft: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -101,6 +117,17 @@ const LoginUserModal = ({ isHidden, form, setForm, handleLoginUser, setIsLoginUs
|
|||||||
>
|
>
|
||||||
Create User
|
Create User
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleGuestLogin}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.general.primary,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.general.disabled,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Login as Guest
|
||||||
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -115,6 +142,7 @@ LoginUserModal.propTypes = {
|
|||||||
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,
|
||||||
setIsLoginUserHidden: PropTypes.func.isRequired,
|
setIsLoginUserHidden: PropTypes.func.isRequired,
|
||||||
setIsCreateUserHidden: PropTypes.func.isRequired,
|
setIsCreateUserHidden: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
176
src/modals/NoAuthenticationModal.jsx
Normal file
176
src/modals/NoAuthenticationModal.jsx
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { CssVarsProvider } from '@mui/joy/styles';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
Input,
|
||||||
|
Stack,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
ModalDialog,
|
||||||
|
IconButton,
|
||||||
|
Select,
|
||||||
|
Option,
|
||||||
|
} from '@mui/joy';
|
||||||
|
import theme from '/src/theme';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Visibility from '@mui/icons-material/Visibility';
|
||||||
|
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||||
|
|
||||||
|
const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, handleAuthSubmit }) => {
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
|
const isFormValid = () => {
|
||||||
|
if (form.authMethod === 'Select Auth') return false;
|
||||||
|
if (form.authMethod === 'rsaKey' && !form.rsaKey) return false;
|
||||||
|
if (form.authMethod === 'password' && !form.password) return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (isFormValid()) {
|
||||||
|
handleAuthSubmit(form);
|
||||||
|
setForm({ authMethod: 'Select Auth', password: '', rsaKey: '' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CssVarsProvider theme={theme}>
|
||||||
|
<Modal
|
||||||
|
open={!isHidden}
|
||||||
|
onClose={(e, reason) => {
|
||||||
|
if (reason !== 'backdropClick') {
|
||||||
|
setIsNoAuthHidden(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disableBackdropClic
|
||||||
|
>
|
||||||
|
<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>Authentication Required</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
|
||||||
|
<FormControl error={!form.authMethod || form.authMethod === 'Select Auth'}>
|
||||||
|
<FormLabel sx={{ color: theme.palette.text.primary }}>Authentication Method</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={form.authMethod || 'Select Auth'}
|
||||||
|
onChange={(e, newValue) => setForm({ ...form, authMethod: newValue })}
|
||||||
|
required
|
||||||
|
sx={{
|
||||||
|
backgroundColor: !form.authMethod || form.authMethod === 'Select Auth' ? theme.palette.general.tertiary : theme.palette.general.primary,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.general.disabled,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Option value="Select Auth" disabled>Select Auth</Option>
|
||||||
|
<Option value="password">Password</Option>
|
||||||
|
<Option value="rsaKey">RSA Key</Option>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
{form.authMethod === 'password' && (
|
||||||
|
<FormControl error={!form.password}>
|
||||||
|
<FormLabel sx={{ color: theme.palette.text.primary }}>Password</FormLabel>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Input
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
value={form.password}
|
||||||
|
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
||||||
|
required
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.general.primary,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
marginLeft: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
{form.authMethod === 'rsaKey' && (
|
||||||
|
<FormControl error={!form.rsaKey}>
|
||||||
|
<FormLabel sx={{ color: theme.palette.text.primary }}>RSA Key</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="file"
|
||||||
|
onChange={(e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
setForm({ ...form, rsaKey: event.target.result });
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.general.primary,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
padding: 1,
|
||||||
|
textAlign: 'center',
|
||||||
|
width: '100%',
|
||||||
|
minWidth: 'auto',
|
||||||
|
minHeight: 'auto',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={!isFormValid()}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.general.primary,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.general.disabled,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</ModalDialog>
|
||||||
|
</Modal>
|
||||||
|
</CssVarsProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
NoAuthenticationModal.propTypes = {
|
||||||
|
isHidden: PropTypes.bool.isRequired,
|
||||||
|
form: PropTypes.object.isRequired,
|
||||||
|
setForm: PropTypes.func.isRequired,
|
||||||
|
setIsNoAuthHidden: PropTypes.func.isRequired,
|
||||||
|
handleAuthSubmit: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NoAuthenticationModal;
|
||||||
@@ -56,10 +56,12 @@ const ProfileModal = ({ isHidden, getUser, handleDeleteUser, handleLogoutUser, s
|
|||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Username: <br />
|
User: {getUserName()}
|
||||||
{getUserName()}
|
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent sx={{ width: "100%" }}>
|
<DialogContent sx={{ width: "100%" }}>
|
||||||
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden", mt: 1.5 }}>
|
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden", mt: 1.5 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user