mirror of
https://github.com/openziti/zrok.git
synced 2025-03-13 15:08:21 +01:00
roughed in modals for account actions (#822)
This commit is contained in:
parent
449d45c3a5
commit
41384a97c8
75
ui100/package-lock.json
generated
75
ui100/package-lock.json
generated
@ -15,6 +15,7 @@
|
||||
"@mui/x-charts": "^7.23.2",
|
||||
"@xyflow/react": "^12.3.5",
|
||||
"d3-hierarchy": "^3.1.2",
|
||||
"formik": "^2.4.6",
|
||||
"material-react-table": "^3.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -2100,6 +2101,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz",
|
||||
"integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
@ -2988,6 +2999,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
|
||||
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delaunator": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
|
||||
@ -3392,6 +3412,31 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/formik": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz",
|
||||
"integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://opencollective.com/formik"
|
||||
}
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"deepmerge": "^2.1.1",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react-fast-compare": "^2.0.1",
|
||||
"tiny-warning": "^1.0.2",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@ -3729,6 +3774,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
@ -4117,6 +4174,12 @@
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-fast-compare": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
|
||||
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
@ -4401,6 +4464,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@ -4427,6 +4496,12 @@
|
||||
"typescript": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/turbo-stream": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
||||
|
@ -17,6 +17,7 @@
|
||||
"@mui/x-charts": "^7.23.2",
|
||||
"@xyflow/react": "^12.3.5",
|
||||
"d3-hierarchy": "^3.1.2",
|
||||
"formik": "^2.4.6",
|
||||
"material-react-table": "^3.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
@ -16,12 +16,11 @@ interface AccessPanelProps {
|
||||
const AccessPanel = ({ access }: AccessPanelProps) => {
|
||||
const user = useApiConsoleStore((state) => state.user);
|
||||
const [detail, setDetail] = useState<Frontend>(null);
|
||||
const [releaseAccessOpen, setReleaseAccessOpen] = useState(false);
|
||||
const [releaseAccessOpen, setReleaseAccessOpen] = useState<boolean>(false);
|
||||
|
||||
const openReleaseAccess = () => {
|
||||
setReleaseAccessOpen(true);
|
||||
}
|
||||
|
||||
const closeReleaseAccess = () => {
|
||||
setReleaseAccessOpen(false);
|
||||
}
|
||||
|
@ -6,6 +6,9 @@ import SecretToggle from "./SecretToggle.tsx";
|
||||
import useApiConsoleStore from "./model/store.ts";
|
||||
import PasswordIcon from "@mui/icons-material/Password";
|
||||
import TokenIcon from "@mui/icons-material/Key";
|
||||
import {useState} from "react";
|
||||
import AccountPasswordChangeModal from "./AccountPasswordChangeModal.tsx";
|
||||
import RegenerateAccountTokenModal from "./RegenerateAccountTokenModal.tsx";
|
||||
|
||||
interface AccountPanelProps {
|
||||
account: Node;
|
||||
@ -13,6 +16,20 @@ interface AccountPanelProps {
|
||||
|
||||
const AccountPanel = ({ account }: AccountPanelProps) => {
|
||||
const user = useApiConsoleStore((state) => state.user);
|
||||
const [changePasswordOpen, setChangePasswordOpen] = useState<boolean>(false);
|
||||
const openChangePassword = () => {
|
||||
setChangePasswordOpen(true);
|
||||
}
|
||||
const closeChangePassword = () => {
|
||||
setChangePasswordOpen(false);
|
||||
}
|
||||
const [regenerateOpen, setRegenerateOpen] = useState<boolean>(false);
|
||||
const openRegenerate = () => {
|
||||
setRegenerateOpen(true);
|
||||
}
|
||||
const closeRegenerate = () => {
|
||||
setRegenerateOpen(false);
|
||||
}
|
||||
|
||||
const customProps = {
|
||||
token: row => <SecretToggle secret={row.value} />
|
||||
@ -23,28 +40,32 @@ const AccountPanel = ({ account }: AccountPanelProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography component="div">
|
||||
<Grid2 container sx={{ flexGrow: 1 }} alignItems="center">
|
||||
<Grid2 display="flex"><AccountIcon sx={{ fontSize: 30, mr: 0.5 }}/></Grid2>
|
||||
<Grid2 display="flex" component="h3">{String(account.data.label)}</Grid2>
|
||||
</Grid2>
|
||||
<Grid2 container sx={{ flexGrow: 1, mt: 0, mb: 2 }} alignItems="center">
|
||||
<h5 style={{ margin: 0 }}>Your zrok account, <code>{user.email}</code></h5>
|
||||
</Grid2>
|
||||
<Grid2 container sx={{ flexGrow: 1, mb: 3 }} alignItems="left">
|
||||
<Tooltip title="Change Password">
|
||||
<Button variant="contained" color="error"><PasswordIcon /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Regenerate Account Token" sx={{ ml: 1 }}>
|
||||
<Button variant="contained" color="error"><TokenIcon /></Button>
|
||||
</Tooltip>
|
||||
</Grid2>
|
||||
<Grid2 container sx={{ flexGrow: 1 }}>
|
||||
<Grid2 display="flex">
|
||||
<PropertyTable object={user} custom={customProps} labels={label} />
|
||||
<>
|
||||
<Typography component="div">
|
||||
<Grid2 container sx={{ flexGrow: 1 }} alignItems="center">
|
||||
<Grid2 display="flex"><AccountIcon sx={{ fontSize: 30, mr: 0.5 }}/></Grid2>
|
||||
<Grid2 display="flex" component="h3">{String(account.data.label)}</Grid2>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
</Typography>
|
||||
<Grid2 container sx={{ flexGrow: 1, mt: 0, mb: 2 }} alignItems="center">
|
||||
<h5 style={{ margin: 0 }}>Your zrok account, <code>{user.email}</code></h5>
|
||||
</Grid2>
|
||||
<Grid2 container sx={{ flexGrow: 1, mb: 3 }} alignItems="left">
|
||||
<Tooltip title="Change Password">
|
||||
<Button variant="contained" color="error" onClick={openChangePassword}><PasswordIcon /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Regenerate Account Token" sx={{ ml: 1 }}>
|
||||
<Button variant="contained" color="error" onClick={openRegenerate}><TokenIcon /></Button>
|
||||
</Tooltip>
|
||||
</Grid2>
|
||||
<Grid2 container sx={{ flexGrow: 1 }}>
|
||||
<Grid2 display="flex">
|
||||
<PropertyTable object={user} custom={customProps} labels={label} />
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
</Typography>
|
||||
<AccountPasswordChangeModal close={closeChangePassword} isOpen={changePasswordOpen} user={user} />
|
||||
<RegenerateAccountTokenModal close={closeRegenerate} isOpen={regenerateOpen} user={user} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
79
ui100/src/AccountPasswordChangeModal.tsx
Normal file
79
ui100/src/AccountPasswordChangeModal.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import {User} from "./model/user.ts";
|
||||
import {useState} from "react";
|
||||
import {modalStyle} from "./styling/theme.ts";
|
||||
import {Box, Button, Grid2, Modal, TextField, Typography} from "@mui/material";
|
||||
import {useFormik} from "formik";
|
||||
|
||||
interface AccountPasswordChangeModalProps {
|
||||
close: () => void;
|
||||
isOpen: boolean;
|
||||
user: User;
|
||||
}
|
||||
|
||||
const AccountPasswordChangeModal =({ close, isOpen, user }: AccountPasswordChangeModalProps) => {
|
||||
const [errorMessage, setErrorMessage] = useState<React.JSX.Element>(null);
|
||||
|
||||
const passwordChangeForm = useFormik({
|
||||
initialValues: {
|
||||
currentPassword: "",
|
||||
newPassword: "",
|
||||
duplicateNewPassword: "",
|
||||
},
|
||||
onSubmit: v => {
|
||||
setErrorMessage(null as React.JSX.Element);
|
||||
// api
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onClose={close}>
|
||||
<Box sx={{ ...modalStyle }}>
|
||||
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
|
||||
<Typography variant="h5"><strong>Change Password</strong></Typography>
|
||||
</Grid2>
|
||||
<form onSubmit={passwordChangeForm.handleSubmit}>
|
||||
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
|
||||
<TextField
|
||||
fullWidth
|
||||
type="password"
|
||||
id="currentPassword"
|
||||
name="currentPassword"
|
||||
label="Current Password"
|
||||
value={passwordChangeForm.values.currentPassword}
|
||||
onChange={passwordChangeForm.handleChange}
|
||||
onBlur={passwordChangeForm.handleBlur}
|
||||
sx={{ mt: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
type="password"
|
||||
id="newPassword"
|
||||
name="newPassword"
|
||||
label="New Password"
|
||||
value={passwordChangeForm.values.newPassword}
|
||||
onChange={passwordChangeForm.handleChange}
|
||||
onBlur={passwordChangeForm.handleBlur}
|
||||
sx={{ mt: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
type="password"
|
||||
id="duplicateNewPassword"
|
||||
name="duplicateNewPassword"
|
||||
label="Re-enter New Password"
|
||||
value={passwordChangeForm.values.duplicateNewPassword}
|
||||
onChange={passwordChangeForm.handleChange}
|
||||
onBlur={passwordChangeForm.handleBlur}
|
||||
sx={{ mt: 2 }}
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
|
||||
<Button color="primary" variant="contained" type="submit" sx={{ mt: 2 }}>Change Password</Button>
|
||||
</Grid2>
|
||||
</form>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccountPasswordChangeModal;
|
51
ui100/src/RegenerateAccountTokenModal.tsx
Normal file
51
ui100/src/RegenerateAccountTokenModal.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import {User} from "./model/user.ts";
|
||||
import {useEffect, useRef, useState} from "react";
|
||||
import {modalStyle} from "./styling/theme.ts";
|
||||
import {Box, Button, Checkbox, FormControlLabel, Grid2, Modal, Typography} from "@mui/material";
|
||||
|
||||
interface RegenerateAccountTokenModalProps {
|
||||
close: () => void;
|
||||
isOpen: boolean;
|
||||
user: User;
|
||||
}
|
||||
|
||||
const RegenerateAccountTokenModal = ({ close, isOpen, user }: RegenerateAccountTokenModalProps) => {
|
||||
const [errorMessage, setErrorMessage] = useState<React.JSX.Element>(null);
|
||||
const [checked, setChecked] = useState<boolean>(false);
|
||||
const checkedRef = useRef<boolean>(checked);
|
||||
|
||||
const toggleChecked = () => {
|
||||
setChecked(!checkedRef.current);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setChecked(false);
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onClose={close}>
|
||||
<Box sx={{ ...modalStyle }}>
|
||||
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
|
||||
<Typography variant="h5"><strong>Regenerate Account Token</strong></Typography>
|
||||
</Grid2>
|
||||
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
|
||||
<Typography>Regenerating your account token will stop all environments and shares from operating properly!</Typography>
|
||||
</Grid2>
|
||||
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
|
||||
<Typography>You will need to manually edit your $HOME/.zrok/environment.json files (in each environment) to use the new zrok_token. Updating these files will restore the functionality of your environments.</Typography>
|
||||
</Grid2>
|
||||
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
|
||||
<Typography>Alternatively, you can just zrok disable any enabled environments and re-enable using the new account token. Running zrok disable will delete your environments and any shares they contain (including reserved shares). So if you have environments and reserved shares you need to preserve, your best option is to update the zrok_token in those environments as described above.</Typography>
|
||||
</Grid2>
|
||||
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
|
||||
<FormControlLabel control={<Checkbox checked={checked} onChange={toggleChecked} />} label={<p>I confirm that I want to regenerate my account token</p>} sx={{ mt: 2 }} />
|
||||
</Grid2>
|
||||
<Grid2 container sx={{ flexGrow: 1 }} alignItems="center">
|
||||
<Button color="error" variant="contained" disabled={!checked}>Release</Button>
|
||||
</Grid2>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default RegenerateAccountTokenModal;
|
@ -14,13 +14,6 @@ export const objectToRows = (obj) => {
|
||||
|
||||
export const camelToWords = (s) => s.replace(/([A-Z])/g, ' $1').replace(/^./, function(str){ return str.toUpperCase(); });
|
||||
|
||||
export const rowToValue = (row) => {
|
||||
if(row.property.endsWith("At")) {
|
||||
return new Date(row.value).toLocaleString();
|
||||
}
|
||||
return row.value.toString();
|
||||
};
|
||||
|
||||
export const bytesToSize = (bytes: number): string => {
|
||||
let i = -1;
|
||||
const byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
@ -32,9 +25,7 @@ export const bytesToSize = (bytes: number): string => {
|
||||
}
|
||||
|
||||
function getReadableFileSizeString(fileSizeInBytes) {
|
||||
var i = -1;
|
||||
var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
|
||||
let i = -1;
|
||||
let byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ export const theme = createTheme({
|
||||
|
||||
export const modalStyle = {
|
||||
position: 'absolute',
|
||||
top: '25%',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 600,
|
||||
|
Loading…
Reference in New Issue
Block a user