Files
object-browser/portal-ui/src/screens/Console/Speedtest/Speedtest.tsx
2022-07-27 21:00:54 -05:00

343 lines
12 KiB
TypeScript

// 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, useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
import { Button, Grid } from "@mui/material";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import moment from "moment/moment";
import PageHeader from "../Common/PageHeader/PageHeader";
import {
actionsTray,
advancedFilterToggleStyles,
containerForHeader,
formFieldStyles,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import { wsProtocol } from "../../../utils/wsUtils";
import { SpeedTestResponse } from "./types";
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 ProgressBarWrapper from "../Common/ProgressBarWrapper/ProgressBarWrapper";
import InputUnitMenu from "../Common/FormComponents/InputUnitMenu/InputUnitMenu";
import PageLayout from "../Common/Layout/PageLayout";
import { SecureComponent } from "../../../common/SecureComponent";
import DistributedOnly from "../Common/DistributedOnly/DistributedOnly";
import HelpBox from "../../../common/HelpBox";
import WarnIcon from "../../../icons/WarnIcon";
import Loader from "../Common/Loader/Loader";
import { selDistSet } from "../../../systemSlice";
import makeStyles from "@mui/styles/makeStyles";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
advancedContent: {
backgroundColor: "#FBFAFA",
maxHeight: 0,
transitionDuration: "0.3s",
overflow: "hidden",
padding: "0 15px",
marginTop: 15,
justifyContent: "space-between",
"&.open": {
maxHeight: 400,
paddingBottom: 15,
},
},
stepProgressText: {
fontSize: 13,
marginBottom: 8,
},
...advancedFilterToggleStyles,
...actionsTray,
...searchField,
...formFieldStyles,
...containerForHeader(theme.spacing(4)),
})
);
const Speedtest = () => {
const distributedSetup = useSelector(selDistSet);
const classes = useStyles();
const [start, setStart] = useState<boolean>(false);
const [currStatus, setCurrStatus] = useState<SpeedTestResponse[] | null>(
null
);
const [size, setSize] = useState<string>("64");
const [sizeUnit, setSizeUnit] = useState<string>("MB");
const [duration, setDuration] = useState<string>("10");
const [topDate, setTopDate] = useState<number>(0);
const [currentValue, setCurrentValue] = useState<number>(0);
const [totalSeconds, setTotalSeconds] = useState<number>(0);
const [speedometerValue, setSpeedometerValue] = useState<number>(0);
useEffect(() => {
// begin watch if bucketName in bucketList and start pressed
if (start) {
const url = new URL(window.location.toString());
const isDev = process.env.NODE_ENV === "development";
const port = isDev ? "9090" : url.port;
// check if we are using base path, if not this always is `/`
const baseLocation = new URL(document.baseURI);
const baseUrl = baseLocation.pathname;
const wsProt = wsProtocol(url.protocol);
const c = new W3CWebSocket(
`${wsProt}://${url.hostname}:${port}${baseUrl}ws/speedtest?&size=${size}${sizeUnit}&duration=${duration}s`
);
const baseDate = moment();
const currentTime = baseDate.unix() / 1000;
const incrementDate =
baseDate
.add(parseInt("10") * 2, "s" as moment.unitOfTime.DurationConstructor)
.unix() / 1000;
const totalSeconds = (incrementDate - currentTime) / 1000;
setTopDate(incrementDate);
setCurrentValue(currentTime);
setTotalSeconds(totalSeconds);
let interval: any | null = null;
if (c !== null) {
c.onopen = () => {
console.log("WebSocket Client Connected");
c.send("ok");
interval = setInterval(() => {
c.send("ok");
}, 10 * 1000);
};
c.onmessage = (message: IMessageEvent) => {
const data: SpeedTestResponse = JSON.parse(message.data.toString());
setCurrStatus((prevStatus) => {
let prSt: SpeedTestResponse[] = [];
if (prevStatus) {
prSt = [...prevStatus];
}
const insertData = data.servers !== 0 ? [data] : [];
return [...prSt, ...insertData];
});
const currTime = moment().unix() / 1000;
setCurrentValue(currTime);
};
c.onclose = () => {
clearInterval(interval);
console.log("connection closed by server");
// reset start status
setStart(false);
};
return () => {
// close websocket on useEffect cleanup
c.close(1000);
clearInterval(interval);
console.log("closing websockets");
};
}
} else {
// reset start status
setStart(false);
}
}, [size, sizeUnit, start, duration]);
useEffect(() => {
const actualSeconds = (topDate - currentValue) / 1000;
let percToDisplay = 100 - (actualSeconds * 100) / totalSeconds;
if (percToDisplay > 100) {
percToDisplay = 100;
}
setSpeedometerValue(percToDisplay);
}, [start, currentValue, topDate, totalSeconds]);
return (
<Fragment>
<PageHeader label="Performance" />
<PageLayout>
{!distributedSetup ? (
<DistributedOnly
iconComponent={<SpeedtestIcon />}
entity={"Speedtest"}
/>
) : (
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_HEAL]}
resource={CONSOLE_UI_RESOURCE}
>
<Grid item xs={12} className={classes.boxy}>
<Grid container>
<Grid item md={4} sm={12}>
<div className={classes.stepProgressText}>
{start ? (
<Fragment>
Speedtest in progress...
<Loader style={{ width: 15, height: 15 }} />
</Fragment>
) : (
<Fragment>
{currStatus && !start ? (
<b>Speed Test results:</b>
) : (
<b>Performance test</b>
)}
</Fragment>
)}
</div>
<div>
<ProgressBarWrapper
value={speedometerValue}
ready={currStatus !== null && !start}
indeterminate={start}
size={"small"}
/>
</div>
</Grid>
<Grid item md sm={12}>
<div style={{ marginLeft: 10, width: 300 }}>
<InputBoxWrapper
id={"size"}
name={"size"}
label={"Object Size"}
onChange={(e) => {
setSize(e.target.value);
}}
noLabelMinWidth={true}
value={size}
disabled={start}
overlayObject={
<InputUnitMenu
id={"size-unit"}
onUnitChange={setSizeUnit}
unitSelected={sizeUnit}
unitsList={[
{ label: "KiB", value: "KiB" },
{ label: "MiB", value: "MiB" },
{ label: "GiB", value: "GiB" },
]}
disabled={start}
/>
}
/>
</div>
</Grid>
<Grid item md sm={12}>
<div style={{ marginLeft: 10, width: 300 }}>
<InputBoxWrapper
id={"duration"}
name={"duration"}
label={"Duration"}
onChange={(e) => {
if (e.target.validity.valid) {
setDuration(e.target.value);
}
}}
noLabelMinWidth={true}
value={duration}
disabled={start}
overlayObject={
<InputUnitMenu
id={"size-unit"}
onUnitChange={() => {}}
unitSelected={"s"}
unitsList={[{ label: "s", value: "s" }]}
disabled={start}
/>
}
pattern={"[0-9]*"}
/>
</div>
</Grid>
<Grid item md={1} sm={12} textAlign={"right"}>
<Button
onClick={() => {
setCurrStatus(null);
setStart(true);
}}
color="primary"
type="button"
id={"start-speed-test"}
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>
<Grid container className={classes.multiModule}>
<Grid item xs={12}>
<Fragment>
<Grid item xs={12}>
{currStatus !== null && (
<Fragment>
<STResults results={currStatus} start={start} />
</Fragment>
)}
</Grid>
</Fragment>
</Grid>
</Grid>
</Grid>
{!start && !currStatus && (
<Fragment>
<br />
<HelpBox
title={
"During the speed test all your production traffic will be temporarily suspended."
}
iconComponent={<WarnIcon />}
help={<Fragment />}
/>
</Fragment>
)}
</SecureComponent>
)}
</PageLayout>
</Fragment>
);
};
export default Speedtest;