Files
object-browser/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx
Lenin Alevski 8540168133 Identity Provider screen for TenantDetails (#1809)
- fixing encryption page styles
- removing extra fields on gemalto configuration
- backend endpoints for tenant identity provider details
- force restart tenant pods when identity provider configuration change
- force restart tenant pods when tls certificates change
- existing tls secrets are not deleted from tenant namespace, just removed from the tenant

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-04-07 18:40:09 -07:00

637 lines
19 KiB
TypeScript

// 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, useEffect, useState } from "react";
import { connect } from "react-redux";
import { Link, Redirect, Route, Router, Switch } from "react-router-dom";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import get from "lodash/get";
import Grid from "@mui/material/Grid";
import { setErrorSnackMessage, setSnackBarMessage } from "../../../../actions";
import {
setTenantDetailsLoad,
setTenantInfo,
setTenantName,
setTenantTab,
} from "../actions";
import { ITenant } from "../ListTenants/types";
import {
containerForHeader,
pageContentStyles,
tenantDetailsStyles,
} from "../../Common/FormComponents/common/styleLibrary";
import { AppState } from "../../../../store";
import { ErrorResponseHandler } from "../../../../common/types";
import api from "../../../../common/api";
import PageHeader from "../../Common/PageHeader/PageHeader";
import { CircleIcon, MinIOTierIconXs, TrashIcon } from "../../../../icons";
import { niceBytes } from "../../../../common/utils";
import ScreenTitle from "../../Common/ScreenTitle/ScreenTitle";
import EditIcon from "../../../../icons/EditIcon";
import RefreshIcon from "../../../../icons/RefreshIcon";
import TenantsIcon from "../../../../icons/TenantsIcon";
import PageLayout from "../../Common/Layout/PageLayout";
import BackLink from "../../../../common/BackLink";
import VerticalTabs from "../../Common/VerticalTabs/VerticalTabs";
import BoxIconButton from "../../Common/BoxIconButton/BoxIconButton";
import withSuspense from "../../Common/Components/withSuspense";
import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
import { tenantIsOnline } from "../ListTenants/utils";
const TenantYAML = withSuspense(React.lazy(() => import("./TenantYAML")));
const TenantSummary = withSuspense(React.lazy(() => import("./TenantSummary")));
const TenantLicense = withSuspense(React.lazy(() => import("./TenantLicense")));
const PoolsSummary = withSuspense(React.lazy(() => import("./PoolsSummary")));
const PodsSummary = withSuspense(React.lazy(() => import("./PodsSummary")));
const TenantLogging = withSuspense(React.lazy(() => import("./TenantLogging")));
const TenantEvents = withSuspense(React.lazy(() => import("./TenantEvents")));
const VolumesSummary = withSuspense(
React.lazy(() => import("./VolumesSummary"))
);
const TenantMetrics = withSuspense(React.lazy(() => import("./TenantMetrics")));
const TenantTrace = withSuspense(React.lazy(() => import("./TenantTrace")));
const TenantVolumes = withSuspense(
React.lazy(() => import("./pvcs/TenantVolumes"))
);
const TenantIdentityProvider = withSuspense(
React.lazy(() => import("./TenantIdentityProvider"))
);
const TenantSecurity = withSuspense(
React.lazy(() => import("./TenantSecurity"))
);
const TenantEncryption = withSuspense(
React.lazy(() => import("./TenantEncryption"))
);
const DeleteTenant = withSuspense(
React.lazy(() => import("../ListTenants/DeleteTenant"))
);
const PodDetails = withSuspense(React.lazy(() => import("./pods/PodDetails")));
const TenantMonitoring = withSuspense(
React.lazy(() => import("./TenantMonitoring"))
);
interface ITenantDetailsProps {
classes: any;
match: any;
history: any;
loadingTenant: boolean;
currentTab: string;
selectedTenant: string;
tenantInfo: ITenant | null;
selectedNamespace: string;
setErrorSnackMessage: typeof setErrorSnackMessage;
setSnackBarMessage: typeof setSnackBarMessage;
setTenantDetailsLoad: typeof setTenantDetailsLoad;
setTenantName: typeof setTenantName;
setTenantInfo: typeof setTenantInfo;
setTenantTab: typeof setTenantTab;
}
const styles = (theme: Theme) =>
createStyles({
...tenantDetailsStyles,
pageContainer: {
border: "1px solid #EAEAEA",
width: "100%",
height: "100%",
},
contentSpacer: {
...pageContentStyles.contentSpacer,
minHeight: 400,
},
redState: {
color: theme.palette.error.main,
"& .min-icon": {
width: 16,
height: 16,
},
},
yellowState: {
color: theme.palette.warning.main,
"& .min-icon": {
width: 16,
height: 16,
},
},
greenState: {
color: theme.palette.success.main,
"& .min-icon": {
width: 16,
height: 16,
},
},
greyState: {
color: "grey",
"& .min-icon": {
width: 16,
height: 16,
},
},
healthStatusIcon: {
position: "relative",
fontSize: 10,
left: 26,
height: 10,
top: 4,
},
...containerForHeader(theme.spacing(4)),
tenantActionButton: {
"& span": {
fontSize: 14,
"@media (max-width: 900px)": {
display: "none",
},
},
"& .min-icon": {
width: 12,
marginLeft: 5,
"@media (max-width: 900px)": {
width: 16,
marginLeft: 0,
},
},
},
deleteBtn: {
color: "#f44336",
border: "1px solid rgba(244, 67, 54, 0.5)",
},
});
const TenantDetails = ({
classes,
match,
history,
loadingTenant,
selectedTenant,
tenantInfo,
selectedNamespace,
setErrorSnackMessage,
setSnackBarMessage,
setTenantDetailsLoad,
setTenantName,
setTenantInfo,
}: ITenantDetailsProps) => {
const [yamlScreenOpen, setYamlScreenOpen] = useState<boolean>(false);
const tenantName = match.params["tenantName"];
const tenantNamespace = match.params["tenantNamespace"];
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
useEffect(() => {
if (!loadingTenant) {
if (
tenantName !== selectedTenant ||
tenantNamespace !== selectedNamespace
) {
setTenantName(tenantName, tenantNamespace);
setTenantDetailsLoad(true);
}
}
}, [
loadingTenant,
selectedTenant,
selectedNamespace,
setTenantDetailsLoad,
setTenantInfo,
setTenantName,
tenantName,
tenantNamespace,
]);
useEffect(() => {
if (loadingTenant) {
api
.invoke(
"GET",
`/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}`
)
.then((res: ITenant) => {
// add computed fields
const resPools = !res.pools ? [] : res.pools;
let totalInstances = 0;
let totalVolumes = 0;
let poolNamedIndex = 0;
for (let pool of resPools) {
const cap =
pool.volumes_per_server *
pool.servers *
pool.volume_configuration.size;
pool.label = `pool-${poolNamedIndex}`;
if (pool.name === undefined || pool.name === "") {
pool.name = pool.label;
}
pool.capacity = niceBytes(cap + "");
pool.volumes = pool.servers * pool.volumes_per_server;
totalInstances += pool.servers;
totalVolumes += pool.volumes;
poolNamedIndex += 1;
}
res.total_instances = totalInstances;
res.total_volumes = totalVolumes;
setTenantInfo(res);
setTenantDetailsLoad(false);
})
.catch((err: ErrorResponseHandler) => {
setErrorSnackMessage(err);
setTenantDetailsLoad(false);
});
}
}, [
loadingTenant,
tenantNamespace,
tenantName,
setTenantInfo,
setTenantDetailsLoad,
setErrorSnackMessage,
]);
const path = get(match, "path", "/");
const splitSections = path.split("/");
let highlightedTab = splitSections[splitSections.length - 1] || "summary";
if (highlightedTab === ":podName" || highlightedTab === "pods") {
// It has SUB Route
highlightedTab = "pods";
}
const [activeTab, setActiveTab] = useState(highlightedTab);
useEffect(() => {
setActiveTab(highlightedTab);
}, [highlightedTab]);
const editYaml = () => {
setYamlScreenOpen(true);
};
const closeYAMLModalAndRefresh = () => {
setYamlScreenOpen(false);
setTenantDetailsLoad(true);
};
const getRoutePath = (newValue: string) => {
return `/namespaces/${tenantNamespace}/tenants/${tenantName}/${newValue}`;
};
const confirmDeleteTenant = () => {
setDeleteOpen(true);
};
const closeDeleteModalAndRefresh = (reloadData: boolean) => {
setDeleteOpen(false);
if (reloadData) {
setSnackBarMessage("Tenant Deleted");
history.push(`/tenants`);
}
};
const healthStatusToClass = (health_status: string) => {
return health_status === "red"
? classes.redState
: health_status === "yellow"
? classes.yellowState
: health_status === "green"
? classes.greenState
: classes.greyState;
};
return (
<Fragment>
{yamlScreenOpen && (
<TenantYAML
open={yamlScreenOpen}
closeModalAndRefresh={closeYAMLModalAndRefresh}
tenant={tenantName}
namespace={tenantNamespace}
/>
)}
{deleteOpen && tenantInfo !== null && (
<DeleteTenant
deleteOpen={deleteOpen}
selectedTenant={tenantInfo}
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
/>
)}
<PageHeader
label={
<Fragment>
<BackLink to={IAM_PAGES.TENANTS} label="Tenants" />
</Fragment>
}
actions={<React.Fragment />}
/>
<PageLayout className={classes.pageContainer}>
<Grid item xs={12}>
<ScreenTitle
icon={
<Fragment>
<div className={classes.healthStatusIcon}>
{tenantInfo && tenantInfo.status && (
<span
className={healthStatusToClass(
tenantInfo.status.health_status
)}
>
<CircleIcon />
</span>
)}
</div>
<TenantsIcon />
</Fragment>
}
title={match.params["tenantName"]}
subTitle={
<Fragment>
Namespace: {tenantNamespace} / Capacity:{" "}
{niceBytes((tenantInfo?.total_size || 0).toString(10))}
</Fragment>
}
actions={
<div>
<BoxIconButton
tooltip={"Delete"}
variant="outlined"
aria-label="Delete"
onClick={() => {
confirmDeleteTenant();
}}
color="secondary"
classes={{
root: `${classes.tenantActionButton} ${classes.deleteBtn}`,
}}
size="large"
>
<span>Delete</span> <TrashIcon />
</BoxIconButton>
<BoxIconButton
classes={{
root: classes.tenantActionButton,
}}
tooltip={"Edit YAML"}
color="primary"
variant="outlined"
aria-label="Edit YAML"
onClick={() => {
editYaml();
}}
size="large"
>
<span>YAML</span>
<EditIcon />
</BoxIconButton>
<BoxIconButton
classes={{
root: classes.tenantActionButton,
}}
tooltip={"Management Console"}
onClick={() => {
history.push(
`/namespaces/${tenantNamespace}/tenants/${tenantName}/hop`
);
}}
disabled={!tenantInfo || !tenantIsOnline(tenantInfo)}
variant={"outlined"}
color="primary"
>
<span>Console</span>{" "}
<MinIOTierIconXs style={{ height: 16 }} />
</BoxIconButton>
<BoxIconButton
classes={{
root: classes.tenantActionButton,
}}
tooltip={"Refresh"}
color="primary"
variant="outlined"
aria-label="Refresh List"
onClick={() => {
setTenantDetailsLoad(true);
}}
>
<span>Refresh</span> <RefreshIcon />
</BoxIconButton>
</div>
}
/>
</Grid>
<VerticalTabs
selectedTab={activeTab}
isRouteTabs
routes={
<div className={classes.contentSpacer}>
<Router history={history}>
<Switch>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_SUMMARY}
component={TenantSummary}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_METRICS}
component={TenantMetrics}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_TRACE}
component={TenantTrace}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_IDENTITY_PROVIDER}
component={TenantIdentityProvider}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_SECURITY}
component={TenantSecurity}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_ENCRYPTION}
component={TenantEncryption}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_POOLS}
component={PoolsSummary}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_PODS}
component={PodDetails}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_PODS_LIST}
component={PodsSummary}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_PVCS}
component={TenantVolumes}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_VOLUMES}
component={VolumesSummary}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_LICENSE}
component={TenantLicense}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_MONITORING}
component={TenantMonitoring}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_LOGGING}
component={TenantLogging}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT_EVENTS}
component={TenantEvents}
/>
<Route
path={IAM_PAGES.NAMESPACE_TENANT}
component={() => (
<Redirect
to={`/namespaces/${tenantNamespace}/tenants/${tenantName}/summary`}
/>
)}
/>
</Switch>
</Router>
</div>
}
>
{{
tabConfig: {
label: "Summary",
value: "summary",
component: Link,
to: getRoutePath("summary"),
},
}}
{{
tabConfig: {
label: "Metrics",
value: "metrics",
component: Link,
to: getRoutePath("metrics"),
},
}}
{{
tabConfig: {
label: "Identity Provider",
value: "identity-provider",
component: Link,
to: getRoutePath("identity-provider"),
},
}}
{{
tabConfig: {
label: "Security",
value: "security",
component: Link,
to: getRoutePath("security"),
},
}}
{{
tabConfig: {
label: "Encryption",
value: "encryption",
component: Link,
to: getRoutePath("encryption"),
},
}}
{{
tabConfig: {
label: "Pools",
value: "pools",
component: Link,
to: getRoutePath("pools"),
},
}}
{{
tabConfig: {
label: "Pods",
value: "pods",
component: Link,
to: getRoutePath("pods"),
},
}}
{{
tabConfig: {
label: "Monitoring",
value: "monitoring",
component: Link,
to: getRoutePath("monitoring"),
},
}}
{{
tabConfig: {
label: "Logging",
value: "logging",
component: Link,
to: getRoutePath("logging"),
},
}}
{{
tabConfig: {
label: "Volumes",
value: "volumes",
component: Link,
to: getRoutePath("volumes"),
},
}}
{{
tabConfig: {
label: "Events",
value: "events",
component: Link,
to: getRoutePath("events"),
},
}}
{{
tabConfig: {
label: "License",
value: "license",
component: Link,
to: getRoutePath("license"),
},
}}
</VerticalTabs>
</PageLayout>
</Fragment>
);
};
const mapState = (state: AppState) => ({
loadingTenant: state.tenants.tenantDetails.loadingTenant,
selectedTenant: state.tenants.tenantDetails.currentTenant,
selectedNamespace: state.tenants.tenantDetails.currentNamespace,
tenantInfo: state.tenants.tenantDetails.tenantInfo,
});
const connector = connect(mapState, {
setErrorSnackMessage,
setSnackBarMessage,
setTenantDetailsLoad,
setTenantName,
setTenantInfo,
});
export default withStyles(styles)(connector(TenantDetails));