Created DistributedOnly component & implemented for Heal & Speedtest pages (#1313)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2021-12-10 12:47:54 -06:00
committed by GitHub
parent 5618e08bbe
commit 9444dadc16
3 changed files with 363 additions and 333 deletions

View File

@@ -0,0 +1,59 @@
// 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, { Fragment } from "react";
import { Grid } from "@mui/material";
import HelpBox from "../../../../common/HelpBox";
interface IDistributedOnly {
iconComponent: any;
entity: string;
}
const DistributedOnly = ({ iconComponent, entity }: IDistributedOnly) => {
return (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
title={`${entity} not available`}
iconComponent={iconComponent}
help={
<Fragment>
This feature is not available for a single-disk setup.
<br />
Please deploy a server in{" "}
<a
href="https://docs.min.io/minio/baremetal/installation/deploy-minio-distributed.html?ref=con"
target="_blank"
rel="noreferrer"
>
Distributed Mode
</a>{" "}
to use this feature.
</Fragment>
}
/>
</Grid>
</Grid>
);
};
export default DistributedOnly;

View File

@@ -14,7 +14,7 @@
// 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, { useEffect, useState } from "react";
import React, { useEffect, useState, Fragment } from "react";
import { connect } from "react-redux";
import { HorizontalBar } from "react-chartjs-2";
import { Redirect } from "react-router-dom";
@@ -47,12 +47,14 @@ import {
} from "../../../common/SecureComponent/permissions";
import { AppState } from "../../../store";
import { ErrorResponseHandler } from "../../../common/types";
import { HealIcon } from "../../../icons";
import CheckboxWrapper from "../Common/FormComponents/CheckboxWrapper/CheckboxWrapper";
import PageHeader from "../Common/PageHeader/PageHeader";
import api from "../../../common/api";
import BackLink from "../../../common/BackLink";
import PageLayout from "../Common/Layout/PageLayout";
import SecureComponent from "../../../common/SecureComponent/SecureComponent";
import DistributedOnly from "../Common/DistributedOnly/DistributedOnly";
const styles = (theme: Theme) =>
createStyles({
@@ -252,142 +254,143 @@ const Heal = ({ classes, distributedSetup }: IHeal) => {
label: bucketName.name,
value: bucketName.name,
}));
if (!distributedSetup) {
return null;
}
return (
<React.Fragment>
<Fragment>
<PageHeader label="Heal" />
<BackLink to="/tools" label="Return to Tools" />
<PageLayout>
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_HEAL_ACTION]}
resource={CONSOLE_UI_RESOURCE}
RenderError={<Redirect to={"/"} />}
>
<Grid xs={12} className={classes.formBox}>
<Grid item xs={12} className={classes.actionsTray}>
<FormControl variant="outlined" className={classes.bucketField}>
<Select
label="Bucket"
id="bucket-name"
name="bucket-name"
value={bucketName}
onChange={(e) => {
setBucketName(e.target.value as string);
}}
className={classes.searchField}
input={<SelectStyled />}
displayEmpty
>
<MenuItem value="" key={`select-bucket-name-default`}>
Select Bucket
</MenuItem>
{bucketNames.map((option) => (
<MenuItem
value={option.value}
key={`select-bucket-name-${option.label}`}
>
{option.label}
{!distributedSetup ? (
<DistributedOnly entity={"Heal"} iconComponent={<HealIcon />} />
) : (
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_HEAL_ACTION]}
resource={CONSOLE_UI_RESOURCE}
RenderError={<Redirect to={"/"} />}
>
<Grid xs={12} item className={classes.formBox}>
<Grid item xs={12} className={classes.actionsTray}>
<FormControl variant="outlined" className={classes.bucketField}>
<Select
label="Bucket"
id="bucket-name"
name="bucket-name"
value={bucketName}
onChange={(e) => {
setBucketName(e.target.value as string);
}}
className={classes.searchField}
input={<SelectStyled />}
displayEmpty
>
<MenuItem value="" key={`select-bucket-name-default`}>
Select Bucket
</MenuItem>
))}
</Select>
</FormControl>
<TextField
label="Prefix"
className={classes.prefixField}
id="prefix-resource"
disabled={false}
InputProps={{
disableUnderline: true,
}}
onChange={(e) => {
setPrefix(e.target.value);
}}
variant="standard"
/>
{bucketNames.map((option) => (
<MenuItem
value={option.value}
key={`select-bucket-name-${option.label}`}
>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
label="Prefix"
className={classes.prefixField}
id="prefix-resource"
disabled={false}
InputProps={{
disableUnderline: true,
}}
onChange={(e) => {
setPrefix(e.target.value);
}}
variant="standard"
/>
</Grid>
<Grid item xs={12} className={classes.inlineCheckboxes}>
<CheckboxWrapper
name="recursive"
id="recursive"
value="recursive"
checked={recursive}
onChange={(e) => {
setRecursive(e.target.checked);
}}
disabled={false}
label="Recursive"
/>
<CheckboxWrapper
name="forceStart"
id="forceStart"
value="forceStart"
checked={forceStart}
onChange={(e) => {
setForceStart(e.target.checked);
}}
disabled={false}
label="Force Start"
/>
<CheckboxWrapper
name="forceStop"
id="forceStop"
value="forceStop"
checked={forceStop}
onChange={(e) => {
setForceStop(e.target.checked);
}}
disabled={false}
label="Force Stop"
/>
</Grid>
<Grid item xs={12} className={classes.buttonBar}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={start}
onClick={() => setStart(true)}
>
Start
</Button>
</Grid>
</Grid>
<Grid item xs={12} className={classes.inlineCheckboxes}>
<CheckboxWrapper
name="recursive"
id="recursive"
value="recursive"
checked={recursive}
onChange={(e) => {
setRecursive(e.target.checked);
<Grid item xs={12} className={classes.graphContainer}>
<HorizontalBar
data={data}
width={80}
height={30}
options={{
title: {
display: true,
text: "Item's Health Status [%]",
fontSize: 20,
},
legend: {
display: true,
position: "right",
},
}}
disabled={false}
label="Recursive"
/>
<CheckboxWrapper
name="forceStart"
id="forceStart"
value="forceStart"
checked={forceStart}
onChange={(e) => {
setForceStart(e.target.checked);
}}
disabled={false}
label="Force Start"
/>
<CheckboxWrapper
name="forceStop"
id="forceStop"
value="forceStop"
checked={forceStop}
onChange={(e) => {
setForceStop(e.target.checked);
}}
disabled={false}
label="Force Stop"
/>
<Grid item xs={12} className={classes.scanInfo}>
<div className={classes.scanData}>
<strong>Size scanned:</strong> {hStatus.sizeScanned}
</div>
<div className={classes.scanData}>
<strong>Objects healed:</strong> {hStatus.objectsHealed} /{" "}
{hStatus.objectsScanned}
</div>
<div className={classes.scanData}>
<strong>Healing time:</strong> {hStatus.healDuration}s
</div>
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonBar}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={start}
onClick={() => setStart(true)}
>
Start
</Button>
</Grid>
</Grid>
<Grid item xs={12} className={classes.graphContainer}>
<HorizontalBar
data={data}
width={80}
height={30}
options={{
title: {
display: true,
text: "Item's Health Status [%]",
fontSize: 20,
},
legend: {
display: true,
position: "right",
},
}}
/>
<Grid item xs={12} className={classes.scanInfo}>
<div className={classes.scanData}>
<strong>Size scanned:</strong> {hStatus.sizeScanned}
</div>
<div className={classes.scanData}>
<strong>Objects healed:</strong> {hStatus.objectsHealed} /{" "}
{hStatus.objectsScanned}
</div>
<div className={classes.scanData}>
<strong>Healing time:</strong> {hStatus.healDuration}s
</div>
</Grid>
</Grid>
</SecureComponent>
</SecureComponent>
)}
</PageLayout>
</React.Fragment>
</Fragment>
);
};

