Register api key section (#2180)
Split register section in tabs Add register API key section
This commit is contained in:
112
portal-ui/src/screens/Console/Support/ApiKeyRegister.tsx
Normal file
112
portal-ui/src/screens/Console/Support/ApiKeyRegister.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import { OnlineRegistrationIcon } from "../../../icons";
|
||||
import { FormTitle } from "./utils";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import GetApiKeyModal from "./GetApiKeyModal";
|
||||
|
||||
interface IApiKeyRegister {
|
||||
classes: any;
|
||||
apiKey: string;
|
||||
setApiKey: (v: string) => void;
|
||||
onRegister: () => void;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const ApiKeyRegister = ({
|
||||
classes,
|
||||
apiKey,
|
||||
setApiKey,
|
||||
loading,
|
||||
onRegister,
|
||||
}: IApiKeyRegister) => {
|
||||
const [showApiKeyModal, setShowApiKeyModal] = useState(false);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Box
|
||||
sx={{
|
||||
"& .title-text": {
|
||||
marginLeft: "27px",
|
||||
fontWeight: 600,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<FormTitle
|
||||
icon={<OnlineRegistrationIcon />}
|
||||
title={`API key activation of MinIO Subscription Network License`}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flex: "1",
|
||||
paddingTop: "30px",
|
||||
}}
|
||||
>
|
||||
<InputBoxWrapper
|
||||
className={classes.spacerBottom}
|
||||
classes={{
|
||||
inputLabel: classes.sizedLabel,
|
||||
}}
|
||||
id="api-key"
|
||||
name="api-key"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setApiKey(event.target.value)
|
||||
}
|
||||
label="API Key"
|
||||
value={apiKey}
|
||||
/>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
className={classes.spacerRight}
|
||||
disabled={loading}
|
||||
onClick={() => setShowApiKeyModal(true)}
|
||||
>
|
||||
Get from SUBNET
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={loading || apiKey.trim().length === 0}
|
||||
onClick={() => onRegister()}
|
||||
>
|
||||
Register
|
||||
</Button>
|
||||
<GetApiKeyModal
|
||||
open={showApiKeyModal}
|
||||
closeModal={() => setShowApiKeyModal(false)}
|
||||
onSet={setApiKey}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiKeyRegister;
|
||||
229
portal-ui/src/screens/Console/Support/GetApiKeyModal.tsx
Normal file
229
portal-ui/src/screens/Console/Support/GetApiKeyModal.tsx
Normal file
@@ -0,0 +1,229 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye";
|
||||
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
||||
import {
|
||||
containerForHeader,
|
||||
spacingUtils,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import React, { useState } from "react";
|
||||
import { InfoIcon, UsersIcon } from "../../../icons";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import { setErrorSnackMessage } from "../../../systemSlice";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
pageTitle: {
|
||||
fontSize: 18,
|
||||
marginBottom: 20,
|
||||
textAlign: "center",
|
||||
},
|
||||
pageSubTitle: {
|
||||
textAlign: "center",
|
||||
},
|
||||
sizedLabel: {
|
||||
minWidth: "75px",
|
||||
},
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
...spacingUtils,
|
||||
});
|
||||
|
||||
interface IGetApiKeyModalProps {
|
||||
open: boolean;
|
||||
closeModal: () => void;
|
||||
onSet: (apiKey: string) => void;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const GetApiKeyModal = ({
|
||||
open,
|
||||
closeModal,
|
||||
classes,
|
||||
onSet,
|
||||
}: IGetApiKeyModalProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [mfaToken, setMfaToken] = useState("");
|
||||
const [subnetOTP, setSubnetOTP] = useState("");
|
||||
|
||||
const onError = (err: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
closeModal();
|
||||
setEmail("");
|
||||
setPassword("");
|
||||
setShowPassword(false);
|
||||
setMfaToken("");
|
||||
setSubnetOTP("");
|
||||
};
|
||||
|
||||
const onSuccess = (res: any) => {
|
||||
if (res.mfa_token) {
|
||||
setMfaToken(res.mfa_token);
|
||||
} else if (res.access_token) {
|
||||
invokeApi("GET", `/api/v1/subnet/apikey?token=${res.access_token}`);
|
||||
} else {
|
||||
onSet(res.apiKey);
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
const [isLoading, invokeApi] = useApi(onSuccess, onError);
|
||||
|
||||
const onConfirm = () => {
|
||||
if (mfaToken !== "") {
|
||||
invokeApi("POST", "/api/v1/subnet/login/mfa", {
|
||||
username: email,
|
||||
otp: subnetOTP,
|
||||
mfa_token: mfaToken,
|
||||
});
|
||||
} else {
|
||||
invokeApi("POST", "/api/v1/subnet/login", { username: email, password });
|
||||
}
|
||||
};
|
||||
|
||||
const getDialogContent = () => {
|
||||
if (mfaToken === "") {
|
||||
return getCredentialsDialog();
|
||||
}
|
||||
return getMFADialog();
|
||||
};
|
||||
|
||||
const getCredentialsDialog = () => {
|
||||
return (
|
||||
<Box sx={{ width: 500 }}>
|
||||
<InputBoxWrapper
|
||||
className={classes.spacerBottom}
|
||||
classes={{
|
||||
inputLabel: classes.sizedLabel,
|
||||
}}
|
||||
id="subnet-email"
|
||||
name="subnet-email"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setEmail(event.target.value)
|
||||
}
|
||||
label="Email"
|
||||
value={email}
|
||||
overlayIcon={<UsersIcon />}
|
||||
/>
|
||||
<InputBoxWrapper
|
||||
className={classes.spacerBottom}
|
||||
classes={{
|
||||
inputLabel: classes.sizedLabel,
|
||||
}}
|
||||
id="subnet-password"
|
||||
name="subnet-password"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setPassword(event.target.value)
|
||||
}
|
||||
label="Password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={password}
|
||||
overlayIcon={
|
||||
showPassword ? <VisibilityOffIcon /> : <RemoveRedEyeIcon />
|
||||
}
|
||||
overlayAction={() => setShowPassword(!showPassword)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const getMFADialog = () => {
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<Box sx={{ display: "flex", flexFlow: "column", flex: "2" }}>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
marginTop: "30px",
|
||||
marginBottom: "30px",
|
||||
}}
|
||||
>
|
||||
Two-Factor Authentication
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
Please enter the 6-digit verification code that was sent to your
|
||||
email address. This code will be valid for 5 minutes.
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flex: "1",
|
||||
marginTop: "30px",
|
||||
}}
|
||||
>
|
||||
<InputBoxWrapper
|
||||
overlayIcon={<LockOutlinedIcon />}
|
||||
id="subnet-otp"
|
||||
name="subnet-otp"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setSubnetOTP(event.target.value)
|
||||
}
|
||||
placeholder=""
|
||||
label=""
|
||||
value={subnetOTP}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
return open ? (
|
||||
<ConfirmDialog
|
||||
title={"Get API Key from SUBNET"}
|
||||
confirmText={"Get API Key"}
|
||||
isOpen={open}
|
||||
titleIcon={<InfoIcon />}
|
||||
isLoading={isLoading}
|
||||
cancelText={"Cancel"}
|
||||
onConfirm={onConfirm}
|
||||
onClose={closeModal}
|
||||
confirmButtonProps={{
|
||||
color: "info",
|
||||
disabled: !email || !password || isLoading,
|
||||
hidden: true,
|
||||
}}
|
||||
cancelButtonProps={{
|
||||
disabled: isLoading,
|
||||
}}
|
||||
confirmationContent={getDialogContent()}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default withStyles(styles)(GetApiKeyModal);
|
||||
@@ -65,7 +65,8 @@ import { useAppDispatch } from "../../../store";
|
||||
import Tabs from "@mui/material/Tabs";
|
||||
import Tab from "@mui/material/Tab";
|
||||
import { TabPanel } from "../../shared/tabs";
|
||||
|
||||
import { ClusterRegistered, FormTitle } from "./utils";
|
||||
import ApiKeyRegister from "./ApiKeyRegister";
|
||||
interface IRegister {
|
||||
classes: any;
|
||||
}
|
||||
@@ -136,21 +137,6 @@ const styles = (theme: Theme) =>
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const FormTitle = ({ icon = null, title }: { icon?: any; title: any }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
<div className="title-text">{title}</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Register = ({ classes }: IRegister) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const operatorMode = useSelector(selOpMode);
|
||||
@@ -592,6 +578,35 @@ const Register = ({ classes }: IRegister) => {
|
||||
);
|
||||
}
|
||||
|
||||
const apiKeyRegistration = (
|
||||
<Fragment>
|
||||
<Box
|
||||
sx={{
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
padding: "43px",
|
||||
}}
|
||||
>
|
||||
{clusterRegistered && licenseInfo ? (
|
||||
<ClusterRegistered
|
||||
email={licenseInfo.email}
|
||||
linkClass={classes.link}
|
||||
/>
|
||||
) : (
|
||||
<ApiKeyRegister
|
||||
classes={classes}
|
||||
setApiKey={setLicense}
|
||||
apiKey={license}
|
||||
loading={loading}
|
||||
onRegister={subnetLogin}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const offlineRegistration = (
|
||||
<Fragment>
|
||||
<Box
|
||||
@@ -603,31 +618,11 @@ const Register = ({ classes }: IRegister) => {
|
||||
padding: "43px",
|
||||
}}
|
||||
>
|
||||
{clusterRegistered && (
|
||||
<RegistrationStatusBanner email={licenseInfo?.email} />
|
||||
)}
|
||||
{clusterRegistered ? (
|
||||
<Grid item xs={12} marginTop={"25px"}>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "20px",
|
||||
"& a": {
|
||||
color: "#2781B0",
|
||||
cursor: "pointer",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Login to{" "}
|
||||
<Link
|
||||
href="https://subnet.min.io"
|
||||
target="_blank"
|
||||
className={classes.link}
|
||||
>
|
||||
SUBNET
|
||||
</Link>{" "}
|
||||
to avail support for this MinIO cluster
|
||||
</Box>
|
||||
</Grid>
|
||||
{clusterRegistered && licenseInfo ? (
|
||||
<ClusterRegistered
|
||||
email={licenseInfo.email}
|
||||
linkClass={classes.link}
|
||||
/>
|
||||
) : null}
|
||||
<Box
|
||||
sx={{
|
||||
@@ -976,9 +971,14 @@ const Register = ({ classes }: IRegister) => {
|
||||
aria-controls="simple-tabpanel-0"
|
||||
/>
|
||||
<Tab
|
||||
label="Offline Activation"
|
||||
label="API Key Activation"
|
||||
id="simple-tab-1"
|
||||
aria-controls="simple-tabpanel-1"
|
||||
/>
|
||||
<Tab
|
||||
label="Offline Activation"
|
||||
id="simple-tab-2"
|
||||
aria-controls="simple-tabpanel-2"
|
||||
onClick={() => fetchSubnetRegToken()}
|
||||
/>
|
||||
</Tabs>
|
||||
@@ -987,6 +987,9 @@ const Register = ({ classes }: IRegister) => {
|
||||
{uiToShow}
|
||||
</TabPanel>
|
||||
<TabPanel index={1} value={curTab}>
|
||||
{apiKeyRegistration}
|
||||
</TabPanel>
|
||||
<TabPanel index={2} value={curTab}>
|
||||
{offlineRegistration}
|
||||
</TabPanel>
|
||||
</PageLayout>
|
||||
|
||||
59
portal-ui/src/screens/Console/Support/utils.tsx
Normal file
59
portal-ui/src/screens/Console/Support/utils.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Box, Grid, Link } from "@mui/material";
|
||||
import { Fragment } from "react";
|
||||
import RegistrationStatusBanner from "./RegistrationStatusBanner";
|
||||
|
||||
export const FormTitle = ({
|
||||
icon = null,
|
||||
title,
|
||||
}: {
|
||||
icon?: any;
|
||||
title: any;
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
<div className="title-text">{title}</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const ClusterRegistered = ({
|
||||
email,
|
||||
linkClass,
|
||||
}: {
|
||||
email: string;
|
||||
linkClass: string;
|
||||
}) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<RegistrationStatusBanner email={email} />
|
||||
<Grid item xs={12} marginTop={"25px"}>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "20px",
|
||||
"& a": {
|
||||
color: "#2781B0",
|
||||
cursor: "pointer",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Login to{" "}
|
||||
<Link
|
||||
href="https://subnet.min.io"
|
||||
target="_blank"
|
||||
className={linkClass}
|
||||
>
|
||||
SUBNET
|
||||
</Link>{" "}
|
||||
to avail support for this MinIO cluster
|
||||
</Box>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user