Add Server Metrics Info Tab (#2340)
This commit is contained in:
@@ -264,7 +264,6 @@ const DateRangeSelector = ({
|
||||
alignItems: "flex-end",
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
marginRight: "35px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -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 can’t retrieve advanced metrics at this time."}
|
||||
help={
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
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,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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 can’t retrieve advanced metrics at this time."}
|
||||
help={
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
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,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 can’t 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 can’t 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 can’t 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>
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
36
portal-ui/src/screens/Console/Dashboard/dashboardThunks.ts
Normal file
36
portal-ui/src/screens/Console/Dashboard/dashboardThunks.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user