mirror of
https://github.com/OpenMaxIO/openmaxio-object-browser
synced 2026-07-01 07:41:18 -07:00
Move Register Component to Redux (#2630)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
@@ -30,11 +30,12 @@ import { spacingUtils } from "../Common/FormComponents/common/styleLibrary";
|
|||||||
import { Theme } from "@mui/material/styles";
|
import { Theme } from "@mui/material/styles";
|
||||||
import createStyles from "@mui/styles/createStyles";
|
import createStyles from "@mui/styles/createStyles";
|
||||||
import withStyles from "@mui/styles/withStyles";
|
import withStyles from "@mui/styles/withStyles";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
|
||||||
|
|
||||||
interface IApiKeyRegister {
|
interface IApiKeyRegister {
|
||||||
classes: any;
|
classes: any;
|
||||||
registerEndpoint: string;
|
registerEndpoint: string;
|
||||||
afterRegister: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
@@ -45,11 +46,9 @@ const styles = (theme: Theme) =>
|
|||||||
...spacingUtils,
|
...spacingUtils,
|
||||||
});
|
});
|
||||||
|
|
||||||
const ApiKeyRegister = ({
|
const ApiKeyRegister = ({ classes, registerEndpoint }: IApiKeyRegister) => {
|
||||||
classes,
|
const navigate = useNavigate();
|
||||||
registerEndpoint,
|
|
||||||
afterRegister,
|
|
||||||
}: IApiKeyRegister) => {
|
|
||||||
const [showApiKeyModal, setShowApiKeyModal] = useState(false);
|
const [showApiKeyModal, setShowApiKeyModal] = useState(false);
|
||||||
const [apiKey, setApiKey] = useState("");
|
const [apiKey, setApiKey] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -67,8 +66,7 @@ const ApiKeyRegister = ({
|
|||||||
.then((resp: SubnetLoginResponse) => {
|
.then((resp: SubnetLoginResponse) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (resp && resp.registered) {
|
if (resp && resp.registered) {
|
||||||
reset();
|
navigate(IAM_PAGES.LICENSE);
|
||||||
afterRegister();
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err: ErrorResponseHandler) => {
|
.catch((err: ErrorResponseHandler) => {
|
||||||
@@ -76,7 +74,7 @@ const ApiKeyRegister = ({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
reset();
|
reset();
|
||||||
});
|
});
|
||||||
}, [afterRegister, apiKey, dispatch, loading, registerEndpoint]);
|
}, [apiKey, dispatch, loading, registerEndpoint, navigate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fromModal) {
|
if (fromModal) {
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
// This file is part of MinIO Console Server
|
||||||
|
// Copyright (c) 2023 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 from "react";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { FormTitle } from "./utils";
|
||||||
|
import SelectWrapper from "../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||||
|
import { setLoading, setSelectedSubnetOrganization } from "./registerSlice";
|
||||||
|
import { Button } from "mds";
|
||||||
|
import RegisterHelpBox from "./RegisterHelpBox";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { AppState, useAppDispatch } from "../../../store";
|
||||||
|
import { callRegister } from "./registerThunks";
|
||||||
|
|
||||||
|
const ClusterRegistrationForm = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const subnetAccessToken = useSelector(
|
||||||
|
(state: AppState) => state.register.subnetAccessToken
|
||||||
|
);
|
||||||
|
const selectedSubnetOrganization = useSelector(
|
||||||
|
(state: AppState) => state.register.selectedSubnetOrganization
|
||||||
|
);
|
||||||
|
const subnetOrganizations = useSelector(
|
||||||
|
(state: AppState) => state.register.subnetOrganizations
|
||||||
|
);
|
||||||
|
const loading = useSelector((state: AppState) => state.register.loading);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: "column",
|
||||||
|
flex: "2",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
marginTop: "15px",
|
||||||
|
marginBottom: "15px",
|
||||||
|
"& .title-text": {
|
||||||
|
marginLeft: "0px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormTitle title={`Register MinIO cluster`} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<SelectWrapper
|
||||||
|
id="subnet-organization"
|
||||||
|
name="subnet-organization"
|
||||||
|
onChange={(e) =>
|
||||||
|
dispatch(setSelectedSubnetOrganization(e.target.value as string))
|
||||||
|
}
|
||||||
|
label="Select an organization"
|
||||||
|
value={selectedSubnetOrganization}
|
||||||
|
options={subnetOrganizations.map((organization) => ({
|
||||||
|
label: organization.company,
|
||||||
|
value: organization.accountId.toString(),
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
marginTop: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
id={"register-cluster"}
|
||||||
|
onClick={() => () => {
|
||||||
|
if (loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(setLoading(true));
|
||||||
|
if (subnetAccessToken && selectedSubnetOrganization) {
|
||||||
|
dispatch(
|
||||||
|
callRegister({
|
||||||
|
token: subnetAccessToken,
|
||||||
|
account_id: selectedSubnetOrganization,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={loading || subnetAccessToken.trim().length === 0}
|
||||||
|
variant="callAction"
|
||||||
|
label={"Register"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<RegisterHelpBox />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClusterRegistrationForm;
|
||||||
159
portal-ui/src/screens/Console/Support/OfflineRegistration.tsx
Normal file
159
portal-ui/src/screens/Console/Support/OfflineRegistration.tsx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
// This file is part of MinIO Console Server
|
||||||
|
// Copyright (c) 2023 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 } from "react";
|
||||||
|
import { Box, Link } from "@mui/material";
|
||||||
|
import { ClusterRegistered, FormTitle } from "./utils";
|
||||||
|
import { Button, CopyIcon, OfflineRegistrationIcon } from "mds";
|
||||||
|
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
|
||||||
|
import CopyToClipboard from "react-copy-to-clipboard";
|
||||||
|
import RegisterHelpBox from "./RegisterHelpBox";
|
||||||
|
import { AppState } from "../../../store";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
|
const OfflineRegistration = () => {
|
||||||
|
const subnetRegToken = useSelector(
|
||||||
|
(state: AppState) => state.register.subnetRegToken
|
||||||
|
);
|
||||||
|
const clusterRegistered = useSelector(
|
||||||
|
(state: AppState) => state.register.clusterRegistered
|
||||||
|
);
|
||||||
|
const licenseInfo = useSelector(
|
||||||
|
(state: AppState) => state.register.licenseInfo
|
||||||
|
);
|
||||||
|
|
||||||
|
const offlineRegUrl = `https://subnet.min.io/cluster/register?token=${subnetRegToken}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
border: "1px solid #eaeaea",
|
||||||
|
borderRadius: "2px",
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: "column",
|
||||||
|
padding: "43px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{clusterRegistered && licenseInfo ? (
|
||||||
|
<ClusterRegistered email={licenseInfo.email} />
|
||||||
|
) : null}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
"& .title-text": {
|
||||||
|
marginLeft: "27px",
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormTitle
|
||||||
|
icon={<OfflineRegistrationIcon />}
|
||||||
|
title={`Register cluster in an Air-gap environment`}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: "column",
|
||||||
|
flex: "2",
|
||||||
|
marginTop: "15px",
|
||||||
|
"& .step-number": {
|
||||||
|
color: "#ffffff",
|
||||||
|
height: "25px",
|
||||||
|
width: "25px",
|
||||||
|
background: "#081C42",
|
||||||
|
marginRight: "10px",
|
||||||
|
textAlign: "center",
|
||||||
|
fontWeight: 600,
|
||||||
|
borderRadius: "50%",
|
||||||
|
},
|
||||||
|
|
||||||
|
"& .step-row": {
|
||||||
|
fontSize: "16px",
|
||||||
|
display: "flex",
|
||||||
|
marginTop: "15px",
|
||||||
|
marginBottom: "15px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Box className="step-row">
|
||||||
|
<div className="step-text">
|
||||||
|
Click on the link to register this cluster in SUBNET
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flex: "1",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
style={{
|
||||||
|
color: "#2781B0",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
color="inherit"
|
||||||
|
href={offlineRegUrl}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
https://subnet.min.io/cluster/register
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<TooltipWrapper tooltip={"Copy to Clipboard"}>
|
||||||
|
<CopyToClipboard text={offlineRegUrl}>
|
||||||
|
<Button
|
||||||
|
type={"button"}
|
||||||
|
id={"copy-ult-to-clip-board"}
|
||||||
|
icon={<CopyIcon />}
|
||||||
|
color={"primary"}
|
||||||
|
variant={"regular"}
|
||||||
|
/>
|
||||||
|
</CopyToClipboard>
|
||||||
|
</TooltipWrapper>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: "25px",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontStyle: "italic",
|
||||||
|
color: "#5E5E5E",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
If this machine does not have internet connection, Copy paste
|
||||||
|
the following URL in a browser where you access SUBNET and
|
||||||
|
follow the instructions to complete the registration
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<RegisterHelpBox />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OfflineRegistration;
|
||||||
190
portal-ui/src/screens/Console/Support/OnlineRegistration.tsx
Normal file
190
portal-ui/src/screens/Console/Support/OnlineRegistration.tsx
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
// This file is part of MinIO Console Server
|
||||||
|
// Copyright (c) 2023 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 } from "react";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { FormTitle } from "./utils";
|
||||||
|
import { Button, OnlineRegistrationIcon, UsersIcon } from "mds";
|
||||||
|
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||||
|
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
|
||||||
|
import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye";
|
||||||
|
import RegisterHelpBox from "./RegisterHelpBox";
|
||||||
|
import { Theme } from "@mui/material/styles";
|
||||||
|
import createStyles from "@mui/styles/createStyles";
|
||||||
|
import { spacingUtils } from "../Common/FormComponents/common/styleLibrary";
|
||||||
|
import makeStyles from "@mui/styles/makeStyles";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { selOpMode } from "../../../systemSlice";
|
||||||
|
import { AppState, useAppDispatch } from "../../../store";
|
||||||
|
import {
|
||||||
|
setShowPassword,
|
||||||
|
setSubnetEmail,
|
||||||
|
setSubnetPassword,
|
||||||
|
} from "./registerSlice";
|
||||||
|
import { subnetLogin } from "./registerThunks";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
sizedLabel: {
|
||||||
|
minWidth: "75px",
|
||||||
|
},
|
||||||
|
...spacingUtils,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const OnlineRegistration = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const operatorMode = useSelector(selOpMode);
|
||||||
|
const subnetPassword = useSelector(
|
||||||
|
(state: AppState) => state.register.subnetPassword
|
||||||
|
);
|
||||||
|
const subnetEmail = useSelector(
|
||||||
|
(state: AppState) => state.register.subnetEmail
|
||||||
|
);
|
||||||
|
const showPassword = useSelector(
|
||||||
|
(state: AppState) => state.register.showPassword
|
||||||
|
);
|
||||||
|
const loading = useSelector((state: AppState) => state.register.loading);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
"& .title-text": {
|
||||||
|
marginLeft: "27px",
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormTitle
|
||||||
|
icon={<OnlineRegistrationIcon />}
|
||||||
|
title={`Online activation of MinIO Subscription Network License`}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: {
|
||||||
|
xs: "column",
|
||||||
|
md: "row",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: "column",
|
||||||
|
flex: "2",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
fontSize: "16px",
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: "column",
|
||||||
|
marginTop: "30px",
|
||||||
|
marginBottom: "30px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Use your MinIO Subscription Network login credentials to register
|
||||||
|
this cluster.
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flex: "1",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InputBoxWrapper
|
||||||
|
className={classes.spacerBottom}
|
||||||
|
classes={{
|
||||||
|
inputLabel: classes.sizedLabel,
|
||||||
|
}}
|
||||||
|
id="subnet-email"
|
||||||
|
name="subnet-email"
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(setSubnetEmail(event.target.value))
|
||||||
|
}
|
||||||
|
label="Email"
|
||||||
|
value={subnetEmail}
|
||||||
|
overlayIcon={<UsersIcon />}
|
||||||
|
/>
|
||||||
|
<InputBoxWrapper
|
||||||
|
className={classes.spacerBottom}
|
||||||
|
classes={{
|
||||||
|
inputLabel: classes.sizedLabel,
|
||||||
|
}}
|
||||||
|
id="subnet-password"
|
||||||
|
name="subnet-password"
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(setSubnetPassword(event.target.value))
|
||||||
|
}
|
||||||
|
label="Password"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
value={subnetPassword}
|
||||||
|
overlayIcon={
|
||||||
|
showPassword ? <VisibilityOffIcon /> : <RemoveRedEyeIcon />
|
||||||
|
}
|
||||||
|
overlayAction={() => dispatch(setShowPassword(!showPassword))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
"& button": {
|
||||||
|
marginLeft: "8px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
id={"sign-up"}
|
||||||
|
type="submit"
|
||||||
|
className={classes.spacerRight}
|
||||||
|
variant="regular"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
window.open(
|
||||||
|
`https://min.io/signup?ref=${operatorMode ? "op" : "con"}`,
|
||||||
|
"_blank"
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
label={"Sign up"}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
id={"register-credentials"}
|
||||||
|
type="submit"
|
||||||
|
variant="callAction"
|
||||||
|
disabled={
|
||||||
|
loading ||
|
||||||
|
subnetEmail.trim().length === 0 ||
|
||||||
|
subnetPassword.trim().length === 0
|
||||||
|
}
|
||||||
|
onClick={() => dispatch(subnetLogin())}
|
||||||
|
label={"Register"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<RegisterHelpBox />
|
||||||
|
</Box>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OnlineRegistration;
|
||||||
@@ -14,61 +14,37 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// 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/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Fragment, useCallback, useEffect, useState } from "react";
|
import React, { Fragment, useEffect, useState } from "react";
|
||||||
import { Theme } from "@mui/material/styles";
|
import { Theme } from "@mui/material/styles";
|
||||||
import {
|
import { PageHeader } from "mds";
|
||||||
Button,
|
|
||||||
CopyIcon,
|
|
||||||
OfflineRegistrationIcon,
|
|
||||||
OnlineRegistrationIcon,
|
|
||||||
PageHeader,
|
|
||||||
UsersIcon,
|
|
||||||
} from "mds";
|
|
||||||
import createStyles from "@mui/styles/createStyles";
|
import createStyles from "@mui/styles/createStyles";
|
||||||
import {
|
import { spacingUtils } from "../Common/FormComponents/common/styleLibrary";
|
||||||
actionsTray,
|
|
||||||
containerForHeader,
|
|
||||||
searchField,
|
|
||||||
spacingUtils,
|
|
||||||
} from "../Common/FormComponents/common/styleLibrary";
|
|
||||||
import withStyles from "@mui/styles/withStyles";
|
import withStyles from "@mui/styles/withStyles";
|
||||||
import { Box, Link } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import PageLayout from "../Common/Layout/PageLayout";
|
import PageLayout from "../Common/Layout/PageLayout";
|
||||||
import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye";
|
|
||||||
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
|
|
||||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
|
||||||
import api from "../../../common/api";
|
import api from "../../../common/api";
|
||||||
|
|
||||||
import {
|
import { SubnetRegTokenResponse } from "../License/types";
|
||||||
SubnetInfo,
|
|
||||||
SubnetLoginRequest,
|
|
||||||
SubnetLoginResponse,
|
|
||||||
SubnetLoginWithMFARequest,
|
|
||||||
SubnetOrganization,
|
|
||||||
SubnetRegisterRequest,
|
|
||||||
SubnetRegTokenResponse,
|
|
||||||
} from "../License/types";
|
|
||||||
import { ErrorResponseHandler } from "../../../common/types";
|
import { ErrorResponseHandler } from "../../../common/types";
|
||||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
|
||||||
import SelectWrapper from "../Common/FormComponents/SelectWrapper/SelectWrapper";
|
|
||||||
import { hasPermission } from "../../../common/SecureComponent";
|
|
||||||
import {
|
|
||||||
CONSOLE_UI_RESOURCE,
|
|
||||||
IAM_PAGES,
|
|
||||||
IAM_PAGES_PERMISSIONS,
|
|
||||||
} from "../../../common/SecureComponent/permissions";
|
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
import { setErrorSnackMessage } from "../../../systemSlice";
|
||||||
import RegisterHelpBox from "./RegisterHelpBox";
|
import { AppState, useAppDispatch } from "../../../store";
|
||||||
import { selOpMode, setErrorSnackMessage } from "../../../systemSlice";
|
|
||||||
import { useAppDispatch } from "../../../store";
|
|
||||||
import Tabs from "@mui/material/Tabs";
|
import Tabs from "@mui/material/Tabs";
|
||||||
import Tab from "@mui/material/Tab";
|
import Tab from "@mui/material/Tab";
|
||||||
import { TabPanel } from "../../shared/tabs";
|
import { TabPanel } from "../../shared/tabs";
|
||||||
import { ClusterRegistered, FormTitle, ProxyConfiguration } from "./utils";
|
import { ClusterRegistered, ProxyConfiguration } from "./utils";
|
||||||
import ApiKeyRegister from "./ApiKeyRegister";
|
import ApiKeyRegister from "./ApiKeyRegister";
|
||||||
import CopyToClipboard from "react-copy-to-clipboard";
|
import { fetchLicenseInfo } from "./registerThunks";
|
||||||
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
|
import {
|
||||||
|
resetRegisterForm,
|
||||||
|
setCurTab,
|
||||||
|
setLoading,
|
||||||
|
setSubnetRegToken,
|
||||||
|
} from "./registerSlice";
|
||||||
|
import OfflineRegistration from "./OfflineRegistration";
|
||||||
|
import SubnetMFAToken from "./SubnetMFAToken";
|
||||||
|
import ClusterRegistrationForm from "./ClusterRegistrationForm";
|
||||||
|
import OnlineRegistration from "./OnlineRegistration";
|
||||||
|
|
||||||
interface IRegister {
|
interface IRegister {
|
||||||
classes: any;
|
classes: any;
|
||||||
@@ -79,469 +55,83 @@ const styles = (theme: Theme) =>
|
|||||||
sizedLabel: {
|
sizedLabel: {
|
||||||
minWidth: "75px",
|
minWidth: "75px",
|
||||||
},
|
},
|
||||||
...actionsTray,
|
|
||||||
...searchField,
|
|
||||||
...spacingUtils,
|
...spacingUtils,
|
||||||
...containerForHeader,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const Register = ({ classes }: IRegister) => {
|
const Register = ({ classes }: IRegister) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const operatorMode = useSelector(selOpMode);
|
|
||||||
const [license, setLicense] = useState<string>("");
|
const subnetMFAToken = useSelector(
|
||||||
const [subnetPassword, setSubnetPassword] = useState<string>("");
|
(state: AppState) => state.register.subnetMFAToken
|
||||||
const [subnetEmail, setSubnetEmail] = useState<string>("");
|
);
|
||||||
const [subnetMFAToken, setSubnetMFAToken] = useState<string>("");
|
const subnetAccessToken = useSelector(
|
||||||
const [subnetOTP, setSubnetOTP] = useState<string>("");
|
(state: AppState) => state.register.subnetAccessToken
|
||||||
const [subnetAccessToken, setSubnetAccessToken] = useState<string>("");
|
);
|
||||||
const [selectedSubnetOrganization, setSelectedSubnetOrganization] =
|
|
||||||
useState<string>("");
|
const subnetRegToken = useSelector(
|
||||||
const [subnetRegToken, setSubnetRegToken] = useState<string>("");
|
(state: AppState) => state.register.subnetRegToken
|
||||||
const [subnetOrganizations, setSubnetOrganizations] = useState<
|
);
|
||||||
SubnetOrganization[]
|
const subnetOrganizations = useSelector(
|
||||||
>([]);
|
(state: AppState) => state.register.subnetOrganizations
|
||||||
const [showPassword, setShowPassword] = useState<boolean>(false);
|
);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
|
||||||
const [loadingLicenseInfo, setLoadingLicenseInfo] = useState<boolean>(false);
|
const loading = useSelector((state: AppState) => state.register.loading);
|
||||||
const [clusterRegistered, setClusterRegistered] = useState<boolean>(false);
|
const loadingLicenseInfo = useSelector(
|
||||||
const [licenseInfo, setLicenseInfo] = useState<SubnetInfo | undefined>();
|
(state: AppState) => state.register.loadingLicenseInfo
|
||||||
const [curTab, setCurTab] = useState<number>(0);
|
);
|
||||||
|
const clusterRegistered = useSelector(
|
||||||
|
(state: AppState) => state.register.clusterRegistered
|
||||||
|
);
|
||||||
|
const licenseInfo = useSelector(
|
||||||
|
(state: AppState) => state.register.licenseInfo
|
||||||
|
);
|
||||||
|
const curTab = useSelector((state: AppState) => state.register.curTab);
|
||||||
|
|
||||||
const [initialLicenseLoading, setInitialLicenseLoading] =
|
const [initialLicenseLoading, setInitialLicenseLoading] =
|
||||||
useState<boolean>(true);
|
useState<boolean>(true);
|
||||||
const clearForm = () => {
|
|
||||||
setSubnetAccessToken("");
|
useEffect(() => {
|
||||||
setSelectedSubnetOrganization("");
|
// when unmounted, reset
|
||||||
setSubnetRegToken("");
|
return () => {
|
||||||
setShowPassword(false);
|
dispatch(resetRegisterForm());
|
||||||
setSubnetOrganizations([]);
|
|
||||||
setLicense("");
|
|
||||||
setSubnetPassword("");
|
|
||||||
setSubnetEmail("");
|
|
||||||
setSubnetMFAToken("");
|
|
||||||
setSubnetOTP("");
|
|
||||||
};
|
};
|
||||||
|
}, [dispatch]);
|
||||||
const getSubnetInfo = hasPermission(
|
|
||||||
CONSOLE_UI_RESOURCE,
|
|
||||||
IAM_PAGES_PERMISSIONS[IAM_PAGES.LICENSE],
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchLicenseInfo = useCallback(() => {
|
|
||||||
if (loadingLicenseInfo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (getSubnetInfo) {
|
|
||||||
setLoadingLicenseInfo(true);
|
|
||||||
api
|
|
||||||
.invoke("GET", `/api/v1/subnet/info`)
|
|
||||||
.then((res: SubnetInfo) => {
|
|
||||||
setLicenseInfo(res);
|
|
||||||
setClusterRegistered(true);
|
|
||||||
setLoadingLicenseInfo(false);
|
|
||||||
})
|
|
||||||
.catch((err: ErrorResponseHandler) => {
|
|
||||||
if (
|
|
||||||
err.detailedError.toLowerCase() !==
|
|
||||||
"License is not present".toLowerCase()
|
|
||||||
) {
|
|
||||||
dispatch(setErrorSnackMessage(err));
|
|
||||||
}
|
|
||||||
setClusterRegistered(false);
|
|
||||||
setLoadingLicenseInfo(false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setLoadingLicenseInfo(false);
|
|
||||||
}
|
|
||||||
}, [loadingLicenseInfo, getSubnetInfo, dispatch]);
|
|
||||||
|
|
||||||
const fetchSubnetRegToken = () => {
|
const fetchSubnetRegToken = () => {
|
||||||
if (loading || subnetRegToken) {
|
if (loading || subnetRegToken) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
dispatch(setLoading(true));
|
||||||
api
|
api
|
||||||
.invoke("GET", "/api/v1/subnet/registration-token")
|
.invoke("GET", "/api/v1/subnet/registration-token")
|
||||||
.then((resp: SubnetRegTokenResponse) => {
|
.then((resp: SubnetRegTokenResponse) => {
|
||||||
setLoading(false);
|
dispatch(setLoading(false));
|
||||||
if (resp && resp.regToken) {
|
if (resp && resp.regToken) {
|
||||||
setSubnetRegToken(resp.regToken);
|
dispatch(setSubnetRegToken(resp.regToken));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err: ErrorResponseHandler) => {
|
.catch((err: ErrorResponseHandler) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
dispatch(setErrorSnackMessage(err));
|
dispatch(setErrorSnackMessage(err));
|
||||||
setLoading(false);
|
dispatch(setLoading(false));
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const callRegister = (token: string, account_id: string) => {
|
|
||||||
const request: SubnetRegisterRequest = {
|
|
||||||
token: token,
|
|
||||||
account_id: account_id,
|
|
||||||
};
|
|
||||||
api
|
|
||||||
.invoke("POST", "/api/v1/subnet/register", request)
|
|
||||||
.then(() => {
|
|
||||||
setLoading(false);
|
|
||||||
clearForm();
|
|
||||||
fetchLicenseInfo();
|
|
||||||
})
|
|
||||||
.catch((err: ErrorResponseHandler) => {
|
|
||||||
dispatch(setErrorSnackMessage(err));
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const subnetRegister = () => {
|
|
||||||
if (loading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoading(true);
|
|
||||||
if (subnetAccessToken && selectedSubnetOrganization) {
|
|
||||||
callRegister(subnetAccessToken, selectedSubnetOrganization);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const subnetLoginWithMFA = () => {
|
|
||||||
if (loading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoading(true);
|
|
||||||
const request: SubnetLoginWithMFARequest = {
|
|
||||||
username: subnetEmail,
|
|
||||||
otp: subnetOTP,
|
|
||||||
mfa_token: subnetMFAToken,
|
|
||||||
};
|
|
||||||
api
|
|
||||||
.invoke("POST", "/api/v1/subnet/login/mfa", request)
|
|
||||||
.then((resp: SubnetLoginResponse) => {
|
|
||||||
setLoading(false);
|
|
||||||
if (resp && resp.access_token && resp.organizations.length > 0) {
|
|
||||||
if (resp.organizations.length === 1) {
|
|
||||||
callRegister(
|
|
||||||
resp.access_token,
|
|
||||||
resp.organizations[0].accountId.toString()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setSubnetAccessToken(resp.access_token);
|
|
||||||
setSubnetOrganizations(resp.organizations);
|
|
||||||
setSelectedSubnetOrganization(
|
|
||||||
resp.organizations[0].accountId.toString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err: ErrorResponseHandler) => {
|
|
||||||
dispatch(setErrorSnackMessage(err));
|
|
||||||
setLoading(false);
|
|
||||||
setSubnetOTP("");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const subnetLogin = () => {
|
|
||||||
if (loading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoading(true);
|
|
||||||
let request: SubnetLoginRequest = {
|
|
||||||
username: subnetEmail,
|
|
||||||
password: subnetPassword,
|
|
||||||
apiKey: license,
|
|
||||||
};
|
|
||||||
api
|
|
||||||
.invoke("POST", "/api/v1/subnet/login", request)
|
|
||||||
.then((resp: SubnetLoginResponse) => {
|
|
||||||
setLoading(false);
|
|
||||||
if (resp && resp.registered) {
|
|
||||||
clearForm();
|
|
||||||
fetchLicenseInfo();
|
|
||||||
} else if (resp && resp.mfa_token) {
|
|
||||||
setSubnetMFAToken(resp.mfa_token);
|
|
||||||
} else if (resp && resp.access_token && resp.organizations.length > 0) {
|
|
||||||
setSubnetAccessToken(resp.access_token);
|
|
||||||
setSubnetOrganizations(resp.organizations);
|
|
||||||
setSelectedSubnetOrganization(
|
|
||||||
resp.organizations[0].accountId.toString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err: ErrorResponseHandler) => {
|
|
||||||
dispatch(setErrorSnackMessage(err));
|
|
||||||
setLoading(false);
|
|
||||||
clearForm();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialLicenseLoading) {
|
if (initialLicenseLoading) {
|
||||||
fetchLicenseInfo();
|
dispatch(fetchLicenseInfo());
|
||||||
setInitialLicenseLoading(false);
|
setInitialLicenseLoading(false);
|
||||||
}
|
}
|
||||||
}, [fetchLicenseInfo, initialLicenseLoading, setInitialLicenseLoading]);
|
}, [initialLicenseLoading, setInitialLicenseLoading, dispatch]);
|
||||||
|
|
||||||
let clusterRegistrationForm: JSX.Element = <Fragment />;
|
let clusterRegistrationForm: JSX.Element = <Fragment />;
|
||||||
|
|
||||||
if (subnetAccessToken && subnetOrganizations.length > 0) {
|
if (subnetAccessToken && subnetOrganizations.length > 0) {
|
||||||
clusterRegistrationForm = (
|
clusterRegistrationForm = <ClusterRegistrationForm />;
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexFlow: "column",
|
|
||||||
flex: "2",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
marginTop: "15px",
|
|
||||||
marginBottom: "15px",
|
|
||||||
"& .title-text": {
|
|
||||||
marginLeft: "0px",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormTitle title={`Register MinIO cluster`} />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<SelectWrapper
|
|
||||||
id="subnet-organization"
|
|
||||||
name="subnet-organization"
|
|
||||||
onChange={(e) =>
|
|
||||||
setSelectedSubnetOrganization(e.target.value as string)
|
|
||||||
}
|
|
||||||
label="Select an organization"
|
|
||||||
value={selectedSubnetOrganization}
|
|
||||||
options={subnetOrganizations.map((organization) => ({
|
|
||||||
label: organization.company,
|
|
||||||
value: organization.accountId.toString(),
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
marginTop: "15px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
id={"register-cluster"}
|
|
||||||
onClick={() => subnetRegister()}
|
|
||||||
disabled={loading || subnetAccessToken.trim().length === 0}
|
|
||||||
variant="callAction"
|
|
||||||
label={"Register"}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<RegisterHelpBox />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
} else if (subnetMFAToken) {
|
} else if (subnetMFAToken) {
|
||||||
clusterRegistrationForm = (
|
clusterRegistrationForm = <SubnetMFAToken />;
|
||||||
<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",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
id={"verify"}
|
|
||||||
onClick={() => subnetLoginWithMFA()}
|
|
||||||
disabled={
|
|
||||||
loading ||
|
|
||||||
subnetOTP.trim().length === 0 ||
|
|
||||||
subnetMFAToken.trim().length === 0
|
|
||||||
}
|
|
||||||
variant="callAction"
|
|
||||||
label={"Verify"}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<RegisterHelpBox />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
clusterRegistrationForm = (
|
clusterRegistrationForm = <OnlineRegistration />;
|
||||||
<Fragment>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
"& .title-text": {
|
|
||||||
marginLeft: "27px",
|
|
||||||
fontWeight: 600,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormTitle
|
|
||||||
icon={<OnlineRegistrationIcon />}
|
|
||||||
title={`Online activation of MinIO Subscription Network License`}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexFlow: {
|
|
||||||
xs: "column",
|
|
||||||
md: "row",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexFlow: "column",
|
|
||||||
flex: "2",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
fontSize: "16px",
|
|
||||||
display: "flex",
|
|
||||||
flexFlow: "column",
|
|
||||||
marginTop: "30px",
|
|
||||||
marginBottom: "30px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Use your MinIO Subscription Network login credentials to register
|
|
||||||
this cluster.
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
flex: "1",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<InputBoxWrapper
|
|
||||||
className={classes.spacerBottom}
|
|
||||||
classes={{
|
|
||||||
inputLabel: classes.sizedLabel,
|
|
||||||
}}
|
|
||||||
id="subnet-email"
|
|
||||||
name="subnet-email"
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
setSubnetEmail(event.target.value)
|
|
||||||
}
|
|
||||||
label="Email"
|
|
||||||
value={subnetEmail}
|
|
||||||
overlayIcon={<UsersIcon />}
|
|
||||||
/>
|
|
||||||
<InputBoxWrapper
|
|
||||||
className={classes.spacerBottom}
|
|
||||||
classes={{
|
|
||||||
inputLabel: classes.sizedLabel,
|
|
||||||
}}
|
|
||||||
id="subnet-password"
|
|
||||||
name="subnet-password"
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
setSubnetPassword(event.target.value)
|
|
||||||
}
|
|
||||||
label="Password"
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
value={subnetPassword}
|
|
||||||
overlayIcon={
|
|
||||||
showPassword ? <VisibilityOffIcon /> : <RemoveRedEyeIcon />
|
|
||||||
}
|
|
||||||
overlayAction={() => setShowPassword(!showPassword)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
"& button": {
|
|
||||||
marginLeft: "8px",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
id={"sign-up"}
|
|
||||||
type="submit"
|
|
||||||
className={classes.spacerRight}
|
|
||||||
variant="regular"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
window.open(
|
|
||||||
`https://min.io/signup?ref=${
|
|
||||||
operatorMode ? "op" : "con"
|
|
||||||
}`,
|
|
||||||
"_blank"
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
label={"Sign up"}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
id={"register-credentials"}
|
|
||||||
type="submit"
|
|
||||||
variant="callAction"
|
|
||||||
disabled={
|
|
||||||
loading ||
|
|
||||||
subnetEmail.trim().length === 0 ||
|
|
||||||
subnetPassword.trim().length === 0
|
|
||||||
}
|
|
||||||
onClick={() => subnetLogin()}
|
|
||||||
label={"Register"}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<RegisterHelpBox />
|
|
||||||
</Box>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiKeyRegistration = (
|
const apiKeyRegistration = (
|
||||||
@@ -558,131 +148,14 @@ const Register = ({ classes }: IRegister) => {
|
|||||||
{clusterRegistered && licenseInfo ? (
|
{clusterRegistered && licenseInfo ? (
|
||||||
<ClusterRegistered email={licenseInfo.email} />
|
<ClusterRegistered email={licenseInfo.email} />
|
||||||
) : (
|
) : (
|
||||||
<ApiKeyRegister
|
<ApiKeyRegister registerEndpoint={"/api/v1/subnet/login"} />
|
||||||
afterRegister={fetchLicenseInfo}
|
|
||||||
registerEndpoint={"/api/v1/subnet/login"}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<ProxyConfiguration linkClass={classes.link} />
|
<ProxyConfiguration />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
const offlineRegUrl = `https://subnet.min.io/cluster/register?token=${subnetRegToken}`;
|
const offlineRegistration = <OfflineRegistration />;
|
||||||
const offlineRegistration = (
|
|
||||||
<Fragment>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
border: "1px solid #eaeaea",
|
|
||||||
borderRadius: "2px",
|
|
||||||
display: "flex",
|
|
||||||
flexFlow: "column",
|
|
||||||
padding: "43px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{clusterRegistered && licenseInfo ? (
|
|
||||||
<ClusterRegistered email={licenseInfo.email} />
|
|
||||||
) : null}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
"& .title-text": {
|
|
||||||
marginLeft: "27px",
|
|
||||||
fontWeight: 600,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormTitle
|
|
||||||
icon={<OfflineRegistrationIcon />}
|
|
||||||
title={`Register cluster in an Airgap environment`}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexFlow: "column",
|
|
||||||
flex: "2",
|
|
||||||
marginTop: "15px",
|
|
||||||
"& .step-number": {
|
|
||||||
color: "#ffffff",
|
|
||||||
height: "25px",
|
|
||||||
width: "25px",
|
|
||||||
background: "#081C42",
|
|
||||||
marginRight: "10px",
|
|
||||||
textAlign: "center",
|
|
||||||
fontWeight: 600,
|
|
||||||
borderRadius: "50%",
|
|
||||||
},
|
|
||||||
|
|
||||||
"& .step-row": {
|
|
||||||
fontSize: "16px",
|
|
||||||
display: "flex",
|
|
||||||
marginTop: "15px",
|
|
||||||
marginBottom: "15px",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<Box className="step-row">
|
|
||||||
<div className="step-text">
|
|
||||||
Click on the link to register this cluster in SUBNET
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
flex: "1",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 3,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
className={classes.link}
|
|
||||||
color="inherit"
|
|
||||||
href={offlineRegUrl}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
https://subnet.min.io/cluster/register
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<TooltipWrapper tooltip={"Copy to Clipboard"}>
|
|
||||||
<CopyToClipboard text={offlineRegUrl}>
|
|
||||||
<Button
|
|
||||||
type={"button"}
|
|
||||||
id={"copy-ult-to-clip-board"}
|
|
||||||
icon={<CopyIcon />}
|
|
||||||
color={"primary"}
|
|
||||||
variant={"regular"}
|
|
||||||
/>
|
|
||||||
</CopyToClipboard>
|
|
||||||
</TooltipWrapper>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginTop: "25px",
|
|
||||||
fontSize: "14px",
|
|
||||||
fontStyle: "italic",
|
|
||||||
color: "#5E5E5E",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
If this machine does not have internet connection, Copy paste
|
|
||||||
the following URL in a browser where you access SUBNET and
|
|
||||||
follow the instructions to complete the registration
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<RegisterHelpBox />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
|
|
||||||
const regUi = (
|
const regUi = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@@ -702,7 +175,7 @@ const Register = ({ classes }: IRegister) => {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{!clusterRegistered && <ProxyConfiguration linkClass={classes.link} />}
|
{!clusterRegistered && <ProxyConfiguration />}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -720,7 +193,7 @@ const Register = ({ classes }: IRegister) => {
|
|||||||
<Tabs
|
<Tabs
|
||||||
value={curTab}
|
value={curTab}
|
||||||
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
|
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
|
||||||
setCurTab(newValue);
|
dispatch(setCurTab(newValue));
|
||||||
}}
|
}}
|
||||||
indicatorColor="primary"
|
indicatorColor="primary"
|
||||||
textColor="primary"
|
textColor="primary"
|
||||||
@@ -731,17 +204,17 @@ const Register = ({ classes }: IRegister) => {
|
|||||||
<Tab
|
<Tab
|
||||||
label="Credentials"
|
label="Credentials"
|
||||||
id="simple-tab-0"
|
id="simple-tab-0"
|
||||||
aria-controls="simple-tabpanel-0"
|
aria-controls="simple-tab-panel-0"
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
label="API Key"
|
label="API Key"
|
||||||
id="simple-tab-1"
|
id="simple-tab-1"
|
||||||
aria-controls="simple-tabpanel-1"
|
aria-controls="simple-tab-panel-1"
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
label="Airgap"
|
label="Air-Gap"
|
||||||
id="simple-tab-2"
|
id="simple-tab-2"
|
||||||
aria-controls="simple-tabpanel-2"
|
aria-controls="simple-tab-panel-2"
|
||||||
onClick={() => fetchSubnetRegToken()}
|
onClick={() => fetchSubnetRegToken()}
|
||||||
/>
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -82,10 +82,7 @@ const RegisterOperator = ({ classes }: IRegister) => {
|
|||||||
{apiKeyRegistered ? (
|
{apiKeyRegistered ? (
|
||||||
<ClusterRegistered email={"Operator"} />
|
<ClusterRegistered email={"Operator"} />
|
||||||
) : (
|
) : (
|
||||||
<ApiKeyRegister
|
<ApiKeyRegister registerEndpoint={"/api/v1/subnet/apikey/register"} />
|
||||||
registerEndpoint={"/api/v1/subnet/apikey/register"}
|
|
||||||
afterRegister={fetchAPIKeyInfo}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@@ -78,7 +78,10 @@ function RegisterStatus({ classes, showHelp }: IRegisterStatus) {
|
|||||||
<Link
|
<Link
|
||||||
href="https://subnet.min.io"
|
href="https://subnet.min.io"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className={classes.link}
|
style={{
|
||||||
|
color: "#2781B0",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
SUBNET
|
SUBNET
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
|
|||||||
110
portal-ui/src/screens/Console/Support/SubnetMFAToken.tsx
Normal file
110
portal-ui/src/screens/Console/Support/SubnetMFAToken.tsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// This file is part of MinIO Console Server
|
||||||
|
// Copyright (c) 2023 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 from "react";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||||
|
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
||||||
|
import { setSubnetOTP } from "./registerSlice";
|
||||||
|
import { Button } from "mds";
|
||||||
|
import RegisterHelpBox from "./RegisterHelpBox";
|
||||||
|
import { AppState, useAppDispatch } from "../../../store";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { subnetLoginWithMFA } from "./registerThunks";
|
||||||
|
|
||||||
|
const SubnetMFAToken = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const subnetMFAToken = useSelector(
|
||||||
|
(state: AppState) => state.register.subnetMFAToken
|
||||||
|
);
|
||||||
|
const subnetOTP = useSelector((state: AppState) => state.register.subnetOTP);
|
||||||
|
const loading = useSelector((state: AppState) => state.register.loading);
|
||||||
|
|
||||||
|
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>) =>
|
||||||
|
dispatch(setSubnetOTP(event.target.value))
|
||||||
|
}
|
||||||
|
placeholder=""
|
||||||
|
label=""
|
||||||
|
value={subnetOTP}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
id={"verify"}
|
||||||
|
onClick={() => dispatch(subnetLoginWithMFA())}
|
||||||
|
disabled={
|
||||||
|
loading ||
|
||||||
|
subnetOTP.trim().length === 0 ||
|
||||||
|
subnetMFAToken.trim().length === 0
|
||||||
|
}
|
||||||
|
variant="callAction"
|
||||||
|
label={"Verify"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<RegisterHelpBox />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default SubnetMFAToken;
|
||||||
132
portal-ui/src/screens/Console/Support/registerSlice.ts
Normal file
132
portal-ui/src/screens/Console/Support/registerSlice.ts
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// This file is part of MinIO Console Server
|
||||||
|
// Copyright (c) 2023 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 { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
import { SubnetInfo, SubnetOrganization } from "../License/types";
|
||||||
|
|
||||||
|
export interface RegisterState {
|
||||||
|
license: string;
|
||||||
|
subnetPassword: string;
|
||||||
|
subnetEmail: string;
|
||||||
|
subnetMFAToken: string;
|
||||||
|
subnetOTP: string;
|
||||||
|
subnetAccessToken: string;
|
||||||
|
selectedSubnetOrganization: string;
|
||||||
|
subnetRegToken: string;
|
||||||
|
subnetOrganizations: SubnetOrganization[];
|
||||||
|
showPassword: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
loadingLicenseInfo: boolean;
|
||||||
|
clusterRegistered: boolean;
|
||||||
|
licenseInfo: SubnetInfo | undefined;
|
||||||
|
curTab: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: RegisterState = {
|
||||||
|
license: "",
|
||||||
|
subnetPassword: "",
|
||||||
|
subnetEmail: "",
|
||||||
|
subnetMFAToken: "",
|
||||||
|
subnetOTP: "",
|
||||||
|
subnetAccessToken: "",
|
||||||
|
selectedSubnetOrganization: "",
|
||||||
|
subnetRegToken: "",
|
||||||
|
subnetOrganizations: [],
|
||||||
|
showPassword: false,
|
||||||
|
loading: false,
|
||||||
|
loadingLicenseInfo: false,
|
||||||
|
clusterRegistered: false,
|
||||||
|
licenseInfo: undefined,
|
||||||
|
curTab: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const registerSlice = createSlice({
|
||||||
|
name: "register",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setLicense: (state, action: PayloadAction<string>) => {
|
||||||
|
state.license = action.payload;
|
||||||
|
},
|
||||||
|
setSubnetPassword: (state, action: PayloadAction<string>) => {
|
||||||
|
state.subnetPassword = action.payload;
|
||||||
|
},
|
||||||
|
setSubnetEmail: (state, action: PayloadAction<string>) => {
|
||||||
|
state.subnetEmail = action.payload;
|
||||||
|
},
|
||||||
|
setSubnetMFAToken: (state, action: PayloadAction<string>) => {
|
||||||
|
state.subnetMFAToken = action.payload;
|
||||||
|
},
|
||||||
|
setSubnetOTP: (state, action: PayloadAction<string>) => {
|
||||||
|
state.subnetOTP = action.payload;
|
||||||
|
},
|
||||||
|
setSubnetAccessToken: (state, action: PayloadAction<string>) => {
|
||||||
|
state.subnetAccessToken = action.payload;
|
||||||
|
},
|
||||||
|
setSelectedSubnetOrganization: (state, action: PayloadAction<string>) => {
|
||||||
|
state.selectedSubnetOrganization = action.payload;
|
||||||
|
},
|
||||||
|
setSubnetRegToken: (state, action: PayloadAction<string>) => {
|
||||||
|
state.subnetRegToken = action.payload;
|
||||||
|
},
|
||||||
|
setSubnetOrganizations: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<SubnetOrganization[]>
|
||||||
|
) => {
|
||||||
|
state.subnetOrganizations = action.payload;
|
||||||
|
},
|
||||||
|
setShowPassword: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.showPassword = action.payload;
|
||||||
|
},
|
||||||
|
setLoading: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.loading = action.payload;
|
||||||
|
},
|
||||||
|
setLoadingLicenseInfo: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.loadingLicenseInfo = action.payload;
|
||||||
|
},
|
||||||
|
setClusterRegistered: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.clusterRegistered = action.payload;
|
||||||
|
},
|
||||||
|
setLicenseInfo: (state, action: PayloadAction<SubnetInfo | undefined>) => {
|
||||||
|
state.licenseInfo = action.payload;
|
||||||
|
},
|
||||||
|
setCurTab: (state, action: PayloadAction<number>) => {
|
||||||
|
state.curTab = action.payload;
|
||||||
|
},
|
||||||
|
resetRegisterForm: () => initialState,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Action creators are generated for each case reducer function
|
||||||
|
export const {
|
||||||
|
setLicense,
|
||||||
|
setSubnetPassword,
|
||||||
|
setSubnetEmail,
|
||||||
|
setSubnetMFAToken,
|
||||||
|
setSubnetOTP,
|
||||||
|
setSubnetAccessToken,
|
||||||
|
setSelectedSubnetOrganization,
|
||||||
|
setSubnetRegToken,
|
||||||
|
setSubnetOrganizations,
|
||||||
|
setShowPassword,
|
||||||
|
setLoading,
|
||||||
|
setLoadingLicenseInfo,
|
||||||
|
setClusterRegistered,
|
||||||
|
setLicenseInfo,
|
||||||
|
setCurTab,
|
||||||
|
resetRegisterForm,
|
||||||
|
} = registerSlice.actions;
|
||||||
|
|
||||||
|
export default registerSlice.reducer;
|
||||||
211
portal-ui/src/screens/Console/Support/registerThunks.ts
Normal file
211
portal-ui/src/screens/Console/Support/registerThunks.ts
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
// This file is part of MinIO Console Server
|
||||||
|
// Copyright (c) 2023 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 {
|
||||||
|
resetRegisterForm,
|
||||||
|
setClusterRegistered,
|
||||||
|
setLicenseInfo,
|
||||||
|
setLoading,
|
||||||
|
setLoadingLicenseInfo,
|
||||||
|
setSelectedSubnetOrganization,
|
||||||
|
setSubnetAccessToken,
|
||||||
|
setSubnetMFAToken,
|
||||||
|
setSubnetOrganizations,
|
||||||
|
setSubnetOTP,
|
||||||
|
} from "./registerSlice";
|
||||||
|
import api from "../../../common/api";
|
||||||
|
import {
|
||||||
|
SubnetInfo,
|
||||||
|
SubnetLoginRequest,
|
||||||
|
SubnetLoginResponse,
|
||||||
|
SubnetLoginWithMFARequest,
|
||||||
|
SubnetRegisterRequest,
|
||||||
|
} from "../License/types";
|
||||||
|
import { ErrorResponseHandler } from "../../../common/types";
|
||||||
|
import { setErrorSnackMessage } from "../../../systemSlice";
|
||||||
|
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||||
|
import { AppState } from "../../../store";
|
||||||
|
import { hasPermission } from "../../../common/SecureComponent";
|
||||||
|
import {
|
||||||
|
CONSOLE_UI_RESOURCE,
|
||||||
|
IAM_PAGES,
|
||||||
|
IAM_PAGES_PERMISSIONS,
|
||||||
|
} from "../../../common/SecureComponent/permissions";
|
||||||
|
|
||||||
|
export const fetchLicenseInfo = createAsyncThunk(
|
||||||
|
"register/fetchLicenseInfo",
|
||||||
|
async (_, { getState, dispatch }) => {
|
||||||
|
const state = getState() as AppState;
|
||||||
|
|
||||||
|
const getSubnetInfo = hasPermission(
|
||||||
|
CONSOLE_UI_RESOURCE,
|
||||||
|
IAM_PAGES_PERMISSIONS[IAM_PAGES.LICENSE],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadingLicenseInfo = state.register.loadingLicenseInfo;
|
||||||
|
|
||||||
|
if (loadingLicenseInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (getSubnetInfo) {
|
||||||
|
dispatch(setLoadingLicenseInfo(true));
|
||||||
|
api
|
||||||
|
.invoke("GET", `/api/v1/subnet/info`)
|
||||||
|
.then((res: SubnetInfo) => {
|
||||||
|
dispatch(setLicenseInfo(res));
|
||||||
|
dispatch(setClusterRegistered(true));
|
||||||
|
dispatch(setLoadingLicenseInfo(false));
|
||||||
|
})
|
||||||
|
.catch((err: ErrorResponseHandler) => {
|
||||||
|
if (
|
||||||
|
err.detailedError.toLowerCase() !==
|
||||||
|
"License is not present".toLowerCase() &&
|
||||||
|
err.detailedError.toLowerCase() !==
|
||||||
|
"license not found".toLowerCase()
|
||||||
|
) {
|
||||||
|
dispatch(setErrorSnackMessage(err));
|
||||||
|
}
|
||||||
|
dispatch(setClusterRegistered(false));
|
||||||
|
dispatch(setLoadingLicenseInfo(false));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dispatch(setLoadingLicenseInfo(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface ClassRegisterArgs {
|
||||||
|
token: string;
|
||||||
|
account_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const callRegister = createAsyncThunk(
|
||||||
|
"register/callRegister",
|
||||||
|
async (args: ClassRegisterArgs, { dispatch }) => {
|
||||||
|
const request: SubnetRegisterRequest = {
|
||||||
|
token: args.token,
|
||||||
|
account_id: args.account_id,
|
||||||
|
};
|
||||||
|
api
|
||||||
|
.invoke("POST", "/api/v1/subnet/register", request)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(setLoading(false));
|
||||||
|
dispatch(resetRegisterForm());
|
||||||
|
dispatch(fetchLicenseInfo());
|
||||||
|
})
|
||||||
|
.catch((err: ErrorResponseHandler) => {
|
||||||
|
dispatch(setErrorSnackMessage(err));
|
||||||
|
dispatch(setLoading(false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const subnetLoginWithMFA = createAsyncThunk(
|
||||||
|
"register/subnetLoginWithMFA",
|
||||||
|
async (_, { getState, rejectWithValue, dispatch }) => {
|
||||||
|
const state = getState() as AppState;
|
||||||
|
|
||||||
|
const subnetEmail = state.register.subnetEmail;
|
||||||
|
const subnetMFAToken = state.register.subnetMFAToken;
|
||||||
|
const subnetOTP = state.register.subnetOTP;
|
||||||
|
const loading = state.register.loading;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(setLoading(true));
|
||||||
|
const request: SubnetLoginWithMFARequest = {
|
||||||
|
username: subnetEmail,
|
||||||
|
otp: subnetOTP,
|
||||||
|
mfa_token: subnetMFAToken,
|
||||||
|
};
|
||||||
|
api
|
||||||
|
.invoke("POST", "/api/v1/subnet/login/mfa", request)
|
||||||
|
.then((resp: SubnetLoginResponse) => {
|
||||||
|
dispatch(setLoading(false));
|
||||||
|
if (resp && resp.access_token && resp.organizations.length > 0) {
|
||||||
|
if (resp.organizations.length === 1) {
|
||||||
|
dispatch(
|
||||||
|
callRegister({
|
||||||
|
token: resp.access_token,
|
||||||
|
account_id: resp.organizations[0].accountId.toString(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
dispatch(setSubnetAccessToken(resp.access_token));
|
||||||
|
dispatch(setSubnetOrganizations(resp.organizations));
|
||||||
|
dispatch(
|
||||||
|
setSelectedSubnetOrganization(
|
||||||
|
resp.organizations[0].accountId.toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: ErrorResponseHandler) => {
|
||||||
|
dispatch(setErrorSnackMessage(err));
|
||||||
|
dispatch(setLoading(false));
|
||||||
|
dispatch(setSubnetOTP(""));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const subnetLogin = createAsyncThunk(
|
||||||
|
"register/subnetLogin",
|
||||||
|
async (_, { getState, rejectWithValue, dispatch }) => {
|
||||||
|
const state = getState() as AppState;
|
||||||
|
|
||||||
|
const license = state.register.license;
|
||||||
|
const subnetPassword = state.register.subnetPassword;
|
||||||
|
const subnetEmail = state.register.subnetEmail;
|
||||||
|
const loading = state.register.loading;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(setLoading(true));
|
||||||
|
let request: SubnetLoginRequest = {
|
||||||
|
username: subnetEmail,
|
||||||
|
password: subnetPassword,
|
||||||
|
apiKey: license,
|
||||||
|
};
|
||||||
|
api
|
||||||
|
.invoke("POST", "/api/v1/subnet/login", request)
|
||||||
|
.then((resp: SubnetLoginResponse) => {
|
||||||
|
dispatch(setLoading(false));
|
||||||
|
if (resp && resp.registered) {
|
||||||
|
dispatch(resetRegisterForm());
|
||||||
|
dispatch(fetchLicenseInfo());
|
||||||
|
} else if (resp && resp.mfa_token) {
|
||||||
|
dispatch(setSubnetMFAToken(resp.mfa_token));
|
||||||
|
} else if (resp && resp.access_token && resp.organizations.length > 0) {
|
||||||
|
dispatch(setSubnetAccessToken(resp.access_token));
|
||||||
|
dispatch(setSubnetOrganizations(resp.organizations));
|
||||||
|
dispatch(
|
||||||
|
setSelectedSubnetOrganization(
|
||||||
|
resp.organizations[0].accountId.toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: ErrorResponseHandler) => {
|
||||||
|
dispatch(setErrorSnackMessage(err));
|
||||||
|
dispatch(setLoading(false));
|
||||||
|
dispatch(resetRegisterForm());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -58,7 +58,7 @@ export const ClusterRegistered = ({ email }: { email: string }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProxyConfiguration = ({ linkClass }: { linkClass: string }) => {
|
export const ProxyConfiguration = () => {
|
||||||
const proxyConfigurationCommand =
|
const proxyConfigurationCommand =
|
||||||
"mc admin config set {alias} subnet proxy={proxy}";
|
"mc admin config set {alias} subnet proxy={proxy}";
|
||||||
const [displaySubnetProxy, setDisplaySubnetProxy] = useState(false);
|
const [displaySubnetProxy, setDisplaySubnetProxy] = useState(false);
|
||||||
@@ -104,7 +104,10 @@ export const ProxyConfiguration = ({ linkClass }: { linkClass: string }) => {
|
|||||||
>
|
>
|
||||||
For airgap/firewalled environments it is possible to{" "}
|
For airgap/firewalled environments it is possible to{" "}
|
||||||
<Link
|
<Link
|
||||||
className={linkClass}
|
style={{
|
||||||
|
color: "#2781B0",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
href="https://min.io/docs/minio/linux/reference/minio-mc-admin/mc-admin-config.html?ref=con"
|
href="https://min.io/docs/minio/linux/reference/minio-mc-admin/mc-admin-config.html?ref=con"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import editTenantAuditLoggingReducer from "./screens/Console/Tenants/TenantDetai
|
|||||||
import editTenantSecurityContextReducer from "./screens/Console/Tenants/tenantSecurityContextSlice";
|
import editTenantSecurityContextReducer from "./screens/Console/Tenants/tenantSecurityContextSlice";
|
||||||
import directPVReducer from "./screens/Console/DirectPV/directPVSlice";
|
import directPVReducer from "./screens/Console/DirectPV/directPVSlice";
|
||||||
import licenseReducer from "./screens/Console/License/licenseSlice";
|
import licenseReducer from "./screens/Console/License/licenseSlice";
|
||||||
|
import registerReducer from "./screens/Console/Support/registerSlice";
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
system: systemReducer,
|
system: systemReducer,
|
||||||
@@ -50,6 +51,7 @@ const rootReducer = combineReducers({
|
|||||||
objectBrowser: objectBrowserReducer,
|
objectBrowser: objectBrowserReducer,
|
||||||
healthInfo: healthInfoReducer,
|
healthInfo: healthInfoReducer,
|
||||||
dashboard: dashboardReducer,
|
dashboard: dashboardReducer,
|
||||||
|
register: registerReducer,
|
||||||
// Operator Reducers
|
// Operator Reducers
|
||||||
tenants: tenantsReducer,
|
tenants: tenantsReducer,
|
||||||
createTenant: createTenantReducer,
|
createTenant: createTenantReducer,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func registerSubnetHandlers(api *operations.ConsoleAPI) {
|
|||||||
})
|
})
|
||||||
// Get subnet info
|
// Get subnet info
|
||||||
api.SubnetSubnetInfoHandler = subnetApi.SubnetInfoHandlerFunc(func(params subnetApi.SubnetInfoParams, session *models.Principal) middleware.Responder {
|
api.SubnetSubnetInfoHandler = subnetApi.SubnetInfoHandlerFunc(func(params subnetApi.SubnetInfoParams, session *models.Principal) middleware.Responder {
|
||||||
resp, err := GetSubnetInfoResponse(params)
|
resp, err := GetSubnetInfoResponse(session, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return subnetApi.NewSubnetInfoDefault(int(err.Code)).WithPayload(err)
|
return subnetApi.NewSubnetInfoDefault(int(err.Code)).WithPayload(err)
|
||||||
}
|
}
|
||||||
@@ -318,14 +318,47 @@ func subnetRegisterResponse(ctx context.Context, minioClient MinioAdmin, params
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSubnetInfoResponse(params subnetApi.SubnetInfoParams) (*models.License, *models.Error) {
|
var ErrSubnetLicenseNotFound = errors.New("license not found")
|
||||||
|
|
||||||
|
func GetSubnetInfoResponse(session *models.Principal, params subnetApi.SubnetInfoParams) (*models.License, *models.Error) {
|
||||||
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
|
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
client := &xhttp.Client{
|
client := &xhttp.Client{
|
||||||
Client: GetConsoleHTTPClient(""),
|
Client: GetConsoleHTTPClient(""),
|
||||||
}
|
}
|
||||||
|
// license gets seeded to us by MinIO
|
||||||
|
seededLicense := os.Getenv(EnvSubnetLicense)
|
||||||
|
// if it's missing, we will gracefully fallback to attempt to fetch it from MinIO
|
||||||
|
if seededLicense == "" {
|
||||||
|
mAdmin, err := NewMinioAdminClient(session)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrorWithContext(ctx, err)
|
||||||
|
}
|
||||||
|
adminClient := AdminClient{Client: mAdmin}
|
||||||
|
|
||||||
licenseInfo, err := subnet.ParseLicense(client, os.Getenv(EnvSubnetLicense))
|
configBytes, err := adminClient.getConfigKV(params.HTTPRequest.Context(), "subnet")
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrorWithContext(ctx, err)
|
||||||
|
}
|
||||||
|
subSysConfigs, err := madmin.ParseServerConfigOutput(string(configBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrorWithContext(ctx, err)
|
||||||
|
}
|
||||||
|
// search for licese
|
||||||
|
for _, v := range subSysConfigs {
|
||||||
|
for _, sv := range v.KV {
|
||||||
|
if sv.Key == "license" {
|
||||||
|
seededLicense = sv.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// still empty means not found
|
||||||
|
if seededLicense == "" {
|
||||||
|
return nil, ErrorWithContext(ctx, ErrSubnetLicenseNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
licenseInfo, err := subnet.ParseLicense(client, seededLicense)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorWithContext(ctx, err)
|
return nil, ErrorWithContext(ctx, err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user