Migrated Register page components to mds (#3034)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -18,14 +18,14 @@ import React, { Fragment } from "react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { selectorTypes } from "../SelectWrapper/SelectWrapper";
|
||||
import { SelectorType } from "mds";
|
||||
import { Menu, MenuItem } from "@mui/material";
|
||||
|
||||
interface IInputUnitBox {
|
||||
classes: any;
|
||||
id: string;
|
||||
unitSelected: string;
|
||||
unitsList: selectorTypes[];
|
||||
unitsList: SelectorType[];
|
||||
disabled?: boolean;
|
||||
onUnitChange?: (newValue: string) => void;
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 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 Grid from "@mui/material/Grid";
|
||||
import {
|
||||
FormControl,
|
||||
InputBase,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
||||
import { HelpIcon } from "mds";
|
||||
|
||||
export interface selectorTypes {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface SelectProps {
|
||||
options: selectorTypes[];
|
||||
value: string;
|
||||
label: string;
|
||||
id: string;
|
||||
name: string;
|
||||
tooltip?: string;
|
||||
onChange: (e: SelectChangeEvent<string>) => void;
|
||||
disabled?: boolean;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
fieldContainer: {
|
||||
display: "flex",
|
||||
"@media (max-width: 600px)": {
|
||||
flexFlow: "column",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const SelectStyled = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
height: 38,
|
||||
lineHeight: 1,
|
||||
"label + &": {
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
},
|
||||
input: {
|
||||
height: 38,
|
||||
position: "relative",
|
||||
color: "#07193E",
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
padding: "8px 20px 10px 10px",
|
||||
border: "#e5e5e5 1px solid",
|
||||
borderRadius: 4,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"&:hover": {
|
||||
borderColor: "#393939",
|
||||
},
|
||||
"&:focus": {
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)(InputBase);
|
||||
|
||||
const SelectWrapper = ({
|
||||
classes,
|
||||
id,
|
||||
name,
|
||||
onChange,
|
||||
options,
|
||||
label,
|
||||
tooltip = "",
|
||||
value,
|
||||
disabled = false,
|
||||
}: SelectProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<div className={classes.tooltip}>
|
||||
<HelpIcon />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
)}
|
||||
<FormControl fullWidth>
|
||||
<Select
|
||||
id={id}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
input={<SelectStyled />}
|
||||
disabled={disabled}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<MenuItem
|
||||
value={option.value}
|
||||
key={`select-${name}-${option.label}`}
|
||||
>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(SelectWrapper);
|
||||
@@ -14,42 +14,27 @@
|
||||
// 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, useCallback, useEffect, useState } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
import { Button, OnlineRegistrationIcon } from "mds";
|
||||
import { FormTitle } from "./utils";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import GetApiKeyModal from "./GetApiKeyModal";
|
||||
import RegisterHelpBox from "./RegisterHelpBox";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Box, Button, FormLayout, InputBox, OnlineRegistrationIcon } from "mds";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { SubnetLoginRequest, SubnetLoginResponse } from "../License/types";
|
||||
import api from "../../../common/api";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import {
|
||||
setErrorSnackMessage,
|
||||
setServerNeedsRestart,
|
||||
} from "../../../systemSlice";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { spacingUtils } from "../Common/FormComponents/common/styleLibrary";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
|
||||
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
|
||||
import GetApiKeyModal from "./GetApiKeyModal";
|
||||
import RegisterHelpBox from "./RegisterHelpBox";
|
||||
import api from "../../../common/api";
|
||||
|
||||
interface IApiKeyRegister {
|
||||
classes: any;
|
||||
registerEndpoint: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
sizedLabel: {
|
||||
minWidth: "75px",
|
||||
},
|
||||
...spacingUtils,
|
||||
});
|
||||
|
||||
const ApiKeyRegister = ({ classes, registerEndpoint }: IApiKeyRegister) => {
|
||||
const ApiKeyRegister = ({ registerEndpoint }: IApiKeyRegister) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [showApiKeyModal, setShowApiKeyModal] = useState(false);
|
||||
@@ -92,108 +77,66 @@ const ApiKeyRegister = ({ classes, registerEndpoint }: IApiKeyRegister) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Box
|
||||
sx={{
|
||||
"& .title-text": {
|
||||
marginLeft: "27px",
|
||||
fontWeight: 600,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<FormTitle
|
||||
icon={<OnlineRegistrationIcon />}
|
||||
title={`Register cluster with API key`}
|
||||
/>
|
||||
</Box>
|
||||
<FormLayout
|
||||
title={"Register cluster with API key"}
|
||||
icon={<OnlineRegistrationIcon />}
|
||||
containerPadding={false}
|
||||
withBorders={false}
|
||||
helpBox={<RegisterHelpBox />}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
display: "flex",
|
||||
flexFlow: {
|
||||
xs: "column",
|
||||
md: "row",
|
||||
},
|
||||
flexFlow: "column",
|
||||
marginBottom: "30px",
|
||||
}}
|
||||
>
|
||||
<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 API Key to register this
|
||||
cluster.
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
flex: "1",
|
||||
}}
|
||||
>
|
||||
<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": {
|
||||
marginLeft: "8px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
id={"get-from-subnet"}
|
||||
variant="regular"
|
||||
className={classes.spacerRight}
|
||||
disabled={loading}
|
||||
onClick={() => setShowApiKeyModal(true)}
|
||||
label={"Get from SUBNET"}
|
||||
/>
|
||||
<Button
|
||||
id={"register"}
|
||||
type="submit"
|
||||
variant="callAction"
|
||||
disabled={loading || apiKey.trim().length === 0}
|
||||
onClick={() => onRegister()}
|
||||
label={"Register"}
|
||||
/>
|
||||
<GetApiKeyModal
|
||||
open={showApiKeyModal}
|
||||
closeModal={() => setShowApiKeyModal(false)}
|
||||
onSet={(value) => {
|
||||
setApiKey(value);
|
||||
setFromModal(true);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<RegisterHelpBox />
|
||||
Use your MinIO Subscription Network API Key to register this cluster.
|
||||
</Box>
|
||||
</Fragment>
|
||||
<Box
|
||||
sx={{
|
||||
flex: "1",
|
||||
}}
|
||||
>
|
||||
<InputBox
|
||||
id="api-key"
|
||||
name="api-key"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setApiKey(event.target.value)
|
||||
}
|
||||
label="API Key"
|
||||
value={apiKey}
|
||||
/>
|
||||
|
||||
<Box sx={modalStyleUtils.modalButtonBar}>
|
||||
<Button
|
||||
id={"get-from-subnet"}
|
||||
variant="regular"
|
||||
disabled={loading}
|
||||
onClick={() => setShowApiKeyModal(true)}
|
||||
label={"Get from SUBNET"}
|
||||
/>
|
||||
<Button
|
||||
id={"register"}
|
||||
type="submit"
|
||||
variant="callAction"
|
||||
disabled={loading || apiKey.trim().length === 0}
|
||||
onClick={() => onRegister()}
|
||||
label={"Register"}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<GetApiKeyModal
|
||||
open={showApiKeyModal}
|
||||
closeModal={() => setShowApiKeyModal(false)}
|
||||
onSet={(value) => {
|
||||
setApiKey(value);
|
||||
setFromModal(true);
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ApiKeyRegister);
|
||||
export default ApiKeyRegister;
|
||||
|
||||
@@ -15,15 +15,13 @@
|
||||
// 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 { Box, Button, FormLayout, Select } from "mds";
|
||||
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";
|
||||
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
|
||||
import RegisterHelpBox from "./RegisterHelpBox";
|
||||
|
||||
const ClusterRegistrationForm = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -40,76 +38,48 @@ const ClusterRegistrationForm = () => {
|
||||
const loading = useSelector((state: AppState) => state.register.loading);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
}}
|
||||
<FormLayout
|
||||
title={"Register MinIO cluster"}
|
||||
containerPadding
|
||||
withBorders={false}
|
||||
helpBox={<RegisterHelpBox />}
|
||||
>
|
||||
<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))
|
||||
<Select
|
||||
id="subnet-organization"
|
||||
name="subnet-organization"
|
||||
onChange={(value) =>
|
||||
dispatch(setSelectedSubnetOrganization(value as string))
|
||||
}
|
||||
label="Select an organization"
|
||||
value={selectedSubnetOrganization}
|
||||
options={subnetOrganizations.map((organization) => ({
|
||||
label: organization.company,
|
||||
value: organization.accountId.toString(),
|
||||
}))}
|
||||
/>
|
||||
<Box sx={modalStyleUtils.modalButtonBar}>
|
||||
<Button
|
||||
id={"register-cluster"}
|
||||
onClick={() => () => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
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>
|
||||
dispatch(setLoading(true));
|
||||
if (subnetAccessToken && selectedSubnetOrganization) {
|
||||
dispatch(
|
||||
callRegister({
|
||||
token: subnetAccessToken,
|
||||
account_id: selectedSubnetOrganization,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}}
|
||||
disabled={loading || subnetAccessToken.trim().length === 0}
|
||||
variant="callAction"
|
||||
label={"Register"}
|
||||
/>
|
||||
</Box>
|
||||
<RegisterHelpBox />
|
||||
</Box>
|
||||
</FormLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,52 +14,24 @@
|
||||
// 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 "mds";
|
||||
import { Box, FormLayout, InfoIcon, InputBox, LockIcon, UsersIcon } from "mds";
|
||||
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({
|
||||
sizedLabel: {
|
||||
minWidth: "75px",
|
||||
},
|
||||
...containerForHeader,
|
||||
...spacingUtils,
|
||||
});
|
||||
import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
|
||||
interface IGetApiKeyModalProps {
|
||||
open: boolean;
|
||||
closeModal: () => void;
|
||||
onSet: (apiKey: string) => void;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const GetApiKeyModal = ({
|
||||
open,
|
||||
closeModal,
|
||||
classes,
|
||||
onSet,
|
||||
}: IGetApiKeyModalProps) => {
|
||||
const GetApiKeyModal = ({ open, closeModal, 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("");
|
||||
|
||||
@@ -68,7 +40,6 @@ const GetApiKeyModal = ({
|
||||
closeModal();
|
||||
setEmail("");
|
||||
setPassword("");
|
||||
setShowPassword(false);
|
||||
setMfaToken("");
|
||||
setSubnetOTP("");
|
||||
};
|
||||
@@ -107,12 +78,8 @@ const GetApiKeyModal = ({
|
||||
|
||||
const getCredentialsDialog = () => {
|
||||
return (
|
||||
<Box sx={{ width: 500 }}>
|
||||
<InputBoxWrapper
|
||||
className={classes.spacerBottom}
|
||||
classes={{
|
||||
inputLabel: classes.sizedLabel,
|
||||
}}
|
||||
<FormLayout withBorders={false} containerPadding={false}>
|
||||
<InputBox
|
||||
id="subnet-email"
|
||||
name="subnet-email"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
@@ -122,25 +89,17 @@ const GetApiKeyModal = ({
|
||||
value={email}
|
||||
overlayIcon={<UsersIcon />}
|
||||
/>
|
||||
<InputBoxWrapper
|
||||
className={classes.spacerBottom}
|
||||
classes={{
|
||||
inputLabel: classes.sizedLabel,
|
||||
}}
|
||||
<InputBox
|
||||
id="subnet-password"
|
||||
name="subnet-password"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setPassword(event.target.value)
|
||||
}
|
||||
label="Password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
type={"password"}
|
||||
value={password}
|
||||
overlayIcon={
|
||||
showPassword ? <VisibilityOffIcon /> : <RemoveRedEyeIcon />
|
||||
}
|
||||
overlayAction={() => setShowPassword(!showPassword)}
|
||||
/>
|
||||
</Box>
|
||||
</FormLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -150,11 +109,11 @@ const GetApiKeyModal = ({
|
||||
<Box sx={{ display: "flex", flexFlow: "column", flex: "2" }}>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontSize: 14,
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
marginTop: "30px",
|
||||
marginBottom: "30px",
|
||||
marginTop: 20,
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
Two-Factor Authentication
|
||||
@@ -171,8 +130,8 @@ const GetApiKeyModal = ({
|
||||
marginTop: "30px",
|
||||
}}
|
||||
>
|
||||
<InputBoxWrapper
|
||||
overlayIcon={<LockOutlinedIcon />}
|
||||
<InputBox
|
||||
overlayIcon={<LockIcon />}
|
||||
id="subnet-otp"
|
||||
name="subnet-otp"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
@@ -183,13 +142,6 @@ const GetApiKeyModal = ({
|
||||
value={subnetOTP}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
@@ -218,4 +170,4 @@ const GetApiKeyModal = ({
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default withStyles(styles)(GetApiKeyModal);
|
||||
export default GetApiKeyModal;
|
||||
|
||||
@@ -15,21 +15,27 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment, useState } 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 {
|
||||
Box,
|
||||
Button,
|
||||
CommentBox,
|
||||
CopyIcon,
|
||||
FormLayout,
|
||||
OfflineRegistrationIcon,
|
||||
} from "mds";
|
||||
import { ClusterRegistered } from "./utils";
|
||||
import { AppState, useAppDispatch } from "../../../store";
|
||||
import { useSelector } from "react-redux";
|
||||
import CommentBoxWrapper from "../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import { fetchLicenseInfo } from "./registerThunks";
|
||||
import {
|
||||
setErrorSnackMessage,
|
||||
setServerNeedsRestart,
|
||||
} from "../../../systemSlice";
|
||||
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
|
||||
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
|
||||
import CopyToClipboard from "react-copy-to-clipboard";
|
||||
import RegisterHelpBox from "./RegisterHelpBox";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
|
||||
const OfflineRegistration = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -66,9 +72,8 @@ const OfflineRegistration = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<Box
|
||||
withBorders
|
||||
sx={{
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
padding: "43px",
|
||||
@@ -77,146 +82,105 @@ const OfflineRegistration = () => {
|
||||
{clusterRegistered && licenseInfo ? (
|
||||
<ClusterRegistered email={licenseInfo.email} />
|
||||
) : (
|
||||
<Fragment>
|
||||
<Box
|
||||
sx={{
|
||||
"& .title-text": {
|
||||
marginLeft: "27px",
|
||||
fontWeight: 600,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<FormTitle
|
||||
icon={<OfflineRegistrationIcon />}
|
||||
title={`Register cluster in an Air-gap environment`}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<FormLayout
|
||||
title={"Register cluster in an Air-gap environment"}
|
||||
icon={<OfflineRegistrationIcon />}
|
||||
helpBox={<RegisterHelpBox />}
|
||||
withBorders={false}
|
||||
containerPadding={false}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
flex: "2",
|
||||
marginTop: "15px",
|
||||
"& .step-row": {
|
||||
fontSize: 14,
|
||||
display: "flex",
|
||||
marginTop: "15px",
|
||||
marginBottom: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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 and
|
||||
get a License Key for this Air-Gap deployment
|
||||
</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",
|
||||
}}
|
||||
>
|
||||
Note: 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
|
||||
sx={{
|
||||
marginTop: "25px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<label style={{ fontWeight: "bold", marginBottom: "10px" }}>
|
||||
Paste the License Key{" "}
|
||||
</label>
|
||||
<CommentBoxWrapper
|
||||
value={licenseKey}
|
||||
disabled={isSaving}
|
||||
label={""}
|
||||
id={"licenseKey"}
|
||||
name={"licenseKey"}
|
||||
placeholder={"License Key"}
|
||||
onChange={(e) => {
|
||||
setLicenseKey(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "25px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
id={"apply-license-key"}
|
||||
onClick={applyAirGapLicense}
|
||||
variant={"callAction"}
|
||||
disabled={!licenseKey || isSaving}
|
||||
label={"Apply Cluster License"}
|
||||
/>
|
||||
<Box>
|
||||
<Box className="step-row">
|
||||
<Box className="step-text">
|
||||
Click on the link to register this cluster in SUBNET and get
|
||||
a License Key for this Air-Gap deployment
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flex: "1",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
<a href={offlineRegUrl} target="_blank">
|
||||
https://subnet.min.io/cluster/register
|
||||
</a>
|
||||
|
||||
<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>
|
||||
|
||||
<Box
|
||||
className={"muted"}
|
||||
sx={{
|
||||
marginTop: "25px",
|
||||
}}
|
||||
>
|
||||
Note: 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
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "25px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<label style={{ fontWeight: "bold", marginBottom: "10px" }}>
|
||||
Paste the License Key{" "}
|
||||
</label>
|
||||
<CommentBox
|
||||
value={licenseKey}
|
||||
disabled={isSaving}
|
||||
label={""}
|
||||
id={"licenseKey"}
|
||||
name={"licenseKey"}
|
||||
placeholder={"License Key"}
|
||||
onChange={(e) => {
|
||||
setLicenseKey(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={modalStyleUtils.modalButtonBar}>
|
||||
<Button
|
||||
id={"apply-license-key"}
|
||||
onClick={applyAirGapLicense}
|
||||
variant={"callAction"}
|
||||
disabled={!licenseKey || isSaving}
|
||||
label={"Apply Cluster License"}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<RegisterHelpBox />
|
||||
</Box>
|
||||
</Fragment>
|
||||
</FormLayout>
|
||||
)}
|
||||
</Box>
|
||||
</Fragment>
|
||||
|
||||
@@ -14,38 +14,23 @@
|
||||
// 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 React from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
FormLayout,
|
||||
InputBox,
|
||||
OnlineRegistrationIcon,
|
||||
UsersIcon,
|
||||
} from "mds";
|
||||
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 { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
|
||||
import { useSelector } from "react-redux";
|
||||
import { AppState, useAppDispatch } from "../../../store";
|
||||
import {
|
||||
setShowPassword,
|
||||
setSubnetEmail,
|
||||
setSubnetPassword,
|
||||
} from "./registerSlice";
|
||||
import { 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 subnetPassword = useSelector(
|
||||
@@ -54,131 +39,79 @@ const OnlineRegistration = () => {
|
||||
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>
|
||||
<FormLayout
|
||||
icon={<OnlineRegistrationIcon />}
|
||||
title={"Online activation of MinIO Subscription Network License"}
|
||||
withBorders={false}
|
||||
containerPadding={false}
|
||||
helpBox={<RegisterHelpBox />}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
display: "flex",
|
||||
flexFlow: {
|
||||
xs: "column",
|
||||
md: "row",
|
||||
},
|
||||
flexFlow: "column",
|
||||
marginBottom: "30px",
|
||||
}}
|
||||
>
|
||||
<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=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 />
|
||||
Use your MinIO Subscription Network login credentials to register this
|
||||
cluster.
|
||||
</Box>
|
||||
</Fragment>
|
||||
<Box
|
||||
sx={{
|
||||
flex: "1",
|
||||
}}
|
||||
>
|
||||
<InputBox
|
||||
id="subnet-email"
|
||||
name="subnet-email"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setSubnetEmail(event.target.value))
|
||||
}
|
||||
label="Email"
|
||||
value={subnetEmail}
|
||||
overlayIcon={<UsersIcon />}
|
||||
/>
|
||||
<InputBox
|
||||
id="subnet-password"
|
||||
name="subnet-password"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setSubnetPassword(event.target.value))
|
||||
}
|
||||
label="Password"
|
||||
type={"password"}
|
||||
value={subnetPassword}
|
||||
/>
|
||||
|
||||
<Box sx={modalStyleUtils.modalButtonBar}>
|
||||
<Button
|
||||
id={"sign-up"}
|
||||
type="submit"
|
||||
variant="regular"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.open(`https://min.io/signup?ref=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>
|
||||
</FormLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -15,23 +15,13 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import { spacingUtils } from "../Common/FormComponents/common/styleLibrary";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Box } from "@mui/material";
|
||||
import api from "../../../common/api";
|
||||
|
||||
import { Box, PageLayout, Tabs } from "mds";
|
||||
import { SubnetRegTokenResponse } from "../License/types";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { useSelector } from "react-redux";
|
||||
import { setErrorSnackMessage, setHelpName } from "../../../systemSlice";
|
||||
import { AppState, useAppDispatch } from "../../../store";
|
||||
import Tabs from "@mui/material/Tabs";
|
||||
import Tab from "@mui/material/Tab";
|
||||
import { TabPanel } from "../../shared/tabs";
|
||||
import { ClusterRegistered, ProxyConfiguration } from "./utils";
|
||||
import ApiKeyRegister from "./ApiKeyRegister";
|
||||
import { fetchLicenseInfo } from "./registerThunks";
|
||||
import {
|
||||
resetRegisterForm,
|
||||
@@ -44,22 +34,11 @@ import SubnetMFAToken from "./SubnetMFAToken";
|
||||
import ClusterRegistrationForm from "./ClusterRegistrationForm";
|
||||
import OnlineRegistration from "./OnlineRegistration";
|
||||
import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
|
||||
import { PageLayout } from "mds";
|
||||
import HelpMenu from "../HelpMenu";
|
||||
import api from "../../../common/api";
|
||||
import ApiKeyRegister from "./ApiKeyRegister";
|
||||
|
||||
interface IRegister {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
sizedLabel: {
|
||||
minWidth: "75px",
|
||||
},
|
||||
...spacingUtils,
|
||||
});
|
||||
|
||||
const Register = ({ classes }: IRegister) => {
|
||||
const Register = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const subnetMFAToken = useSelector(
|
||||
@@ -98,25 +77,27 @@ const Register = ({ classes }: IRegister) => {
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const fetchSubnetRegToken = () => {
|
||||
if (loading || subnetRegToken) {
|
||||
return;
|
||||
useEffect(() => {
|
||||
if (curTab === "simple-tab-2" && !loading && !subnetRegToken) {
|
||||
const fetchSubnetRegToken = () => {
|
||||
dispatch(setLoading(true));
|
||||
api
|
||||
.invoke("GET", "/api/v1/subnet/registration-token")
|
||||
.then((resp: SubnetRegTokenResponse) => {
|
||||
dispatch(setLoading(false));
|
||||
if (resp && resp.regToken) {
|
||||
dispatch(setSubnetRegToken(resp.regToken));
|
||||
}
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
console.error(err);
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
dispatch(setLoading(false));
|
||||
});
|
||||
};
|
||||
fetchSubnetRegToken();
|
||||
}
|
||||
dispatch(setLoading(true));
|
||||
api
|
||||
.invoke("GET", "/api/v1/subnet/registration-token")
|
||||
.then((resp: SubnetRegTokenResponse) => {
|
||||
dispatch(setLoading(false));
|
||||
if (resp && resp.regToken) {
|
||||
dispatch(setSubnetRegToken(resp.regToken));
|
||||
}
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
console.error(err);
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
dispatch(setLoading(false));
|
||||
});
|
||||
};
|
||||
}, [curTab, loading, subnetRegToken, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialLicenseLoading) {
|
||||
@@ -125,7 +106,7 @@ const Register = ({ classes }: IRegister) => {
|
||||
}
|
||||
}, [initialLicenseLoading, setInitialLicenseLoading, dispatch]);
|
||||
|
||||
let clusterRegistrationForm: JSX.Element = <Fragment />;
|
||||
let clusterRegistrationForm: React.ReactElement = <Fragment />;
|
||||
|
||||
if (subnetAccessToken && subnetOrganizations.length > 0) {
|
||||
clusterRegistrationForm = <ClusterRegistrationForm />;
|
||||
@@ -138,9 +119,8 @@ const Register = ({ classes }: IRegister) => {
|
||||
const apiKeyRegistration = (
|
||||
<Fragment>
|
||||
<Box
|
||||
withBorders
|
||||
sx={{
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
padding: "43px",
|
||||
@@ -161,9 +141,8 @@ const Register = ({ classes }: IRegister) => {
|
||||
const regUi = (
|
||||
<Fragment>
|
||||
<Box
|
||||
withBorders
|
||||
sx={{
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
padding: "43px",
|
||||
@@ -197,46 +176,38 @@ const Register = ({ classes }: IRegister) => {
|
||||
|
||||
<PageLayout>
|
||||
<Tabs
|
||||
value={curTab}
|
||||
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
|
||||
horizontal
|
||||
currentTabOrPath={curTab}
|
||||
onTabClick={(newValue: string) => {
|
||||
dispatch(setCurTab(newValue));
|
||||
}}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
aria-label="cluster-tabs"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
>
|
||||
<Tab
|
||||
label="Credentials"
|
||||
id="simple-tab-0"
|
||||
aria-controls="simple-tab-panel-0"
|
||||
/>
|
||||
<Tab
|
||||
label="API Key"
|
||||
id="simple-tab-1"
|
||||
aria-controls="simple-tab-panel-1"
|
||||
/>
|
||||
<Tab
|
||||
label="Air-Gap"
|
||||
id="simple-tab-2"
|
||||
aria-controls="simple-tab-panel-2"
|
||||
onClick={() => fetchSubnetRegToken()}
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
<TabPanel index={0} value={curTab}>
|
||||
{uiToShow}
|
||||
</TabPanel>
|
||||
<TabPanel index={1} value={curTab}>
|
||||
{apiKeyRegistration}
|
||||
</TabPanel>
|
||||
<TabPanel index={2} value={curTab}>
|
||||
{offlineRegistration}
|
||||
</TabPanel>
|
||||
options={[
|
||||
{
|
||||
tabConfig: {
|
||||
label: "Credentials",
|
||||
id: "simple-tab-0",
|
||||
},
|
||||
content: uiToShow,
|
||||
},
|
||||
{
|
||||
tabConfig: {
|
||||
label: "API Key",
|
||||
id: "simple-tab-1",
|
||||
},
|
||||
content: apiKeyRegistration,
|
||||
},
|
||||
{
|
||||
tabConfig: {
|
||||
label: "Air-Gap",
|
||||
id: "simple-tab-2",
|
||||
},
|
||||
content: offlineRegistration,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</PageLayout>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Register);
|
||||
export default Register;
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
import React, { Fragment } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Box } from "@mui/material";
|
||||
import { Button, Grid, HelpBox, WarnIcon } from "mds";
|
||||
import { Box, breakPoints, Button, Grid, HelpBox, WarnIcon } from "mds";
|
||||
|
||||
interface IRegisterCluster {
|
||||
compactMode?: boolean;
|
||||
@@ -81,11 +80,11 @@ const RegisterCluster = ({ compactMode = false }: IRegisterCluster) => {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexFlow: {
|
||||
sm: "row",
|
||||
xs: "column",
|
||||
},
|
||||
flexFlow: "row",
|
||||
marginBottom: "15px",
|
||||
[`@media (max-width: ${breakPoints.sm}px)`]: {
|
||||
flexFlow: "column",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
@@ -113,9 +112,9 @@ const RegisterCluster = ({ compactMode = false }: IRegisterCluster) => {
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexFlow: {
|
||||
xs: "column",
|
||||
md: "row",
|
||||
flexFlow: "row",
|
||||
[`@media (max-width: ${breakPoints.sm}px)`]: {
|
||||
flexFlow: "column",
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -14,14 +14,15 @@
|
||||
// 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, Link } from "@mui/material";
|
||||
import React, { Fragment } from "react";
|
||||
import {
|
||||
CallHomeFeatureIcon,
|
||||
DiagnosticsFeatureIcon,
|
||||
ExtraFeaturesIcon,
|
||||
HelpIconFilled,
|
||||
PerformanceFeatureIcon,
|
||||
Box,
|
||||
HelpBox,
|
||||
} from "mds";
|
||||
|
||||
const FeatureItem = ({
|
||||
@@ -44,92 +45,55 @@ const FeatureItem = ({
|
||||
}}
|
||||
>
|
||||
{icon}{" "}
|
||||
<div style={{ fontSize: "14px", fontStyle: "italic", color: "#5E5E5E" }}>
|
||||
<Box className="muted" style={{ fontSize: "14px", fontStyle: "italic" }}>
|
||||
{description}
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const RegisterHelpBox = ({ hasMargin = true }: { hasMargin?: boolean }) => {
|
||||
const RegisterHelpBox = () => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
padding: "20px",
|
||||
marginLeft: {
|
||||
xs: "0px",
|
||||
sm: "0px",
|
||||
md: hasMargin ? "30px" : "",
|
||||
},
|
||||
marginTop: {
|
||||
xs: "0px",
|
||||
sm: hasMargin ? "30px" : "",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontWeight: 600,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "16px",
|
||||
<HelpBox
|
||||
title={"Why should I register?"}
|
||||
iconComponent={<HelpIconFilled />}
|
||||
help={
|
||||
<Fragment>
|
||||
<Box sx={{ fontSize: "14px", marginBottom: "15px" }}>
|
||||
Registering this cluster with the MinIO Subscription Network
|
||||
(SUBNET) provides the following benefits in addition to the
|
||||
commercial license and SLA backed support.
|
||||
</Box>
|
||||
|
||||
"& .min-icon": {
|
||||
height: "21px",
|
||||
width: "21px",
|
||||
marginRight: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<HelpIconFilled />
|
||||
<div>Why should I register?</div>
|
||||
</Box>
|
||||
<Box sx={{ fontSize: "14px", marginBottom: "15px" }}>
|
||||
Registering this cluster with the MinIO Subscription Network (SUBNET)
|
||||
provides the following benefits in addition to the commercial license
|
||||
and SLA backed support.
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
}}
|
||||
>
|
||||
<FeatureItem
|
||||
icon={<CallHomeFeatureIcon />}
|
||||
description={`Call Home Monitoring`}
|
||||
/>
|
||||
<FeatureItem
|
||||
icon={<DiagnosticsFeatureIcon />}
|
||||
description={`Health Diagnostics`}
|
||||
/>
|
||||
<FeatureItem
|
||||
icon={<PerformanceFeatureIcon />}
|
||||
description={`Performance Analysis`}
|
||||
/>
|
||||
<FeatureItem
|
||||
icon={<ExtraFeaturesIcon />}
|
||||
description={
|
||||
<Link
|
||||
href="https://min.io/signup?ref=con"
|
||||
target="_blank"
|
||||
sx={{
|
||||
color: "#2781B0",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
More Features
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
}}
|
||||
>
|
||||
<FeatureItem
|
||||
icon={<CallHomeFeatureIcon />}
|
||||
description={`Call Home Monitoring`}
|
||||
/>
|
||||
<FeatureItem
|
||||
icon={<DiagnosticsFeatureIcon />}
|
||||
description={`Health Diagnostics`}
|
||||
/>
|
||||
<FeatureItem
|
||||
icon={<PerformanceFeatureIcon />}
|
||||
description={`Performance Analysis`}
|
||||
/>
|
||||
<FeatureItem
|
||||
icon={<ExtraFeaturesIcon />}
|
||||
description={
|
||||
<a href="https://min.io/signup?ref=con" target="_blank">
|
||||
More Features
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
// 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 from "react";
|
||||
import { Box } from "@mui/material";
|
||||
import { VerifiedIcon } from "mds";
|
||||
import { VerifiedIcon, Box, breakPoints } from "mds";
|
||||
|
||||
const RegistrationStatusBanner = ({ email = "" }: { email?: string }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "67px",
|
||||
height: 67,
|
||||
color: "#ffffff",
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
top: "-30px",
|
||||
left: "-32px",
|
||||
top: -30,
|
||||
left: -32,
|
||||
width: "calc(100% + 64px)",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
@@ -51,9 +66,9 @@ const RegistrationStatusBanner = ({ email = "" }: { email?: string }) => {
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
display: {
|
||||
sm: "flex",
|
||||
xs: "none",
|
||||
display: "flex",
|
||||
[`@media (max-width: ${breakPoints.sm}px)`]: {
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -15,15 +15,13 @@
|
||||
// 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 { Box, Button, FormLayout, InputBox, LockIcon } from "mds";
|
||||
import { useSelector } from "react-redux";
|
||||
import { setSubnetOTP } from "./registerSlice";
|
||||
import { AppState, useAppDispatch } from "../../../store";
|
||||
import { subnetLoginWithMFA } from "./registerThunks";
|
||||
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
|
||||
import RegisterHelpBox from "./RegisterHelpBox";
|
||||
|
||||
const SubnetMFAToken = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -35,76 +33,51 @@ const SubnetMFAToken = () => {
|
||||
const loading = useSelector((state: AppState) => state.register.loading);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
}}
|
||||
<FormLayout
|
||||
title={"Two-Factor Authentication"}
|
||||
helpBox={<RegisterHelpBox />}
|
||||
withBorders={false}
|
||||
containerPadding={false}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
flex: "2",
|
||||
marginBottom: "30px",
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
Please enter the 6-digit verification code that was sent to your email
|
||||
address. This code will be valid for 5 minutes.
|
||||
</Box>
|
||||
|
||||
<RegisterHelpBox />
|
||||
</Box>
|
||||
<Box>
|
||||
<InputBox
|
||||
overlayIcon={<LockIcon />}
|
||||
id="subnet-otp"
|
||||
name="subnet-otp"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setSubnetOTP(event.target.value))
|
||||
}
|
||||
placeholder=""
|
||||
label=""
|
||||
value={subnetOTP}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={modalStyleUtils.modalButtonBar}>
|
||||
<Button
|
||||
id={"verify"}
|
||||
onClick={() => dispatch(subnetLoginWithMFA())}
|
||||
disabled={
|
||||
loading ||
|
||||
subnetOTP.trim().length === 0 ||
|
||||
subnetMFAToken.trim().length === 0
|
||||
}
|
||||
variant="callAction"
|
||||
label={"Verify"}
|
||||
/>
|
||||
</Box>
|
||||
</FormLayout>
|
||||
);
|
||||
};
|
||||
export default SubnetMFAToken;
|
||||
|
||||
@@ -27,12 +27,11 @@ export interface RegisterState {
|
||||
selectedSubnetOrganization: string;
|
||||
subnetRegToken: string;
|
||||
subnetOrganizations: SubnetOrganization[];
|
||||
showPassword: boolean;
|
||||
loading: boolean;
|
||||
loadingLicenseInfo: boolean;
|
||||
clusterRegistered: boolean;
|
||||
licenseInfo: SubnetInfo | undefined;
|
||||
curTab: number;
|
||||
curTab: string;
|
||||
}
|
||||
|
||||
const initialState: RegisterState = {
|
||||
@@ -45,12 +44,11 @@ const initialState: RegisterState = {
|
||||
selectedSubnetOrganization: "",
|
||||
subnetRegToken: "",
|
||||
subnetOrganizations: [],
|
||||
showPassword: false,
|
||||
loading: false,
|
||||
loadingLicenseInfo: false,
|
||||
clusterRegistered: false,
|
||||
licenseInfo: undefined,
|
||||
curTab: 0,
|
||||
curTab: "simple-tab-0",
|
||||
};
|
||||
|
||||
export const registerSlice = createSlice({
|
||||
@@ -87,9 +85,6 @@ export const registerSlice = createSlice({
|
||||
) => {
|
||||
state.subnetOrganizations = action.payload;
|
||||
},
|
||||
setShowPassword: (state, action: PayloadAction<boolean>) => {
|
||||
state.showPassword = action.payload;
|
||||
},
|
||||
setLoading: (state, action: PayloadAction<boolean>) => {
|
||||
state.loading = action.payload;
|
||||
},
|
||||
@@ -102,7 +97,7 @@ export const registerSlice = createSlice({
|
||||
setLicenseInfo: (state, action: PayloadAction<SubnetInfo | undefined>) => {
|
||||
state.licenseInfo = action.payload;
|
||||
},
|
||||
setCurTab: (state, action: PayloadAction<number>) => {
|
||||
setCurTab: (state, action: PayloadAction<string>) => {
|
||||
state.curTab = action.payload;
|
||||
},
|
||||
resetRegisterForm: () => initialState,
|
||||
@@ -120,7 +115,6 @@ export const {
|
||||
setSelectedSubnetOrganization,
|
||||
setSubnetRegToken,
|
||||
setSubnetOrganizations,
|
||||
setShowPassword,
|
||||
setLoading,
|
||||
setLoadingLicenseInfo,
|
||||
setClusterRegistered,
|
||||
|
||||
@@ -1,56 +1,37 @@
|
||||
import { Box, Grid, Link } from "@mui/material";
|
||||
import { Fragment, useState } from "react";
|
||||
import { CopyIcon, SettingsIcon } from "mds";
|
||||
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import RegistrationStatusBanner from "./RegistrationStatusBanner";
|
||||
// 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/>.
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { CopyIcon, SettingsIcon, Box, Grid, Switch, InputBox } from "mds";
|
||||
import RegistrationStatusBanner from "./RegistrationStatusBanner";
|
||||
|
||||
export const ClusterRegistered = ({ email }: { email: string }) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<RegistrationStatusBanner email={email} />
|
||||
<Grid item xs={12} marginTop={"25px"}>
|
||||
<Grid item xs={12} sx={{ marginTop: 25 }}>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "20px",
|
||||
"& a": {
|
||||
color: "#2781B0",
|
||||
cursor: "pointer",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Login to{" "}
|
||||
<Link
|
||||
href="https://subnet.min.io"
|
||||
target="_blank"
|
||||
style={{
|
||||
color: "#2781B0",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<a href="https://subnet.min.io" target="_blank">
|
||||
SUBNET
|
||||
</Link>{" "}
|
||||
</a>{" "}
|
||||
to avail support for this MinIO cluster
|
||||
</Box>
|
||||
</Grid>
|
||||
@@ -65,9 +46,8 @@ export const ProxyConfiguration = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<Box
|
||||
withBorders
|
||||
sx={{
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
padding: "23px",
|
||||
marginTop: "40px",
|
||||
@@ -103,21 +83,17 @@ export const ProxyConfiguration = () => {
|
||||
}}
|
||||
>
|
||||
For airgap/firewalled environments it is possible to{" "}
|
||||
<Link
|
||||
style={{
|
||||
color: "#2781B0",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
<a
|
||||
href="https://min.io/docs/minio/linux/reference/minio-mc-admin/mc-admin-config.html?ref=con"
|
||||
target="_blank"
|
||||
>
|
||||
configure a proxy
|
||||
</Link>{" "}
|
||||
</a>{" "}
|
||||
to connect to SUBNET .
|
||||
</Box>
|
||||
<Box>
|
||||
{displaySubnetProxy && (
|
||||
<InputBoxWrapper
|
||||
<InputBox
|
||||
disabled
|
||||
id="subnetProxy"
|
||||
name="subnetProxy"
|
||||
@@ -126,9 +102,6 @@ export const ProxyConfiguration = () => {
|
||||
label=""
|
||||
value={proxyConfigurationCommand}
|
||||
overlayIcon={<CopyIcon />}
|
||||
extraInputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
overlayAction={() =>
|
||||
navigator.clipboard.writeText(proxyConfigurationCommand)
|
||||
}
|
||||
@@ -141,7 +114,7 @@ export const ProxyConfiguration = () => {
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<FormSwitchWrapper
|
||||
<Switch
|
||||
value="enableProxy"
|
||||
id="enableProxy"
|
||||
name="enableProxy"
|
||||
|
||||
Reference in New Issue
Block a user