diff --git a/portal-ui/package.json b/portal-ui/package.json index add539e56..45e78b71d 100644 --- a/portal-ui/package.json +++ b/portal-ui/package.json @@ -22,7 +22,6 @@ "@types/react-router": "^5.1.3", "@types/react-router-dom": "^5.1.2", "@types/react-virtualized": "^9.21.10", - "@types/recharts": "^1.8.19", "@types/superagent": "^4.1.12", "@types/webpack-env": "^1.14.1", "@types/websocket": "^1.0.0", @@ -49,7 +48,7 @@ "react-scripts": "4.0.3", "react-virtualized": "^9.22.2", "react-window-infinite-loader": "^1.0.5", - "recharts": "^2.0.3", + "recharts": "^2.1.1", "redux": "^4.0.5", "redux-thunk": "^2.3.0", "superagent": "^6.1.0", diff --git a/portal-ui/src/screens/Console/Common/FormComponents/DateTimePickerWrapper/DateTimePickerWrapper.tsx b/portal-ui/src/screens/Console/Common/FormComponents/DateTimePickerWrapper/DateTimePickerWrapper.tsx index 08d6f5bad..2a87ecf98 100644 --- a/portal-ui/src/screens/Console/Common/FormComponents/DateTimePickerWrapper/DateTimePickerWrapper.tsx +++ b/portal-ui/src/screens/Console/Common/FormComponents/DateTimePickerWrapper/DateTimePickerWrapper.tsx @@ -34,6 +34,7 @@ interface IDateTimePicker { tooltip?: string; id: string; disabled?: boolean; + noInputIcon?: boolean; } const styles = (theme: Theme) => @@ -93,18 +94,27 @@ const DateTimePickerWrapper = ({ required, id, disabled = false, + noInputIcon = false, }: IDateTimePicker) => { + let adornment = {}; + + if (!noInputIcon) { + adornment = { + startAdornment: ( + + + + ), + }; + } + const inputItem = ( - - - ), + ...adornment, className: forSearchBlock ? classes.dateSelectorOverride : "", }} label="" diff --git a/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts b/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts index e3ead4881..87842f3ac 100644 --- a/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts +++ b/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts @@ -148,9 +148,17 @@ export const containerForHeader = (bottomSpacing: any) => ({ }); export const actionsTray = { + filterTitle: { + color: "#848484", + fontSize: 13, + alignSelf: "center" as const, + whiteSpace: "nowrap" as const, + "&:not(:first-of-type)": { + marginLeft: 10, + }, + }, label: { - color: "#393939", - fontWeight: 600, + color: "#07193E", fontSize: 13, alignSelf: "center" as const, whiteSpace: "nowrap" as const, @@ -160,6 +168,7 @@ export const actionsTray = { }, timeContainers: { height: 40, + maxWidth: 1185, }, actionsTray: { display: "flex" as const, @@ -399,29 +408,29 @@ export const logsCommon = { export const widgetCommon = { singleValueContainer: { - position: "relative" as const, - flexGrow: 1, + height: 200, + minWidth: 280, + maxWidth: 1185, + border: "#eef1f4 2px solid", + borderRadius: 10, width: "100%", - height: "100%", - border: "#EAEAEA 1px solid", - borderRadius: 5, - backgroundColor: "#fff", + padding: 16, }, titleContainer: { - color: "#393939", - fontWeight: 600, - height: 15, - textAlign: "center" as const, - fontSize: 10, + color: "#404143", + fontSize: 14, + textTransform: "uppercase" as const, + fontWeight: 800, + borderBottom: "#eef1f4 1px solid", + paddingBottom: 14, + marginBottom: 5, }, contentContainer: { - flexGrow: 2, justifyContent: "center" as const, alignItems: "center" as const, display: "flex" as const, - position: "absolute" as const, width: "100%", - height: "calc(100% - 15px)", + height: 140, }, contentContainerWithLabel: { height: "calc(100% - 25px)", @@ -445,7 +454,6 @@ export const widgetCommon = { width: 8, height: 8, minWidth: 8, - borderRadius: "100%", marginRight: 5, }, legendLabel: { diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index 7d62d678b..785c86c72 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -18,7 +18,6 @@ import React, { Fragment, useEffect, useState } from "react"; import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; import { Button, LinearProgress } from "@material-ui/core"; import CssBaseline from "@material-ui/core/CssBaseline"; -import Container from "@material-ui/core/Container"; import Snackbar from "@material-ui/core/Snackbar"; import history from "../../history"; import { Redirect, Route, Router, Switch, useLocation } from "react-router-dom"; diff --git a/portal-ui/src/screens/Console/Dashboard/BasicDashboard/BasicDashboard.tsx b/portal-ui/src/screens/Console/Dashboard/BasicDashboard/BasicDashboard.tsx index a11ac7ec5..bec048b37 100644 --- a/portal-ui/src/screens/Console/Dashboard/BasicDashboard/BasicDashboard.tsx +++ b/portal-ui/src/screens/Console/Dashboard/BasicDashboard/BasicDashboard.tsx @@ -20,10 +20,8 @@ import Grid from "@material-ui/core/Grid"; 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, TotalObjectsIcon } from "../../../../icons"; import CommonCard from "../CommonCard"; import TabSelector from "../../Common/TabSelector/TabSelector"; import GeneralUsePaginator from "../../Common/GeneralUsePaginator/GeneralUsePaginator"; @@ -133,23 +131,19 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => { } title={"All Buckets"} metricValue={usage ? prettyNumber(usage.buckets) : 0} /> } title={"Usage"} metricValue={usageToRepresent.total} metricUnit={usageToRepresent.unit} /> } title={"Total Objects"} metricValue={usage ? prettyNumber(usage.objects) : 0} /> } title={"Servers"} metricValue={usage ? prettyNumber(serverArray.length) : 0} subMessage={{ message: "Total" }} diff --git a/portal-ui/src/screens/Console/Dashboard/CommonCard.tsx b/portal-ui/src/screens/Console/Dashboard/CommonCard.tsx index 4b3c2c18a..ff90556ca 100644 --- a/portal-ui/src/screens/Console/Dashboard/CommonCard.tsx +++ b/portal-ui/src/screens/Console/Dashboard/CommonCard.tsx @@ -23,6 +23,7 @@ import { makeStyles, } from "@material-ui/core/styles"; import React, { Fragment } from "react"; +import { widgetCommon } from "../Common/FormComponents/common/styleLibrary"; export interface ISubInterface { message: string; @@ -30,7 +31,6 @@ export interface ISubInterface { } interface ICommonCard { - avatar: any; title: string; metricValue: any; metricUnit?: string; @@ -42,12 +42,10 @@ interface ICommonCard { const styles = (theme: Theme) => createStyles({ + ...widgetCommon, cardRoot: { - border: "#eef1f4 2px solid", - borderRadius: 10, + ...widgetCommon.singleValueContainer, maxWidth: 280, - width: "100%", - margin: 10, }, cardsContainer: { maxHeight: 440, @@ -94,15 +92,9 @@ const styles = (theme: Theme) => }); const cardSubStyles = makeStyles({ - root: { backgroundColor: "#fff" }, + root: { backgroundColor: "#fff", padding: 0 }, title: { - color: "#404144", - fontSize: 14, - textTransform: "uppercase", - fontWeight: "bold", - borderBottom: "#eef1f4 1px solid", - paddingBottom: 14, - marginBottom: 5, + ...widgetCommon.titleContainer }, content: { maxWidth: "100%", @@ -110,7 +102,6 @@ const cardSubStyles = makeStyles({ }); const CommonCard = ({ - avatar, title, metricValue, metricUnit, diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/MergedWidgets.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/MergedWidgets.tsx new file mode 100644 index 000000000..75b280fac --- /dev/null +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/MergedWidgets.tsx @@ -0,0 +1,42 @@ +// 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 . + +import React, { Fragment } from "react"; +import CommonCard from "../CommonCard"; + +interface IMergedWidgets { + title: string; + leftComponent: any; + rightComponent: any; +} + +const MergedWidgets = ({ + title, + leftComponent, + rightComponent, +}: IMergedWidgets) => { + return ( + + + + ); +}; + +export default MergedWidgets; diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx index 700fb3558..49372e076 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx @@ -14,21 +14,16 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { useCallback, useEffect, useState } from "react"; +import React, { Fragment, useCallback, useEffect, useState } from "react"; import { connect } from "react-redux"; -import ReactGridLayout from "react-grid-layout"; import Grid from "@material-ui/core/Grid"; +import ScheduleIcon from "@material-ui/icons/Schedule"; +import WatchLaterIcon from "@material-ui/icons/WatchLater"; import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; import { Button } from "@material-ui/core"; import { actionsTray } from "../../Common/FormComponents/common/styleLibrary"; -import { AutoSizer } from "react-virtualized"; import { IDashboardPanel, widgetType } from "./types"; -import { - getDashboardDistribution, - getWidgetsWithValue, - panelsConfiguration, - saveDashboardDistribution, -} from "./utils"; +import { getWidgetsWithValue, panelsConfiguration } from "./utils"; import { TabPanel } from "../../../shared/tabs"; import { ErrorResponseHandler } from "../../../../common/types"; import { setErrorSnackMessage } from "../../../../actions"; @@ -40,8 +35,9 @@ import SingleRepWidget from "./Widgets/SingleRepWidget"; import DateTimePickerWrapper from "../../Common/FormComponents/DateTimePickerWrapper/DateTimePickerWrapper"; import api from "../../../../common/api"; import SyncIcon from "../../../../icons/SyncIcon"; -import Tabs from "@material-ui/core/Tabs"; -import Tab from "@material-ui/core/Tab"; +import TabSelector from "../../Common/TabSelector/TabSelector"; +import SimpleWidget from "./Widgets/SimpleWidget"; +import MergedWidgets from "./MergedWidgets"; interface IPrDashboard { classes: any; @@ -53,8 +49,11 @@ const styles = (theme: Theme) => createStyles({ ...actionsTray, widgetsContainer: { - height: "calc(100vh - 250px)", - paddingBottom: 235, + position: "relative", + display: "flex", + flexGrow: 1, + width: "100%", + height: "100%", }, syncButton: { "&.MuiButton-root .MuiButton-iconSizeMedium > *:first-child": { @@ -65,6 +64,24 @@ const styles = (theme: Theme) => ...actionsTray.actionsTray, padding: "0 10px", }, + dashboardRow: { + display: "flex", + flexDirection: "row", + justifyContent: "flex-start", + flexWrap: "wrap", + maxWidth: 1180, + }, + widgetPanelDelimiter: { + margin: 10, + }, + schedulerIcon: { + opacity: 0.4, + fontSize: 10, + "& svg": { + width: 18, + height: 18, + }, + }, }); const PrDashboard = ({ @@ -79,23 +96,8 @@ const PrDashboard = ({ useState(panelsConfiguration); const [curTab, setCurTab] = useState(0); - const minHeight = 600; - const colsInGrid = 8; - const xSpacing = 10; - const ySpacing = 10; - - const dashboardDistr = getDashboardDistribution(panelInformation.length); - - const autoSizerStyleProp = { - width: "100%", - height: "auto", - paddingBottom: 45, - }; - const panels = useCallback( - (width: number, filterPanels?: number[] | null) => { - const singlePanelWidth = width / colsInGrid + xSpacing / 2; - + (tabName: string, filterPanels?: number[][] | null) => { const componentToUse = (value: IDashboardPanel, index: number) => { switch (value.type) { case widgetType.singleValue: @@ -109,6 +111,18 @@ const PrDashboard = ({ apiPrefix={apiPrefix} /> ); + case widgetType.simpleWidget: + return ( + + ); case widgetType.pieChart: return ( ); case widgetType.linearGraph: + case widgetType.areaGraph: return ( ); case widgetType.barChart: @@ -169,21 +180,66 @@ const PrDashboard = ({ } }; - return panelInformation - .filter((val) => { - if (filterPanels) { - return filterPanels.indexOf(val.id) > -1; - } else { - return true; - } - }) - .map((val, index) => { - return ( -
{componentToUse(val, index)}
- ); - }); + return filterPanels?.map((panelLine, indexLine) => { + const totalPanelsContained = panelLine.length; + + const perc = 100 / totalPanelsContained; + + return ( + + {panelLine.map((panelInline, indexPanel) => { + const panelInfo = panelInformation.find( + (panel) => panel.id === panelInline + ); + + return ( +
+ {panelInfo ? ( + + {panelInfo.mergedPanels ? ( + + + + ) : ( + componentToUse(panelInfo, indexPanel) + )} + + ) : null} +
+ ); + })} +
+ ); + }); }, - [panelInformation, dashboardDistr, timeEnd, timeStart, loading, apiPrefix] + [ + timeStart, + timeEnd, + loading, + apiPrefix, + classes.dashboardRow, + classes.widgetPanelDelimiter, + panelInformation, + ] ); const fetchUsage = useCallback(() => { @@ -235,145 +291,91 @@ const PrDashboard = ({ } }, [loading, fetchUsage]); - const a11yProps = (index: any) => { - return { - id: `simple-tab-${index}`, - "aria-controls": `simple-tabpanel-${index}`, - }; - }; - const summaryPanels = [ - 1, 64, 65, 68, 52, 44, 61, 80, 81, 66, 62, 53, 63, 50, 69, 70, 9, 78, + [80, 81, 1], + [68, 52], + [63, 70], + [66, 50, 44, 500], + [501, 502, 61, 62], ]; - const resourcesPanels = [76, 77, 11, 8, 82, 74]; - const requestsPanels = [60, 71, 17, 73]; + const resourcesPanels = [ + [76, 77], + [11, 8], + [82, 74], + ]; + const requestsPanels = [[60], [71, 17], [73]]; return ( - + - Start Time + Filter: + + + + Start Time: - End Time + + + + End Time: - , newValue: number) => { + { setCurTab(newValue); }} - > - - - - + tabOptions={[ + { label: "Summary" }, + { label: "Traffic" }, + { label: "Resources" }, + ]} + /> - - {({ width, height }: any) => { - let hpanel = height < minHeight ? minHeight : height; - if (hpanel > 380) { - hpanel = 480; - } - const totalWidth = width > 1920 ? 1920 : width; - return ( - - {panels(width, summaryPanels)} - - ); - }} - + {panels("Summary", summaryPanels)} - - {({ width, height }: any) => { - let hpanel = height < minHeight ? minHeight : height; - if (hpanel > 380) { - hpanel = 480; - } - const totalWidth = width > 1920 ? 1920 : width; - return ( - - {panels(width, requestsPanels)} - - ); - }} - + {panels("Traffic", requestsPanels)} - - {({ width, height }: any) => { - let hpanel = height < minHeight ? minHeight : height; - if (hpanel > 380) { - hpanel = 480; - } - const totalWidth = width > 1920 ? 1920 : width; - return ( - - {panels(width, resourcesPanels)} - - ); - }} - + {panels("Resources", resourcesPanels)} - + ); }; +/* +< +*/ const connector = connect(null, { displayErrorMessage: setErrorSnackMessage, diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/BarChartWidget.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/BarChartWidget.tsx index f0b195f4c..c91b931ad 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/BarChartWidget.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/BarChartWidget.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, Fragment } from "react"; import { connect } from "react-redux"; import { Bar, @@ -23,6 +23,7 @@ import { Tooltip, XAxis, YAxis, + Cell, } from "recharts"; import { MaterialUiPickersDate } from "@material-ui/pickers/typings/date"; import { CircularProgress } from "@material-ui/core"; @@ -58,14 +59,15 @@ const styles = (theme: Theme) => }, }); -const CustomizedAxisTick = ({ x, y, payload }: any) => { +const CustomizedAxisTick = ({ y, payload }: any) => { return ( {payload.value} @@ -131,6 +133,19 @@ const BarChartWidget = ({ ? (result.widgetConfiguration as IBarChartConfiguration[]) : []; + let greatestIndex = 0; + let currentValue = 0; + + if (barChartConfiguration.length === 1) { + const dataGraph = barChartConfiguration[0]; + data.forEach((item: any, index: number) => { + if (item[dataGraph.dataKey] > currentValue) { + currentValue = item[dataGraph.dataKey]; + greatestIndex = index; + } + }); + } + return (
{title}
@@ -163,7 +178,23 @@ const BarChartWidget = ({ dataKey={bar.dataKey} fill={bar.color} background={bar.background} - /> + barSize={12} + > + {barChartConfiguration.length === 1 ? ( + + {data.map((_: any, index: number) => ( + + ))} + + ) : null} + ))} string; xAxisFormatter?: (item: string) => string; - panelWidth?: number; + areaWidget?: boolean; } const styles = (theme: Theme) => @@ -57,24 +57,29 @@ const styles = (theme: Theme) => ...widgetCommon, containerElements: { display: "flex", - flexDirection: "column", - height: "calc(100% - 18px)", + flexDirection: "row", + height: "100%", + flexGrow: 1, }, chartCont: { position: "relative", - flexGrow: 1, - minHeight: "65%", - height: 1, + height: 140, + width: "100%", }, legendChart: { display: "flex", - flexWrap: "wrap", + flexDirection: "column", flex: "0 1 auto", - maxHeight: "35%", + height: 130, margin: 0, overflowY: "auto", position: "relative", textAlign: "center", + width: "100%", + justifyContent: "flex-start", + color: "#404143", + fontWeight: "bold", + fontSize: 12, }, loadingAlign: { margin: "auto", @@ -91,9 +96,9 @@ const LinearGraphWidget = ({ panelItem, apiPrefix, hideYAxis = false, + areaWidget = false, yAxisFormatter = (item: string) => item, xAxisFormatter = (item: string) => item, - panelWidth = 0, }: ILinearGraphWidget) => { const [loading, setLoading] = useState(true); const [data, setData] = useState([]); @@ -153,26 +158,21 @@ const LinearGraphWidget = ({ } }, [loading, panelItem, timeEnd, timeStart, displayErrorMessage, apiPrefix]); - let intervalCount = 5; - - if (panelWidth !== 0) { - if (panelWidth > 400) { - intervalCount = 5; - } else if (panelWidth > 350) { - intervalCount = 10; - } else if (panelWidth > 300) { - intervalCount = 15; - } else if (panelWidth > 250) { - intervalCount = 20; - } else { - intervalCount = 30; - } - } + let intervalCount = Math.floor(data.length / 5); const linearConfiguration = result ? (result?.widgetConfiguration as ILinearGraphConfiguration[]) : []; + const CustomizedDot = (prop: any) => { + const { cx, cy, index } = prop; + + if (index % 3 !== 0) { + return null; + } + return ; + }; + return (
{title}
@@ -191,24 +191,52 @@ const LinearGraphWidget = ({ bottom: 0, }} > + {areaWidget && ( + + + + + + + )} xAxisFormatter(value)} interval={intervalCount} - tick={{ fontSize: "70%" }} + tick={{ + fontSize: "70%", + fontWeight: "bold", + color: "#404143", + }} tickCount={10} + stroke={"#082045"} /> yAxisFormatter(value)} - tick={{ fontSize: "70%" }} + tick={{ + fontSize: "70%", + fontWeight: "bold", + color: "#404143", + }} + stroke={"#082045"} /> {linearConfiguration.map((section, index) => { return ( @@ -217,8 +245,10 @@ const LinearGraphWidget = ({ type="monotone" dataKey={section.dataKey} stroke={section.lineColor} - fill={section.fillColor} - fillOpacity={0.3} + fill={areaWidget ? "url(#colorUv)" : section.fillColor} + fillOpacity={areaWidget ? 0.3 : 0} + strokeWidth={areaWidget ? 0 : 2} + dot={areaWidget ? : false} /> ); })} @@ -236,24 +266,26 @@ const LinearGraphWidget = ({
-
- {linearConfiguration.map((section, index) => { - return ( -
+ {!areaWidget && ( +
+ {linearConfiguration.map((section, index) => { + return (
-
- {section.keyLabel} + className={classes.singleLegendContainer} + key={`legend-${section.keyLabel}-${index.toString()}`} + > +
+
+ {section.keyLabel} +
-
- ); - })} -
+ ); + })} +
+ )} )}
diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/PieChartWidget.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/PieChartWidget.tsx index 11ec11294..0c5b3966b 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/PieChartWidget.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/PieChartWidget.tsx @@ -24,7 +24,7 @@ import { IPieChartConfiguration } from "./types"; import { widgetCommon } from "../../../Common/FormComponents/common/styleLibrary"; import { setErrorSnackMessage } from "../../../../../actions"; import { IDashboardPanel } from "../types"; -import { widgetDetailsToPanel } from "../utils"; +import { splitSizeMetric, widgetDetailsToPanel } from "../utils"; import { ErrorResponseHandler } from "../../../../../common/types"; import get from "lodash/get"; import api from "../../../../../common/api"; @@ -49,6 +49,20 @@ const styles = (theme: Theme) => textAlign: "center", margin: "auto", }, + pieChartLabel: { + fontSize: 60, + color: "#07193E", + fontWeight: "bold", + width: "100%", + "& .unitText": { + color: "#767676", + fontSize: 12, + }, + }, + chartContainer: { + width: "100%", + height: 140, + }, }); const PieChartWidget = ({ @@ -125,104 +139,97 @@ const PieChartWidget = ({ )} {!loading && (
- - - {dataOuter && ( - - {dataOuter.map((entry, index) => ( - - ))} - - )} - {dataInner && ( - - {dataInner.map((entry, index) => { - return ( + + {middleLabel && splitSizeMetric(middleLabel)} + +
+ + + {dataOuter && ( + + {dataOuter.map((entry, index) => ( - ); - })} - - )} - {middleLabel && ( - - {middleLabel} - - )} - - + ))} + + )} + {dataInner && ( + + {dataInner.map((entry, index) => { + return ( + + ); + })} + + )} + + +
)}
diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SimpleWidget.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SimpleWidget.tsx index 2bf60af2b..25bc10dbd 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SimpleWidget.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SimpleWidget.tsx @@ -14,14 +14,27 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React from "react"; +import React, { Fragment, useEffect, useState } from "react"; +import { connect } from "react-redux"; import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import { MaterialUiPickersDate } from "@material-ui/pickers/typings/date"; +import { CircularProgress } from "@material-ui/core"; +import api from "../../../../../common/api"; +import { widgetDetailsToPanel } from "../utils"; +import { IDashboardPanel } from "../types"; +import { setErrorSnackMessage } from "../../../../../actions"; +import { ErrorResponseHandler } from "../../../../../common/types"; interface ISimpleWidget { classes: any; iconWidget: any; - label: string; - value: string; + title: string; + panelItem: IDashboardPanel; + timeStart: MaterialUiPickersDate; + timeEnd: MaterialUiPickersDate; + propLoading: boolean; + displayErrorMessage: any; + apiPrefix: string; } const styles = (theme: Theme) => @@ -47,14 +60,80 @@ const styles = (theme: Theme) => }, }); -const SimpleWidget = ({ classes, iconWidget, label, value }: ISimpleWidget) => { +const SimpleWidget = ({ + classes, + iconWidget, + title, + panelItem, + timeStart, + timeEnd, + propLoading, + displayErrorMessage, + apiPrefix, +}: ISimpleWidget) => { + const [loading, setLoading] = useState(true); + const [data, setData] = useState(""); + + useEffect(() => { + if (propLoading) { + setLoading(true); + } + }, [propLoading]); + + useEffect(() => { + if (loading) { + 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/widgets/${ + panelItem.id + }/?step=${stepCalc}&${ + timeStart !== null ? `&start=${timeStart.unix()}` : "" + }${timeStart !== null && timeEnd !== null ? "&" : ""}${ + timeEnd !== null ? `end=${timeEnd.unix()}` : "" + }` + ) + .then((res: any) => { + const widgetsWithValue = widgetDetailsToPanel(res, panelItem); + setData(widgetsWithValue.data); + setLoading(false); + }) + .catch((err: ErrorResponseHandler) => { + displayErrorMessage(err); + setLoading(false); + }); + } + }, [loading, panelItem, timeEnd, timeStart, displayErrorMessage, apiPrefix]); + return ( - - {iconWidget ? iconWidget : null} - {label}: - {value} - + + {loading && ( +
+ +
+ )} + {!loading && ( + + {iconWidget ? iconWidget : null} + {title}: + {data} + + )} +
); }; -export default withStyles(styles)(SimpleWidget); +const connector = connect(null, { + displayErrorMessage: setErrorSnackMessage, +}); + + +export default withStyles(styles)(connector(SimpleWidget)); diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleRepWidget.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleRepWidget.tsx index 32da6938c..199653cc2 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleRepWidget.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleRepWidget.tsx @@ -107,6 +107,8 @@ const SingleRepWidget = ({ }); } }, [loading, panelItem, timeEnd, timeStart, displayErrorMessage, apiPrefix]); + const gradientID= `colorGradient-${title.split(" ").join('-')}`; + return (
{title}
@@ -119,6 +121,12 @@ const SingleRepWidget = ({
+ + + + + + dataMax * 2]} hide={true} @@ -127,17 +135,17 @@ const SingleRepWidget = ({ type="monotone" dataKey={"value"} stroke={color} - fill={fillColor} + fill={`url(#${gradientID})`} fillOpacity={1} /> {result ? result.innerLabel : ""} diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleValueWidget.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleValueWidget.tsx index 8d7651673..a67c39cf4 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleValueWidget.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleValueWidget.tsx @@ -14,11 +14,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { useEffect, useState } from "react"; +import React, { Fragment, useEffect, useState } from "react"; import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; import { widgetCommon } from "../../../Common/FormComponents/common/styleLibrary"; import api from "../../../../../common/api"; -import { widgetDetailsToPanel } from "../utils"; +import { splitSizeMetric, widgetDetailsToPanel } from "../utils"; import { MaterialUiPickersDate } from "@material-ui/pickers/typings/date"; import { IDashboardPanel } from "../types"; import { connect } from "react-redux"; @@ -49,10 +49,30 @@ const styles = (theme: Theme) => }, loadingAlign: { width: "100%", - paddingTop: "15px", textAlign: "center", margin: "auto", }, + metric: { + fontSize: 60, + lineHeight: 1, + color: "#07193E", + fontWeight: 700, + }, + titleElement: { + fontSize: 10, + color: "#767676", + fontWeight: 700, + }, + containerAlignment: { + display: "flex", + height: 140, + flexDirection: "column", + justifyContent: "center", + "& .unitText": { + color: "#767676", + fontSize: 12, + } + }, }); const SingleValueWidget = ({ @@ -107,14 +127,18 @@ const SingleValueWidget = ({ } }, [loading, panelItem, timeEnd, timeStart, displayErrorMessage, apiPrefix]); return ( -
-
{title}
+
{loading && (
)} - {!loading &&
{data}
} + {!loading && ( + +
{splitSizeMetric(data)}
+
{title}
+
+ )}
); }; diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/types.ts b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/types.ts index a7f959160..4d4c1bd5d 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/types.ts +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/types.ts @@ -25,6 +25,7 @@ export interface IBarChartConfiguration { dataKey: string; color: string; background?: object; + greatestColor?: string; } export interface IPieChartConfiguration { diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/types.ts b/portal-ui/src/screens/Console/Dashboard/Prometheus/types.ts index a113ea500..9f62a5fcd 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/types.ts +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/types.ts @@ -25,18 +25,21 @@ import { export enum widgetType { singleValue = "singleValue", linearGraph = "linearGraph", + areaGraph = "areaGraph", barChart = "barChart", pieChart = "pieChart", singleRep = "singleRep", + simpleWidget = "simpleWidget", } export interface IDashboardPanel { id: number; + mergedPanels?: IDashboardPanel[]; title: string; - data: string | object[] | IDataSRep[]; + data?: string | object[] | IDataSRep[]; dataOuter?: string | object[]; - type: widgetType; - layoutIdentifier: string; + type?: widgetType; + widgetIcon?: any; widgetConfiguration?: | ILinearGraphConfiguration[] | IBarChartConfiguration[] diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/utils.ts b/portal-ui/src/screens/Console/Dashboard/Prometheus/utils.tsx similarity index 74% rename from portal-ui/src/screens/Console/Dashboard/Prometheus/utils.ts rename to portal-ui/src/screens/Console/Dashboard/Prometheus/utils.tsx index 4aa241ae2..07acda5f6 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/utils.ts +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/utils.tsx @@ -14,311 +14,32 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +import React, { Fragment } from "react"; import get from "lodash/get"; -import { Layout } from "react-grid-layout"; import { IDashboardPanel, widgetType } from "./types"; import { getTimeFromTimestamp, niceBytes, niceDays, textToRGBColor, + units, } from "../../../../common/utils"; +import HealIcon from "../../../../icons/HealIcon"; +import DiagnosticsIcon from "../../../../icons/DiagnosticsIcon"; +import HistoryIcon from "../../../../icons/HistoryIcon"; const dLocalStorageV = "dashboardConfig"; -export const defaultWidgetsLayout: Layout[] = [ - { - w: 1, - h: 2, - x: 0, - y: 0, - i: "panel-0", - minW: 1, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 1, - y: 2, - i: "panel-1", - minW: 1, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 1, - y: 3, - i: "panel-2", - minW: 1, - moved: false, - static: false, - }, - { - w: 1, - h: 2, - x: 2, - y: 0, - i: "panel-3", - minW: 1, - moved: false, - static: false, - }, - { - w: 4, - h: 2, - x: 4, - y: 2, - i: "panel-4", - minW: 2, - moved: false, - static: false, - }, - { - w: 4, - h: 2, - x: 4, - y: 0, - i: "panel-5", - minW: 2, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 0, - y: 2, - i: "panel-6", - minW: 1, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 0, - y: 3, - i: "panel-7", - minW: 1, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 2, - y: 2, - i: "panel-8", - minW: 1, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 2, - y: 3, - i: "panel-9", - minW: 1, - moved: false, - static: false, - }, - { - w: 4, - h: 3, - x: 0, - y: 4, - i: "panel-10", - minW: 2, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 3, - y: 0, - i: "panel-11", - minW: 1, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 3, - y: 1, - i: "panel-12", - minW: 1, - moved: false, - static: false, - }, - { - w: 4, - h: 3, - x: 0, - y: 10, - i: "panel-13", - minW: 2, - moved: false, - static: false, - }, - { - w: 4, - h: 3, - x: 0, - y: 4, - i: "panel-14", - minW: 2, - moved: false, - static: false, - }, - { - w: 4, - h: 3, - x: 4, - y: 4, - i: "panel-15", - minW: 2, - moved: false, - static: false, - }, - { - w: 8, - h: 3, - x: 0, - y: 7, - i: "panel-16", - minW: 2, - moved: false, - static: false, - }, - { - w: 8, - h: 3, - x: 0, - y: 19, - i: "panel-19", - minW: 2, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 3, - y: 2, - i: "panel-20", - minW: 1, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 3, - y: 3, - i: "panel-21", - minW: 1, - moved: false, - static: false, - }, - { - w: 4, - h: 3, - x: 4, - y: 4, - i: "panel-22", - minW: 2, - moved: false, - static: false, - }, - { - w: 4, - h: 3, - x: 4, - y: 10, - i: "panel-23", - minW: 2, - moved: false, - static: false, - }, - { - w: 4, - h: 3, - x: 0, - y: 13, - i: "panel-24", - minW: 2, - moved: false, - static: false, - }, - { - w: 4, - h: 3, - x: 4, - y: 13, - i: "panel-25", - minW: 2, - moved: false, - static: false, - }, - { - w: 4, - h: 3, - x: 0, - y: 16, - i: "panel-26", - minW: 2, - moved: false, - static: false, - }, - { - w: 4, - h: 3, - x: 4, - y: 16, - i: "panel-27", - minW: 2, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 1, - y: 0, - i: "panel-28", - minW: 1, - moved: false, - static: false, - }, - { - w: 1, - h: 1, - x: 1, - y: 1, - i: "panel-29", - minW: 1, - moved: false, - static: false, - }, -]; - const colorsMain = [ - "#6992B7", - "#E2AD17", - "#22B573", - "#F7655E", - "#0071BC", + "#C4D4E9", + "#DCD1EE", + "#D1EEE7", + "#EEDED1", + "#AAF38F", "#F9E6C5", - "#A6E8C4", + "#C83B51", "#F4CECE", - "#ADD5E0", + "#D6D6D6", ]; const niceDaysFromNS = (seconds: string) => { @@ -334,24 +55,10 @@ export const panelsConfiguration: IDashboardPanel[] = [ id: 1, title: "Uptime", data: "N/A", - type: widgetType.singleValue, - layoutIdentifier: "panel-0", + type: widgetType.simpleWidget, + widgetIcon: , labelDisplayFunction: niceDays, }, - { - id: 9, - title: "Total Online Disks", - data: "N/A", - type: widgetType.singleValue, - layoutIdentifier: "panel-1", - }, - { - id: 78, - title: "Total Offline Disks", - data: "N/A", - type: widgetType.singleValue, - layoutIdentifier: "panel-2", - }, { id: 50, title: "Current Usable Capacity", @@ -360,21 +67,20 @@ export const panelsConfiguration: IDashboardPanel[] = [ widgetConfiguration: { outerChart: { colorList: ["#9c9c9c"], - innerRadius: 51, - outerRadius: 54, - startAngle: -15, - endAngle: 195, + innerRadius: 0, + outerRadius: 0, + startAngle: 0, + endAngle: 0, }, innerChart: { colorList: colorsMain, - innerRadius: 35, + innerRadius: 20, outerRadius: 50, - startAngle: -15, - endAngle: 195, + startAngle: 90, + endAngle: -200, }, }, type: widgetType.pieChart, - layoutIdentifier: "panel-3", innerLabel: "N/A", labelDisplayFunction: niceBytes, }, @@ -390,8 +96,7 @@ export const panelsConfiguration: IDashboardPanel[] = [ fillColor: "#000", }, ], - type: widgetType.linearGraph, - layoutIdentifier: "panel-4", + type: widgetType.areaGraph, yAxisFormatter: niceBytes, xAxisFormatter: getTimeFromTimestamp, }, @@ -404,8 +109,9 @@ export const panelsConfiguration: IDashboardPanel[] = [ dataKey: "a", color: colorsMain[0], background: { - fill: "rgba(0,0,0,0.1)", + fill: "#EEF1F4", }, + greatestColor: "#081C42", }, ], customStructure: [ @@ -436,21 +142,6 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.barChart, - layoutIdentifier: "panel-5", - }, - { - id: 53, - title: "Total Online Servers", - data: "N/A", - type: widgetType.singleValue, - layoutIdentifier: "panel-6", - }, - { - id: 69, - title: "Total Offline Servers", - data: "N/A", - type: widgetType.singleValue, - layoutIdentifier: "panel-7", }, { id: 66, @@ -460,7 +151,6 @@ export const panelsConfiguration: IDashboardPanel[] = [ type: widgetType.singleRep, color: "#0071BC", fillColor: "#ADD5E0", - layoutIdentifier: "panel-8", }, { id: 44, @@ -470,7 +160,6 @@ export const panelsConfiguration: IDashboardPanel[] = [ type: widgetType.singleRep, color: "#0071BC", fillColor: "#ADD5E0", - layoutIdentifier: "panel-9", }, { id: 63, @@ -485,7 +174,7 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-10", + xAxisFormatter: getTimeFromTimestamp, yAxisFormatter: niceBytes, }, @@ -495,7 +184,6 @@ export const panelsConfiguration: IDashboardPanel[] = [ data: [], innerLabel: "N/A", type: widgetType.singleRep, - layoutIdentifier: "panel-11", color: "#22B573", fillColor: "#A6E8C4", }, @@ -505,7 +193,6 @@ export const panelsConfiguration: IDashboardPanel[] = [ data: [], innerLabel: "N/A", type: widgetType.singleRep, - layoutIdentifier: "panel-12", color: "#F7655E", fillColor: "#F4CECE", }, @@ -522,7 +209,7 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-13", + yAxisFormatter: roundNumber, xAxisFormatter: getTimeFromTimestamp, }, @@ -539,7 +226,7 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-14", + xAxisFormatter: getTimeFromTimestamp, }, { @@ -555,7 +242,7 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-15", + xAxisFormatter: getTimeFromTimestamp, yAxisFormatter: niceBytes, }, @@ -572,7 +259,7 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-16", + yAxisFormatter: niceBytes, xAxisFormatter: getTimeFromTimestamp, }, @@ -589,7 +276,7 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-19", + yAxisFormatter: niceBytes, xAxisFormatter: getTimeFromTimestamp, }, @@ -597,16 +284,16 @@ export const panelsConfiguration: IDashboardPanel[] = [ id: 80, title: "Time Since Last Heal Activity", data: "N/A", - type: widgetType.singleValue, - layoutIdentifier: "panel-20", + type: widgetType.simpleWidget, + widgetIcon: , labelDisplayFunction: niceDaysFromNS, }, { id: 81, title: "Time Since Last Scan Activity", data: "N/A", - type: widgetType.singleValue, - layoutIdentifier: "panel-21", + type: widgetType.simpleWidget, + widgetIcon: , labelDisplayFunction: niceDaysFromNS, }, { @@ -622,7 +309,7 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-22", + xAxisFormatter: getTimeFromTimestamp, }, { @@ -638,7 +325,7 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-23", + xAxisFormatter: getTimeFromTimestamp, yAxisFormatter: niceBytes, }, @@ -655,7 +342,7 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-24", + xAxisFormatter: getTimeFromTimestamp, yAxisFormatter: niceBytes, }, @@ -672,7 +359,7 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-25", + disableYAxis: true, xAxisFormatter: getTimeFromTimestamp, }, @@ -689,7 +376,6 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-26", yAxisFormatter: roundNumber, xAxisFormatter: getTimeFromTimestamp, }, @@ -706,25 +392,66 @@ export const panelsConfiguration: IDashboardPanel[] = [ }, ], type: widgetType.linearGraph, - layoutIdentifier: "panel-27", yAxisFormatter: roundNumber, xAxisFormatter: getTimeFromTimestamp, }, { - id: 65, - title: "Total S3 Traffic Inbound", - data: "N/A", - type: widgetType.singleValue, - layoutIdentifier: "panel-28", - labelDisplayFunction: niceBytes, + id: 500, + mergedPanels: [ + { + id: 53, + title: "Online Servers", + data: "N/A", + type: widgetType.singleValue, + }, + { + id: 69, + title: "Offline Servers", + data: "N/A", + type: widgetType.singleValue, + }, + ], + title: "Servers", }, { - id: 64, - title: "Total S3 Traffic Outbound", - data: "N/A", - type: widgetType.singleValue, - layoutIdentifier: "panel-29", - labelDisplayFunction: niceBytes, + id: 501, + mergedPanels: [ + { + id: 9, + title: "Online Disks", + data: "N/A", + type: widgetType.singleValue, + }, + { + id: 78, + title: "Offline Disks", + data: "N/A", + type: widgetType.singleValue, + }, + ], + title: "Disks", + }, + { + id: 502, + mergedPanels: [ + { + id: 65, + title: "Inbound Traffic", + data: "N/A", + type: widgetType.singleValue, + + labelDisplayFunction: niceBytes, + }, + { + id: 64, + title: "Outbound Traffic", + data: "N/A", + type: widgetType.singleValue, + + labelDisplayFunction: niceBytes, + }, + ], + title: "Total S3 Traffic", }, ]; @@ -804,6 +531,7 @@ export const widgetDetailsToPanel = ( switch (panelItem.type) { case widgetType.singleValue: + case widgetType.simpleWidget: if (typeOfPayload === "stat" || typeOfPayload === "singlestat") { // We sort values & get the last value let elements = get(payloadData, "targets[0].result[0].values", []); @@ -873,6 +601,7 @@ export const widgetDetailsToPanel = ( } break; case widgetType.linearGraph: + case widgetType.areaGraph: if (typeOfPayload === "graph") { let targets = get(payloadData, "targets", []); if (targets === null) { @@ -1064,30 +793,21 @@ export const widgetDetailsToPanel = ( return panelItem; }; -export const saveDashboardDistribution = (configuration: Layout[]) => { - localStorage.setItem(dLocalStorageV, btoa(JSON.stringify(configuration))); -}; - -export const getDashboardDistribution = (currentItems: number) => { - const storedConfiguration = localStorage.getItem(dLocalStorageV); - - if (!storedConfiguration) { - return defaultWidgetsLayout; - } - - const parsedConfig = JSON.parse(atob(storedConfiguration)); - - if ( - parsedConfig.length === 0 || - (parsedConfig.length > 0 && !parsedConfig[0].minW) - ) { - return defaultWidgetsLayout; - } - - // Stored Widgets length is not the same as the currentItems, then we return the new configuration - if (currentItems !== 0 && parsedConfig.length !== currentItems) { - return defaultWidgetsLayout; - } - - return parsedConfig; +export const splitSizeMetric = (val: string) => { + const splittedText = val.split(" "); + // Value is not a size metric, we return as common string + if (splittedText.length !== 2) { + return {val}; + } + + if (!units.includes(splittedText[1])) { + return {val}; + } + + return ( + + {splittedText[0]} + {splittedText[1]} + + ); }; diff --git a/portal-ui/yarn.lock b/portal-ui/yarn.lock index 98401907f..1155440bb 100644 --- a/portal-ui/yarn.lock +++ b/portal-ui/yarn.lock @@ -1831,13 +1831,6 @@ dependencies: "@types/d3-time" "*" -"@types/d3-shape@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.5.tgz#c0164c1be1429473016f855871d487f806c4e968" - integrity sha512-aPEax03owTAKynoK8ZkmkZEDZvvT4Y5pWgii4Jp4oQt0gH45j6siDl9gNDVC5kl64XHN2goN9jbYoHK88tFAcA== - dependencies: - "@types/d3-path" "^1" - "@types/d3-shape@^2.0.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-2.1.0.tgz#cc7bbc9fc2c25f092bd457887a3224a21a55ca55" @@ -2070,14 +2063,6 @@ "@types/prop-types" "*" csstype "^3.0.2" -"@types/recharts@^1.8.19": - version "1.8.19" - resolved "https://registry.yarnpkg.com/@types/recharts/-/recharts-1.8.19.tgz#047f72cf4c25df545aa1085fe3a085e58a2483c1" - integrity sha512-Fd2cYnBlWz/ga8rLmjwsZNBAc4CzXZiuTYPPqMIgrtQ02yI/OTm8WPM6ZyUuYlSdyangtsvFmHWzZ7W4tuknDA== - dependencies: - "@types/d3-shape" "^1" - "@types/react" "*" - "@types/resize-observer-browser@^0.1.5": version "0.1.5" resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.5.tgz#36d897708172ac2380cd486da7a3daf1161c1e23" @@ -9970,10 +9955,10 @@ recharts-scale@^0.4.4: dependencies: decimal.js-light "^2.4.1" -recharts@^2.0.3: - version "2.0.9" - resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.0.9.tgz#048068eb01383291104548388712026948275f70" - integrity sha512-JNsXE80PuF3hugUCE7JqDOMSvu5xQLxtjOaqFKKZI2pCJ1PVJzhwDv4TWk0nO4AvADbeWzYEHbg8C5Hcrh42UA== +recharts@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.1.2.tgz#ceeb01e53fb46da0d946a1e0f82d783ddf5b9d06" + integrity sha512-rwFQT6T4imhLzD1kYtg9ql8YOesbFRdSwZi95KWgi5udbBdLGRCR4SgaPO8kf0URHcC23mdRbLLTMYCnXng7zQ== dependencies: "@types/d3-scale" "^3.0.0" "@types/d3-shape" "^2.0.0"