diff --git a/portal-ui/src/screens/Console/Support/ApiKeyRegister.tsx b/portal-ui/src/screens/Console/Support/ApiKeyRegister.tsx new file mode 100644 index 000000000..6c6ea2b57 --- /dev/null +++ b/portal-ui/src/screens/Console/Support/ApiKeyRegister.tsx @@ -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 . + +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 ( + + + } + title={`API key activation of MinIO Subscription Network License`} + /> + + + + ) => + setApiKey(event.target.value) + } + label="API Key" + value={apiKey} + /> + + + + + setShowApiKeyModal(false)} + onSet={setApiKey} + /> + + + + ); +}; + +export default ApiKeyRegister; diff --git a/portal-ui/src/screens/Console/Support/GetApiKeyModal.tsx b/portal-ui/src/screens/Console/Support/GetApiKeyModal.tsx new file mode 100644 index 000000000..bbc3cb096 --- /dev/null +++ b/portal-ui/src/screens/Console/Support/GetApiKeyModal.tsx @@ -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 . + +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(""); + 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 ( + + ) => + setEmail(event.target.value) + } + label="Email" + value={email} + overlayIcon={} + /> + ) => + setPassword(event.target.value) + } + label="Password" + type={showPassword ? "text" : "password"} + value={password} + overlayIcon={ + showPassword ? : + } + overlayAction={() => setShowPassword(!showPassword)} + /> + + ); + }; + + const getMFADialog = () => { + return ( + + + + Two-Factor Authentication + + + + Please enter the 6-digit verification code that was sent to your + email address. This code will be valid for 5 minutes. + + + + } + id="subnet-otp" + name="subnet-otp" + onChange={(event: React.ChangeEvent) => + setSubnetOTP(event.target.value) + } + placeholder="" + label="" + value={subnetOTP} + /> + + + + + ); + }; + + return open ? ( + } + 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); diff --git a/portal-ui/src/screens/Console/Support/Register.tsx b/portal-ui/src/screens/Console/Support/Register.tsx index 643a618b3..8fd2a0397 100644 --- a/portal-ui/src/screens/Console/Support/Register.tsx +++ b/portal-ui/src/screens/Console/Support/Register.tsx @@ -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 ( - - {icon} -
{title}
-
- ); -}; - const Register = ({ classes }: IRegister) => { const dispatch = useAppDispatch(); const operatorMode = useSelector(selOpMode); @@ -592,6 +578,35 @@ const Register = ({ classes }: IRegister) => { ); } + const apiKeyRegistration = ( + + + {clusterRegistered && licenseInfo ? ( + + ) : ( + + )} + + + ); + const offlineRegistration = ( { padding: "43px", }} > - {clusterRegistered && ( - - )} - {clusterRegistered ? ( - - - Login to{" "} - - SUBNET - {" "} - to avail support for this MinIO cluster - - + {clusterRegistered && licenseInfo ? ( + ) : null} { aria-controls="simple-tabpanel-0" /> + fetchSubnetRegToken()} /> @@ -987,6 +987,9 @@ const Register = ({ classes }: IRegister) => { {uiToShow} + {apiKeyRegistration} + + {offlineRegistration} diff --git a/portal-ui/src/screens/Console/Support/utils.tsx b/portal-ui/src/screens/Console/Support/utils.tsx new file mode 100644 index 000000000..a4bfb8b25 --- /dev/null +++ b/portal-ui/src/screens/Console/Support/utils.tsx @@ -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 ( + + {icon} +
{title}
+
+ ); +}; + +export const ClusterRegistered = ({ + email, + linkClass, +}: { + email: string; + linkClass: string; +}) => { + return ( + + + + + Login to{" "} + + SUBNET + {" "} + to avail support for this MinIO cluster + + + + ); +};