Register api key section (#2180)

Split register section in tabs
Add register API key section
This commit is contained in:
Javier Adriel
2022-07-22 13:27:53 -05:00
committed by GitHub
parent 9655fc4490
commit e0b6bf5aa6
4 changed files with 445 additions and 42 deletions

View 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;

View 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);

View File

@@ -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>

View 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>
);
};