Disabled speedtest in standalone mode (#1278)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2021-12-01 18:47:29 -06:00
committed by GitHub
parent 65dcdc674a
commit 4fb8c2f684
8 changed files with 385 additions and 284 deletions

View File

@@ -121,6 +121,7 @@ export const IAM_SCOPES = {
ADMIN_CREATE_POLICY: "admin:CreatePolicy",
ADMIN_DELETE_POLICY: "admin:DeletePolicy",
ADMIN_ATTACH_USER_OR_GROUP_POLICY: "admin:AttachUserOrGroupPolicy",
ADMIN_HEAL_ACTION: "admin:Heal",
S3_ALL_ACTIONS: "s3:*",
ADMIN_ALL_ACTIONS: "admin:*",
};
@@ -179,6 +180,7 @@ export const IAM_PERMISSIONS = {
IAM_SCOPES.ADMIN_GET_POLICY,
IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
IAM_SCOPES.ADMIN_LIST_USERS,
IAM_SCOPES.ADMIN_HEAL_ACTION,
],
};

View File

@@ -25,6 +25,7 @@ interface ISettingsCard {
classes: any;
configuration: IElement;
prefix?: string;
disabled?: boolean;
}
const styles = (theme: Theme) =>
@@ -53,6 +54,11 @@ const styles = (theme: Theme) =>
"&:hover": {
backgroundColor: "#FBFAFA",
},
"&.disabled": {
backgroundColor: "#F9F9F9",
color: "#ababab",
cursor: "not-allowed"
},
},
});
@@ -60,11 +66,14 @@ const SettingsCard = ({
classes,
configuration,
prefix = "settings",
disabled = false,
}: ISettingsCard) => {
return (
<Link
to={`/${prefix}/${configuration.configuration_id}`}
className={classes.configurationLink}
to={
disabled ? `/${prefix}` : `/${prefix}/${configuration.configuration_id}`
}
className={`${classes.configurationLink} ${disabled ? "disabled" : ""}`}
>
{configuration.icon}
{configuration.configuration_label}

View File

@@ -17,6 +17,7 @@
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { HorizontalBar } from "react-chartjs-2";
import { Redirect } from "react-router-dom";
import {
Button,
FormControl,
@@ -40,6 +41,10 @@ import {
inlineCheckboxes,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import {
CONSOLE_UI_RESOURCE,
IAM_SCOPES,
} from "../../../common/SecureComponent/permissions";
import { AppState } from "../../../store";
import { ErrorResponseHandler } from "../../../common/types";
import CheckboxWrapper from "../Common/FormComponents/CheckboxWrapper/CheckboxWrapper";
@@ -47,6 +52,7 @@ 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";
const styles = (theme: Theme) =>
createStyles({
@@ -273,128 +279,134 @@ const Heal = ({ classes, distributedSetup }: IHeal) => {
<PageHeader label="Heal" />
<BackLink to="/tools" label="Return to Tools" />
<PageLayout>
<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}
<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>
))}
</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"
classes={classes}
value="recursive"
checked={recursive}
onChange={(e) => {
setRecursive(e.target.checked);
}}
disabled={false}
label="Recursive"
/>
<CheckboxWrapper
name="forceStart"
id="forceStart"
classes={classes}
value="forceStart"
checked={forceStart}
onChange={(e) => {
setForceStart(e.target.checked);
}}
disabled={false}
label="Force Start"
/>
<CheckboxWrapper
name="forceStop"
id="forceStop"
classes={classes}
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"
classes={classes}
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"
classes={classes}
value="forceStart"
checked={forceStart}
onChange={(e) => {
setForceStart(e.target.checked);
}}
disabled={false}
label="Force Start"
/>
<CheckboxWrapper
name="forceStop"
id="forceStop"
classes={classes}
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>
</PageLayout>
</React.Fragment>
);

View File

@@ -15,11 +15,13 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import { connect } from "react-redux";
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
import { Button, CircularProgress, Grid } from "@mui/material";
import { Theme } from "@mui/material/styles";
import { Redirect } from "react-router-dom";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Button, CircularProgress, Grid } from "@mui/material";
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
import moment from "moment/moment";
import PageHeader from "../Common/PageHeader/PageHeader";
@@ -31,6 +33,12 @@ import {
} from "../Common/FormComponents/common/styleLibrary";
import { wsProtocol } from "../../../utils/wsUtils";
import { SpeedTestResponse } from "./types";
import { AppState } from "../../../store";
import { SpeedtestIcon } from "../../../icons";
import {
CONSOLE_UI_RESOURCE,
IAM_SCOPES,
} from "../../../common/SecureComponent/permissions";
import STResults from "./STResults";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import BackLink from "../../../common/BackLink";
@@ -38,9 +46,12 @@ 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";
interface ISpeedtest {
classes: any;
distributedSetup: boolean;
}
const styles = (theme: Theme) =>
@@ -74,7 +85,7 @@ const styles = (theme: Theme) =>
...containerForHeader(theme.spacing(4)),
});
const Speedtest = ({ classes }: ISpeedtest) => {
const Speedtest = ({ classes, distributedSetup }: ISpeedtest) => {
const [start, setStart] = useState<boolean>(false);
const [currStatus, setCurrStatus] = useState<SpeedTestResponse[] | null>(
@@ -182,179 +193,235 @@ const Speedtest = ({ classes }: 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>
<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
<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>
</PageLayout>
</Fragment>
);
};
export default withStyles(styles)(Speedtest);
const mapState = (state: AppState) => ({
distributedSetup: state.system.distributedSetup,
});
const connector = connect(mapState, null);
export default connector(withStyles(styles)(Speedtest));

View File

@@ -88,6 +88,7 @@ const ToolsList = ({ classes }: IConfigurationOptions) => {
prefix={"tools"}
configuration={element}
key={`configItem-${element.configuration_label}`}
disabled={element.disabled || false}
/>
))}
</div>

View File

@@ -58,4 +58,5 @@ export interface IElement {
configuration_id: string;
configuration_label: string;
icon?: any;
disabled?: boolean;
}

View File

@@ -24,6 +24,13 @@ import {
WatchIcon,
} from "../../../icons";
import SpeedtestIcon from "../../../icons/SpeedtestIcon";
import {
CONSOLE_UI_RESOURCE,
IAM_SCOPES,
} from "../../../common/SecureComponent/permissions";
import {
hasPermission,
} from "../../../common/SecureComponent/SecureComponent";
export const configurationElements: IElement[] = [
{
@@ -50,6 +57,9 @@ export const configurationElements: IElement[] = [
icon: <HealIcon />,
configuration_id: "heal",
configuration_label: "heal",
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
IAM_SCOPES.ADMIN_HEAL_ACTION,
]),
},
{
icon: <DiagnosticsIcon />,
@@ -60,5 +70,8 @@ export const configurationElements: IElement[] = [
icon: <SpeedtestIcon />,
configuration_id: "speedtest",
configuration_label: "Speedtest",
disabled: !hasPermission(CONSOLE_UI_RESOURCE, [
IAM_SCOPES.ADMIN_HEAL_ACTION,
]),
},
];

View File

@@ -116,10 +116,6 @@ func (c wsConn) readMessage() (messageType int, p []byte, err error) {
// on the path.
// Request should come like ws://<host>:<port>/ws/<api>
func serveWS(w http.ResponseWriter, req *http.Request) {
upgrader.CheckOrigin = func(r *http.Request) bool {
return true
}
// Perform authentication before upgrading to a Websocket Connection
// authenticate WS connection with Console
session, err := auth.GetClaimsFromTokenInRequest(req)