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