Added New styles to Dashboard (#1054)

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

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
Alex
2021-09-16 11:43:37 -05:00
committed by GitHub
parent 52ee9bb49b
commit 2ade4ca1cf
13 changed files with 819 additions and 272 deletions

View File

@@ -80,7 +80,7 @@
},
"proxy": "http://localhost:9090/",
"devDependencies": {
"prettier": "2.3.1",
"typescript": "^4.3.2"
"prettier": "2.3.2",
"typescript": "^4.4.3"
}
}

View File

@@ -508,7 +508,7 @@ export const calculateBytes = (
const bytes = parseInt(x, 10);
if (bytes === 0) {
return { total: 0, unit: k8sCalcUnits[0] };
return { total: 0, unit: units[0] };
}
// Gi : GiB
@@ -525,7 +525,7 @@ export const calculateBytes = (
// Get Unit parsed
const unitParsed = parseFloat(roundedUnit.toFixed(fractionDigits));
const finalUnit = k8sCalcUnits[i];
const finalUnit = units[i];
return { total: unitParsed, unit: finalUnit };
};

View File

@@ -730,3 +730,107 @@ export const inlineCheckboxes = {
justifyContent: "flex-start",
},
};
const commonStateIcon = {
marginRight: 10,
lineHeight: 1,
display: "inline-flex",
marginTop: 6 ,
};
export const commonDashboardInfocard = {
cardIconContainer: {
display: "flex" as const,
position: "relative" as const,
alignItems: "center" as const,
},
stateContainer: {
display: "flex" as const,
flexWrap: "wrap" as const,
justifyContent: "flex-start" as const,
},
infoValue: {
fontWeight: 500,
color: "#07193E",
fontSize: 16,
margin: "8px 40px 5px 0",
display: "inline-flex" as const,
"& strong": {
marginRight: 4,
},
"& .MuiSvgIcon-root": {
width: 20,
height: 20,
},
alignItems: "center" as const,
},
redState: {
color: "#F55B5B",
...commonStateIcon,
},
greenState: {
color: "#9FF281",
...commonStateIcon,
},
yellowState: {
color: "#F7A25A",
...commonStateIcon,
},
greyState: {
color: "grey",
...commonStateIcon,
},
healthStatusIcon: {
position: "absolute" as const,
fontSize: 8,
left: 18,
height: 10,
bottom: 2,
marginRight: 10,
"& .MuiSvgIcon-root": {
width: 5,
height: 5,
},
},
innerState: {
fontSize: 8,
display: "flex" as const,
alignItems: "center" as const,
marginTop: -3,
"& .MuiSvgIcon-root": {
marginTop: 5,
width: 10,
height: 10,
},
},
cardContainer: {
border: "#EEF1F4 2px solid",
borderRadius: 10,
boxShadow: "0 0 15px #00000029",
maxWidth: 1185,
marginBottom: 30,
},
cardHeader: {
"& .MuiCardHeader-title": {
fontWeight: "bolder" as const,
},
},
cardNumber: {
color: "#848484",
fontSize: 16,
fontWeight: 400,
marginBottom: 10,
},
referenceTitle: {
display: "flex",
alignItems: "center" as const,
lineHeight: 1,
fontWeight: "bold" as const,
textTransform: "capitalize" as const,
"& .MuiSvgIcon-root": {
width: 10,
height: 10,
marginTop: -5,
},
},
};

View File

