From 0c8025b39f5ab94af6781148d3acc1c181b7c603 Mon Sep 17 00:00:00 2001 From: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> Date: Wed, 16 Jun 2021 14:50:04 -0700 Subject: [PATCH] Bring Tenant Metrics to Tenant Details (#813) * Bring Tenant Metrics to Tenant Details Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> --- pkg/acl/endpoints.go | 2 + pkg/acl/endpoints_test.go | 8 +- .../Account/ChangeUserPasswordModal.tsx | 3 +- .../Buckets/BucketDetails/AddEvent.tsx | 1 - .../AutocompleteWrapper.tsx | 8 +- portal-ui/src/screens/Console/Console.tsx | 4 + .../screens/Console/Dashboard/Dashboard.tsx | 4 +- .../Dashboard/Prometheus/PrDashboard.tsx | 22 +- .../Prometheus/Widgets/BarChartWidget.tsx | 8 +- .../Prometheus/Widgets/LinearGraphWidget.tsx | 25 +- .../Prometheus/Widgets/PieChartWidget.tsx | 8 +- .../Prometheus/Widgets/SingleRepWidget.tsx | 8 +- .../Prometheus/Widgets/SingleValueWidget.tsx | 8 +- .../Tenants/TenantDetails/TenantDetails.tsx | 7 + .../Tenants/TenantDetails/TenantMetrics.tsx | 155 +++++++++++ .../Tenants/TenantDetails/TenantSummary.tsx | 7 +- .../Console/Users/DeleteUserString.tsx | 2 +- restapi/admin_info.go | 38 ++- restapi/admin_tenants.go | 21 +- restapi/configure_console.go | 2 + restapi/embedded_spec.go | 202 ++++++++++++++- .../operations/admin_api/tenant_details.go | 88 +++++++ .../admin_api/tenant_details_parameters.go | 112 ++++++++ .../admin_api/tenant_details_responses.go | 133 ++++++++++ .../admin_api/tenant_details_urlbuilder.go | 124 +++++++++ restapi/operations/admin_api/tenant_info.go | 2 +- .../admin_api/tenant_info_responses.go | 6 +- .../admin_api/tenant_info_urlbuilder.go | 2 +- .../admin_api/tenant_widget_details.go | 88 +++++++ .../tenant_widget_details_parameters.go | 241 ++++++++++++++++++ .../tenant_widget_details_responses.go | 133 ++++++++++ .../tenant_widget_details_urlbuilder.go | 166 ++++++++++++ restapi/operations/console_api.go | 26 +- restapi/operator_info.go | 138 ++++++++++ swagger.yml | 68 ++++- 35 files changed, 1804 insertions(+), 66 deletions(-) create mode 100644 portal-ui/src/screens/Console/Tenants/TenantDetails/TenantMetrics.tsx create mode 100644 restapi/operations/admin_api/tenant_details.go create mode 100644 restapi/operations/admin_api/tenant_details_parameters.go create mode 100644 restapi/operations/admin_api/tenant_details_responses.go create mode 100644 restapi/operations/admin_api/tenant_details_urlbuilder.go create mode 100644 restapi/operations/admin_api/tenant_widget_details.go create mode 100644 restapi/operations/admin_api/tenant_widget_details_parameters.go create mode 100644 restapi/operations/admin_api/tenant_widget_details_responses.go create mode 100644 restapi/operations/admin_api/tenant_widget_details_urlbuilder.go create mode 100644 restapi/operator_info.go diff --git a/pkg/acl/endpoints.go b/pkg/acl/endpoints.go index 0da209c6b..2f2b90006 100644 --- a/pkg/acl/endpoints.go +++ b/pkg/acl/endpoints.go @@ -45,6 +45,7 @@ var ( tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName" podsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName" tenantsDetailSummary = "/namespaces/:tenantNamespace/tenants/:tenantName/summary" + tenantsDetailMetrics = "/namespaces/:tenantNamespace/tenants/:tenantName/metrics" tenantsDetailPods = "/namespaces/:tenantNamespace/tenants/:tenantName/pods" tenantsDetailPools = "/namespaces/:tenantNamespace/tenants/:tenantName/pools" tenantsDetailLicense = "/namespaces/:tenantNamespace/tenants/:tenantName/license" @@ -325,6 +326,7 @@ var operatorRules = map[string]ConfigurationActionSet{ tenants: tenantsActionSet, tenantsDetail: tenantsActionSet, tenantsDetailSummary: tenantsActionSet, + tenantsDetailMetrics: tenantsActionSet, tenantsDetailPods: tenantsActionSet, tenantsDetailPools: tenantsActionSet, tenantsDetailLicense: tenantsActionSet, diff --git a/pkg/acl/endpoints_test.go b/pkg/acl/endpoints_test.go index 5c205f5e8..0fd377d50 100644 --- a/pkg/acl/endpoints_test.go +++ b/pkg/acl/endpoints_test.go @@ -116,7 +116,7 @@ func TestOperatorOnlyEndpoints(t *testing.T) { "admin:*", }, }, - want: 11, + want: 12, }, { name: "Operator Only - all s3 endpoints", @@ -125,7 +125,7 @@ func TestOperatorOnlyEndpoints(t *testing.T) { "s3:*", }, }, - want: 11, + want: 12, }, { name: "Operator Only - all admin and s3 endpoints", @@ -135,14 +135,14 @@ func TestOperatorOnlyEndpoints(t *testing.T) { "s3:*", }, }, - want: 11, + want: 12, }, { name: "Operator Only - default endpoints", args: args{ []string{}, }, - want: 11, + want: 12, }, } diff --git a/portal-ui/src/screens/Console/Account/ChangeUserPasswordModal.tsx b/portal-ui/src/screens/Console/Account/ChangeUserPasswordModal.tsx index 73840af21..7c934fb58 100644 --- a/portal-ui/src/screens/Console/Account/ChangeUserPasswordModal.tsx +++ b/portal-ui/src/screens/Console/Account/ChangeUserPasswordModal.tsx @@ -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); diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/AddEvent.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/AddEvent.tsx index 22be607da..de1b3d822 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/AddEvent.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/AddEvent.tsx @@ -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) => diff --git a/portal-ui/src/screens/Console/Common/FormComponents/AutocompleteWrapper/AutocompleteWrapper.tsx b/portal-ui/src/screens/Console/Common/FormComponents/AutocompleteWrapper/AutocompleteWrapper.tsx index b4339d191..aa816262f 100644 --- a/portal-ui/src/screens/Console/Common/FormComponents/AutocompleteWrapper/AutocompleteWrapper.tsx +++ b/portal-ui/src/screens/Console/Common/FormComponents/AutocompleteWrapper/AutocompleteWrapper.tsx @@ -13,16 +13,16 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -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"; diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index 8b4fb5eff..99e947ffe 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -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", diff --git a/portal-ui/src/screens/Console/Dashboard/Dashboard.tsx b/portal-ui/src/screens/Console/Dashboard/Dashboard.tsx index 37575cc0f..cc03bf541 100644 --- a/portal-ui/src/screens/Console/Dashboard/Dashboard.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Dashboard.tsx @@ -74,7 +74,9 @@ const Dashboard = ({ classes, displayErrorMessage }: IDashboardSimple) => { ) : ( {widgets !== null ? ( - + + + ) : ( )} diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx index 312cdb241..6b7854269 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/PrDashboard.tsx @@ -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(null); const [timeEnd, setTimeEnd] = useState(null); const [loading, setLoading] = useState(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 ( - + { - + ); }; 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 a05c7d1c2..4db6f5bce 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/BarChartWidget.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/BarChartWidget.tsx @@ -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(true); const [data, setData] = useState([]); @@ -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[]) diff --git a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/LinearGraphWidget.tsx b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/LinearGraphWidget.tsx index 5225517b8..8e562ff1e 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/LinearGraphWidget.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/LinearGraphWidget.tsx @@ -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(true); const [data, setData] = useState([]); + const [dataMax, setDataMax] = useState(0); const [result, setResult] = useState(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} /> dataMax * 4]} + type={"number"} + domain={[0, dataMax * 1.1]} hide={hideYAxis} tickFormatter={(value: any) => yAxisFormatter(value)} tick={{ fontSize: "70%" }} 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 81bdcc0b5..d75aa48b6 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/PieChartWidget.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/PieChartWidget.tsx @@ -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(true); const [dataInner, setDataInner] = useState([]); @@ -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) 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 7674c6925..f700f9e14 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleRepWidget.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleRepWidget.tsx @@ -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(true); const [data, setData] = useState([]); @@ -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 (
{title}
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 724efafe4..0f97b2aa9 100644 --- a/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleValueWidget.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Prometheus/Widgets/SingleValueWidget.tsx @@ -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(false); const [data, setData] = useState(""); @@ -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 (
{title}
diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx index fd875e0fd..8c517e623 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx @@ -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" > + @@ -244,6 +247,10 @@ const TenantDetails = ({ path="/namespaces/:tenantNamespace/tenants/:tenantName/summary" component={TenantSummary} /> + . + +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(true); + const [basicResult, setBasicResult] = useState(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 ( + +
+ {loadingWidgets ? ( + + + + ) : ( + + {widgets !== null ? ( + + ) : ( + + )} + + )} +
+ ); +}; + +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)); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSummary.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSummary.tsx index 86ad2a723..4972dc769 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSummary.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSummary.tsx @@ -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(0); const [poolCount, setPoolCount] = useState(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)); diff --git a/portal-ui/src/screens/Console/Users/DeleteUserString.tsx b/portal-ui/src/screens/Console/Users/DeleteUserString.tsx index d435ef8bd..096db4e0d 100644 --- a/portal-ui/src/screens/Console/Users/DeleteUserString.tsx +++ b/portal-ui/src/screens/Console/Users/DeleteUserString.tsx @@ -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"; diff --git a/restapi/admin_info.go b/restapi/admin_info.go index 05d3d718f..4b735071a 100644 --- a/restapi/admin_info.go +++ b/restapi/admin_info.go @@ -27,6 +27,8 @@ import ( "strings" "time" + "github.com/minio/madmin-go" + "github.com/go-openapi/swag" "github.com/go-openapi/runtime/middleware" @@ -788,12 +790,21 @@ type LabelResults struct { // getAdminInfoResponse returns the response containing total buckets, objects and usage. func getAdminInfoResponse(session *models.Principal) (*models.AdminInfoResponse, *models.Error) { prometheusURL := getPrometheusURL() + mAdmin, err := newAdminClient(session) + if err != nil { + return nil, prepareError(err) + } + sessionResp, err2 := getUsageWidgetsForDeployment(prometheusURL, mAdmin) + if err2 != nil { + return nil, err2 + } + + return sessionResp, nil +} + +func getUsageWidgetsForDeployment(prometheusURL string, mAdmin *madmin.AdminClient) (*models.AdminInfoResponse, *models.Error) { if prometheusURL == "" { - mAdmin, err := newAdminClient(session) - if err != nil { - return nil, prepareError(err) - } // create a minioClient interface implementation // defining the client to be used adminClient := adminClient{client: mAdmin} @@ -837,7 +848,6 @@ func getAdminInfoResponse(session *models.Principal) (*models.AdminInfoResponse, sessionResp := &models.AdminInfoResponse{} sessionResp.Widgets = wdgts - return sessionResp, nil } @@ -872,6 +882,10 @@ func getAdminInfoWidgetResponse(params admin_api.DashboardWidgetDetailsParams) ( prometheusURL := getPrometheusURL() prometheusJobID := getPrometheusJobID() + return getWidgetDetails(prometheusURL, prometheusJobID, params.WidgetID, params.Step, params.Start, params.End) +} + +func getWidgetDetails(prometheusURL string, prometheusJobID string, widgetID int32, step *int32, start *int64, end *int64) (*models.WidgetDetails, *models.Error) { labelResultsCh := make(chan LabelResults) for _, lbl := range labels { @@ -907,14 +921,14 @@ LabelsWaitLoop: // launch a goroutines per widget for _, m := range widgets { - if m.ID != params.WidgetID { + if m.ID != widgetID { continue } targetResults := make(chan *models.ResultTarget) // for each target we will launch another goroutine to fetch the values for _, target := range m.Targets { - go func(target Target, params admin_api.DashboardWidgetDetailsParams) { + go func(target Target, inStep *int32, inStart *int64, inEnd *int64) { apiType := "query_range" now := time.Now() @@ -924,15 +938,15 @@ LabelsWaitLoop: if target.Step > 0 { step = target.Step } - if params.Step != nil && *params.Step > 0 { - step = *params.Step + if inStep != nil && *inStep > 0 { + step = *inStep } if step > 0 { extraParamters = fmt.Sprintf("%s&step=%d", extraParamters, step) } - if params.Start != nil && params.End != nil { - extraParamters = fmt.Sprintf("&start=%d&end=%d&step=%d", *params.Start, *params.End, *params.Step) + if inStart != nil && inEnd != nil { + extraParamters = fmt.Sprintf("&start=%d&end=%d&step=%d", *inStart, *inEnd, *inStep) } // replace the `$__interval` global for step with unit (s for seconds) @@ -969,7 +983,7 @@ LabelsWaitLoop: targetResults <- &targetResult - }(target, params) + }(target, step, start, end) } wdgtResult := models.WidgetDetails{ diff --git a/restapi/admin_tenants.go b/restapi/admin_tenants.go index b00aef368..e8edba4db 100644 --- a/restapi/admin_tenants.go +++ b/restapi/admin_tenants.go @@ -91,12 +91,12 @@ func registerTenantHandlers(api *operations.ConsoleAPI) { }) // Detail Tenant - api.AdminAPITenantInfoHandler = admin_api.TenantInfoHandlerFunc(func(params admin_api.TenantInfoParams, session *models.Principal) middleware.Responder { - resp, err := getTenantInfoResponse(session, params) + api.AdminAPITenantDetailsHandler = admin_api.TenantDetailsHandlerFunc(func(params admin_api.TenantDetailsParams, session *models.Principal) middleware.Responder { + resp, err := getTenantDetailsResponse(session, params) if err != nil { - return admin_api.NewTenantInfoDefault(int(err.Code)).WithPayload(err) + return admin_api.NewTenantDetailsDefault(int(err.Code)).WithPayload(err) } - return admin_api.NewTenantInfoOK().WithPayload(resp) + return admin_api.NewTenantDetailsOK().WithPayload(resp) }) @@ -367,7 +367,7 @@ func getTenantInfo(tenant *miniov2.Tenant) *models.Tenant { } } -func getTenantInfoResponse(session *models.Principal, params admin_api.TenantInfoParams) (*models.Tenant, *models.Error) { +func getTenantDetailsResponse(session *models.Principal, params admin_api.TenantDetailsParams) (*models.Tenant, *models.Error) { // 5 seconds timeout ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -408,7 +408,9 @@ func getTenantInfoResponse(session *models.Principal, params admin_api.TenantInf // detect if OpenID is enabled oicEnabled := false - consoleSecret, err := clientSet.CoreV1().Secrets(minTenant.Namespace).Get(ctx, minTenant.Name, metav1.GetOptions{}) + consoleSelector := fmt.Sprintf("%s-console", minTenant.Name) + consoleSecretName := fmt.Sprintf("%s-secret", consoleSelector) + consoleSecret, err := clientSet.CoreV1().Secrets(minTenant.Namespace).Get(ctx, consoleSecretName, metav1.GetOptions{}) // we can tolerate not getting this secret if err != nil { LogError("unable to fetch existing secrets for %s: %v", minTenant.Name, err) @@ -418,6 +420,13 @@ func getTenantInfoResponse(session *models.Principal, params admin_api.TenantInf oicEnabled = true } } + if minTenant.HasConsoleEnabled() { + for _, env := range minTenant.Spec.Console.Env { + if env.Name == "CONSOLE_IDP_URL" { + oicEnabled = true + } + } + } info.LogEnabled = minTenant.HasLogEnabled() info.MonitoringEnabled = minTenant.HasPrometheusEnabled() diff --git a/restapi/configure_console.go b/restapi/configure_console.go index 5d3dfdc60..7c04ba37f 100644 --- a/restapi/configure_console.go +++ b/restapi/configure_console.go @@ -117,6 +117,8 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler { // Operator Console // Register tenant handlers registerTenantHandlers(api) + // Register admin info handlers + registerOperatorTenantInfoHandlers(api) // Register ResourceQuota handlers registerResourceQuotaHandlers(api) // Register Nodes' handlers diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index c4f54822c..364800351 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -2560,8 +2560,8 @@ func init() { "tags": [ "AdminAPI" ], - "summary": "Tenant Info", - "operationId": "TenantInfo", + "summary": "Tenant Details", + "operationId": "TenantDetails", "parameters": [ { "type": "string", @@ -2755,6 +2755,103 @@ func init() { } } }, + "/namespaces/{namespace}/tenants/{tenant}/info": { + "get": { + "tags": [ + "AdminAPI" + ], + "summary": "Tenant Info", + "operationId": "TenantInfo", + "parameters": [ + { + "type": "string", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "tenant", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/adminInfoResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, + "/namespaces/{namespace}/tenants/{tenant}/info/widgets/{widgetId}": { + "get": { + "tags": [ + "AdminAPI" + ], + "summary": "Returns information about a tenant deployment", + "operationId": "TenantWidgetDetails", + "parameters": [ + { + "type": "string", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "tenant", + "in": "path", + "required": true + }, + { + "type": "integer", + "format": "int32", + "name": "widgetId", + "in": "path", + "required": true + }, + { + "type": "integer", + "name": "start", + "in": "query" + }, + { + "type": "integer", + "name": "end", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "name": "step", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/widgetDetails" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/namespaces/{namespace}/tenants/{tenant}/pods": { "get": { "tags": [ @@ -9925,8 +10022,8 @@ func init() { "tags": [ "AdminAPI" ], - "summary": "Tenant Info", - "operationId": "TenantInfo", + "summary": "Tenant Details", + "operationId": "TenantDetails", "parameters": [ { "type": "string", @@ -10120,6 +10217,103 @@ func init() { } } }, + "/namespaces/{namespace}/tenants/{tenant}/info": { + "get": { + "tags": [ + "AdminAPI" + ], + "summary": "Tenant Info", + "operationId": "TenantInfo", + "parameters": [ + { + "type": "string", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "tenant", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/adminInfoResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, + "/namespaces/{namespace}/tenants/{tenant}/info/widgets/{widgetId}": { + "get": { + "tags": [ + "AdminAPI" + ], + "summary": "Returns information about a tenant deployment", + "operationId": "TenantWidgetDetails", + "parameters": [ + { + "type": "string", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "tenant", + "in": "path", + "required": true + }, + { + "type": "integer", + "format": "int32", + "name": "widgetId", + "in": "path", + "required": true + }, + { + "type": "integer", + "name": "start", + "in": "query" + }, + { + "type": "integer", + "name": "end", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "name": "step", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/widgetDetails" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/namespaces/{namespace}/tenants/{tenant}/pods": { "get": { "tags": [ diff --git a/restapi/operations/admin_api/tenant_details.go b/restapi/operations/admin_api/tenant_details.go new file mode 100644 index 000000000..979ee5d80 --- /dev/null +++ b/restapi/operations/admin_api/tenant_details.go @@ -0,0 +1,88 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/minio/console/models" +) + +// TenantDetailsHandlerFunc turns a function with the right signature into a tenant details handler +type TenantDetailsHandlerFunc func(TenantDetailsParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn TenantDetailsHandlerFunc) Handle(params TenantDetailsParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// TenantDetailsHandler interface for that can handle valid tenant details params +type TenantDetailsHandler interface { + Handle(TenantDetailsParams, *models.Principal) middleware.Responder +} + +// NewTenantDetails creates a new http.Handler for the tenant details operation +func NewTenantDetails(ctx *middleware.Context, handler TenantDetailsHandler) *TenantDetails { + return &TenantDetails{Context: ctx, Handler: handler} +} + +/* TenantDetails swagger:route GET /namespaces/{namespace}/tenants/{tenant} AdminAPI tenantDetails + +Tenant Details + +*/ +type TenantDetails struct { + Context *middleware.Context + Handler TenantDetailsHandler +} + +func (o *TenantDetails) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewTenantDetailsParams() + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + *r = *aCtx + } + var principal *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise + } + + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params, principal) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/restapi/operations/admin_api/tenant_details_parameters.go b/restapi/operations/admin_api/tenant_details_parameters.go new file mode 100644 index 000000000..67d97d460 --- /dev/null +++ b/restapi/operations/admin_api/tenant_details_parameters.go @@ -0,0 +1,112 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" +) + +// NewTenantDetailsParams creates a new TenantDetailsParams object +// +// There are no default values defined in the spec. +func NewTenantDetailsParams() TenantDetailsParams { + + return TenantDetailsParams{} +} + +// TenantDetailsParams contains all the bound params for the tenant details operation +// typically these are obtained from a http.Request +// +// swagger:parameters TenantDetails +type TenantDetailsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + Namespace string + /* + Required: true + In: path + */ + Tenant string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewTenantDetailsParams() beforehand. +func (o *TenantDetailsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rNamespace, rhkNamespace, _ := route.Params.GetOK("namespace") + if err := o.bindNamespace(rNamespace, rhkNamespace, route.Formats); err != nil { + res = append(res, err) + } + + rTenant, rhkTenant, _ := route.Params.GetOK("tenant") + if err := o.bindTenant(rTenant, rhkTenant, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindNamespace binds and validates parameter Namespace from path. +func (o *TenantDetailsParams) bindNamespace(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.Namespace = raw + + return nil +} + +// bindTenant binds and validates parameter Tenant from path. +func (o *TenantDetailsParams) bindTenant(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.Tenant = raw + + return nil +} diff --git a/restapi/operations/admin_api/tenant_details_responses.go b/restapi/operations/admin_api/tenant_details_responses.go new file mode 100644 index 000000000..bde73c4e7 --- /dev/null +++ b/restapi/operations/admin_api/tenant_details_responses.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/minio/console/models" +) + +// TenantDetailsOKCode is the HTTP code returned for type TenantDetailsOK +const TenantDetailsOKCode int = 200 + +/*TenantDetailsOK A successful response. + +swagger:response tenantDetailsOK +*/ +type TenantDetailsOK struct { + + /* + In: Body + */ + Payload *models.Tenant `json:"body,omitempty"` +} + +// NewTenantDetailsOK creates TenantDetailsOK with default headers values +func NewTenantDetailsOK() *TenantDetailsOK { + + return &TenantDetailsOK{} +} + +// WithPayload adds the payload to the tenant details o k response +func (o *TenantDetailsOK) WithPayload(payload *models.Tenant) *TenantDetailsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the tenant details o k response +func (o *TenantDetailsOK) SetPayload(payload *models.Tenant) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *TenantDetailsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +/*TenantDetailsDefault Generic error response. + +swagger:response tenantDetailsDefault +*/ +type TenantDetailsDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewTenantDetailsDefault creates TenantDetailsDefault with default headers values +func NewTenantDetailsDefault(code int) *TenantDetailsDefault { + if code <= 0 { + code = 500 + } + + return &TenantDetailsDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the tenant details default response +func (o *TenantDetailsDefault) WithStatusCode(code int) *TenantDetailsDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the tenant details default response +func (o *TenantDetailsDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the tenant details default response +func (o *TenantDetailsDefault) WithPayload(payload *models.Error) *TenantDetailsDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the tenant details default response +func (o *TenantDetailsDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *TenantDetailsDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/restapi/operations/admin_api/tenant_details_urlbuilder.go b/restapi/operations/admin_api/tenant_details_urlbuilder.go new file mode 100644 index 000000000..f137096d1 --- /dev/null +++ b/restapi/operations/admin_api/tenant_details_urlbuilder.go @@ -0,0 +1,124 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" +) + +// TenantDetailsURL generates an URL for the tenant details operation +type TenantDetailsURL struct { + Namespace string + Tenant string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *TenantDetailsURL) WithBasePath(bp string) *TenantDetailsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *TenantDetailsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *TenantDetailsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/namespaces/{namespace}/tenants/{tenant}" + + namespace := o.Namespace + if namespace != "" { + _path = strings.Replace(_path, "{namespace}", namespace, -1) + } else { + return nil, errors.New("namespace is required on TenantDetailsURL") + } + + tenant := o.Tenant + if tenant != "" { + _path = strings.Replace(_path, "{tenant}", tenant, -1) + } else { + return nil, errors.New("tenant is required on TenantDetailsURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *TenantDetailsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *TenantDetailsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *TenantDetailsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on TenantDetailsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on TenantDetailsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *TenantDetailsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/operations/admin_api/tenant_info.go b/restapi/operations/admin_api/tenant_info.go index f67b472ae..6fe5b8f5a 100644 --- a/restapi/operations/admin_api/tenant_info.go +++ b/restapi/operations/admin_api/tenant_info.go @@ -48,7 +48,7 @@ func NewTenantInfo(ctx *middleware.Context, handler TenantInfoHandler) *TenantIn return &TenantInfo{Context: ctx, Handler: handler} } -/* TenantInfo swagger:route GET /namespaces/{namespace}/tenants/{tenant} AdminAPI tenantInfo +/* TenantInfo swagger:route GET /namespaces/{namespace}/tenants/{tenant}/info AdminAPI tenantInfo Tenant Info diff --git a/restapi/operations/admin_api/tenant_info_responses.go b/restapi/operations/admin_api/tenant_info_responses.go index d9373f1b0..e0a20d1fc 100644 --- a/restapi/operations/admin_api/tenant_info_responses.go +++ b/restapi/operations/admin_api/tenant_info_responses.go @@ -42,7 +42,7 @@ type TenantInfoOK struct { /* In: Body */ - Payload *models.Tenant `json:"body,omitempty"` + Payload *models.AdminInfoResponse `json:"body,omitempty"` } // NewTenantInfoOK creates TenantInfoOK with default headers values @@ -52,13 +52,13 @@ func NewTenantInfoOK() *TenantInfoOK { } // WithPayload adds the payload to the tenant info o k response -func (o *TenantInfoOK) WithPayload(payload *models.Tenant) *TenantInfoOK { +func (o *TenantInfoOK) WithPayload(payload *models.AdminInfoResponse) *TenantInfoOK { o.Payload = payload return o } // SetPayload sets the payload to the tenant info o k response -func (o *TenantInfoOK) SetPayload(payload *models.Tenant) { +func (o *TenantInfoOK) SetPayload(payload *models.AdminInfoResponse) { o.Payload = payload } diff --git a/restapi/operations/admin_api/tenant_info_urlbuilder.go b/restapi/operations/admin_api/tenant_info_urlbuilder.go index 042cdc0ed..bd4330b27 100644 --- a/restapi/operations/admin_api/tenant_info_urlbuilder.go +++ b/restapi/operations/admin_api/tenant_info_urlbuilder.go @@ -58,7 +58,7 @@ func (o *TenantInfoURL) SetBasePath(bp string) { func (o *TenantInfoURL) Build() (*url.URL, error) { var _result url.URL - var _path = "/namespaces/{namespace}/tenants/{tenant}" + var _path = "/namespaces/{namespace}/tenants/{tenant}/info" namespace := o.Namespace if namespace != "" { diff --git a/restapi/operations/admin_api/tenant_widget_details.go b/restapi/operations/admin_api/tenant_widget_details.go new file mode 100644 index 000000000..75f1a8ca4 --- /dev/null +++ b/restapi/operations/admin_api/tenant_widget_details.go @@ -0,0 +1,88 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/minio/console/models" +) + +// TenantWidgetDetailsHandlerFunc turns a function with the right signature into a tenant widget details handler +type TenantWidgetDetailsHandlerFunc func(TenantWidgetDetailsParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn TenantWidgetDetailsHandlerFunc) Handle(params TenantWidgetDetailsParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// TenantWidgetDetailsHandler interface for that can handle valid tenant widget details params +type TenantWidgetDetailsHandler interface { + Handle(TenantWidgetDetailsParams, *models.Principal) middleware.Responder +} + +// NewTenantWidgetDetails creates a new http.Handler for the tenant widget details operation +func NewTenantWidgetDetails(ctx *middleware.Context, handler TenantWidgetDetailsHandler) *TenantWidgetDetails { + return &TenantWidgetDetails{Context: ctx, Handler: handler} +} + +/* TenantWidgetDetails swagger:route GET /namespaces/{namespace}/tenants/{tenant}/info/widgets/{widgetId} AdminAPI tenantWidgetDetails + +Returns information about a tenant deployment + +*/ +type TenantWidgetDetails struct { + Context *middleware.Context + Handler TenantWidgetDetailsHandler +} + +func (o *TenantWidgetDetails) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewTenantWidgetDetailsParams() + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + *r = *aCtx + } + var principal *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise + } + + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params, principal) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/restapi/operations/admin_api/tenant_widget_details_parameters.go b/restapi/operations/admin_api/tenant_widget_details_parameters.go new file mode 100644 index 000000000..2769c4bf6 --- /dev/null +++ b/restapi/operations/admin_api/tenant_widget_details_parameters.go @@ -0,0 +1,241 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewTenantWidgetDetailsParams creates a new TenantWidgetDetailsParams object +// +// There are no default values defined in the spec. +func NewTenantWidgetDetailsParams() TenantWidgetDetailsParams { + + return TenantWidgetDetailsParams{} +} + +// TenantWidgetDetailsParams contains all the bound params for the tenant widget details operation +// typically these are obtained from a http.Request +// +// swagger:parameters TenantWidgetDetails +type TenantWidgetDetailsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: query + */ + End *int64 + /* + Required: true + In: path + */ + Namespace string + /* + In: query + */ + Start *int64 + /* + In: query + */ + Step *int32 + /* + Required: true + In: path + */ + Tenant string + /* + Required: true + In: path + */ + WidgetID int32 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewTenantWidgetDetailsParams() beforehand. +func (o *TenantWidgetDetailsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qEnd, qhkEnd, _ := qs.GetOK("end") + if err := o.bindEnd(qEnd, qhkEnd, route.Formats); err != nil { + res = append(res, err) + } + + rNamespace, rhkNamespace, _ := route.Params.GetOK("namespace") + if err := o.bindNamespace(rNamespace, rhkNamespace, route.Formats); err != nil { + res = append(res, err) + } + + qStart, qhkStart, _ := qs.GetOK("start") + if err := o.bindStart(qStart, qhkStart, route.Formats); err != nil { + res = append(res, err) + } + + qStep, qhkStep, _ := qs.GetOK("step") + if err := o.bindStep(qStep, qhkStep, route.Formats); err != nil { + res = append(res, err) + } + + rTenant, rhkTenant, _ := route.Params.GetOK("tenant") + if err := o.bindTenant(rTenant, rhkTenant, route.Formats); err != nil { + res = append(res, err) + } + + rWidgetID, rhkWidgetID, _ := route.Params.GetOK("widgetId") + if err := o.bindWidgetID(rWidgetID, rhkWidgetID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindEnd binds and validates parameter End from query. +func (o *TenantWidgetDetailsParams) bindEnd(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("end", "query", "int64", raw) + } + o.End = &value + + return nil +} + +// bindNamespace binds and validates parameter Namespace from path. +func (o *TenantWidgetDetailsParams) bindNamespace(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.Namespace = raw + + return nil +} + +// bindStart binds and validates parameter Start from query. +func (o *TenantWidgetDetailsParams) bindStart(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("start", "query", "int64", raw) + } + o.Start = &value + + return nil +} + +// bindStep binds and validates parameter Step from query. +func (o *TenantWidgetDetailsParams) bindStep(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt32(raw) + if err != nil { + return errors.InvalidType("step", "query", "int32", raw) + } + o.Step = &value + + return nil +} + +// bindTenant binds and validates parameter Tenant from path. +func (o *TenantWidgetDetailsParams) bindTenant(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.Tenant = raw + + return nil +} + +// bindWidgetID binds and validates parameter WidgetID from path. +func (o *TenantWidgetDetailsParams) bindWidgetID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt32(raw) + if err != nil { + return errors.InvalidType("widgetId", "path", "int32", raw) + } + o.WidgetID = value + + return nil +} diff --git a/restapi/operations/admin_api/tenant_widget_details_responses.go b/restapi/operations/admin_api/tenant_widget_details_responses.go new file mode 100644 index 000000000..c6f9d5f0f --- /dev/null +++ b/restapi/operations/admin_api/tenant_widget_details_responses.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/minio/console/models" +) + +// TenantWidgetDetailsOKCode is the HTTP code returned for type TenantWidgetDetailsOK +const TenantWidgetDetailsOKCode int = 200 + +/*TenantWidgetDetailsOK A successful response. + +swagger:response tenantWidgetDetailsOK +*/ +type TenantWidgetDetailsOK struct { + + /* + In: Body + */ + Payload *models.WidgetDetails `json:"body,omitempty"` +} + +// NewTenantWidgetDetailsOK creates TenantWidgetDetailsOK with default headers values +func NewTenantWidgetDetailsOK() *TenantWidgetDetailsOK { + + return &TenantWidgetDetailsOK{} +} + +// WithPayload adds the payload to the tenant widget details o k response +func (o *TenantWidgetDetailsOK) WithPayload(payload *models.WidgetDetails) *TenantWidgetDetailsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the tenant widget details o k response +func (o *TenantWidgetDetailsOK) SetPayload(payload *models.WidgetDetails) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *TenantWidgetDetailsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +/*TenantWidgetDetailsDefault Generic error response. + +swagger:response tenantWidgetDetailsDefault +*/ +type TenantWidgetDetailsDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewTenantWidgetDetailsDefault creates TenantWidgetDetailsDefault with default headers values +func NewTenantWidgetDetailsDefault(code int) *TenantWidgetDetailsDefault { + if code <= 0 { + code = 500 + } + + return &TenantWidgetDetailsDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the tenant widget details default response +func (o *TenantWidgetDetailsDefault) WithStatusCode(code int) *TenantWidgetDetailsDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the tenant widget details default response +func (o *TenantWidgetDetailsDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the tenant widget details default response +func (o *TenantWidgetDetailsDefault) WithPayload(payload *models.Error) *TenantWidgetDetailsDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the tenant widget details default response +func (o *TenantWidgetDetailsDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *TenantWidgetDetailsDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/restapi/operations/admin_api/tenant_widget_details_urlbuilder.go b/restapi/operations/admin_api/tenant_widget_details_urlbuilder.go new file mode 100644 index 000000000..5f322664f --- /dev/null +++ b/restapi/operations/admin_api/tenant_widget_details_urlbuilder.go @@ -0,0 +1,166 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// TenantWidgetDetailsURL generates an URL for the tenant widget details operation +type TenantWidgetDetailsURL struct { + Namespace string + Tenant string + WidgetID int32 + + End *int64 + Start *int64 + Step *int32 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *TenantWidgetDetailsURL) WithBasePath(bp string) *TenantWidgetDetailsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *TenantWidgetDetailsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *TenantWidgetDetailsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/namespaces/{namespace}/tenants/{tenant}/info/widgets/{widgetId}" + + namespace := o.Namespace + if namespace != "" { + _path = strings.Replace(_path, "{namespace}", namespace, -1) + } else { + return nil, errors.New("namespace is required on TenantWidgetDetailsURL") + } + + tenant := o.Tenant + if tenant != "" { + _path = strings.Replace(_path, "{tenant}", tenant, -1) + } else { + return nil, errors.New("tenant is required on TenantWidgetDetailsURL") + } + + widgetID := swag.FormatInt32(o.WidgetID) + if widgetID != "" { + _path = strings.Replace(_path, "{widgetId}", widgetID, -1) + } else { + return nil, errors.New("widgetId is required on TenantWidgetDetailsURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var endQ string + if o.End != nil { + endQ = swag.FormatInt64(*o.End) + } + if endQ != "" { + qs.Set("end", endQ) + } + + var startQ string + if o.Start != nil { + startQ = swag.FormatInt64(*o.Start) + } + if startQ != "" { + qs.Set("start", startQ) + } + + var stepQ string + if o.Step != nil { + stepQ = swag.FormatInt32(*o.Step) + } + if stepQ != "" { + qs.Set("step", stepQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *TenantWidgetDetailsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *TenantWidgetDetailsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *TenantWidgetDetailsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on TenantWidgetDetailsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on TenantWidgetDetailsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *TenantWidgetDetailsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/operations/console_api.go b/restapi/operations/console_api.go index e7f24c696..b3c3e16b2 100644 --- a/restapi/operations/console_api.go +++ b/restapi/operations/console_api.go @@ -384,6 +384,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI { AdminAPITenantAddPoolHandler: admin_api.TenantAddPoolHandlerFunc(func(params admin_api.TenantAddPoolParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.TenantAddPool has not yet been implemented") }), + AdminAPITenantDetailsHandler: admin_api.TenantDetailsHandlerFunc(func(params admin_api.TenantDetailsParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation admin_api.TenantDetails has not yet been implemented") + }), AdminAPITenantInfoHandler: admin_api.TenantInfoHandlerFunc(func(params admin_api.TenantInfoParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.TenantInfo has not yet been implemented") }), @@ -396,6 +399,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI { AdminAPITenantUpdatePoolsHandler: admin_api.TenantUpdatePoolsHandlerFunc(func(params admin_api.TenantUpdatePoolsParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.TenantUpdatePools has not yet been implemented") }), + AdminAPITenantWidgetDetailsHandler: admin_api.TenantWidgetDetailsHandlerFunc(func(params admin_api.TenantWidgetDetailsParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation admin_api.TenantWidgetDetails has not yet been implemented") + }), AdminAPITiersListHandler: admin_api.TiersListHandlerFunc(func(params admin_api.TiersListParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.TiersList has not yet been implemented") }), @@ -681,6 +687,8 @@ type ConsoleAPI struct { AdminAPISubscriptionValidateHandler admin_api.SubscriptionValidateHandler // AdminAPITenantAddPoolHandler sets the operation handler for the tenant add pool operation AdminAPITenantAddPoolHandler admin_api.TenantAddPoolHandler + // AdminAPITenantDetailsHandler sets the operation handler for the tenant details operation + AdminAPITenantDetailsHandler admin_api.TenantDetailsHandler // AdminAPITenantInfoHandler sets the operation handler for the tenant info operation AdminAPITenantInfoHandler admin_api.TenantInfoHandler // AdminAPITenantUpdateCertificateHandler sets the operation handler for the tenant update certificate operation @@ -689,6 +697,8 @@ type ConsoleAPI struct { AdminAPITenantUpdateEncryptionHandler admin_api.TenantUpdateEncryptionHandler // AdminAPITenantUpdatePoolsHandler sets the operation handler for the tenant update pools operation AdminAPITenantUpdatePoolsHandler admin_api.TenantUpdatePoolsHandler + // AdminAPITenantWidgetDetailsHandler sets the operation handler for the tenant widget details operation + AdminAPITenantWidgetDetailsHandler admin_api.TenantWidgetDetailsHandler // AdminAPITiersListHandler sets the operation handler for the tiers list operation AdminAPITiersListHandler admin_api.TiersListHandler // UserAPIUpdateBucketLifecycleHandler sets the operation handler for the update bucket lifecycle operation @@ -1106,6 +1116,9 @@ func (o *ConsoleAPI) Validate() error { if o.AdminAPITenantAddPoolHandler == nil { unregistered = append(unregistered, "admin_api.TenantAddPoolHandler") } + if o.AdminAPITenantDetailsHandler == nil { + unregistered = append(unregistered, "admin_api.TenantDetailsHandler") + } if o.AdminAPITenantInfoHandler == nil { unregistered = append(unregistered, "admin_api.TenantInfoHandler") } @@ -1118,6 +1131,9 @@ func (o *ConsoleAPI) Validate() error { if o.AdminAPITenantUpdatePoolsHandler == nil { unregistered = append(unregistered, "admin_api.TenantUpdatePoolsHandler") } + if o.AdminAPITenantWidgetDetailsHandler == nil { + unregistered = append(unregistered, "admin_api.TenantWidgetDetailsHandler") + } if o.AdminAPITiersListHandler == nil { unregistered = append(unregistered, "admin_api.TiersListHandler") } @@ -1665,7 +1681,11 @@ func (o *ConsoleAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } - o.handlers["GET"]["/namespaces/{namespace}/tenants/{tenant}"] = admin_api.NewTenantInfo(o.context, o.AdminAPITenantInfoHandler) + o.handlers["GET"]["/namespaces/{namespace}/tenants/{tenant}"] = admin_api.NewTenantDetails(o.context, o.AdminAPITenantDetailsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/namespaces/{namespace}/tenants/{tenant}/info"] = admin_api.NewTenantInfo(o.context, o.AdminAPITenantInfoHandler) if o.handlers["PUT"] == nil { o.handlers["PUT"] = make(map[string]http.Handler) } @@ -1681,6 +1701,10 @@ func (o *ConsoleAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/namespaces/{namespace}/tenants/{tenant}/info/widgets/{widgetId}"] = admin_api.NewTenantWidgetDetails(o.context, o.AdminAPITenantWidgetDetailsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/admin/tiers"] = admin_api.NewTiersList(o.context, o.AdminAPITiersListHandler) if o.handlers["PUT"] == nil { o.handlers["PUT"] = make(map[string]http.Handler) diff --git a/restapi/operator_info.go b/restapi/operator_info.go new file mode 100644 index 000000000..e58f42c4b --- /dev/null +++ b/restapi/operator_info.go @@ -0,0 +1,138 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package restapi + +import ( + "fmt" + + "github.com/go-openapi/runtime/middleware" + "github.com/minio/console/cluster" + "github.com/minio/console/models" + "github.com/minio/console/restapi/operations" + "github.com/minio/console/restapi/operations/admin_api" + miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" +) + +func registerOperatorTenantInfoHandlers(api *operations.ConsoleAPI) { + // return usage stats + api.AdminAPITenantInfoHandler = admin_api.TenantInfoHandlerFunc(func(params admin_api.TenantInfoParams, session *models.Principal) middleware.Responder { + infoResp, err := getTenantInfoResponse(session, params) + if err != nil { + return admin_api.NewTenantInfoDefault(int(err.Code)).WithPayload(err) + } + return admin_api.NewTenantInfoOK().WithPayload(infoResp) + }) + // return single widget results + api.AdminAPITenantWidgetDetailsHandler = admin_api.TenantWidgetDetailsHandlerFunc(func(params admin_api.TenantWidgetDetailsParams, session *models.Principal) middleware.Responder { + infoResp, err := getTenantWidgetResponse(session, params) + if err != nil { + return admin_api.NewDashboardWidgetDetailsDefault(int(err.Code)).WithPayload(err) + } + return admin_api.NewDashboardWidgetDetailsOK().WithPayload(infoResp) + }) +} + +func getTenantInfoResponse(session *models.Principal, params admin_api.TenantInfoParams) (*models.AdminInfoResponse, *models.Error) { + opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken) + if err != nil { + return nil, prepareError(err, errorUnableToGetTenantUsage) + } + clientSet, err := cluster.K8sClient(session.STSSessionToken) + if err != nil { + return nil, prepareError(err, errorUnableToGetTenantUsage) + } + + opClient := &operatorClient{ + client: opClientClientSet, + } + k8sClient := &k8sClient{ + client: clientSet, + } + + tenant, err := getTenant(params.HTTPRequest.Context(), opClient, params.Namespace, params.Tenant) + if err != nil { + return nil, prepareError(err, errorUnableToGetTenantUsage) + } + tenant.EnsureDefaults() + + svcURL := GetTenantServiceURL(tenant) + // getTenantAdminClient will use all certificates under ~/.console/certs/CAs to trust the TLS connections with MinIO tenants + mAdmin, err := getTenantAdminClient( + params.HTTPRequest.Context(), + k8sClient, + tenant, + svcURL, + ) + if err != nil { + return nil, prepareError(err, errorUnableToGetTenantUsage) + } + + prometheusURL := getPrometheusURLForTenant(tenant) + + sessionResp, err2 := getUsageWidgetsForDeployment(prometheusURL, mAdmin) + if err2 != nil { + return nil, err2 + } + + return sessionResp, nil +} + +func getTenantWidgetResponse(session *models.Principal, params admin_api.TenantWidgetDetailsParams) (*models.WidgetDetails, *models.Error) { + + opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken) + if err != nil { + return nil, prepareError(err, errorUnableToGetTenantUsage) + } + + opClient := &operatorClient{ + client: opClientClientSet, + } + + tenant, err := getTenant(params.HTTPRequest.Context(), opClient, params.Namespace, params.Tenant) + if err != nil { + return nil, prepareError(err, errorUnableToGetTenantUsage) + } + tenant.EnsureDefaults() + + prometheusURL := getPrometheusURLForTenant(tenant) + prometheusJobID := getPrometheusJobID() + // check for special values + if tenant.HasConsoleEnabled() { + for _, env := range tenant.Spec.Console.Env { + if env.Name == "CONSOLE_PROMETHEUS_JOB_ID" { + prometheusJobID = env.Value + } + } + + } + + return getWidgetDetails(prometheusURL, prometheusJobID, params.WidgetID, params.Step, params.Start, params.End) +} + +func getPrometheusURLForTenant(tenant *miniov2.Tenant) string { + prometheusURL := fmt.Sprintf("http://%s.%s:%d", tenant.PrometheusHLServiceName(), tenant.Namespace, miniov2.PrometheusAPIPort) + // check for special values + if tenant.HasConsoleEnabled() { + for _, env := range tenant.Spec.Console.Env { + if env.Name == "CONSOLE_PROMETHEUS_URL" { + prometheusURL = env.Value + } + } + + } + return prometheusURL +} diff --git a/swagger.yml b/swagger.yml index dc5a72112..b6d29aa74 100644 --- a/swagger.yml +++ b/swagger.yml @@ -2229,8 +2229,8 @@ paths: /namespaces/{namespace}/tenants/{tenant}: get: - summary: Tenant Info - operationId: TenantInfo + summary: Tenant Details + operationId: TenantDetails parameters: - name: namespace in: path @@ -2303,6 +2303,70 @@ paths: $ref: "#/definitions/error" tags: - AdminAPI + /namespaces/{namespace}/tenants/{tenant}/info: + get: + summary: Tenant Info + operationId: TenantInfo + parameters: + - name: namespace + in: path + required: true + type: string + - name: tenant + in: path + required: true + type: string + responses: + 200: + description: A successful response. + schema: + $ref: "#/definitions/adminInfoResponse" + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - AdminAPI + + /namespaces/{namespace}/tenants/{tenant}/info/widgets/{widgetId}: + get: + summary: Returns information about a tenant deployment + operationId: TenantWidgetDetails + parameters: + - name: namespace + in: path + required: true + type: string + - name: tenant + in: path + required: true + type: string + - name: widgetId + in: path + type: integer + format: int32 + required: true + - name: start + in: query + type: integer + - name: end + in: query + type: integer + - name: step + in: query + type: integer + format: int32 + responses: + 200: + description: A successful response. + schema: + $ref: "#/definitions/widgetDetails" + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - AdminAPI /namespaces/{namespace}/tenants/{tenant}/pools: post: