Add Server Metrics Info Tab (#2340)

This commit is contained in:
Daniel Valdivia
2022-10-07 21:19:40 -07:00
committed by GitHub
parent 43db7729c4
commit 0c778f57d3
20 changed files with 440 additions and 278 deletions

View File

@@ -264,7 +264,6 @@ const DateRangeSelector = ({
alignItems: "flex-end",
display: "flex",
justifyContent: "flex-end",
marginRight: "35px",
}}
>
<Button

View File

@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment } from "react";
import React from "react";
import { Box } from "@mui/material";
import {
ArrowRightIcon,
@@ -67,7 +67,7 @@ interface IDashboardProps {
const getServersList = (usage: Usage | null) => {
if (usage !== null) {
return usage.servers.sort(function (a, b) {
return [...usage.servers].sort(function (a, b) {
const nameA = a.endpoint.toLowerCase();
const nameB = b.endpoint.toLowerCase();
if (nameA < nameB) {
@@ -114,14 +114,8 @@ const BasicDashboard = ({ usage }: IDashboardProps) => {
serversGroup;
const drivesGroup = groupBy(allDrivesArray, "state");
const { offline: offlineDrives = [], ok: onlineDrives = [] } = drivesGroup;
return (
<Box
sx={{
maxWidth: "1536px",
margin: "auto",
}}
>
<Box>
<Box
sx={{
display: "grid",
@@ -129,69 +123,8 @@ const BasicDashboard = ({ usage }: IDashboardProps) => {
gridTemplateColumns: "1fr",
gap: "27px",
marginBottom: "40px",
marginTop: "40px",
marginLeft: "40px",
marginRight: "40px",
}}
>
<Box>
{usage?.prometheusNotReady && (
<HelpBox
iconComponent={<PrometheusErrorIcon />}
title={"We can't retrieve advanced metrics at this time"}
help={
<Fragment>
MinIO Dashboard will display basic metrics as we couldn't
connect to Prometheus successfully.
<br /> <br />
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.
</Fragment>
}
/>
)}
{!usage?.prometheusNotReady && (
<HelpBox
iconComponent={<PrometheusErrorIcon />}
title={"We cant retrieve advanced metrics at this time."}
help={
<Box>
<Box
sx={{
fontSize: "14px",
}}
>
MinIO Dashboard will display basic metrics as we couldnt
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,
},
}}
>
<a
href="https://min.io/docs/minio/linux/operations/monitoring/collect-minio-metrics-using-prometheus.html"
target="_blank"
rel="noreferrer"
>
Read more about Prometheus on our Docs site.
</a>
</Box>
</Box>
}
/>
)}
</Box>
<Box
sx={{
display: "grid",
@@ -355,6 +288,46 @@ const BasicDashboard = ({ usage }: IDashboardProps) => {
<ServersList data={serverList} />
</Box>
</Box>
{usage?.advancedMetricsStatus === "not configured" && (
<Box>
<HelpBox
iconComponent={<PrometheusErrorIcon />}
title={"We cant retrieve advanced metrics at this time."}
help={
<Box>
<Box
sx={{
fontSize: "14px",
}}
>
MinIO Dashboard will display basic metrics as we couldnt
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,
},
}}
>
<a
href="https://min.io/docs/minio/linux/operations/monitoring/collect-minio-metrics-using-prometheus.html"
target="_blank"
rel="noreferrer"
>
Read more about Prometheus on our Docs site.
</a>
</Box>
</Box>
}
/>
</Box>
)}
</Box>
</Box>
);

View File

@@ -70,7 +70,10 @@ const ServersList = ({ data }: { data: ServerInfo[] }) => {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
border: "1px solid #f1f1f1",
borderTop: index === 0 ? "1px solid #f1f1f1" : "",
borderBottom: "1px solid #f1f1f1",
borderLeft: "1px solid #f1f1f1",
borderRight: "1px solid #f1f1f1",
padding: "3px 10px 3px 10px",
"&:hover": {

View File

@@ -14,9 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useCallback, useEffect, useState } from "react";
import get from "lodash/get";
import React, { Fragment, useEffect, useState } from "react";
import PrDashboard from "./Prometheus/PrDashboard";
import PageHeader from "../Common/PageHeader/PageHeader";
import Grid from "@mui/material/Grid";
@@ -25,13 +23,9 @@ import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { LinearProgress } from "@mui/material";
import api from "../../../common/api";
import { Usage } from "./types";
import { ErrorResponseHandler } from "../../../common/types";
import BasicDashboard from "./BasicDashboard/BasicDashboard";
import { setErrorSnackMessage } from "../../../systemSlice";
import { useAppDispatch } from "../../../store";
import { AppState, useAppDispatch } from "../../../store";
import { getUsageAsync } from "./dashboardThunks";
import { useSelector } from "react-redux";
interface IDashboardSimple {
classes: any;
@@ -45,28 +39,15 @@ const styles = (theme: Theme) =>
const Dashboard = ({ classes }: IDashboardSimple) => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState<boolean>(true);
const [basicResult, setBasicResult] = useState<Usage | null>(null);
const fetchUsage = useCallback(() => {
api
.invoke("GET", `/api/v1/admin/info`)
.then((res: Usage) => {
setBasicResult(res);
setLoading(false);
})
.catch((err: ErrorResponseHandler) => {
dispatch(setErrorSnackMessage(err));
setLoading(false);
});
}, [setBasicResult, setLoading, dispatch]);
const usage = useSelector((state: AppState) => state.dashboard.usage);
useEffect(() => {
if (loading) {
fetchUsage();
setLoading(false);
dispatch(getUsageAsync());
}
}, [loading, fetchUsage]);
const widgets = get(basicResult, "widgets", null);
}, [loading, dispatch]);
return (
<Fragment>
@@ -78,13 +59,7 @@ const Dashboard = ({ classes }: IDashboardSimple) => {
</Grid>
</Grid>
) : (
<Fragment>
{widgets !== null ? (
<PrDashboard />
) : (
<BasicDashboard usage={basicResult} />
)}
</Fragment>
<PrDashboard usage={usage} />
)}
</Fragment>
);

View File

@@ -14,22 +14,20 @@
// 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, useCallback, useEffect, useState } from "react";
import React, { Fragment, useState } from "react";
import { useSelector } from "react-redux";
import Grid from "@mui/material/Grid";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Box } from "@mui/material";
import { Box, LinearProgress } from "@mui/material";
import {
actionsTray,
widgetContainerCommon,
} from "../../Common/FormComponents/common/styleLibrary";
import { IDashboardPanel } from "./types";
import { getWidgetsWithValue, panelsConfiguration } from "./utils";
import { panelsConfiguration } from "./utils";
import { TabPanel } from "../../../shared/tabs";
import { ErrorResponseHandler } from "../../../../common/types";
import api from "../../../../common/api";
import TabSelector from "../../Common/TabSelector/TabSelector";
import { componentToUse } from "./widgetUtils";
@@ -47,11 +45,20 @@ import {
} from "./Widgets/LayoutUtil";
import MergedWidgetsRenderer from "./Widgets/MergedWidgetsRenderer";
import PageLayout from "../../Common/Layout/PageLayout";
import { setErrorSnackMessage } from "../../../../systemSlice";
import { Usage } from "../types";
import BasicDashboard from "../BasicDashboard/BasicDashboard";
import SyncIcon from "../../../../icons/SyncIcon";
import { Button } from "mds";
import { ITabOption } from "../../Common/TabSelector/types";
import { getUsageAsync } from "../dashboardThunks";
import { reloadWidgets } from "../dashboardSlice";
import HelpBox from "../../../../common/HelpBox";
import { PrometheusErrorIcon } from "../../../../icons";
interface IPrDashboard {
classes?: any;
apiPrefix?: string;
usage: Usage | null;
}
const styles = (theme: Theme) =>
@@ -66,7 +73,7 @@ const styles = (theme: Theme) =>
},
});
const PrDashboard = ({ apiPrefix = "admin" }: IPrDashboard) => {
const PrDashboard = ({ apiPrefix = "admin", usage }: IPrDashboard) => {
const dispatch = useAppDispatch();
const zoomOpen = useSelector(
(state: AppState) => state.dashboard.zoom.openZoom
@@ -77,66 +84,17 @@ const PrDashboard = ({ apiPrefix = "admin" }: IPrDashboard) => {
const [timeStart, setTimeStart] = useState<any>(null);
const [timeEnd, setTimeEnd] = useState<any>(null);
const [loading, setLoading] = useState<boolean>(true);
const [panelInformation, setPanelInformation] =
useState<IDashboardPanel[]>(panelsConfiguration);
const panelInformation = panelsConfiguration;
const [curTab, setCurTab] = useState<number>(0);
const getPanelDetails = (id: number) => {
return panelInformation.find((panel) => panel.id === id);
};
const fetchUsage = useCallback(() => {
let stepCalc = 0;
if (timeStart !== null && timeEnd !== null) {
const secondsInPeriod = timeEnd.unix() - timeStart.unix();
const periods = Math.floor(secondsInPeriod / 60);
stepCalc = periods < 1 ? 15 : periods;
}
api
.invoke(
"GET",
`/api/v1/${apiPrefix}/info?step=${stepCalc}&${
timeStart !== null ? `&start=${timeStart.unix()}` : ""
}${timeStart !== null && timeEnd !== null ? "&" : ""}${
timeEnd !== null ? `end=${timeEnd.unix()}` : ""
}`
)
.then((res: any) => {
if (res.widgets) {
const widgetsWithValue = getWidgetsWithValue(res.widgets);
setPanelInformation(widgetsWithValue);
} else {
dispatch(
setErrorSnackMessage({
errorMessage:
"Widget information could not be retrieved at this time. Please try again",
detailedError: "",
})
);
}
setLoading(false);
})
.catch((err: ErrorResponseHandler) => {
dispatch(setErrorSnackMessage(err));
setLoading(false);
});
}, [timeStart, timeEnd, dispatch, apiPrefix]);
const triggerLoad = () => {
setLoading(true);
dispatch(reloadWidgets());
};
useEffect(() => {
if (loading) {
fetchUsage();
}
}, [loading, fetchUsage]);
const renderCmpByConfig = (
panelInfo: IDashboardPanel | undefined,
key: string
@@ -151,7 +109,7 @@ const PrDashboard = ({ apiPrefix = "admin" }: IPrDashboard) => {
info={panelInfo}
timeStart={timeStart}
timeEnd={timeEnd}
loading={loading}
loading={true}
apiPrefix={apiPrefix}
/>
) : (
@@ -159,7 +117,7 @@ const PrDashboard = ({ apiPrefix = "admin" }: IPrDashboard) => {
panelInfo,
timeStart,
timeEnd,
loading,
true,
apiPrefix,
zoomOpen
)
@@ -205,6 +163,23 @@ const PrDashboard = ({ apiPrefix = "admin" }: IPrDashboard) => {
return renderPanelItems(resourcesPanelsLayoutAdvanced);
};
let tabs: ITabOption[];
if (usage?.advancedMetricsStatus !== "not configured") {
tabs = [
{ label: "Usage" },
{ label: "Traffic" },
{ label: "Resources" },
{ label: "Info" },
];
} else {
tabs = [
{ label: "Info" },
{ label: "Usage", disabled: true },
{ label: "Traffic", disabled: true },
{ label: "Resources", disabled: true },
];
}
return (
<PageLayout>
{zoomOpen && (
@@ -224,11 +199,7 @@ const PrDashboard = ({ apiPrefix = "admin" }: IPrDashboard) => {
onChange={(newValue: number) => {
setCurTab(newValue);
}}
tabOptions={[
{ label: "Usage" },
{ label: "Traffic" },
{ label: "Resources" },
]}
tabOptions={tabs}
/>
</Grid>
<Grid
@@ -243,26 +214,113 @@ const PrDashboard = ({ apiPrefix = "admin" }: IPrDashboard) => {
marginBottom: "20px",
}}
>
<DateRangeSelector
timeStart={timeStart}
setTimeStart={setTimeStart}
timeEnd={timeEnd}
setTimeEnd={setTimeEnd}
triggerSync={triggerLoad}
/>
{curTab ===
(usage?.advancedMetricsStatus === "not configured" ? 0 : 3) ? (
<Grid container>
<Grid item>
<Box
sx={{
color: "#000",
fontSize: 18,
lineHeight: 2,
fontWeight: 700,
marginLeft: "21px",
display: "flex",
}}
>
Server Information
</Box>
</Grid>
<Grid item xs>
<Grid container direction="row-reverse">
<Grid item>
<Button
id={"sync"}
type="button"
variant="callAction"
onClick={() => {
dispatch(getUsageAsync());
}}
icon={<SyncIcon />}
label={"Sync"}
/>
</Grid>
</Grid>
</Grid>
</Grid>
) : (
<DateRangeSelector
timeStart={timeStart}
setTimeStart={setTimeStart}
timeEnd={timeEnd}
setTimeEnd={setTimeEnd}
triggerSync={triggerLoad}
/>
)}
</Box>
<TabPanel index={0} value={curTab}>
<TabPanel
index={usage?.advancedMetricsStatus === "not configured" ? 3 : 0}
value={curTab}
>
<RowPanelLayout>
{usage?.advancedMetricsStatus === "unavailable" && (
<HelpBox
iconComponent={<PrometheusErrorIcon />}
title={"We cant retrieve advanced metrics at this time."}
help={
<Box
sx={{
fontSize: "14px",
}}
>
It looks like Prometheus is not available or reachable at
the moment.
</Box>
}
/>
)}
{panelInformation.length ? renderSummaryPanels() : null}
</RowPanelLayout>
</TabPanel>
<TabPanel index={1} value={curTab}>
<RowPanelLayout>
{usage?.advancedMetricsStatus === "unavailable" && (
<HelpBox
iconComponent={<PrometheusErrorIcon />}
title={"We cant retrieve advanced metrics at this time."}
help={
<Box
sx={{
fontSize: "14px",
}}
>
It looks like Prometheus is not available or reachable at
the moment.
</Box>
}
/>
)}
{panelInformation.length ? renderTrafficPanels() : null}
</RowPanelLayout>
</TabPanel>
<TabPanel index={2} value={curTab}>
<RowPanelLayout>
{usage?.advancedMetricsStatus === "unavailable" && (
<HelpBox
iconComponent={<PrometheusErrorIcon />}
title={"We cant retrieve advanced metrics at this time."}
help={
<Box
sx={{
fontSize: "14px",
}}
>
It looks like Prometheus is not available or reachable at
the moment.
</Box>
}
/>
)}
{panelInformation.length ? renderResourcesPanels() : null}
<h2 style={{ margin: 0, borderBottom: "1px solid #dedede" }}>
Advanced
@@ -270,6 +328,13 @@ const PrDashboard = ({ apiPrefix = "admin" }: IPrDashboard) => {
{panelInformation.length ? renderAdvancedResourcesPanels() : null}
</RowPanelLayout>
</TabPanel>
<TabPanel
index={usage?.advancedMetricsStatus === "not configured" ? 0 : 3}
value={curTab}
>
{!usage && <LinearProgress />}
{usage && <BasicDashboard usage={usage} />}
</TabPanel>
</Grid>
</PageLayout>
);

View File

@@ -40,8 +40,9 @@ import { useTheme } from "@mui/styles";
import Loader from "../../../Common/Loader/Loader";
import ExpandGraphLink from "./ExpandGraphLink";
import { setErrorSnackMessage } from "../../../../../systemSlice";
import { useAppDispatch } from "../../../../../store";
import { AppState, useAppDispatch } from "../../../../../store";
import DownloadWidgetDataButton from "../../DownloadWidgetDataButton";
import { useSelector } from "react-redux";
interface IBarChartWidget {
classes: any;
@@ -92,12 +93,14 @@ const BarChartWidget = ({
zoomActivated = false,
}: IBarChartWidget) => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
const [data, setData] = useState<any>([]);
const [result, setResult] = useState<IDashboardPanel | null>(null);
const [hover, setHover] = useState<boolean>(false);
const componentRef = useRef<HTMLElement>();
const widgetVersion = useSelector(
(state: AppState) => state.dashboard.widgetLoadVersion
);
const onHover = () => {
setHover(true);
};
@@ -106,10 +109,8 @@ const BarChartWidget = ({
};
useEffect(() => {
if (propLoading) {
setLoading(true);
}
}, [propLoading]);
setLoading(true);
}, [widgetVersion]);
useEffect(() => {
if (loading) {

View File

@@ -30,7 +30,8 @@ import { Cell, Pie, PieChart } from "recharts";
import { ReportedUsageIcon } from "../../../../../icons";
import Loader from "../../../Common/Loader/Loader";
import { setErrorSnackMessage } from "../../../../../systemSlice";
import { useAppDispatch } from "../../../../../store";
import { AppState, useAppDispatch } from "../../../../../store";
import { useSelector } from "react-redux";
const CapacityItem = ({
value,
@@ -46,18 +47,19 @@ const CapacityItem = ({
apiPrefix: string;
}) => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
const [totalUsableFree, setTotalUsableFree] = useState<number>(0);
const [totalUsableFreeRatio, setTotalUsableFreeRatio] = useState<number>(0);
const [totalUsed, setTotalUsed] = useState<number>(0);
const [totalUsable, setTotalUsable] = useState<number>(0);
const widgetVersion = useSelector(
(state: AppState) => state.dashboard.widgetLoadVersion
);
useEffect(() => {
if (propLoading) {
setLoading(true);
}
}, [propLoading]);
setLoading(true);
}, [widgetVersion]);
useEffect(() => {
if (loading) {

View File

@@ -23,7 +23,8 @@ import { IDashboardPanel } from "../types";
import Loader from "../../../Common/Loader/Loader";
import { setErrorSnackMessage } from "../../../../../systemSlice";
import { useAppDispatch } from "../../../../../store";
import { AppState, useAppDispatch } from "../../../../../store";
import { useSelector } from "react-redux";
const EntityStateStatItem = ({
panelItem,
@@ -41,14 +42,15 @@ const EntityStateStatItem = ({
statLabel: any;
}) => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
const [data, setData] = useState<string>("");
const widgetVersion = useSelector(
(state: AppState) => state.dashboard.widgetLoadVersion
);
useEffect(() => {
if (propLoading) {
setLoading(true);
}
}, [propLoading]);
setLoading(true);
}, [widgetVersion]);
useEffect(() => {
if (loading) {

View File

@@ -39,8 +39,9 @@ import { useTheme } from "@mui/styles";
import Loader from "../../../Common/Loader/Loader";
import ExpandGraphLink from "./ExpandGraphLink";
import { setErrorSnackMessage } from "../../../../../systemSlice";
import { useAppDispatch } from "../../../../../store";
import { AppState, useAppDispatch } from "../../../../../store";
import DownloadWidgetDataButton from "../../DownloadWidgetDataButton";
import { useSelector } from "react-redux";
interface ILinearGraphWidget {
classes: any;
@@ -106,20 +107,21 @@ const LinearGraphWidget = ({
zoomActivated = false,
}: ILinearGraphWidget) => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
const [hover, setHover] = useState<boolean>(false);
const [data, setData] = useState<object[]>([]);
const [csvData, setCsvData] = useState<object[]>([]);
const [dataMax, setDataMax] = useState<number>(0);
const [result, setResult] = useState<IDashboardPanel | null>(null);
const widgetVersion = useSelector(
(state: AppState) => state.dashboard.widgetLoadVersion
);
const componentRef = useRef<HTMLElement>();
useEffect(() => {
if (propLoading) {
setLoading(true);
}
}, [propLoading]);
setLoading(true);
}, [widgetVersion]);
useEffect(() => {
if (loading) {

View File

@@ -30,7 +30,8 @@ import get from "lodash/get";
import api from "../../../../../common/api";
import Loader from "../../../Common/Loader/Loader";
import { setErrorSnackMessage } from "../../../../../systemSlice";
import { useAppDispatch } from "../../../../../store";
import { AppState, useAppDispatch } from "../../../../../store";
import { useSelector } from "react-redux";
interface IPieChartWidget {
classes: any;
@@ -79,16 +80,17 @@ const PieChartWidget = ({
apiPrefix,
}: IPieChartWidget) => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
const [dataInner, setDataInner] = useState<object[]>([]);
const [dataOuter, setDataOuter] = useState<object[]>([]);
const [result, setResult] = useState<IDashboardPanel | null>(null);
const widgetVersion = useSelector(
(state: AppState) => state.dashboard.widgetLoadVersion
);
useEffect(() => {
if (propLoading) {
setLoading(true);
}
}, [propLoading]);
setLoading(true);
}, [widgetVersion]);
useEffect(() => {
if (loading) {

View File

@@ -15,7 +15,7 @@
// 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 { connect, useSelector } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
@@ -26,7 +26,7 @@ import { IDashboardPanel } from "../types";
import { ErrorResponseHandler } from "../../../../../common/types";
import Loader from "../../../Common/Loader/Loader";
import { setErrorSnackMessage } from "../../../../../systemSlice";
import { useAppDispatch } from "../../../../../store";
import { AppState, useAppDispatch } from "../../../../../store";
interface ISimpleWidget {
classes: any;
@@ -76,14 +76,15 @@ const SimpleWidget = ({
renderFn,
}: ISimpleWidget) => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
const [data, setData] = useState<string>("");
const widgetVersion = useSelector(
(state: AppState) => state.dashboard.widgetLoadVersion
);
useEffect(() => {
if (propLoading) {
setLoading(true);
}
}, [propLoading]);
setLoading(true);
}, [widgetVersion]);
useEffect(() => {
if (loading) {

View File

@@ -15,7 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { connect, useSelector } from "react-redux";
import { IDashboardPanel } from "../types";
import { widgetDetailsToPanel } from "../utils";
import { ErrorResponseHandler } from "../../../../../common/types";
@@ -25,7 +25,7 @@ import DashboardItemBox from "../../DashboardItemBox";
import BucketsCountItem from "./BucketsCountItem";
import ObjectsCountItem from "./ObjectsCountItem";
import { setErrorSnackMessage } from "../../../../../systemSlice";
import { useAppDispatch } from "../../../../../store";
import { AppState, useAppDispatch } from "../../../../../store";
interface ISingleRepWidget {
title: string;
@@ -49,14 +49,15 @@ const SingleRepWidget = ({
apiPrefix,
}: ISingleRepWidget) => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
const [result, setResult] = useState<IDashboardPanel | null>(null);
const widgetVersion = useSelector(
(state: AppState) => state.dashboard.widgetLoadVersion
);
useEffect(() => {
if (propLoading) {
setLoading(true);
}
}, [propLoading]);
setLoading(true);
}, [widgetVersion]);
useEffect(() => {
if (loading) {

View File

@@ -15,7 +15,7 @@
// 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 { connect, useSelector } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
@@ -26,7 +26,7 @@ import { splitSizeMetric, widgetDetailsToPanel } from "../utils";
import { IDashboardPanel } from "../types";
import { ErrorResponseHandler } from "../../../../../common/types";
import { setErrorSnackMessage } from "../../../../../systemSlice";
import { useAppDispatch } from "../../../../../store";
import { AppState, useAppDispatch } from "../../../../../store";
interface ISingleValueWidget {
title: string;
@@ -82,14 +82,15 @@ const SingleValueWidget = ({
renderFn,
}: ISingleValueWidget) => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
const [data, setData] = useState<string>("");
const widgetVersion = useSelector(
(state: AppState) => state.dashboard.widgetLoadVersion
);
useEffect(() => {
if (propLoading) {
setLoading(true);
}
}, [propLoading]);
setLoading(true);
}, [widgetVersion]);
useEffect(() => {
if (loading) {

View File

@@ -15,11 +15,15 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { zoomState } from "./types";
import { Usage, zoomState } from "./types";
import { IDashboardPanel } from "./Prometheus/types";
import { getUsageAsync } from "./dashboardThunks";
export interface DashboardState {
zoom: zoomState;
usage: Usage | null;
loadingUsage: boolean;
widgetLoadVersion: number;
}
const initialState: DashboardState = {
@@ -27,6 +31,9 @@ const initialState: DashboardState = {
openZoom: false,
widgetRender: null,
},
usage: null,
loadingUsage: true,
widgetLoadVersion: 0,
};
export const dashboardSlice = createSlice({
name: "dashboard",
@@ -40,8 +47,25 @@ export const dashboardSlice = createSlice({
state.zoom.openZoom = false;
state.zoom.widgetRender = null;
},
reloadWidgets: (state) => {
state.widgetLoadVersion++;
},
},
extraReducers: (builder) => {
builder
.addCase(getUsageAsync.pending, (state) => {
state.loadingUsage = true;
})
.addCase(getUsageAsync.rejected, (state) => {
state.loadingUsage = false;
})
.addCase(getUsageAsync.fulfilled, (state, action) => {
state.loadingUsage = false;
state.usage = action.payload;
});
},
});
export const { openZoomPage, closeZoomPage } = dashboardSlice.actions;
export const { openZoomPage, closeZoomPage, reloadWidgets } =
dashboardSlice.actions;
export default dashboardSlice.reducer;

View File

@@ -0,0 +1,36 @@
// 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 { createAsyncThunk } from "@reduxjs/toolkit";
import api from "../../../common/api";
import { ErrorResponseHandler } from "../../../common/types";
import { setErrorSnackMessage } from "../../../systemSlice";
import { Usage } from "./types";
export const getUsageAsync = createAsyncThunk(
"dashboard/getUsageAsync",
async (_, { getState, rejectWithValue, dispatch }) => {
return api
.invoke("GET", `/api/v1/admin/info`)
.then((res: Usage) => {
return res;
})
.catch((err: ErrorResponseHandler) => {
dispatch(setErrorSnackMessage(err));
return rejectWithValue(err);
});
}
);

View File

@@ -20,7 +20,7 @@ export interface Usage {
usage: number;
buckets: number;
objects: number;
prometheusNotReady?: boolean;
advancedMetricsStatus: "available" | "unavailable" | "not configured";
widgets?: any;
servers: ServerInfo[];
//TODO