@@ -0,0 +1,141 @@
// 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 {
createStyles,
Theme,
withStyles,
makeStyles,
} from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import Pagination from "@material-ui/lab/Pagination";
interface IGeneralUsePaginator {
classes: any;
page: number;
itemsPerPage?: number;
entity: string;
totalItems: number;
onChange: (newPage: number) => void;
}
const styles = (theme: Theme) =>
createStyles({
paginatorContainer: {
margin: "10px 0 18px",
flexWrap: "wrap",
},
paginatorInformation: {
color: "#848484",
fontSize: 12,
fontStyle: "italic",
whiteSpace: "nowrap",
},
paginatorEntity: {
color: "#767676",
fontSize: 12,
fontWeight: "bold",
},
paginationElement: {
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "flex-end",
color: "#848484",
fontSize: 12,
},
});
const paginatorStyling = makeStyles({
ul: {
"& .MuiPaginationItem-root": {
color: "#07193E",
fontSize: 14,
"&.Mui-selected": {
backgroundColor: "transparent",
fontWeight: "bold",
"&::after": {
backgroundColor: "#07193E",
width: "100%",
height: 3,
content: '" "',
position: "absolute",
bottom: -3,
},
},
},
},
});
const GeneralUsePaginator = ({
classes,
page = 1,
itemsPerPage = 5,
entity,
totalItems,
onChange,
}: IGeneralUsePaginator) => {
const paginatorStyles = paginatorStyling();
const currentInitialItem = page * itemsPerPage - itemsPerPage + 1;
const currentEndItem = currentInitialItem + itemsPerPage - 1;
const displayCurrentEndItem =
currentEndItem > totalItems ? totalItems : currentEndItem;
const totalPages = Math.ceil(totalItems / itemsPerPage);
return (
<Fragment>
<Grid container className={classes.paginatorContainer}>
<Grid item xs={6}>
<span className={classes.paginatorEntity}>{entity}</span>
<br />
<span className={classes.paginatorInformation}>
Showing{" "}
{totalPages > 1 ? (
<Fragment>
{currentInitialItem} - {displayCurrentEndItem} out of{" "}
</Fragment>
) : null}
{totalItems} Total {entity}
</span>
</Grid>
<Grid item xs={6} className={classes.paginationElement}>
{totalPages > 1 && (
<Fragment>
Go to:{" "}
<Pagination
count={totalPages}
variant={"text"}
siblingCount={3}
page={page}
size="small"
hideNextButton
hidePrevButton
onChange={(_, newPage: number) => {
onChange(newPage);
}}
classes={{ ul: paginatorStyles.ul }}
/>
</Fragment>
)}
</Grid>
</Grid>
</Fragment>
);
};
export default withStyles(styles)(GeneralUsePaginator);

View File

@@ -0,0 +1,138 @@
// 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 {
createStyles,
Theme,
withStyles,
makeStyles,
} from "@material-ui/core/styles";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import { ITabOption } from "./types";
interface ITabSelector {
selectedTab: number;
onChange: (newValue: number) => void;
tabOptions: ITabOption[];
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
cardsContainer: {
maxHeight: 440,
overflowY: "auto",
overflowX: "hidden",
},
generalStatusCards: {
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
},
generalStatusTitle: {
color: "#767676",
fontSize: 16,
fontWeight: "bold",
margin: "15px 10px 0 10px",
},
});
const tabSubStyles = makeStyles({
root: {
backgroundColor: "transparent",
paddingTop: 0,
paddingBottom: 0,
},
wrapper: { fontSize: 22, textTransform: "uppercase", color: "#D0D0D0" },
selected: { "& .MuiTab-wrapper": { color: "#07193E", fontWeight: "bold" } },
indicator: {
background:
"transparent linear-gradient(90deg, #072B4E 0%, #081C42 100%) 0% 0% no-repeat padding-box;",
height: 4,
},
scroller: {
maxWidth: 1185,
position: "relative",
"&::after": {
content: '" "',
backgroundColor: "#EEF1F4",
height: 4,
width: "100%",
display: "block",
},
},
});
const TabSelector = ({
selectedTab,
onChange,
tabOptions,
classes,
}: ITabSelector) => {
const subStyles = tabSubStyles();
return (
<Fragment>
<Tabs
indicatorColor="primary"
textColor="primary"
aria-label="cluster-tabs"
variant="scrollable"
scrollButtons="auto"
value={selectedTab}
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
onChange(newValue);
}}
classes={{
indicator: subStyles.indicator,
scroller: subStyles.scroller,
}}
>
{tabOptions.map((option, index) => {
let tabOptions: ITabOption = {
label: option.label,
};
if (option.value) {
tabOptions = { ...tabOptions, value: option.value };
}
if (option.disabled) {
tabOptions = { ...tabOptions, disabled: option.disabled };
}
return (
<Tab
{...tabOptions}
classes={{
root: subStyles.root,
wrapper: subStyles.wrapper,
selected: subStyles.selected,
}}
id={`simple-tab-${index}`}
aria-controls={`simple-tabpanel-${index}`}
key={`tab-${index}-${option.label}`}
/>
);
})}
</Tabs>
</Fragment>
);
};
export default withStyles(styles)(TabSelector);

View File

@@ -0,0 +1,21 @@
// 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/>.
export interface ITabOption {
label: string;
value?: string;
disabled?: boolean;
}

View File

@@ -14,44 +14,42 @@
// 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 React, { Fragment, useState } from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { Usage } from "../types";
import { niceBytes } from "../../../../common/utils";
import { IDriveInfo, Usage } from "../types";
import { calculateBytes } from "../../../../common/utils";
import { TabPanel } from "../../../shared/tabs";
import ReportedUsageIcon from "../../../../icons/ReportedUsageIcon";
import ServerInfoCard from "./ServerInfoCard";
import DriveInfoCard from "./DriveInfoCard";
import {
BucketsIcon,
ServersIcon,
StorageIcon,
TotalObjectsIcon,
VersionIcon,
} from "../../../../icons";
import { Card, CardHeader } from "@material-ui/core";
import { BucketsIcon, TotalObjectsIcon } from "../../../../icons";
import CommonCard from "../CommonCard";
import TabSelector from "../../Common/TabSelector/TabSelector";
import GeneralUsePaginator from "../../Common/GeneralUsePaginator/GeneralUsePaginator";
const styles = (theme: Theme) =>
createStyles({
dashboardBG: {
width: 390,
height: 255,
zIndex: -1,
position: "fixed",
backgroundSize: "fill",
backgroundImage: "url(/images/BG_IllustrationDarker.svg)",
backgroundPosition: "right bottom",
right: 0,
bottom: 0,
backgroundRepeat: "no-repeat",
},
cardsContainer: {
maxHeight: 440,
overflowY: "auto",
overflowX: "hidden",
},
generalStatusCards: {
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
},
generalStatusTitle: {
color: "#767676",
fontSize: 16,
fontWeight: "bold",
margin: "15px 10px 0 10px",
},
paginatorContainer: {
maxWidth: 1185,
width: "100%",
},
});
interface IDashboardProps {
@@ -59,24 +57,21 @@ interface IDashboardProps {
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 "0";
return { total: "0", unit: "Mi" };
}
const niceBytesUsage = niceBytes(usage).split(" ");
const calculatedBytes = calculateBytes(usage);
if (niceBytesUsage.length !== 2) {
return niceBytesUsage.join(" ");
}
return (
<Fragment>
{niceBytesUsage[0]}
<span className={classes.smallUnit}>{niceBytesUsage[1]}</span>
</Fragment>
);
return calculatedBytes;
};
const prettyNumber = (usage: number | undefined) => {
@@ -105,95 +100,101 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => {
const serverArray = makeServerArray(usage);
const usageToRepresent = prettyUsage(usage ? usage.usage.toString() : "0");
let allDrivesArray: IDriveInfo[] = [];
serverArray.forEach((server) => {
const drivesInput = server.drives.map((drive) => {
return drive;
});
allDrivesArray = [...allDrivesArray, ...drivesInput];
});
const splitedServers = serverArray.slice(
serversPageNumber * itemsPerPage - itemsPerPage,
serversPageNumber * itemsPerPage
);
const splitedDrives = allDrivesArray.slice(
drivesPageNumber * itemsPerPage - itemsPerPage,
drivesPageNumber * itemsPerPage
);
return (
<Fragment>
<div className={classes.dashboardBG} />
<Grid container spacing={2}>
<Grid item xs={6}>
<Grid container spacing={2}>
<Grid item xs={6}>
<Card className={classes.cardRoot}>
<CardHeader
avatar={<BucketsIcon />}
title="Number of Buckets"
subheader={usage ? prettyNumber(usage.buckets) : 0}
/>
</Card>
</Grid>
<Grid item xs={6}>
<Card className={classes.cardRoot}>
<CardHeader
avatar={<ReportedUsageIcon />}
title="Usage"
subheader={usage ? prettyUsage(usage.usage + "") : 0}
/>
</Card>
</Grid>
<Grid item xs={6}>
<Card className={classes.cardRoot}>
<CardHeader
avatar={<TotalObjectsIcon />}
title="Total Objects"
subheader={usage ? prettyNumber(usage.objects) : 0}
/>
</Card>
</Grid>
<Grid item xs={6}>
<Card className={classes.cardRoot}>
{usage
? usage.servers.length !== 0 && (
<CardHeader
avatar={<VersionIcon />}
title="MinIO Version"
subheader={usage ? usage.servers[0].version : 0}
/>
)
: 0}
</Card>
</Grid>
<Grid item xs={6} />
</Grid>
<Grid item xs={12}>
<Grid container alignItems="center" spacing={2}>
<Grid item>
<StorageIcon />
</Grid>
<Grid item>
<Typography variant="h5">Drives Status</Typography>
</Grid>
</Grid>
<Grid container spacing={1} className={classes.cardsContainer}>
{serverArray.map((server, index) =>
server.drives.map((drive) => (
<Grid item xs={12} key={drive.uuid}>
<DriveInfoCard drive={drive} />
</Grid>
))
)}
</Grid>
</Grid>
<Grid item xs={12} className={classes.generalStatusTitle}>
General Status
</Grid>
<Grid item xs={6}>
<Grid container alignItems="center" spacing={2}>
<Grid item>
<ServersIcon />
</Grid>
<Grid item>
<Typography variant="h5">Servers Status</Typography>
</Grid>
</Grid>
<Grid container spacing={1}>
{serverArray.map((server, index) => (
<Grid item xs={12}>
<ServerInfoCard
server={server}
key={`serverDS-${index.toString()}`}
/>
<Grid item xs={12} className={classes.generalStatusCards}>
<CommonCard
avatar={<BucketsIcon />}
title={"All Buckets"}
metricValue={usage ? prettyNumber(usage.buckets) : 0}
/>
<CommonCard
avatar={<ReportedUsageIcon />}
title={"Usage"}
metricValue={usageToRepresent.total}
metricUnit={usageToRepresent.unit}
/>
<CommonCard
avatar={<TotalObjectsIcon />}
title={"Total Objects"}
metricValue={usage ? prettyNumber(usage.objects) : 0}
/>
<CommonCard
avatar={<TotalObjectsIcon />}
title={"Servers"}
metricValue={usage ? prettyNumber(serverArray.length) : 0}
subMessage={{ message: "Total" }}
/>
</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>
))}
</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>
</Fragment>

View File

@@ -20,65 +20,12 @@ import Grid from "@material-ui/core/Grid";
import { IDriveInfo } from "../types";
import { niceBytes } from "../../../../common/utils";
import { Card, CardHeader } from "@material-ui/core";
import { CircleIcon, StorageIcon } from "../../../../icons";
import { CircleIcon } from "../../../../icons";
import { commonDashboardInfocard } from "../../Common/FormComponents/common/styleLibrary";
const styles = (theme: Theme) =>
createStyles({
cardIconContainer: {
display: "flex",
position: "relative",
alignItems: "center",
},
stateContainer: {
display: "flex",
flexWrap: "wrap",
justifyContent: "space-between",
},
infoValue: {
fontWeight: 500,
color: "#777777",
fontSize: 14,
margin: "5px 4px",
display: "inline-flex",
"& strong": {
marginRight: 4,
},
},
redState: {
color: theme.palette.error.main,
},
greenState: {
color: theme.palette.success.main,
},
yellowState: {
color: theme.palette.warning.main,
},
greyState: {
color: "grey",
},
healthStatusIcon: {
position: "absolute",
fontSize: 10,
left: 18,
height: 10,
bottom: 2,
"& .MuiSvgIcon-root": {
width: 10,
height: 10,
},
},
innerState: {
fontSize: 10,
marginLeft: 5,
display: "flex",
alignItems: "center",
marginTop: -3,
},
cardHeader: {
"& .MuiCardHeader-title": {
fontWeight: "bolder",
},
},
...commonDashboardInfocard,
});
interface ICardProps {
@@ -100,22 +47,19 @@ const DriveInfoCard = ({ classes, drive }: ICardProps) => {
return (
<Fragment>
<Card>
<Card className={classes.cardContainer}>
<CardHeader
className={classes.cardHeader}
avatar={
<div className={classes.cardIconContainer}>
<StorageIcon className="computerIcon" />
<div className={classes.healthStatusIcon}>
{drive.state && (
<span className={driveStatusToClass(drive.state)}>
<CircleIcon />
</span>
)}
</div>
title={
<div className={classes.referenceTitle}>
{drive.state && (
<span className={driveStatusToClass(drive.state)}>
<CircleIcon />
</span>
)}
{drive.endpoint || ""}
</div>
}
title={drive.endpoint || ""}
subheader={
<Grid item xs={12} className={classes.stateContainer}>
<span className={classes.infoValue}>

View File

@@ -16,83 +16,28 @@
import React from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import ComputerIcon from "@material-ui/icons/Computer";
import Grid from "@material-ui/core/Grid";
import { ServerInfo } from "../types";
import { niceDays } from "../../../../common/utils";
import { Card, CardHeader } from "@material-ui/core";
import { CircleIcon } from "../../../../icons";
import { CircleIcon, VersionIcon } from "../../../../icons";
import get from "lodash/get";
import { commonDashboardInfocard } from "../../Common/FormComponents/common/styleLibrary";
const styles = (theme: Theme) =>
createStyles({
cardIconContainer: {
display: "flex",
position: "relative",
alignItems: "center",
},
stateContainer: {
display: "flex",
flexWrap: "wrap",
justifyContent: "space-between",
},
infoValue: {
fontWeight: 500,
color: "#777777",
fontSize: 14,
margin: "5px 4px",
display: "inline-flex",
"& strong": {
marginRight: 4,
},
"& .MuiSvgIcon-root": {
width: 16,
height: 16,
},
},
redState: {
color: theme.palette.error.main,
},
greenState: {
color: theme.palette.success.main,
},
yellowState: {
color: theme.palette.warning.main,
},
greyState: {
color: "grey",
},
healthStatusIcon: {
position: "absolute",
fontSize: 10,
left: 18,
height: 10,
bottom: 2,
"& .MuiSvgIcon-root": {
width: 10,
height: 10,
},
},
innerState: {
fontSize: 10,
marginLeft: 5,
display: "flex",
alignItems: "center",
marginTop: -3,
},
cardHeader: {
"& .MuiCardHeader-title": {
fontWeight: "bolder",
},
},
...commonDashboardInfocard,
});
interface ICardProps {
classes: any;
server: ServerInfo;
index: number;
}
const ServerInfoCard = ({ classes, server }: ICardProps) => {
const ServerInfoCard = ({ classes, server, index }: ICardProps) => {
const serverStatusToClass = (health_status: string) => {
switch (health_status) {
case "offline":
@@ -122,26 +67,25 @@ const ServerInfoCard = ({ classes, server }: ICardProps) => {
: 0;
return (
<Card>
<Card className={classes.cardContainer}>
<CardHeader
className={classes.cardHeader}
avatar={
<div className={classes.cardIconContainer}>
<ComputerIcon className="computerIcon" />
<div className={classes.healthStatusIcon}>
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>
}
title={server.endpoint || ""}
subheader={
<Grid item xs={12} className={classes.stateContainer}>
<span className={classes.infoValue}>
<strong>Drives:</strong> {activeDisks}/{totalDrives}{" "}
<span
className={`${classes.innerState} ${
activeDisks <= totalDrives / 2 && classes.redState
@@ -151,9 +95,9 @@ const ServerInfoCard = ({ classes, server }: ICardProps) => {
>
<CircleIcon />
</span>
Drives: {activeDisks}/{totalDrives}{" "}
</span>
<span className={classes.infoValue}>
<strong>Network:</strong> {activeNetwork}/{networkTotal}{" "}
<span
className={`${classes.innerState} ${
activeNetwork <= networkTotal / 2 && classes.redState
@@ -163,10 +107,14 @@ const ServerInfoCard = ({ classes, server }: ICardProps) => {
>
<CircleIcon />
</span>
Network: {activeNetwork}/{networkTotal}{" "}
</span>
<span className={classes.infoValue}>
<strong>Uptime:</strong>{" "}
{server.uptime ? niceDays(server.uptime) : "N/A"}
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>
}

View File

@@ -0,0 +1,190 @@
// 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 { Card, CardHeader } from "@material-ui/core";
import { Link } from "react-router-dom";
import {
createStyles,
Theme,
withStyles,
makeStyles,
} from "@material-ui/core/styles";
import React, { Fragment } from "react";
export interface ISubInterface {
message: string;
fontWeight?: "normal" | "bold";
}
interface ICommonCard {
avatar: any;
title: string;
metricValue: any;
metricUnit?: string;
subMessage?: ISubInterface;
moreLink?: string;
rightComponent?: any;
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
cardRoot: {
border: "#eef1f4 2px solid",
borderRadius: 10,
maxWidth: 280,
width: "100%",
margin: 10,
},
cardsContainer: {
maxHeight: 440,
overflowY: "auto",
overflowX: "hidden",
},
metricText: {
fontSize: 70,
lineHeight: 1.1,
color: "#07193E",
fontWeight: "bold",
},
unitText: {
fontSize: 10,
color: "#767676",
fontWeight: "normal",
},
subHearderContainer: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
subMessage: {
fontSize: 10,
color: "#767676",
"&.bold": {
fontWeight: "bold",
},
},
headerContainer: {
display: "flex",
justifyContent: "space-between",
},
viewAll: {
fontSize: 10,
color: "#C83B51",
textTransform: "capitalize",
"& a, & a:hover, & a:visited, & a:active": {
color: "#C83B51",
},
},
});
const cardSubStyles = makeStyles({
root: { backgroundColor: "#fff" },
title: {
color: "#404144",
fontSize: 14,
textTransform: "uppercase",
fontWeight: "bold",
borderBottom: "#eef1f4 1px solid",
paddingBottom: 14,
marginBottom: 5,
},
content: {
maxWidth: "100%",
},
});
const CommonCard = ({
avatar,
title,
metricValue,
metricUnit,
subMessage,
moreLink,
rightComponent,
classes,
}: ICommonCard) => {
const subStyles = cardSubStyles();
const SubHeader = () => {
return (
<Fragment>
<div className={classes.subHearderContainer}>
<div className={classes.leftSide}>
<div>
<span className={classes.metricText}>
{metricValue}
<span className={classes.unitText}>{metricUnit}</span>
</span>
</div>
{subMessage && (
<div
className={`${classes.subMessage} ${
subMessage.fontWeight ? subMessage.fontWeight : ""
}`}
>
{subMessage.message}
</div>
)}
</div>
<div className={classes.rightSide}>{rightComponent}</div>
</div>
</Fragment>
);
};
const Header = () => {
return (
<Fragment>
<div className={classes.headerContainer}>
<span className={classes.title}>{title}</span>
{moreLink && (
<Fragment>
<span className={classes.viewAll}>
<Link to={moreLink}>View All</Link>
</span>
</Fragment>
)}
</div>
</Fragment>
);
};
return (
<Fragment>
<Card className={classes.cardRoot}>
{metricValue !== "" && (
<CardHeader
title={<Header />}
subheader={
<Fragment>
<SubHeader />
</Fragment>
}
classes={{
root: subStyles.root,
title: subStyles.title,
content: subStyles.content,
}}
/>
)}
</Card>
</Fragment>
);
};
export default withStyles(styles)(CommonCard);

View File

@@ -0,0 +1,60 @@
// 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
interface ISimpleWidget {
classes: any;
iconWidget: any;
label: string;
value: string;
}
const styles = (theme: Theme) =>
createStyles({
mainWidgetContainer: {
display: "inline-flex",
color: "#072A4D",
alignItems: "center",
},
icon: {
color: "#072A4D",
fill: "#072A4D",
marginRight: 5,
marginLeft: 12,
},
widgetLabel: {
fontWeight: "bold",
textTransform: "uppercase",
marginRight: 10,
},
widgetValue: {
marginRight: 25,
}
});
const SimpleWidget = ({ classes, iconWidget, label, value }: ISimpleWidget) => {
return (
<span className={classes.mainWidgetContainer}>
<span className={classes.icon}>{iconWidget ? iconWidget : null}</span>
<span className={classes.widgetLabel}>{label}: </span>
<span className={classes.widgetValue}>{value}</span>
</span>
);
};
export default withStyles(styles)(SimpleWidget);

View File

@@ -106,7 +106,7 @@ const Hop = ({ classes, match }: IHopSimple) => {
const next = `${loc}${add}cp=y`;
consoleFrame.current.contentDocument.location.replace(next);
} else {
consoleFrame.current.contentDocument.location.reload(true);
consoleFrame.current.contentDocument.location.reload();
}
}
}}

View File

@@ -9306,10 +9306,10 @@ prepend-http@^1.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
prettier@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6"
integrity sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==
prettier@2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d"
integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==
pretty-bytes@^5.3.0:
version "5.6.0"
@@ -11499,10 +11499,10 @@ typeface-roboto@^0.0.75:
resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-0.0.75.tgz#98d5ba35ec234bbc7172374c8297277099cc712b"
integrity sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg==
typescript@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==
typescript@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324"
integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==
unbox-primitive@^1.0.1:
version "1.0.1"