View File

@@ -46,8 +46,8 @@ import ProgressBarWrapper from "../Common/ProgressBarWrapper/ProgressBarWrapper"
import InputUnitMenu from "../Common/FormComponents/InputUnitMenu/InputUnitMenu";
import CheckboxWrapper from "../Common/FormComponents/CheckboxWrapper/CheckboxWrapper";
import PageLayout from "../Common/Layout/PageLayout";
import HelpBox from "../../../common/HelpBox";
import SecureComponent from "../../../common/SecureComponent/SecureComponent";
import DistributedOnly from "../Common/DistributedOnly/DistributedOnly";
interface ISpeedtest {
classes: any;
@@ -193,224 +193,192 @@ const Speedtest = ({ classes, distributedSetup }: ISpeedtest) => {
setSpeedometerValue(percToDisplay);
}, [start, currentValue, topDate, totalSeconds]);
if (!distributedSetup) {
return (
<Fragment>
<PageHeader label="Speedtest" />
<BackLink to="/tools" label="Return to Tools" />
<PageLayout>
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"center"}
>
<Grid item xs={8}>
<HelpBox
title={"Speedtest not available"}
iconComponent={<SpeedtestIcon />}
help={
<Fragment>
This feature is not available for a single-disk setup.
<br />
Please deploy a server in{" "}
<a
href="https://docs.min.io/minio/baremetal/installation/deploy-minio-distributed.html?ref=con"
target="_blank"
rel="noreferrer"
>
Distributed Mode
</a>{" "}
to use this feature.
</Fragment>
}
/>
</Grid>
</Grid>
</PageLayout>
</Fragment>
);
}
return (
<Fragment>
<PageHeader label="Speedtest" />
<BackLink to="/tools" label="Return to Tools" />
<PageLayout>
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_HEAL_ACTION]}
resource={CONSOLE_UI_RESOURCE}
RenderError={<Redirect to={"/"} />}
>
<Grid item xs={12} className={classes.boxy}>
<Grid container>
<Grid item>
<Button
onClick={() => {
setCurrStatus(null);
setStart(true);
}}
color="primary"
type="button"
variant={
currStatus !== null && !start ? "contained" : "outlined"
}
className={`${classes.buttonBackground} ${classes.speedStart}`}
disabled={
duration.trim() === "" || size.trim() === "" || start
}
>
{!start && (
<Fragment>
{currStatus !== null ? "Retest" : "Start"}
</Fragment>
)}
{start ? "Start" : ""}
</Button>
</Grid>
<Grid item md={9} sm={12} className={classes.progressContainer}>
<div className={classes.stepProgressText}>
{start ? (
"Speedtest in progress..."
) : (
<Fragment>
{currStatus && !start ? "Done!" : "Start a new test"}
</Fragment>
)}
&nbsp;&nbsp;&nbsp;{start && <CircularProgress size={15} />}
</div>
<div>
<ProgressBarWrapper
value={speedometerValue}
ready={currStatus !== null && !start}
indeterminate={autotune && start}
/>
</div>
</Grid>
<Grid item className={classes.advancedButton}>
<button
onClick={() => {
setAdvancedOpen(!advancedOpen);
}}
className={classes.advancedConfiguration}
>
{advancedOpen ? "Hide" : "Show"} advanced options{" "}
<span
className={
advancedOpen
? classes.advancedOpen
: classes.advancedClosed
{!distributedSetup ? (
<DistributedOnly
iconComponent={<SpeedtestIcon />}
entity={"Speedtest"}
/>
) : (
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_HEAL_ACTION]}
resource={CONSOLE_UI_RESOURCE}
RenderError={<Redirect to={"/"} />}
>
<Grid item xs={12} className={classes.boxy}>
<Grid container>
<Grid item>
<Button
onClick={() => {
setCurrStatus(null);
setStart(true);
}}
color="primary"
type="button"
variant={
currStatus !== null && !start ? "contained" : "outlined"
}
className={`${classes.buttonBackground} ${classes.speedStart}`}
disabled={
duration.trim() === "" || size.trim() === "" || start
}
>
<ArrowForwardIosIcon />
</span>
</button>
</Grid>
</Grid>
<Grid
container
className={`${classes.advancedContent} ${
advancedOpen ? "open" : ""
}`}
>
<Grid item xs={12}>
<CheckboxWrapper
checked={autotune}
onChange={(e) => setAutotune(e.target.checked)}
id={"autotune"}
name={"autotune"}
label={"Enable Autotune"}
tooltip={
"Autotune gets the maximum stats for the system by running with multiple configurations at once. \
This configuration is enabled by default and disables the rest of available options"
}
value="true"
disabled={start}
/>
</Grid>
<Grid item xs={12} md={3} className={classes.advancedOption}>
<InputBoxWrapper
id={"duration"}
name={"duration"}
label={"Duration"}
onChange={(e) => {
setDuration(e.target.value);
}}
value={duration}
disabled={start || autotune}
overlayObject={
<InputUnitMenu
id={"duration-unit"}
onUnitChange={setDurationUnit}
unitSelected={durationUnit}
unitsList={[
{ label: "miliseconds", value: "ms" },
{ label: "seconds", value: "s" },
]}
disabled={start || autotune}
/>
}
/>
</Grid>
<Grid item xs={12} md={3} className={classes.advancedOption}>
<InputBoxWrapper
id={"size"}
name={"size"}
label={"Object Size"}
onChange={(e) => {
setSize(e.target.value);
}}
value={size}
disabled={start || autotune}
overlayObject={
<InputUnitMenu
id={"size-unit"}
onUnitChange={setSizeUnit}
unitSelected={sizeUnit}
unitsList={[
{ label: "KB", value: "KB" },
{ label: "MB", value: "MB" },
{ label: "GB", value: "GB" },
]}
disabled={start || autotune}
/>
}
/>
</Grid>
<Grid item xs={12} md={3} className={classes.advancedOption}>
<InputBoxWrapper
type="number"
min="0"
id={"concurrent"}
name={"concurrent"}
label={"Concurrent Requests"}
onChange={(e) => {
setConcurrent(e.target.value);
}}
value={concurrent}
disabled={start || autotune}
/>
</Grid>
</Grid>
<Grid container className={classes.multiModule}>
<Grid item xs={12}>
<Fragment>
<Grid item xs={12}>
{currStatus !== null && (
{!start && (
<Fragment>
<STResults
results={currStatus}
start={start}
autotune={autotune}
/>
{currStatus !== null ? "Retest" : "Start"}
</Fragment>
)}
</Grid>
</Fragment>
{start ? "Start" : ""}
</Button>
</Grid>
<Grid item md={9} sm={12} className={classes.progressContainer}>
<div className={classes.stepProgressText}>
{start ? (
"Speedtest in progress..."
) : (
<Fragment>
{currStatus && !start ? "Done!" : "Start a new test"}
</Fragment>
)}
&nbsp;&nbsp;&nbsp;{start && <CircularProgress size={15} />}
</div>
<div>
<ProgressBarWrapper
value={speedometerValue}
ready={currStatus !== null && !start}
indeterminate={autotune && start}
/>
</div>
</Grid>
<Grid item className={classes.advancedButton}>
<button
onClick={() => {
setAdvancedOpen(!advancedOpen);
}}
className={classes.advancedConfiguration}
>
{advancedOpen ? "Hide" : "Show"} advanced options{" "}
<span
className={
advancedOpen
? classes.advancedOpen
: classes.advancedClosed
}
>
<ArrowForwardIosIcon />
</span>
</button>
</Grid>
</Grid>
<Grid
container
className={`${classes.advancedContent} ${
advancedOpen ? "open" : ""
}`}
>
<Grid item xs={12}>
<CheckboxWrapper
checked={autotune}
onChange={(e) => setAutotune(e.target.checked)}
id={"autotune"}
name={"autotune"}
label={"Enable Autotune"}
tooltip={
"Autotune gets the maximum stats for the system by running with multiple configurations at once. \
This configuration is enabled by default and disables the rest of available options"
}
value="true"
disabled={start}
/>
</Grid>
<Grid item xs={12} md={3} className={classes.advancedOption}>
<InputBoxWrapper
id={"duration"}
name={"duration"}
label={"Duration"}
onChange={(e) => {
setDuration(e.target.value);
}}
value={duration}
disabled={start || autotune}
overlayObject={
<InputUnitMenu
id={"duration-unit"}
onUnitChange={setDurationUnit}
unitSelected={durationUnit}
unitsList={[
{ label: "miliseconds", value: "ms" },
{ label: "seconds", value: "s" },
]}
disabled={start || autotune}
/>
}
/>
</Grid>
<Grid item xs={12} md={3} className={classes.advancedOption}>
<InputBoxWrapper
id={"size"}
name={"size"}
label={"Object Size"}
onChange={(e) => {
setSize(e.target.value);
}}
value={size}
disabled={start || autotune}
overlayObject={
<InputUnitMenu
id={"size-unit"}
onUnitChange={setSizeUnit}
unitSelected={sizeUnit}
unitsList={[
{ label: "KB", value: "KB" },
{ label: "MB", value: "MB" },
{ label: "GB", value: "GB" },
]}
disabled={start || autotune}
/>
}
/>
</Grid>
<Grid item xs={12} md={3} className={classes.advancedOption}>
<InputBoxWrapper
type="number"
min="0"
id={"concurrent"}
name={"concurrent"}
label={"Concurrent Requests"}
onChange={(e) => {
setConcurrent(e.target.value);
}}
value={concurrent}
disabled={start || autotune}
/>
</Grid>
</Grid>
<Grid container className={classes.multiModule}>
<Grid item xs={12}>
<Fragment>
<Grid item xs={12}>
{currStatus !== null && (
<Fragment>
<STResults
results={currStatus}
start={start}
autotune={autotune}
/>
</Fragment>
)}
</Grid>
</Fragment>
</Grid>
</Grid>
</Grid>
</Grid>
</SecureComponent>
</SecureComponent>
)}
</PageLayout>
</Fragment>
);