UX Basic Dashboard (#1513)
This commit is contained in:
committed by
GitHub
parent
608a5c3787
commit
1c31aff147
69
portal-ui/src/icons/PrometheusErrorIcon.tsx
Normal file
69
portal-ui/src/icons/PrometheusErrorIcon.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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 * as React from "react";
|
||||
import { SVGProps } from "react";
|
||||
|
||||
const PrometheusErrorIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`min-icon`}
|
||||
fill={"currentcolor"}
|
||||
viewBox="0 0 23.786 22.2"
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<clipPath id="clip-path-prom-error">
|
||||
<rect
|
||||
id="Rectángulo_1578"
|
||||
data-name="Rectángulo 1578"
|
||||
width="23.786"
|
||||
height="22.2"
|
||||
fill="none"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g
|
||||
id="Grupo_2402"
|
||||
data-name="Grupo 2402"
|
||||
clipPath="url(#clip-path-prom-error)"
|
||||
>
|
||||
<path
|
||||
id="Trazado_7049"
|
||||
data-name="Trazado 7049"
|
||||
d="M23.786,7.136a3.967,3.967,0,0,0-4.824-3.871A11.1,11.1,0,1,0,22.2,11.1c0-.26-.01-.518-.027-.773a3.958,3.958,0,0,0,1.613-3.192M11.1,20.776v0a2.92,2.92,0,0,1-3.158-2.6h6.317a2.922,2.922,0,0,1-3.159,2.6m5.217-3.464H5.883V15.42H16.317Zm-.038-2.865H5.913c-.035-.04-.07-.079-.1-.119a7.561,7.561,0,0,1-1.564-2.664c0-.023,1.295.266,2.22.476,0,0,.476.109,1.167.238A4.332,4.332,0,0,1,6.573,9.592c0-2.225,1.707-4.17,1.091-5.741.6.048,1.24,1.269,1.284,3.166a6.8,6.8,0,0,0,.9-3.474c0-1.02.672-2.207,1.348-2.247-.6.988.159,1.835.826,3.937.251.793.22,2.118.414,2.961.064-1.75.366-4.3,1.476-5.185a3.83,3.83,0,0,0,.457,3.167,6,6,0,0,1,1,3.437,4.294,4.294,0,0,1-1.031,2.775c.733-.137,1.239-.262,1.239-.262l2.379-.465a6.749,6.749,0,0,1-1.676,2.785M19.822,10.7A3.568,3.568,0,1,1,23.39,7.136,3.568,3.568,0,0,1,19.822,10.7"
|
||||
transform="translate(0 -0.001)"
|
||||
fill="#c83b51"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_7050"
|
||||
data-name="Trazado 7050"
|
||||
d="M491.022,131.222l.121-2.851h-1.17l.121,2.851Z"
|
||||
transform="translate(-470.607 -123.297)"
|
||||
fill="#c83b51"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_7051"
|
||||
data-name="Trazado 7051"
|
||||
d="M488.865,209.66a.655.655,0,1,0,.65.65.667.667,0,0,0-.65-.65"
|
||||
transform="translate(-468.913 -201.374)"
|
||||
fill="#c83b51"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default PrometheusErrorIcon;
|
||||
@@ -162,3 +162,4 @@ export { default as HelpIconFilled } from "./HelpIconFilled";
|
||||
export { default as CallHomeFeatureIcon } from "./CallHomeFeatureIcon";
|
||||
export { default as DiagnosticsFeatureIcon } from "./DiagnosticsFeatureIcon";
|
||||
export { default as PerformanceFeatureIcon } from "./PerformanceFeatureIcon";
|
||||
export { default as PrometheusErrorIcon } from "./PrometheusErrorIcon";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
// 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
|
||||
@@ -14,95 +14,86 @@
|
||||
// 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, useState } from "react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { IDriveInfo, Usage } from "../types";
|
||||
import { calculateBytes, representationNumber } from "../../../../common/utils";
|
||||
import { TabPanel } from "../../../shared/tabs";
|
||||
import ServerInfoCard from "./ServerInfoCard";
|
||||
import DriveInfoCard from "./DriveInfoCard";
|
||||
import CommonCard from "../CommonCard";
|
||||
import TabSelector from "../../Common/TabSelector/TabSelector";
|
||||
import GeneralUsePaginator from "../../Common/GeneralUsePaginator/GeneralUsePaginator";
|
||||
import { widgetContainerCommon } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { PrometheusIcon } from "../../../../icons";
|
||||
import React, { Fragment } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
import {
|
||||
BucketsIcon,
|
||||
DrivesIcon,
|
||||
PrometheusErrorIcon,
|
||||
ServersIcon,
|
||||
TotalObjectsIcon,
|
||||
} from "../../../../icons";
|
||||
import HelpBox from "../../../../common/HelpBox";
|
||||
import { calculateBytes, representationNumber } from "../../../../common/utils";
|
||||
import { IDriveInfo, Usage } from "../types";
|
||||
import StatusCountCard from "./StatusCountCard";
|
||||
import groupBy from "lodash/groupBy";
|
||||
import ServersList from "./ServersList";
|
||||
import CounterCard from "./CounterCard";
|
||||
import ReportedUsage from "./ReportedUsage";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
generalStatusTitle: {
|
||||
color: "#767676",
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
margin: "15px 10px 0 10px",
|
||||
},
|
||||
paginatorContainer: {
|
||||
maxWidth: 1185,
|
||||
width: "100%",
|
||||
},
|
||||
...widgetContainerCommon,
|
||||
});
|
||||
const BoxItem = ({
|
||||
children,
|
||||
background = "#ffffff",
|
||||
}: {
|
||||
children: any;
|
||||
background?: string;
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
border: "1px solid #f1f1f1",
|
||||
background: background,
|
||||
maxWidth: {
|
||||
sm: "100%",
|
||||
xs: "250px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
interface IDashboardProps {
|
||||
classes: any;
|
||||
usage: Usage | null;
|
||||
}
|
||||
|
||||
const itemsPerPage = 5;
|
||||
|
||||
const BasicDashboard = ({ classes, usage }: IDashboardProps) => {
|
||||
const [curTab, setCurTab] = useState<number>(0);
|
||||
const [serversPageNumber, setServersPageNumber] = useState<number>(1);
|
||||
const [drivesPageNumber, setDrivesPageNumber] = useState<number>(1);
|
||||
|
||||
const prettyUsage = (usage: string | undefined) => {
|
||||
if (usage === undefined) {
|
||||
return { total: "0", unit: "Mi" };
|
||||
}
|
||||
|
||||
const calculatedBytes = calculateBytes(usage);
|
||||
|
||||
return calculatedBytes;
|
||||
};
|
||||
|
||||
const prettyNumber = (usage: number | undefined) => {
|
||||
if (usage === undefined) {
|
||||
const getServersList = (usage: Usage | null) => {
|
||||
if (usage !== null) {
|
||||
return usage.servers.sort(function (a, b) {
|
||||
const nameA = a.endpoint.toLowerCase();
|
||||
const nameB = b.endpoint.toLowerCase();
|
||||
if (nameA < nameB) {
|
||||
return -1;
|
||||
}
|
||||
if (nameA > nameB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return usage.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
};
|
||||
return [];
|
||||
};
|
||||
|
||||
const makeServerArray = (usage: Usage | null) => {
|
||||
if (usage !== null) {
|
||||
return usage.servers.sort(function (a, b) {
|
||||
var nameA = a.endpoint.toLowerCase();
|
||||
var nameB = b.endpoint.toLowerCase();
|
||||
if (nameA < nameB) {
|
||||
return -1;
|
||||
}
|
||||
if (nameA > nameB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
const prettyUsage = (usage: string | undefined) => {
|
||||
if (usage === undefined) {
|
||||
return { total: "0", unit: "Mi" };
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
return calculateBytes(usage);
|
||||
};
|
||||
|
||||
const serverArray = makeServerArray(usage || null);
|
||||
const BasicDashboard = ({ usage }: IDashboardProps) => {
|
||||
const usageValue = usage && usage.usage ? usage.usage.toString() : "0";
|
||||
const usageToRepresent = prettyUsage(usageValue);
|
||||
|
||||
const usageToRepresent = prettyUsage(
|
||||
usage && usage.usage ? usage.usage.toString() : "0"
|
||||
);
|
||||
const serverList = getServersList(usage || null);
|
||||
|
||||
let allDrivesArray: IDriveInfo[] = [];
|
||||
|
||||
serverArray.forEach((server) => {
|
||||
serverList.forEach((server) => {
|
||||
const drivesInput = server.drives.map((drive) => {
|
||||
return drive;
|
||||
});
|
||||
@@ -110,29 +101,35 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => {
|
||||
allDrivesArray = [...allDrivesArray, ...drivesInput];
|
||||
});
|
||||
|
||||
const splitedServers = serverArray.slice(
|
||||
serversPageNumber * itemsPerPage - itemsPerPage,
|
||||
serversPageNumber * itemsPerPage
|
||||
);
|
||||
|
||||
const splitedDrives = allDrivesArray.slice(
|
||||
drivesPageNumber * itemsPerPage - itemsPerPage,
|
||||
drivesPageNumber * itemsPerPage
|
||||
);
|
||||
const serversGroup = groupBy(serverList, "state");
|
||||
const { offline: offlineServers = [], online: onlineServers = [] } =
|
||||
serversGroup;
|
||||
const drivesGroup = groupBy(allDrivesArray, "state");
|
||||
const { offline: offlineDrives = [], ok: onlineDrives = [] } = drivesGroup;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.dashboardBG} />
|
||||
{usage?.prometheusNotReady && (
|
||||
<Grid
|
||||
container
|
||||
justifyContent={"center"}
|
||||
alignContent={"center"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Grid item xs={8}>
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: "1536px",
|
||||
margin: "auto",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateRows: "1fr",
|
||||
gridTemplateColumns: "1fr",
|
||||
gap: "27px",
|
||||
marginBottom: "40px",
|
||||
marginTop: "80px",
|
||||
marginLeft: "60px",
|
||||
marginRight: "60px",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
{usage?.prometheusNotReady && (
|
||||
<HelpBox
|
||||
iconComponent={<PrometheusIcon />}
|
||||
iconComponent={<PrometheusErrorIcon />}
|
||||
title={"We can't retrieve advanced metrics at this time"}
|
||||
help={
|
||||
<Fragment>
|
||||
@@ -145,160 +142,131 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => {
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} className={classes.generalStatusTitle}>
|
||||
General Status
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.dashboardRow}>
|
||||
<Grid
|
||||
item
|
||||
xs={7}
|
||||
sm={8}
|
||||
md={6}
|
||||
lg={3}
|
||||
className={classes.widgetPanelDelimiter}
|
||||
>
|
||||
<CommonCard
|
||||
title={"All Buckets"}
|
||||
metricValue={usage ? representationNumber(usage.buckets) : 0}
|
||||
extraMargin
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={7}
|
||||
sm={8}
|
||||
md={6}
|
||||
lg={3}
|
||||
className={classes.widgetPanelDelimiter}
|
||||
>
|
||||
<CommonCard
|
||||
title={"Usage"}
|
||||
metricValue={usageToRepresent.total}
|
||||
metricUnit={usageToRepresent.unit}
|
||||
extraMargin
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={7}
|
||||
sm={8}
|
||||
md={6}
|
||||
lg={3}
|
||||
className={classes.widgetPanelDelimiter}
|
||||
>
|
||||
<CommonCard
|
||||
title={"Total Objects"}
|
||||
metricValue={usage ? representationNumber(usage.objects) : 0}
|
||||
extraMargin
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={7}
|
||||
sm={8}
|
||||
md={6}
|
||||
lg={3}
|
||||
className={classes.widgetPanelDelimiter}
|
||||
>
|
||||
<CommonCard
|
||||
title={"Servers"}
|
||||
metricValue={usage ? prettyNumber(serverArray.length) : 0}
|
||||
subMessage={{ message: "Total" }}
|
||||
extraMargin
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TabSelector
|
||||
selectedTab={curTab}
|
||||
onChange={(newValue: number) => {
|
||||
setCurTab(newValue);
|
||||
}}
|
||||
tabOptions={[{ label: "Servers" }, { label: "Drives" }]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.widgetsContainer}>
|
||||
<TabPanel index={0} value={curTab}>
|
||||
<div className={classes.paginatorContainer}>
|
||||
<GeneralUsePaginator
|
||||
page={serversPageNumber}
|
||||
entity={"Servers"}
|
||||
totalItems={serverArray.length}
|
||||
onChange={setServersPageNumber}
|
||||
itemsPerPage={itemsPerPage}
|
||||
/>
|
||||
</div>
|
||||
{splitedServers.map((server, index) => (
|
||||
<Grid item xs={12} key={`serverDS-${index.toString()}`}>
|
||||
<ServerInfoCard server={server} index={index + 1} />
|
||||
</Grid>
|
||||
))}
|
||||
</TabPanel>
|
||||
<TabPanel index={1} value={curTab}>
|
||||
<div className={classes.paginatorContainer}>
|
||||
<GeneralUsePaginator
|
||||
page={drivesPageNumber}
|
||||
entity={"Drives"}
|
||||
totalItems={allDrivesArray.length}
|
||||
onChange={setDrivesPageNumber}
|
||||
itemsPerPage={itemsPerPage}
|
||||
/>
|
||||
</div>
|
||||
{splitedDrives.map((drive, index) => (
|
||||
<Grid item xs={12} key={`drive-${index}-${drive.uuid}`}>
|
||||
<DriveInfoCard drive={drive} />
|
||||
</Grid>
|
||||
))}
|
||||
</TabPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{!usage?.prometheusNotReady && (
|
||||
<Grid
|
||||
container
|
||||
justifyContent={"center"}
|
||||
alignContent={"center"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Grid item xs={8}>
|
||||
)}
|
||||
|
||||
{!usage?.prometheusNotReady && (
|
||||
<HelpBox
|
||||
iconComponent={<PrometheusIcon />}
|
||||
title={"Monitoring"}
|
||||
iconComponent={<PrometheusErrorIcon />}
|
||||
title={"We can’t retrieve advanced metrics at this time."}
|
||||
help={
|
||||
<Fragment>
|
||||
The MinIO Dashboard is displaying basic metrics only due to
|
||||
missing the{" "}
|
||||
<a
|
||||
href="https://docs.min.io/minio/baremetal/console/minio-console.html?ref=con#configuration"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
necessary settings
|
||||
</a>{" "}
|
||||
for displaying extended metrics.
|
||||
<br />
|
||||
<br />
|
||||
See{" "}
|
||||
<a
|
||||
href="https://docs.min.io/minio/baremetal/monitoring/metrics-alerts/collect-minio-metrics-using-prometheus.html?ref=con#minio-metrics-collect-using-prometheus"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
MinIO Dashboard will display basic metrics as we couldn’t
|
||||
connect to Prometheus successfully. Please try again in a
|
||||
few minutes. If the problem persists, you can review your
|
||||
configuration and confirm that Prometheus server is up and
|
||||
running.
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
paddingTop: "20px",
|
||||
fontSize: "14px",
|
||||
"& a": {
|
||||
color: (theme) => theme.colors.link,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Collect MinIO Metrics Using Prometheus
|
||||
</a>{" "}
|
||||
for a complete tutorial on scraping and visualizing MinIO
|
||||
metrics with Prometheus.
|
||||
</Fragment>
|
||||
<a
|
||||
href="https://docs.min.io/minio/baremetal/monitoring/metrics-alerts/collect-minio-metrics-using-prometheus.html?ref=con#minio-metrics-collect-using-prometheus"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Read more about Prometheus on our Docs site.
|
||||
</a>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateRows: "1fr .2fr auto",
|
||||
gridTemplateColumns: "1fr",
|
||||
gap: "40px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateRows: "1fr",
|
||||
gridTemplateColumns: {
|
||||
lg: "1fr 1fr 1fr 1fr ",
|
||||
sm: "1fr 1fr",
|
||||
xs: "1fr",
|
||||
},
|
||||
gap: "40px",
|
||||
}}
|
||||
>
|
||||
<BoxItem
|
||||
background={
|
||||
"linear-gradient(-15deg, #2781b0 0%, #ffffff 30%) 0% 0% no-repeat padding-box"
|
||||
}
|
||||
>
|
||||
<CounterCard
|
||||
label={"Buckets"}
|
||||
icon={<BucketsIcon />}
|
||||
counterValue={usage ? representationNumber(usage.buckets) : 0}
|
||||
/>
|
||||
</BoxItem>
|
||||
<BoxItem
|
||||
background={
|
||||
"linear-gradient(-15deg, #4CCB92 0%, #ffffff 30%) 0% 0% no-repeat padding-box"
|
||||
}
|
||||
>
|
||||
<CounterCard
|
||||
label={"Objects"}
|
||||
icon={<TotalObjectsIcon />}
|
||||
counterValue={usage ? representationNumber(usage.objects) : 0}
|
||||
/>
|
||||
</BoxItem>
|
||||
<BoxItem>
|
||||
<StatusCountCard
|
||||
onlineCount={onlineServers.length}
|
||||
offlineCount={offlineServers.length}
|
||||
label={"Servers"}
|
||||
icon={<ServersIcon />}
|
||||
/>
|
||||
</BoxItem>
|
||||
<BoxItem>
|
||||
<StatusCountCard
|
||||
offlineCount={offlineDrives.length}
|
||||
onlineCount={onlineDrives.length}
|
||||
label={"Drives"}
|
||||
icon={<DrivesIcon />}
|
||||
/>
|
||||
</BoxItem>
|
||||
</Box>
|
||||
|
||||
<BoxItem>
|
||||
<ReportedUsage
|
||||
usageValue={usageValue}
|
||||
total={usageToRepresent.total}
|
||||
unit={usageToRepresent.unit}
|
||||
/>
|
||||
</BoxItem>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateRows: "auto",
|
||||
gridTemplateColumns: "1fr",
|
||||
gap: "auto",
|
||||
}}
|
||||
>
|
||||
<BoxItem>
|
||||
<ServersList data={serverList} />
|
||||
</BoxItem>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(BasicDashboard);
|
||||
export default BasicDashboard;
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// 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 { Box, Tooltip } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
const CounterCard = ({
|
||||
counterValue,
|
||||
label = "",
|
||||
icon = null,
|
||||
}: {
|
||||
counterValue: string | number;
|
||||
label?: any;
|
||||
icon?: any;
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
fontFamily: "Lato,sans-serif",
|
||||
color: "#07193E",
|
||||
maxWidth: "300px",
|
||||
minHeight: "200px",
|
||||
display: "flex",
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
cursor: "default",
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
height: "200px",
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
padding: {
|
||||
sm: "0 8px 0 8px",
|
||||
xs: "0 10px 0 10px",
|
||||
},
|
||||
position: "absolute",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
marginTop: "32px",
|
||||
zIndex: 10,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Box>
|
||||
|
||||
<Tooltip title={counterValue} placement="bottom" enterDelay={500}>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: {
|
||||
xl: "55px",
|
||||
lg: "40px",
|
||||
md: "36px",
|
||||
sm: "22px",
|
||||
xs: "14px",
|
||||
},
|
||||
fontWeight: 600,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
maxWidth: {
|
||||
md: 187,
|
||||
xs: 200,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{counterValue}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
marginTop: "26px",
|
||||
maxWidth: "26px",
|
||||
"& .min-icon": {
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CounterCard;
|
||||
@@ -1,91 +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, { Fragment } from "react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { IDriveInfo } from "../types";
|
||||
import { niceBytes } from "../../../../common/utils";
|
||||
import { Card, CardHeader } from "@mui/material";
|
||||
import { CircleIcon } from "../../../../icons";
|
||||
import { commonDashboardInfocard } from "../../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...commonDashboardInfocard,
|
||||
});
|
||||
|
||||
interface ICardProps {
|
||||
classes: any;
|
||||
drive: IDriveInfo;
|
||||
}
|
||||
|
||||
const DriveInfoCard = ({ classes, drive }: ICardProps) => {
|
||||
const driveStatusToClass = (health_status: string) => {
|
||||
switch (health_status) {
|
||||
case "offline":
|
||||
return classes.redState;
|
||||
case "ok":
|
||||
return classes.greenState;
|
||||
default:
|
||||
return classes.greyState;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Card className={classes.cardContainer}>
|
||||
<CardHeader
|
||||
className={classes.cardHeader}
|
||||
title={
|
||||
<div className={classes.referenceTitle}>
|
||||
{drive.state && (
|
||||
<span className={driveStatusToClass(drive.state)}>
|
||||
<CircleIcon />
|
||||
</span>
|
||||
)}
|
||||
{drive.endpoint || ""}
|
||||
</div>
|
||||
}
|
||||
subheader={
|
||||
<Grid item xs={12} className={classes.stateContainer}>
|
||||
<span className={classes.infoValue}>
|
||||
<strong>Capacity:</strong>{" "}
|
||||
{niceBytes(
|
||||
drive.totalSpace ? drive.totalSpace.toString() : "0"
|
||||
)}
|
||||
</span>
|
||||
<span className={classes.infoValue}>
|
||||
<strong>Used:</strong>{" "}
|
||||
{niceBytes(drive.usedSpace ? drive.usedSpace.toString() : "0")}
|
||||
</span>
|
||||
<span className={classes.infoValue}>
|
||||
<strong>Available:</strong>{" "}
|
||||
{niceBytes(
|
||||
drive.availableSpace ? drive.availableSpace.toString() : "0"
|
||||
)}
|
||||
</span>
|
||||
</Grid>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(DriveInfoCard);
|
||||
@@ -0,0 +1,175 @@
|
||||
// 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 { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { IDriveInfo } from "../types";
|
||||
import { niceBytes } from "../../../../common/utils";
|
||||
import { Box } from "@mui/material";
|
||||
import { CircleIcon, DrivesIcon } from "../../../../icons";
|
||||
import { commonDashboardInfocard } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { STATUS_COLORS } from "./Utils";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...commonDashboardInfocard,
|
||||
});
|
||||
|
||||
interface ICardProps {
|
||||
classes?: any;
|
||||
drive: IDriveInfo;
|
||||
}
|
||||
|
||||
const driveStatusColor = (health_status: string) => {
|
||||
switch (health_status) {
|
||||
case "offline":
|
||||
return STATUS_COLORS.RED;
|
||||
case "ok":
|
||||
return STATUS_COLORS.GREEN;
|
||||
default:
|
||||
return STATUS_COLORS.YELLOW;
|
||||
}
|
||||
};
|
||||
|
||||
const DriveInfoItem = ({ classes, drive }: ICardProps) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
paddingBottom: "10px",
|
||||
borderBottom: {
|
||||
xs: "1px solid #eaeaea",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
"& .min-icon": {
|
||||
fill: "#848484",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DrivesIcon />
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
marginLeft: "10px",
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontWeight: 400,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
|
||||
"& .min-icon": {
|
||||
marginRight: "10px",
|
||||
height: "10px",
|
||||
width: "10px",
|
||||
fill: driveStatusColor(drive.state),
|
||||
flexShrink: 0,
|
||||
},
|
||||
|
||||
"& .drive-endpoint": {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-all",
|
||||
fontSize: {
|
||||
md: "14px",
|
||||
xs: "10px",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{drive.state && <CircleIcon />}
|
||||
<div className="drive-endpoint">{drive.endpoint || ""}</div>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
paddingLeft: "20px",
|
||||
marginTop: "10px",
|
||||
flexFlow: {
|
||||
sm: "row",
|
||||
xs: "column",
|
||||
},
|
||||
"& .info-label": {
|
||||
color: "#8399AB",
|
||||
},
|
||||
"& .info-value": {
|
||||
color: "#073052",
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
}}
|
||||
>
|
||||
<label className="info-label">Capacity:</label>
|
||||
<div className="info-value">
|
||||
{niceBytes(drive.totalSpace ? drive.totalSpace.toString() : "0")}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
}}
|
||||
>
|
||||
<label className="info-label">Used:</label>
|
||||
<div className="info-value">
|
||||
{niceBytes(drive.usedSpace ? drive.usedSpace.toString() : "0")}
|
||||
</div>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
}}
|
||||
>
|
||||
<label className="info-label">Available:</label>
|
||||
<div className="info-value">
|
||||
{niceBytes(
|
||||
drive.availableSpace ? drive.availableSpace.toString() : "0"
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(DriveInfoItem);
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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 { ReportedUsageIcon } from "../../../../icons";
|
||||
import { Box, Tooltip } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
const ReportedUsage = ({
|
||||
usageValue,
|
||||
total,
|
||||
unit,
|
||||
}: {
|
||||
usageValue: string;
|
||||
total: number | string;
|
||||
unit: string;
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
maxHeight: "110px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: "19px",
|
||||
|
||||
padding: "10px",
|
||||
"& .unit-value": {
|
||||
fontSize: "50px",
|
||||
color: "#07193E",
|
||||
},
|
||||
"& .unit-type": {
|
||||
fontSize: "18px",
|
||||
color: "#5E5E5E",
|
||||
marginTop: "20px",
|
||||
marginLeft: "5px",
|
||||
},
|
||||
|
||||
"& .usage-label": {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
fontSize: "16px",
|
||||
fontWeight: 600,
|
||||
marginRight: "20px",
|
||||
marginTop: "-10px",
|
||||
"& .min-icon": {
|
||||
marginLeft: "10px",
|
||||
height: 16,
|
||||
width: 16,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="usage-label">
|
||||
<span>Reported Usage</span> <ReportedUsageIcon />
|
||||
</div>
|
||||
|
||||
<Tooltip title={`${usageValue} Bytes`}>
|
||||
<label
|
||||
className={"unit-value"}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{total}
|
||||
</label>
|
||||
</Tooltip>
|
||||
<label className={"unit-type"}>{unit}</label>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportedUsage;
|
||||
@@ -1,121 +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 { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { ServerInfo } from "../types";
|
||||
import { niceDays } from "../../../../common/utils";
|
||||
import { Card, CardHeader } from "@mui/material";
|
||||
import { CircleIcon, VersionIcon } from "../../../../icons";
|
||||
import get from "lodash/get";
|
||||
import { commonDashboardInfocard } from "../../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...commonDashboardInfocard,
|
||||
});
|
||||
|
||||
interface ICardProps {
|
||||
classes: any;
|
||||
server: ServerInfo;
|
||||
index: number;
|
||||
}
|
||||
|
||||
const ServerInfoCard = ({ classes, server, index }: ICardProps) => {
|
||||
const serverStatusToClass = (health_status: string) => {
|
||||
switch (health_status) {
|
||||
case "offline":
|
||||
return classes.redState;
|
||||
case "online":
|
||||
return classes.greenState;
|
||||
default:
|
||||
return classes.greyState;
|
||||
}
|
||||
};
|
||||
const networkKeys = Object.keys(get(server, "network", {}));
|
||||
const networkTotal = networkKeys.length;
|
||||
const totalDrives = server.drives ? server.drives.length : 0;
|
||||
const activeNetwork = networkKeys.reduce((acc: number, currValue: string) => {
|
||||
const item = server.network[currValue];
|
||||
if (item === "online") {
|
||||
return acc + 1;
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
const activeDisks = server.drives
|
||||
? server.drives.filter((element) => element.state === "ok").length
|
||||
: 0;
|
||||
return (
|
||||
<Card className={classes.cardContainer}>
|
||||
<CardHeader
|
||||
className={classes.cardHeader}
|
||||
title={
|
||||
<div>
|
||||
<div className={classes.cardNumber}>Server {index}</div>
|
||||
<div className={classes.referenceTitle}>
|
||||
{server.state && (
|
||||
<span className={serverStatusToClass(server.state)}>
|
||||
<CircleIcon />
|
||||
</span>
|
||||
)}
|
||||
{server.endpoint || ""}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
subheader={
|
||||
<Grid item xs={12} className={classes.stateContainer}>
|
||||
<span className={classes.infoValue}>
|
||||
<span
|
||||
className={`${classes.innerState} ${
|
||||
activeDisks <= totalDrives / 2 && classes.redState
|
||||
} ${
|
||||
totalDrives !== 2 &&
|
||||
activeDisks === totalDrives / 2 + 1 &&
|
||||
classes.yellowState
|
||||
} ${activeDisks === totalDrives && classes.greenState}`}
|
||||
>
|
||||
<CircleIcon />
|
||||
</span>
|
||||
Drives: {activeDisks}/{totalDrives}{" "}
|
||||
</span>
|
||||
<span className={classes.infoValue}>
|
||||
<span
|
||||
className={`${classes.innerState} ${
|
||||
activeNetwork <= networkTotal / 2 && classes.redState
|
||||
} ${
|
||||
activeNetwork === networkTotal / 2 + 1 && classes.yellowState
|
||||
} ${activeNetwork === networkTotal && classes.greenState}`}
|
||||
>
|
||||
<CircleIcon />
|
||||
</span>
|
||||
Network: {activeNetwork}/{networkTotal}{" "}
|
||||
</span>
|
||||
<span className={classes.infoValue}>
|
||||
Uptime: {server.uptime ? niceDays(server.uptime) : "N/A"}
|
||||
</span>
|
||||
<span className={classes.infoValue}>
|
||||
<VersionIcon />
|
||||
<strong>Version</strong> {server.version ? server.version : "N/A"}
|
||||
</span>
|
||||
</Grid>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default withStyles(styles)(ServerInfoCard);
|
||||
@@ -0,0 +1,239 @@
|
||||
// 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 { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { ServerInfo } from "../types";
|
||||
import { niceDays } from "../../../../common/utils";
|
||||
import { Box } from "@mui/material";
|
||||
import {
|
||||
CircleIcon,
|
||||
DrivesIcon,
|
||||
UptimeIcon,
|
||||
VersionIcon,
|
||||
WarpIcon,
|
||||
} from "../../../../icons";
|
||||
import get from "lodash/get";
|
||||
import { commonDashboardInfocard } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
getDriveStatusColor,
|
||||
getNetworkStatusColor,
|
||||
serverStatusColor,
|
||||
} from "./Utils";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...commonDashboardInfocard,
|
||||
});
|
||||
|
||||
interface ICardProps {
|
||||
classes?: any;
|
||||
server: ServerInfo;
|
||||
index: number;
|
||||
}
|
||||
|
||||
const ServerStatItem = ({
|
||||
label = "",
|
||||
value = "",
|
||||
statusColor = "",
|
||||
hasStatus = false,
|
||||
icon = null,
|
||||
}: {
|
||||
label?: string;
|
||||
value?: any;
|
||||
hasStatus?: boolean;
|
||||
statusColor: string | undefined;
|
||||
icon?: any;
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
padding: "5px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexFlow: "column",
|
||||
maxWidth: "40px",
|
||||
"&:first-of-type(svg)": {
|
||||
fill: "#848484",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
{hasStatus ? (
|
||||
<Box
|
||||
sx={{
|
||||
marginRight: "0px",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
"& svg.min-icon": {
|
||||
fill: statusColor,
|
||||
width: "10px",
|
||||
height: "10px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CircleIcon />
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ width: "12px", height: "12px" }} />
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "flex-start",
|
||||
flexFlow: "column",
|
||||
"& .stat-text": { color: "#5E5E5E", fontSize: "14px" },
|
||||
"& .stat-value": {
|
||||
color: "#07193E",
|
||||
display: "flex",
|
||||
fontWeight: 500,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="stat-text">{label}</div>
|
||||
<div className="stat-value">{value}</div>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ServerInfoItem = ({ classes = {}, server, index }: ICardProps) => {
|
||||
const networkKeys = Object.keys(get(server, "network", {}));
|
||||
const networkTotal = networkKeys.length;
|
||||
const totalDrives = server.drives ? server.drives.length : 0;
|
||||
const activeNetwork = networkKeys.reduce((acc: number, currValue: string) => {
|
||||
const item = server.network[currValue];
|
||||
if (item === "online") {
|
||||
return acc + 1;
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
const activeDisks = server.drives
|
||||
? server.drives.filter((element) => element.state === "ok").length
|
||||
: 0;
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
flexFlow: "column",
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "15px",
|
||||
}}
|
||||
>
|
||||
{server?.state && (
|
||||
<Box
|
||||
sx={{
|
||||
marginRight: "8px",
|
||||
"& .min-icon": {
|
||||
fill: serverStatusColor(server.state),
|
||||
height: "14px",
|
||||
width: "14px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CircleIcon />
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
textTransform: "none",
|
||||
}}
|
||||
>
|
||||
{server.endpoint || ""}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "3px",
|
||||
gap: "15px",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
paddingLeft: "20px",
|
||||
|
||||
flexFlow: {
|
||||
sm: "row",
|
||||
xs: "column",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ServerStatItem
|
||||
statusColor={getDriveStatusColor(activeDisks, totalDrives)}
|
||||
label={"Drives"}
|
||||
icon={<DrivesIcon />}
|
||||
hasStatus={true}
|
||||
value={`${activeDisks}/${totalDrives}`}
|
||||
/>
|
||||
<ServerStatItem
|
||||
statusColor={getNetworkStatusColor(activeNetwork, networkTotal)}
|
||||
label={"Network"}
|
||||
icon={<WarpIcon />}
|
||||
hasStatus={true}
|
||||
value={`${activeNetwork}/${networkTotal}`}
|
||||
/>
|
||||
|
||||
<ServerStatItem
|
||||
statusColor={"green"}
|
||||
label={"Up time"}
|
||||
icon={<UptimeIcon />}
|
||||
value={server?.uptime ? niceDays(server.uptime) : "N/A"}
|
||||
/>
|
||||
|
||||
<ServerStatItem
|
||||
statusColor={"green"}
|
||||
label={"Version"}
|
||||
icon={<VersionIcon />}
|
||||
value={
|
||||
<Box
|
||||
sx={{
|
||||
background: "rgb(235, 236, 237)",
|
||||
color: "#000000",
|
||||
paddingLeft: "10px",
|
||||
paddingRight: "10px",
|
||||
borderRadius: "16px",
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
}}
|
||||
>
|
||||
{server.version ? server.version : "N/A"}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default withStyles(styles)(ServerInfoItem);
|
||||
@@ -0,0 +1,146 @@
|
||||
// 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 ListSubheader from "@mui/material/ListSubheader";
|
||||
import List from "@mui/material/List";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import Collapse from "@mui/material/Collapse";
|
||||
import ExpandLess from "@mui/icons-material/ExpandLess";
|
||||
import ExpandMore from "@mui/icons-material/ExpandMore";
|
||||
import { ServerInfo } from "../types";
|
||||
import ServerInfoItem from "./ServerInfoItem";
|
||||
import { Box } from "@mui/material";
|
||||
import DriveInfoItem from "./DriveInfoItem";
|
||||
|
||||
const ServersList = ({ data }: { data: ServerInfo[] }) => {
|
||||
const [expanded, setExpanded] = React.useState<string>("");
|
||||
|
||||
const handleClick = (key: string) => {
|
||||
setExpanded(key);
|
||||
};
|
||||
|
||||
return (
|
||||
<List
|
||||
sx={{ width: "100%", flex: 1 }}
|
||||
component="nav"
|
||||
aria-labelledby="nested-list-subheader"
|
||||
subheader={
|
||||
<ListSubheader
|
||||
component="div"
|
||||
sx={{
|
||||
borderBottom: "1px solid #F8F8F8",
|
||||
}}
|
||||
>
|
||||
Servers ({data.length})
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
{data.map((serverInfo, index) => {
|
||||
const key = `${serverInfo.endpoint}-${index}`;
|
||||
const isExpanded = expanded === key;
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
<ListItemButton
|
||||
disableRipple
|
||||
onClick={() => {
|
||||
if (!isExpanded) {
|
||||
handleClick(key);
|
||||
} else {
|
||||
handleClick("");
|
||||
}
|
||||
}}
|
||||
className={isExpanded ? "expanded" : ""}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
borderBottom: "1px solid #eaeaea",
|
||||
"&:hover": {
|
||||
background: "#F8F8F8",
|
||||
},
|
||||
"&.expanded": {
|
||||
borderBottom: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ServerInfoItem server={serverInfo} index={index} />
|
||||
<Box
|
||||
sx={{
|
||||
height: "25px",
|
||||
width: "25px",
|
||||
marginLeft: "25px",
|
||||
background: "#FBFAFA",
|
||||
borderRadius: "2px",
|
||||
"&:hover": {
|
||||
background: "#fafafa",
|
||||
},
|
||||
display: {
|
||||
md: "block",
|
||||
xs: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isExpanded ? <ExpandLess /> : <ExpandMore />}
|
||||
</Box>
|
||||
</ListItemButton>
|
||||
{isExpanded ? (
|
||||
<React.Fragment key={`${serverInfo.endpoint}-${index}`}>
|
||||
<ListSubheader
|
||||
key={`${index}-drive-details`}
|
||||
component="div"
|
||||
sx={{
|
||||
borderBottom: "1px solid #F8F8F8",
|
||||
}}
|
||||
>
|
||||
Drives ({serverInfo.drives.length})
|
||||
</ListSubheader>
|
||||
|
||||
<Collapse
|
||||
in={isExpanded}
|
||||
timeout="auto"
|
||||
unmountOnExit
|
||||
sx={{
|
||||
width: "100%",
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
padding: { md: "20px 50px", xs: "15px 15px" },
|
||||
"& .MuiCollapse-wrapperInner": {
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
gap: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{serverInfo.drives.map((driveInfo, index) => {
|
||||
return (
|
||||
<DriveInfoItem
|
||||
drive={driveInfo}
|
||||
key={`${driveInfo.endpoint}-${index}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Collapse>
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServersList;
|
||||
@@ -0,0 +1,147 @@
|
||||
// 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 { CircleIcon } from "../../../../icons";
|
||||
|
||||
export const StatusCountCard = ({
|
||||
onlineCount = 0,
|
||||
offlineCount = 0,
|
||||
icon = null,
|
||||
label = "",
|
||||
}: {
|
||||
icon: any;
|
||||
onlineCount: number;
|
||||
offlineCount: number;
|
||||
label: string;
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
fontFamily: "Lato,sans-serif",
|
||||
color: "#07193E",
|
||||
maxWidth: "300px",
|
||||
minHeight: "200px",
|
||||
display: "flex",
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
cursor: "default",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
height: "200px",
|
||||
display: "flex",
|
||||
padding: {
|
||||
sm: "0 8px 0 8px",
|
||||
xs: "0 10px 0 10px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
marginTop: "32px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontWeight: 600,
|
||||
marginBottom: "24px",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
fontSize: {
|
||||
lg: "50px",
|
||||
md: "45px",
|
||||
xs: "35px",
|
||||
},
|
||||
fontWeight: 600,
|
||||
|
||||
"& .stat-text": { color: "#696969", fontSize: "12px" },
|
||||
"& .stat-value": {
|
||||
textAlign: "center",
|
||||
},
|
||||
"& .min-icon": {
|
||||
marginRight: "8px",
|
||||
height: "10px",
|
||||
width: "10px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"& .min-icon": {
|
||||
fill: "#4CCB92",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CircleIcon /> <div className="stat-text">Online</div>
|
||||
</Box>
|
||||
<Box className="stat-value">{onlineCount}</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"& .min-icon": {
|
||||
fill: "#C83B51",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CircleIcon /> <div className="stat-text">Offline</div>
|
||||
</Box>
|
||||
<Box className="stat-value">{offlineCount}</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
marginTop: "26px",
|
||||
maxWidth: "26px",
|
||||
"& .min-icon": {
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusCountCard;
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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 STATUS_COLORS = {
|
||||
RED: "#C83B51",
|
||||
GREEN: "#4CCB92",
|
||||
YELLOW: "#E7A219",
|
||||
};
|
||||
|
||||
export const getDriveStatusColor = (
|
||||
activeDisks: number,
|
||||
totalDrives: number
|
||||
) => {
|
||||
if (activeDisks <= totalDrives / 2) {
|
||||
return STATUS_COLORS.RED;
|
||||
}
|
||||
if (totalDrives !== 2 && activeDisks === totalDrives / 2 + 1) {
|
||||
return STATUS_COLORS.YELLOW;
|
||||
}
|
||||
if (activeDisks === totalDrives) {
|
||||
return STATUS_COLORS.GREEN;
|
||||
}
|
||||
};
|
||||
|
||||
export const serverStatusColor = (health_status: string) => {
|
||||
switch (health_status) {
|
||||
case "offline":
|
||||
return STATUS_COLORS.RED;
|
||||
case "online":
|
||||
return STATUS_COLORS.GREEN;
|
||||
default:
|
||||
return STATUS_COLORS.YELLOW;
|
||||
}
|
||||
};
|
||||
export const getNetworkStatusColor = (
|
||||
activeNetwork: number,
|
||||
networkTotal: number
|
||||
) => {
|
||||
if (activeNetwork <= networkTotal / 2) {
|
||||
return STATUS_COLORS.RED;
|
||||
}
|
||||
if (activeNetwork === networkTotal / 2 + 1) {
|
||||
return STATUS_COLORS.YELLOW;
|
||||
}
|
||||
if (activeNetwork === networkTotal) {
|
||||
return STATUS_COLORS.GREEN;
|
||||
}
|
||||
};
|
||||
@@ -24,12 +24,12 @@ import { containerForHeader } from "../Common/FormComponents/common/styleLibrary
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import BasicDashboard from "./BasicDashboard/BasicDashboard";
|
||||
import { LinearProgress } from "@mui/material";
|
||||
import api from "../../../common/api";
|
||||
import { Usage } from "./types";
|
||||
import { setErrorSnackMessage } from "../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import BasicDashboard from "./BasicDashboard/BasicDashboard";
|
||||
|
||||
interface IDashboardSimple {
|
||||
classes: any;
|
||||
@@ -82,9 +82,7 @@ const Dashboard = ({ classes, displayErrorMessage }: IDashboardSimple) => {
|
||||
<PrDashboard />
|
||||
</Grid>
|
||||
) : (
|
||||
<Grid container className={classes.container}>
|
||||
<BasicDashboard usage={basicResult} />
|
||||
</Grid>
|
||||
<BasicDashboard usage={basicResult} />
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user