Bring Tenant Metrics to Tenant Details (#813)

* Bring Tenant Metrics to Tenant Details

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
Daniel Valdivia
2021-06-16 14:50:04 -07:00
committed by GitHub
parent 7117d87546
commit 0c8025b39f
35 changed files with 1804 additions and 66 deletions

View File

@@ -29,7 +29,6 @@ import {
import { ChangeUserPasswordRequest } from "../Buckets/types";
import api from "../../../common/api";
import { setModalErrorSnackMessage } from "../../../actions";
import { User } from "../Users/types";
const styles = (theme: Theme) =>
createStyles({
@@ -67,7 +66,7 @@ const ChangeUserPassword = ({
return;
}
setLoading(true);
if (newPassword.length < 8) {
setModalErrorSnackMessage("Passwords must be at least 8 characters long");
setLoading(false);

View File

@@ -31,7 +31,6 @@ import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
import { setModalErrorSnackMessage } from "../../../../actions";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import AutocompleteWrapper from "../../Common/FormComponents/AutocompleteWrapper/AutocompleteWrapper";
const styles = (theme: Theme) =>

View File

@@ -13,16 +13,16 @@
//
// 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, { useState, useEffect } from "react";
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import {
FormControl,
InputLabel,
Tooltip,
makeStyles,
OutlinedInputProps,
TextField,
TextFieldProps,
OutlinedInputProps,
makeStyles,
Tooltip,
} from "@material-ui/core";
import Autocomplete from "@material-ui/lab/Autocomplete";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";

View File

@@ -362,6 +362,10 @@ const Console = ({
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/summary",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/metrics",
},
{
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods",

View File

@@ -74,7 +74,9 @@ const Dashboard = ({ classes, displayErrorMessage }: IDashboardSimple) => {
) : (
<Fragment>
{widgets !== null ? (
<PrDashboard />
<Grid container className={classes.container}>
<PrDashboard />
</Grid>
) : (
<BasicDashboard usage={basicResult} />
)}

View File

@@ -50,6 +50,7 @@ import { TabPanel } from "../../../shared/tabs";
interface IPrDashboard {
classes: any;
displayErrorMessage: typeof setErrorSnackMessage;
apiPrefix?: string;
}
const styles = (theme: Theme) =>
@@ -71,7 +72,11 @@ const styles = (theme: Theme) =>
},
});
const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
const PrDashboard = ({
classes,
displayErrorMessage,
apiPrefix = "admin",
}: IPrDashboard) => {
const [timeStart, setTimeStart] = useState<any>(null);
const [timeEnd, setTimeEnd] = useState<any>(null);
const [loading, setLoading] = useState<boolean>(true);
@@ -106,6 +111,7 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
apiPrefix={apiPrefix}
/>
);
case widgetType.pieChart:
@@ -116,6 +122,7 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
apiPrefix={apiPrefix}
/>
);
case widgetType.linearGraph:
@@ -134,6 +141,7 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
? singlePanelWidth * dashboardDistr[index].w
: singlePanelWidth
}
apiPrefix={apiPrefix}
/>
);
case widgetType.barChart:
@@ -144,6 +152,7 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
timeStart={timeStart}
timeEnd={timeEnd}
propLoading={loading}
apiPrefix={apiPrefix}
/>
);
case widgetType.singleRep:
@@ -157,6 +166,7 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
propLoading={loading}
color={value.color as string}
fillColor={fillColor as string}
apiPrefix={apiPrefix}
/>
);
default:
@@ -178,7 +188,7 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
);
});
},
[panelInformation, dashboardDistr, timeEnd, timeStart, loading]
[panelInformation, dashboardDistr, timeEnd, timeStart, loading, apiPrefix]
);
const fetchUsage = useCallback(() => {
@@ -194,7 +204,7 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
api
.invoke(
"GET",
`/api/v1/admin/info?step=${stepCalc}&${
`/api/v1/${apiPrefix}/info?step=${stepCalc}&${
timeStart !== null ? `&start=${timeStart.unix()}` : ""
}${timeStart !== null && timeEnd !== null ? "&" : ""}${
timeEnd !== null ? `end=${timeEnd.unix()}` : ""
@@ -216,7 +226,7 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
displayErrorMessage(err);
setLoading(false);
});
}, [timeStart, timeEnd, displayErrorMessage]);
}, [timeStart, timeEnd, displayErrorMessage, apiPrefix]);
const triggerLoad = () => {
setLoading(true);
@@ -242,7 +252,7 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
const requestsPanels = [60, 71, 17, 73];
return (
<Grid container className={classes.container}>
<React.Fragment>
<Grid
item
xs={12}
@@ -355,7 +365,7 @@ const PrDashboard = ({ classes, displayErrorMessage }: IPrDashboard) => {
</AutoSizer>
</TabPanel>
</Grid>
</Grid>
</React.Fragment>
);
};

View File

@@ -43,6 +43,7 @@ interface IBarChartWidget {
timeEnd: MaterialUiPickersDate;
propLoading: boolean;
displayErrorMessage: any;
apiPrefix: string;
}
const styles = (theme: Theme) =>
@@ -79,6 +80,7 @@ const BarChartWidget = ({
timeEnd,
propLoading,
displayErrorMessage,
apiPrefix,
}: IBarChartWidget) => {
const [loading, setLoading] = useState<boolean>(true);
const [data, setData] = useState<any>([]);
@@ -103,7 +105,9 @@ const BarChartWidget = ({
api
.invoke(
"GET",
`/api/v1/admin/info/widgets/${panelItem.id}/?step=${stepCalc}&${
`/api/v1/${apiPrefix}/info/widgets/${
panelItem.id
}/?step=${stepCalc}&${
timeStart !== null ? `&start=${timeStart.unix()}` : ""
}${timeStart !== null && timeEnd !== null ? "&" : ""}${
timeEnd !== null ? `end=${timeEnd.unix()}` : ""
@@ -120,7 +124,7 @@ const BarChartWidget = ({
setLoading(false);
});
}
}, [loading, panelItem, timeEnd, timeStart, displayErrorMessage]);
}, [loading, panelItem, timeEnd, timeStart, displayErrorMessage, apiPrefix]);
const barChartConfiguration = result
? (result.widgetConfiguration as IBarChartConfiguration[])

View File

@@ -44,6 +44,7 @@ interface ILinearGraphWidget {
timeEnd: MaterialUiPickersDate;
propLoading: boolean;
displayErrorMessage: any;
apiPrefix: string;
hideYAxis?: boolean;
yAxisFormatter?: (item: string) => string;
xAxisFormatter?: (item: string) => string;
@@ -87,6 +88,7 @@ const LinearGraphWidget = ({
timeEnd,
propLoading,
panelItem,
apiPrefix,
hideYAxis = false,
yAxisFormatter = (item: string) => item,
xAxisFormatter = (item: string) => item,
@@ -94,6 +96,7 @@ const LinearGraphWidget = ({
}: ILinearGraphWidget) => {
const [loading, setLoading] = useState<boolean>(true);
const [data, setData] = useState<object[]>([]);
const [dataMax, setDataMax] = useState<number>(0);
const [result, setResult] = useState<IDashboardPanel | null>(null);
useEffect(() => {
@@ -115,7 +118,9 @@ const LinearGraphWidget = ({
api
.invoke(
"GET",
`/api/v1/admin/info/widgets/${panelItem.id}/?step=${stepCalc}&${
`/api/v1/${apiPrefix}/info/widgets/${
panelItem.id
}/?step=${stepCalc}&${
timeStart !== null ? `&start=${timeStart.unix()}` : ""
}${timeStart !== null && timeEnd !== null ? "&" : ""}${
timeEnd !== null ? `end=${timeEnd.unix()}` : ""
@@ -126,13 +131,26 @@ const LinearGraphWidget = ({
setData(widgetsWithValue.data);
setResult(widgetsWithValue);
setLoading(false);
let maxVal = 0;
for (const dp of widgetsWithValue.data) {
for (const key in dp) {
if (key === "name") {
continue;
}
const val = parseInt(dp[key]);
if (maxVal < val) {
maxVal = val;
}
}
}
setDataMax(maxVal);
})
.catch((err) => {
displayErrorMessage(err);
setLoading(false);
});
}
}, [loading, panelItem, timeEnd, timeStart, displayErrorMessage]);
}, [loading, panelItem, timeEnd, timeStart, displayErrorMessage, apiPrefix]);
let intervalCount = 5;
@@ -185,7 +203,8 @@ const LinearGraphWidget = ({
tickCount={10}
/>
<YAxis
domain={[0, (dataMax: number) => dataMax * 4]}
type={"number"}
domain={[0, dataMax * 1.1]}
hide={hideYAxis}
tickFormatter={(value: any) => yAxisFormatter(value)}
tick={{ fontSize: "70%" }}

View File

@@ -36,6 +36,7 @@ interface IPieChartWidget {
timeEnd: MaterialUiPickersDate;
propLoading: boolean;
displayErrorMessage: any;
apiPrefix: string;
}
const styles = (theme: Theme) =>
@@ -57,6 +58,7 @@ const PieChartWidget = ({
timeEnd,
propLoading,
displayErrorMessage,
apiPrefix,
}: IPieChartWidget) => {
const [loading, setLoading] = useState<boolean>(true);
const [dataInner, setDataInner] = useState<object[]>([]);
@@ -82,7 +84,9 @@ const PieChartWidget = ({
api
.invoke(
"GET",
`/api/v1/admin/info/widgets/${panelItem.id}/?step=${stepCalc}&${
`/api/v1/${apiPrefix}/info/widgets/${
panelItem.id
}/?step=${stepCalc}&${
timeStart !== null ? `&start=${timeStart.unix()}` : ""
}${timeStart !== null && timeEnd !== null ? "&" : ""}${
timeEnd !== null ? `end=${timeEnd.unix()}` : ""
@@ -100,7 +104,7 @@ const PieChartWidget = ({
setLoading(false);
});
}
}, [loading, panelItem, timeEnd, timeStart, displayErrorMessage]);
}, [loading, panelItem, timeEnd, timeStart, displayErrorMessage, apiPrefix]);
const pieChartConfiguration = result
? (result.widgetConfiguration as IPieChartConfiguration)

View File

@@ -37,6 +37,7 @@ interface ISingleRepWidget {
displayErrorMessage: any;
color: string;
fillColor: string;
apiPrefix: string;
}
const styles = (theme: Theme) =>
@@ -60,6 +61,7 @@ const SingleRepWidget = ({
displayErrorMessage,
color,
fillColor,
apiPrefix,
}: ISingleRepWidget) => {
const [loading, setLoading] = useState<boolean>(true);
const [data, setData] = useState<IDataSRep[]>([]);
@@ -84,7 +86,9 @@ const SingleRepWidget = ({
api
.invoke(
"GET",
`/api/v1/admin/info/widgets/${panelItem.id}/?step=${stepCalc}&${
`/api/v1/${apiPrefix}/info/widgets/${
panelItem.id
}/?step=${stepCalc}&${
timeStart !== null ? `&start=${timeStart.unix()}` : ""
}${timeStart !== null && timeEnd !== null ? "&" : ""}${
timeEnd !== null ? `end=${timeEnd.unix()}` : ""
@@ -101,7 +105,7 @@ const SingleRepWidget = ({
setLoading(false);
});
}
}, [loading, panelItem, timeEnd, timeStart, displayErrorMessage]);
}, [loading, panelItem, timeEnd, timeStart, displayErrorMessage, apiPrefix]);
return (
<div className={classes.singleValueContainer}>
<div className={classes.titleContainer}>{title}</div>

View File

@@ -33,6 +33,7 @@ interface ISingleValueWidget {
propLoading: boolean;
displayErrorMessage: any;
classes: any;
apiPrefix: string;
}
const styles = (theme: Theme) =>
@@ -61,6 +62,7 @@ const SingleValueWidget = ({
propLoading,
displayErrorMessage,
classes,
apiPrefix,
}: ISingleValueWidget) => {
const [loading, setLoading] = useState<boolean>(false);
const [data, setData] = useState<string>("");
@@ -84,7 +86,9 @@ const SingleValueWidget = ({
api
.invoke(
"GET",
`/api/v1/admin/info/widgets/${panelItem.id}/?step=${stepCalc}&${
`/api/v1/${apiPrefix}/info/widgets/${
panelItem.id
}/?step=${stepCalc}&${
timeStart !== null ? `&start=${timeStart.unix()}` : ""
}${timeStart !== null && timeEnd !== null ? "&" : ""}${
timeEnd !== null ? `end=${timeEnd.unix()}` : ""
@@ -100,7 +104,7 @@ const SingleValueWidget = ({
setLoading(false);
});
}
}, [loading, panelItem, timeEnd, timeStart, displayErrorMessage]);
}, [loading, panelItem, timeEnd, timeStart, displayErrorMessage, apiPrefix]);
return (
<div className={classes.singleValueContainer}>
<div className={classes.titleContainer}>{title}</div>

View File

@@ -44,6 +44,7 @@ import TenantLicense from "./TenantLicense";
import PoolsSummary from "./PoolsSummary";
import PodsSummary from "./PodsSummary";
import { AppState } from "../../../../store";
import TenantMetrics from "./TenantMetrics";
interface ITenantDetailsProps {
classes: any;
@@ -153,6 +154,7 @@ const TenantDetails = ({
switch (section) {
case "pools":
case "pods":
case "metrics":
case "license":
setTenantTab(section);
break;
@@ -232,6 +234,7 @@ const TenantDetails = ({
scrollButtons="auto"
>
<Tab value="summary" label="Summary" />
<Tab value="metrics" label="Metrics" />
<Tab value="pools" label="Pools" />
<Tab value="pods" label="Pods" />
<Tab value="license" label="License" />
@@ -244,6 +247,10 @@ const TenantDetails = ({
path="/namespaces/:tenantNamespace/tenants/:tenantName/summary"
component={TenantSummary}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/metrics"
component={TenantMetrics}
/>
<Route
path="/namespaces/:tenantNamespace/tenants/:tenantName/pools"
component={PoolsSummary}

View File

@@ -0,0 +1,155 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { connect } from "react-redux";
import get from "lodash/get";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import {
containerForHeader,
tenantDetailsStyles,
} from "../../Common/FormComponents/common/styleLibrary";
import Grid from "@material-ui/core/Grid";
import { LinearProgress } from "@material-ui/core";
import api from "../../../../common/api";
import { ITenant } from "../ListTenants/types";
import { setErrorSnackMessage } from "../../../../actions";
import { AppState } from "../../../../store";
import { Usage } from "../../Dashboard/types";
import PrDashboard from "../../Dashboard/Prometheus/PrDashboard";
import BasicDashboard from "../../Dashboard/BasicDashboard/BasicDashboard";
interface ITenantMetrics {
classes: any;
match: any;
tenant: ITenant | null;
setErrorSnackMessage: typeof setErrorSnackMessage;
}
const styles = (theme: Theme) =>
createStyles({
...tenantDetailsStyles,
redState: {
color: theme.palette.error.main,
},
yellowState: {
color: theme.palette.warning.main,
},
greenState: {
color: theme.palette.success.main,
},
greyState: {
color: "grey",
},
centerAlign: {
textAlign: "center",
},
...containerForHeader(theme.spacing(4)),
});
const TenantMetrics = ({
classes,
match,
tenant,
setErrorSnackMessage,
}: ITenantMetrics) => {
const [loadingWidgets, setLoadingWidgets] = useState<boolean>(true);
const [basicResult, setBasicResult] = useState<Usage | null>(null);
const tenantName = match.params["tenantName"];
const tenantNamespace = match.params["tenantNamespace"];
const fetchWidgets = useCallback(() => {
api
.invoke(
"GET",
`/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/info`
)
.then((res: Usage) => {
setBasicResult(res);
setLoadingWidgets(false);
})
.catch((err) => {
setErrorSnackMessage(err);
setLoadingWidgets(false);
});
}, [
setBasicResult,
setLoadingWidgets,
setErrorSnackMessage,
tenantNamespace,
tenantName,
]);
useEffect(() => {
if (loadingWidgets) {
fetchWidgets();
}
}, [loadingWidgets, fetchWidgets, setErrorSnackMessage]);
const widgets = get(basicResult, "widgets", null);
return (
<Fragment>
<br />
{loadingWidgets ? (
<Grid item xs={12} className={classes.container}>
<LinearProgress />
</Grid>
) : (
<Fragment>
{widgets !== null ? (
<PrDashboard
apiPrefix={`namespaces/${tenantNamespace}/tenants/${tenantName}`}
/>
) : (
<BasicDashboard usage={basicResult} />
)}
</Fragment>
)}
</Fragment>
);
};
const mapState = (state: AppState) => ({
loadingTenant: state.tenants.tenantDetails.loadingTenant,
selectedTenant: state.tenants.tenantDetails.currentTenant,
tenant: state.tenants.tenantDetails.tenantInfo,
logEnabled: get(state.tenants.tenantDetails.tenantInfo, "logEnabled", false),
monitoringEnabled: get(
state.tenants.tenantDetails.tenantInfo,
"monitoringEnabled",
false
),
encryptionEnabled: get(
state.tenants.tenantDetails.tenantInfo,
"encryptionEnabled",
false
),
adEnabled: get(state.tenants.tenantDetails.tenantInfo, "idpAdEnabled", false),
oicEnabled: get(
state.tenants.tenantDetails.tenantInfo,
"idpOicEnabled",
false
),
});
const connector = connect(mapState, {
setErrorSnackMessage,
});
export default withStyles(styles)(connector(TenantMetrics));

View File

@@ -30,7 +30,6 @@ import api from "../../../../common/api";
import { ITenant } from "../ListTenants/types";
import UsageBarWrapper from "../../Common/UsageBarWrapper/UsageBarWrapper";
import UpdateTenantModal from "./UpdateTenantModal";
import { setErrorSnackMessage } from "../../../../actions";
import { AppState } from "../../../../store";
interface ITenantsSummary {
@@ -43,7 +42,6 @@ interface ITenantsSummary {
adEnabled: boolean;
oicEnabled: boolean;
loadingTenant: boolean;
setErrorSnackMessage: typeof setErrorSnackMessage;
}
interface ITenantUsage {
@@ -82,7 +80,6 @@ const TenantSummary = ({
adEnabled,
oicEnabled,
loadingTenant,
setErrorSnackMessage,
}: ITenantsSummary) => {
const [capacity, setCapacity] = useState<number>(0);
const [poolCount, setPoolCount] = useState<number>(0);
@@ -441,8 +438,6 @@ const mapState = (state: AppState) => ({
),
});
const connector = connect(mapState, {
setErrorSnackMessage,
});
const connector = connect(mapState, null);
export default withStyles(styles)(connector(TenantSummary));

View File

@@ -26,7 +26,7 @@ import {
LinearProgress,
} from "@material-ui/core";
import api from "../../../common/api";
import { User, UsersList } from "./types";
import { UsersList } from "./types";
import { setErrorSnackMessage } from "../../../actions";
import history from "../../../history";