diff --git a/pkg/acl/endpoints.go b/pkg/acl/endpoints.go index 40332d427..0da209c6b 100644 --- a/pkg/acl/endpoints.go +++ b/pkg/acl/endpoints.go @@ -44,6 +44,10 @@ var ( tenants = "/tenants" tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName" podsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName" + tenantsDetailSummary = "/namespaces/:tenantNamespace/tenants/:tenantName/summary" + tenantsDetailPods = "/namespaces/:tenantNamespace/tenants/:tenantName/pods" + tenantsDetailPools = "/namespaces/:tenantNamespace/tenants/:tenantName/pools" + tenantsDetailLicense = "/namespaces/:tenantNamespace/tenants/:tenantName/license" storage = "/storage" storageVolumes = "/storage/volumes" storageDrives = "/storage/drives" @@ -318,13 +322,17 @@ var endpointRules = map[string]ConfigurationActionSet{ // operatorRules contains the mapping between endpoints and ActionSets for operator only mode var operatorRules = map[string]ConfigurationActionSet{ - tenants: tenantsActionSet, - tenantsDetail: tenantsActionSet, - podsDetail: tenantsActionSet, - storage: storageActionSet, - storageDrives: storageActionSet, - storageVolumes: storageActionSet, - license: licenseActionSet, + tenants: tenantsActionSet, + tenantsDetail: tenantsActionSet, + tenantsDetailSummary: tenantsActionSet, + tenantsDetailPods: tenantsActionSet, + tenantsDetailPools: tenantsActionSet, + tenantsDetailLicense: tenantsActionSet, + podsDetail: tenantsActionSet, + storage: storageActionSet, + storageDrives: storageActionSet, + storageVolumes: storageActionSet, + license: licenseActionSet, } // operatorOnly ENV variable diff --git a/pkg/acl/endpoints_test.go b/pkg/acl/endpoints_test.go index 64d8f90bf..5c205f5e8 100644 --- a/pkg/acl/endpoints_test.go +++ b/pkg/acl/endpoints_test.go @@ -116,7 +116,7 @@ func TestOperatorOnlyEndpoints(t *testing.T) { "admin:*", }, }, - want: 7, + want: 11, }, { name: "Operator Only - all s3 endpoints", @@ -125,7 +125,7 @@ func TestOperatorOnlyEndpoints(t *testing.T) { "s3:*", }, }, - want: 7, + want: 11, }, { name: "Operator Only - all admin and s3 endpoints", @@ -135,14 +135,14 @@ func TestOperatorOnlyEndpoints(t *testing.T) { "s3:*", }, }, - want: 7, + want: 11, }, { name: "Operator Only - default endpoints", args: args{ []string{}, }, - want: 7, + want: 11, }, } diff --git a/portal-ui/src/screens/Console/Account/ChangeUserPasswordModal.tsx b/portal-ui/src/screens/Console/Account/ChangeUserPasswordModal.tsx index 729d46dff..02a24a3ef 100644 --- a/portal-ui/src/screens/Console/Account/ChangeUserPasswordModal.tsx +++ b/portal-ui/src/screens/Console/Account/ChangeUserPasswordModal.tsx @@ -29,7 +29,7 @@ import { import { ChangeUserPasswordRequest } from "../Buckets/types"; import api from "../../../common/api"; import { setModalErrorSnackMessage } from "../../../actions"; -import { User, UsersList } from "../Users/types"; +import { User } from "../Users/types"; const styles = (theme: Theme) => createStyles({ @@ -143,7 +143,7 @@ const ChangeUserPassword = ({ color="primary" disabled={ loading || - !(reNewPassword.length > 0 && newPassword == reNewPassword) + !(reNewPassword.length > 0 && newPassword === reNewPassword) } > Save diff --git a/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts b/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts index e7032e77a..8d3c11a6a 100644 --- a/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts +++ b/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts @@ -593,3 +593,82 @@ export const hrClass = { backgroundColor: "transparent" as const, }, }; + +export const tenantDetailsStyles = { + buttonContainer: { + textAlign: "right" as const, + }, + multiContainer: { + display: "flex" as const, + alignItems: "center" as const, + justifyContent: "flex-start" as const, + }, + sizeFactorContainer: { + marginLeft: 8, + }, + containerHeader: { + display: "flex" as const, + justifyContent: "space-between" as const, + }, + paperContainer: { + padding: "15px 15px 15px 50px", + }, + infoGrid: { + display: "grid" as const, + gridTemplateColumns: "auto auto auto auto", + gridGap: 8, + "& div": { + display: "flex" as const, + alignItems: "center" as const, + }, + "& div:nth-child(odd)": { + justifyContent: "flex-end" as const, + fontWeight: 700, + }, + "& div:nth-child(2n)": { + paddingRight: 35, + }, + }, + masterActions: { + width: "25%", + minWidth: "120px", + "& div": { + margin: "5px 0px", + }, + }, + updateButton: { + backgroundColor: "transparent" as const, + border: 0, + padding: "0 6px", + cursor: "pointer" as const, + "&:focus, &:active": { + outline: "none", + }, + "& svg": { + height: 12, + }, + }, + poolLabel: { + color: "#666666", + }, + titleCol: { + fontWeight: 700, + }, + breadcrumLink: { + textDecoration: "none", + color: "black", + }, + healthCol: { + fontWeight: 700, + paddingRight: "10px", + }, + ...modalBasic, + ...actionsTray, + ...buttonsStyles, + ...searchField, + ...hrClass, + actionsTray: { + ...actionsTray.actionsTray, + padding: "15px 0 0", + }, +}; diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index e07914150..8b4fb5eff 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -358,6 +358,22 @@ const Console = ({ component: PodDetails, path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName", }, + { + component: TenantDetails, + path: "/namespaces/:tenantNamespace/tenants/:tenantName/summary", + }, + { + component: TenantDetails, + path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods", + }, + { + component: TenantDetails, + path: "/namespaces/:tenantNamespace/tenants/:tenantName/pools", + }, + { + component: TenantDetails, + path: "/namespaces/:tenantNamespace/tenants/:tenantName/license", + }, { component: License, path: "/license", diff --git a/portal-ui/src/screens/Console/License/LicenseModal.tsx b/portal-ui/src/screens/Console/License/LicenseModal.tsx index bd7a8ae0b..6f5a10936 100644 --- a/portal-ui/src/screens/Console/License/LicenseModal.tsx +++ b/portal-ui/src/screens/Console/License/LicenseModal.tsx @@ -65,7 +65,7 @@ const LicenseModal = ({ classes, open, closeModal }: ILicenseModalProps) => {

Copyright © 2007 Free Software Foundation, Inc. < - + https://fsf.org/ > @@ -904,7 +904,7 @@ const LicenseModal = ({ classes, open, closeModal }: ILicenseModalProps) => { school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see < - + https://www.gnu.org/licenses/ >. diff --git a/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx b/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx index 6fd98d0a8..acb434fdd 100644 --- a/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx +++ b/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx @@ -216,13 +216,16 @@ const ListTenants = ({ }; const healthStatusToClass = (health_status: string) => { - return health_status == "red" - ? classes.redState - : health_status == "yellow" - ? classes.yellowState - : health_status == "green" - ? classes.greenState - : classes.greyState; + switch(health_status) { + case "red": + return classes.redState; + case "yellow": + return classes.yellowState; + case "green": + return classes.greenState; + default: + return classes.greyState; + } }; return ( diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/PodsSummary.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/PodsSummary.tsx new file mode 100644 index 000000000..fc75ea56d --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/PodsSummary.tsx @@ -0,0 +1,111 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { Fragment, useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import { + containerForHeader, + tenantDetailsStyles, +} from "../../Common/FormComponents/common/styleLibrary"; +import { niceDays } from "../../../../common/utils"; +import { IPodListElement } from "../ListTenants/types"; +import { setErrorSnackMessage } from "../../../../actions"; +import api from "../../../../common/api"; +import TableWrapper from "../../Common/TableWrapper/TableWrapper"; + +interface IPodsSummary { + match: any; + history: any; +} + +const styles = (theme: Theme) => + createStyles({ + ...tenantDetailsStyles, + ...containerForHeader(theme.spacing(4)), + }); + +const PodsSummary = ({ match, history }: IPodsSummary) => { + const [pods, setPods] = useState([]); + const [loadingPods, setLoadingPods] = useState(true); + + const tenantName = match.params["tenantName"]; + const tenantNamespace = match.params["tenantNamespace"]; + + const podViewAction = (pod: IPodListElement) => { + history.push( + `/namespaces/${tenantNamespace}/tenants/${tenantName}/pods/${pod.name}` + ); + return; + }; + const podTableActions = [{ type: "view", onClick: podViewAction }]; + + useEffect(() => { + if (loadingPods) { + api + .invoke( + "GET", + `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/pods` + ) + .then((result: IPodListElement[]) => { + for (let i = 0; i < result.length; i++) { + let currentTime = new Date().getSeconds(); + result[i].time = niceDays( + (currentTime - parseInt(result[i].timeCreated)).toString() + ); + } + setPods(result); + setLoadingPods(false); + }) + .catch((err) => { + setErrorSnackMessage("Error loading pods"); + }); + } + }, [loadingPods, tenantName, tenantNamespace]); + + return ( + +
+ { + return input != null ? input : 0; + }, + }, + { label: "Node", elementKey: "node" }, + ]} + isLoading={loadingPods} + records={pods} + itemActions={podTableActions} + entityName="Servers" + idField="name" + /> +
+ ); +}; + +const connector = connect(null, { + setErrorSnackMessage, +}); + +export default withStyles(styles)(connector(PodsSummary)); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/PoolsSummary.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/PoolsSummary.tsx new file mode 100644 index 000000000..ff27a76a9 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/PoolsSummary.tsx @@ -0,0 +1,170 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { Fragment, useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import { + containerForHeader, + tenantDetailsStyles, +} from "../../Common/FormComponents/common/styleLibrary"; +import { Button, TextField } from "@material-ui/core"; +import Grid from "@material-ui/core/Grid"; +import { CreateIcon } from "../../../../icons"; +import { IPool, ITenant } from "../ListTenants/types"; +import { setErrorSnackMessage } from "../../../../actions"; +import TableWrapper from "../../Common/TableWrapper/TableWrapper"; +import AddPoolModal from "./AddPoolModal"; +import InputAdornment from "@material-ui/core/InputAdornment"; +import SearchIcon from "@material-ui/icons/Search"; +import { AppState } from "../../../../store"; +import { setTenantDetailsLoad } from "../actions"; + +interface IPoolsSummary { + classes: any; + tenant: ITenant | null; + loadingTenant: boolean; + setErrorSnackMessage: typeof setErrorSnackMessage; + setTenantDetailsLoad: typeof setTenantDetailsLoad; +} + +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", + }, + ...containerForHeader(theme.spacing(4)), + }); + +const PoolsSummary = ({ + classes, + tenant, + loadingTenant, + setTenantDetailsLoad +}: IPoolsSummary) => { + const [pools, setPools] = useState([]); + const [addPoolOpen, setAddPool] = useState(false); + const [filter, setFilter] = useState(""); + + useEffect(() => { + if(tenant) { + const resPools = !tenant.pools ? [] : tenant.pools; + setPools(resPools); + } + }, [tenant]); + + const onClosePoolAndRefresh = (reload: boolean) => { + setAddPool(false); + + if (reload) { + setTenantDetailsLoad(true); + } + }; + + const filteredPools = pools.filter((pool) => { + if (pool.name.toLowerCase().includes(filter.toLowerCase())) { + return true; + } + + return false; + }); + + return ( + + {addPoolOpen && tenant !== null && ( + + )} + + + { + setFilter(event.target.value); + }} + InputProps={{ + disableUnderline: true, + startAdornment: ( + + + + ), + }} + /> + + + +
+
+ + + + +
+
+ ); +}; + +const mapState = (state: AppState) => ({ + loadingTenant: state.tenants.tenantDetails.loadingTenant, + selectedTenant: state.tenants.tenantDetails.currentTenant, + tenant: state.tenants.tenantDetails.tenantInfo, +}); + +const connector = connect(mapState, { + setErrorSnackMessage, + setTenantDetailsLoad, +}); + +export default withStyles(styles)(connector(PoolsSummary)); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx index c29622268..2ec57c61d 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx @@ -16,124 +16,53 @@ import React, { Fragment, useEffect, useState } from "react"; import { connect } from "react-redux"; -import get from "lodash/get"; +import { Link, Redirect, Route, Router, Switch } from "react-router-dom"; import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; -import { - actionsTray, - buttonsStyles, - containerForHeader, - hrClass, - modalBasic, - searchField, -} from "../../Common/FormComponents/common/styleLibrary"; +import { IconButton, Menu, MenuItem } from "@material-ui/core"; +import get from 'lodash/get'; import Grid from "@material-ui/core/Grid"; -import { - Button, - IconButton, - Menu, - MenuItem, - TextField, -} from "@material-ui/core"; import Tabs from "@material-ui/core/Tabs"; import Tab from "@material-ui/core/Tab"; -import { CreateIcon } from "../../../../icons"; -import TableWrapper from "../../Common/TableWrapper/TableWrapper"; -import Paper from "@material-ui/core/Paper"; -import { niceBytes, niceDays } from "../../../../common/utils"; -import AddPoolModal from "./AddPoolModal"; -import AddBucket from "../../Buckets/ListBuckets/AddBucket"; -import ReplicationSetup from "./ReplicationSetup"; -import api from "../../../../common/api"; -import { IPodListElement, IPool, ITenant } from "../ListTenants/types"; -import PageHeader from "../../Common/PageHeader/PageHeader"; -import UsageBarWrapper from "../../Common/UsageBarWrapper/UsageBarWrapper"; -import UpdateTenantModal from "./UpdateTenantModal"; -import { LicenseInfo } from "../../License/types"; -import { Link } from "react-router-dom"; import { setErrorSnackMessage } from "../../../../actions"; +import { + setTenantDetailsLoad, + setTenantName, + setTenantInfo, + setTenantTab, +} from "../actions"; +import { ITenant } from "../ListTenants/types"; +import { + containerForHeader, + tenantDetailsStyles, +} from "../../Common/FormComponents/common/styleLibrary"; +import api from "../../../../common/api"; +import PageHeader from "../../Common/PageHeader/PageHeader"; import MoreVertIcon from "@material-ui/icons/MoreVert"; import TenantYAML from "./TenantYAML"; -import SubnetLicenseTenant from "./SubnetLicenseTenant"; -import InputAdornment from "@material-ui/core/InputAdornment"; -import SearchIcon from "@material-ui/icons/Search"; -import history from "../../../../history"; +import TenantSummary from "./TenantSummary"; +import TenantLicense from "./TenantLicense"; +import PoolsSummary from "./PoolsSummary"; +import PodsSummary from "./PodsSummary"; +import { AppState } from "../../../../store"; interface ITenantDetailsProps { classes: any; match: any; + history: any; + loadingTenant: boolean; + currentTab: string; + selectedTenant: string; + selectedNamespace: string; setErrorSnackMessage: typeof setErrorSnackMessage; -} - -interface ITenantUsage { - used: string; - disk_used: string; + setTenantDetailsLoad: typeof setTenantDetailsLoad; + setTenantName: typeof setTenantName; + setTenantInfo: typeof setTenantInfo; + setTenantTab: typeof setTenantTab; } const styles = (theme: Theme) => createStyles({ - buttonContainer: { - textAlign: "right", - }, - multiContainer: { - display: "flex", - alignItems: "center" as const, - justifyContent: "flex-start" as const, - }, - sizeFactorContainer: { - marginLeft: 8, - }, - containerHeader: { - display: "flex", - justifyContent: "space-between", - }, - paperContainer: { - padding: "15px 15px 15px 50px", - }, - infoGrid: { - display: "grid", - gridTemplateColumns: "auto auto auto auto", - gridGap: 8, - "& div": { - display: "flex", - alignItems: "center", - }, - "& div:nth-child(odd)": { - justifyContent: "flex-end", - fontWeight: 700, - }, - "& div:nth-child(2n)": { - paddingRight: 35, - }, - }, - masterActions: { - width: "25%", - minWidth: "120px", - "& div": { - margin: "5px 0px", - }, - }, - updateButton: { - backgroundColor: "transparent", - border: 0, - padding: "0 6px", - cursor: "pointer", - "&:focus, &:active": { - outline: "none", - }, - "& svg": { - height: 12, - }, - }, - poolLabel: { - color: "#666666", - }, - titleCol: { - fontWeight: "bold", - }, - breadcrumLink: { - textDecoration: "none", - color: "black", - }, + ...tenantDetailsStyles, redState: { color: theme.palette.error.main, }, @@ -146,215 +75,91 @@ const styles = (theme: Theme) => greyState: { color: "grey", }, - healthCol: { - fontWeight: "bold", - paddingRight: "10px", - }, - ...modalBasic, - ...actionsTray, - ...buttonsStyles, - ...searchField, - ...hrClass, - actionsTray: { - ...actionsTray.actionsTray, - padding: "15px 0 0", - }, ...containerForHeader(theme.spacing(4)), }); const TenantDetails = ({ classes, match, + history, + loadingTenant, + currentTab, + selectedTenant, + selectedNamespace, setErrorSnackMessage, + setTenantDetailsLoad, + setTenantName, + setTenantInfo, + setTenantTab, }: ITenantDetailsProps) => { - const [selectedTab, setSelectedTab] = useState(0); - const [capacity, setCapacity] = useState(0); - const [poolCount, setPoolCount] = useState(0); - const [pools, setPools] = useState([]); - const [pods, setPods] = useState([]); - const [instances, setInstances] = useState(0); - const [volumes, setVolumes] = useState(0); - const [addPoolOpen, setAddPool] = useState(false); - const [addBucketOpen, setAddBucketOpen] = useState(false); - const [addReplicationOpen, setAddReplicationOpen] = useState(false); - const [tenant, setTenant] = useState(null); - const [loadingUsage, setLoadingUsage] = useState(true); - const [usageError, setUsageError] = useState(""); - const [usage, setUsage] = useState(0); - const [updateMinioVersion, setUpdateMinioVersion] = useState(false); const [yamlScreenOpen, setYamlScreenOpen] = useState(false); - const [licenseInfo, setLicenseInfo] = useState(); - const [loadingLicenseInfo, setLoadingLicenseInfo] = useState(true); - const [loadingActivateProduct, setLoadingActivateProduct] = - useState(false); - const [logEnabled, setLogEnabled] = useState(false); - const [monitoringEnabled, setMonitoringEnabled] = useState(false); - const [encryptionEnabled, setEncryptionEnabled] = useState(false); - const [adEnabled, setAdEnabled] = useState(false); - const [oicEnabled, setOicEnabled] = useState(false); const tenantName = match.params["tenantName"]; const tenantNamespace = match.params["tenantNamespace"]; - const onClosePoolAndRefresh = (reload: boolean) => { - setAddPool(false); - - if (reload) { - loadInfo(); - loadUsage(); - } - }; - - const closeBucketsAndRefresh = () => { - setAddBucketOpen(false); - }; - - const closeReplicationAndRefresh = (reload: boolean) => { - setAddReplicationOpen(false); - - if (reload) { - loadInfo(); - loadUsage(); - } - }; - - const activateProduct = (namespace: string, tenant: string) => { - if (loadingActivateProduct) { - return; - } - setLoadingActivateProduct(true); - api - .invoke( - "POST", - `/api/v1/subscription/namespaces/${namespace}/tenants/${tenant}/activate`, - {} - ) - .then(() => { - setLoadingActivateProduct(false); - loadInfo(); - }) - .catch((err) => { - setLoadingActivateProduct(false); - setErrorSnackMessage(err); - }); - }; - - const loadInfo = () => { - api - .invoke( - "GET", - `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}` - ) - .then((res: ITenant) => { - 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; - } - setCapacity(res.total_size || 0); - setPoolCount(resPools.length); - setVolumes(totalVolumes); - setInstances(totalInstances); - - setPools(resPools); - - setLogEnabled(res.logEnabled); - setMonitoringEnabled(res.monitoringEnabled); - setEncryptionEnabled(res.encryptionEnabled); - setAdEnabled(res.idpAdEnabled); - setOicEnabled(res.idpOicEnabled); - - setTenant(res); - }) - .catch((err) => { - setErrorSnackMessage(err); - }); - }; - - const loadUsage = () => { - api - .invoke( - "GET", - `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/usage` - ) - .then((result: ITenantUsage) => { - const usage = get(result, "disk_used", "0"); - setUsage(parseInt(usage)); - setUsageError(""); - setLoadingUsage(false); - }) - .catch((err) => { - setUsageError(err); - setUsage(0); - setLoadingUsage(false); - }); - }; - - const loadPods = () => { - api - .invoke( - "GET", - `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/pods` - ) - .then((result: IPodListElement[]) => { - for (let i = 0; i < result.length; i++) { - let currentTime = new Date().getSeconds(); - result[i].time = niceDays( - (currentTime - parseInt(result[i].timeCreated)).toString() - ); - } - setPods(result); - }) - .catch((err) => { - setErrorSnackMessage("Error loading pods"); - }); - }; - - const fetchLicenseInfo = () => { - setLoadingLicenseInfo(true); - api - .invoke("GET", `/api/v1/subscription/info`) - .then((res: LicenseInfo) => { - setLicenseInfo(res); - setLoadingLicenseInfo(false); - }) - .catch((err: any) => { - setLoadingLicenseInfo(false); - }); - }; - - useEffect(() => { - loadInfo(); - loadUsage(); - fetchLicenseInfo(); - loadPods(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - const [anchorEl, setAnchorEl] = React.useState(null); - const podViewAction = (pod: IPodListElement) => { - history.push( - `/namespaces/${tenantNamespace}/tenants/${tenantName}/pods/${pod.name}` - ); - return; - }; - const podTableActions = [{ type: "view", onClick: podViewAction }]; + 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) => { + setTenantInfo(res); + setTenantDetailsLoad(false); + }) + .catch((err) => { + setErrorSnackMessage(err); + setTenantDetailsLoad(false); + }); + } + }, [ + loadingTenant, + tenantNamespace, + tenantName, + setTenantInfo, + setTenantDetailsLoad, + setErrorSnackMessage, + ]); + + useEffect(() => { + const path = get(match, 'path', '/'); + const splitSections = path.split('/'); + const section=splitSections[splitSections.length - 1]; + + switch(section) { + case "pools": + case "pods": + case "license": + setTenantTab(section); + break; + default: + setTenantTab('summary'); + } + }, [match, setTenantTab]); const handleTenantMenu = (event: any) => { setAnchorEl(event.currentTarget); @@ -367,50 +172,11 @@ const TenantDetails = ({ const closeYAMLModalAndRefresh = () => { setYamlScreenOpen(false); - loadInfo(); - }; - - const healthStatusToClass = (health_status: string) => { - return health_status == "red" - ? classes.redState - : health_status == "yellow" - ? classes.yellowState - : health_status == "green" - ? classes.greenState - : classes.greyState; + setTenantDetailsLoad(true); }; return ( - - {addPoolOpen && tenant !== null && ( - - )} - {addBucketOpen && ( - - )} - {addReplicationOpen && ( - - )} - {updateMinioVersion && ( - { - setUpdateMinioVersion(false); - }} - idTenant={tenantName} - namespace={tenantNamespace} - /> - )} + {yamlScreenOpen && ( { - setSelectedTab(newValue); + onChange={(_, newValue: string) => { + setTenantTab(newValue); + history.push( + `/namespaces/${tenantNamespace}/tenants/${tenantName}/${newValue}` + ); }} aria-label="tenant-tabs" variant="scrollable" scrollButtons="auto" > - - - - + + + + - {selectedTab === 0 && ( - -
- - - - - - - - - - - - - - - - - - - - - - - - - - {tenant?.endpoints && ( - - - - - - - )} - - - - -
-

Details

-
-
Capacity:{niceBytes(capacity.toString(10))}MinIO: - -
Clusters:{poolCount}Console: - -
Instances:{instances}Volumes:{volumes}
Endpoint: - - {tenant?.endpoints.minio} - - Console: - - {tenant?.endpoints.console} - -
State:{tenant?.currentState}
-
- - -

- {tenant && tenant.status && ( - - ⬤  - - )} - Health -

- - - - - - - - - - - - - -
Drives Online - {tenant?.status?.drives_online - ? tenant?.status?.drives_online - : 0} -
Drives Offline - {tenant?.status?.drives_offline - ? tenant?.status?.drives_offline - : 0} -
Write Quorum - {tenant?.status?.write_quorum - ? tenant?.status?.write_quorum - : 0} -
-
- - -
-
- - - - - - - - - - - - - - - - - - - - - {adEnabled || - (!adEnabled && !oicEnabled && ( - - - - - ))} - {oicEnabled || - (!oicEnabled && !adEnabled && ( - - - - - ))} - -
-

Features

-
-
Logs: - - Monitoring: - -
Encryption: - -
- Active Directory: - - - OpenID: - -
-
-
-
- - )} - {selectedTab === 1 && ( - - - { - // setFilter(event.target.value); - }} - InputProps={{ - disableUnderline: true, - startAdornment: ( - - - - ), - }} - /> - - - -
-
- - - { - console.log(element); - }, - sendOnlyId: true, - }, - ]} - columns={[ - { label: "Name", elementKey: "name" }, - { label: "Capacity", elementKey: "capacity" }, - { label: "# of Instances", elementKey: "servers" }, - { label: "# of Drives", elementKey: "volumes" }, - ]} - isLoading={false} - records={pools} - entityName="Servers" - idField="name" - /> - -
- )} - {selectedTab === 2 && ( - -
- { - return input != null ? input : 0; - }, - }, - { label: "Node", elementKey: "node" }, - ]} - isLoading={false} - records={pods} - entityName="Servers" - idField="name" + + + -
- )} - {selectedTab === 3 && ( - -
- - - + + + ( + - - -
- )} + )} + /> + + - + ); }; -const connector = connect(null, { +const mapState = (state: AppState) => ({ + loadingTenant: state.tenants.tenantDetails.loadingTenant, + currentTab: state.tenants.tenantDetails.currentTab, + selectedTenant: state.tenants.tenantDetails.currentTenant, + selectedNamespace: state.tenants.tenantDetails.currentNamespace, +}); + +const connector = connect(mapState, { setErrorSnackMessage, + setTenantDetailsLoad, + setTenantName, + setTenantInfo, + setTenantTab, }); export default withStyles(styles)(connector(TenantDetails)); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantLicense.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantLicense.tsx new file mode 100644 index 000000000..2a57392b7 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantLicense.tsx @@ -0,0 +1,135 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { Fragment, useEffect, useState } from "react"; +import { connect } from "react-redux"; +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 api from "../../../../common/api"; +import { ITenant } from "../ListTenants/types"; +import { LicenseInfo } from "../../License/types"; +import { setErrorSnackMessage } from "../../../../actions"; +import SubnetLicenseTenant from "./SubnetLicenseTenant"; +import { AppState } from "../../../../store"; +import { setTenantDetailsLoad } from "../actions"; +import { CircularProgress } from "@material-ui/core"; + +interface ITenantLicense { + classes: any; + loadingTenant: boolean; + tenant: ITenant | null; + setTenantDetailsLoad: typeof setTenantDetailsLoad; +} + +const styles = (theme: Theme) => + createStyles({ + ...tenantDetailsStyles, + loaderAlign: { + textAlign: "center", + }, + ...containerForHeader(theme.spacing(4)), + }); + +const TenantLicense = ({ + classes, + tenant, + loadingTenant, + setTenantDetailsLoad, +}: ITenantLicense) => { + const [licenseInfo, setLicenseInfo] = useState(); + const [loadingLicenseInfo, setLoadingLicenseInfo] = useState(true); + const [loadingActivateProduct, setLoadingActivateProduct] = + useState(false); + + const activateProduct = (namespace: string, tenant: string) => { + if (loadingActivateProduct) { + return; + } + setLoadingActivateProduct(true); + api + .invoke( + "POST", + `/api/v1/subscription/namespaces/${namespace}/tenants/${tenant}/activate`, + {} + ) + .then(() => { + setLoadingActivateProduct(false); + setTenantDetailsLoad(true); + setLoadingLicenseInfo(true); + }) + .catch((err) => { + setLoadingActivateProduct(false); + setErrorSnackMessage(err); + }); + }; + + useEffect(() => { + if (loadingLicenseInfo) { + api + .invoke("GET", `/api/v1/subscription/info`) + .then((res: LicenseInfo) => { + setLicenseInfo(res); + setLoadingLicenseInfo(false); + }) + .catch((err: any) => { + setLoadingLicenseInfo(false); + }); + } + }, [loadingLicenseInfo]); + + return ( + +
+ {loadingTenant ? ( +
+ +
+ ) : ( + + {tenant && ( + + + + + + )} + + )} +
+ ); +}; + +const mapState = (state: AppState) => ({ + loadingTenant: state.tenants.tenantDetails.loadingTenant, + tenant: state.tenants.tenantDetails.tenantInfo, +}); + +const connector = connect(mapState, { + setErrorSnackMessage, + setTenantDetailsLoad, +}); + +export default withStyles(styles)(connector(TenantLicense)); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSummary.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSummary.tsx new file mode 100644 index 000000000..201054e21 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSummary.tsx @@ -0,0 +1,446 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { Fragment, 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 { Button, CircularProgress } from "@material-ui/core"; +import Paper from "@material-ui/core/Paper"; +import { niceBytes } from "../../../../common/utils"; +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 { + classes: any; + match: any; + tenant: ITenant | null; + logEnabled: boolean; + monitoringEnabled: boolean; + encryptionEnabled: boolean; + adEnabled: boolean; + oicEnabled: boolean; + loadingTenant: boolean; + setErrorSnackMessage: typeof setErrorSnackMessage; +} + +interface ITenantUsage { + used: string; + disk_used: string; +} + +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 TenantSummary = ({ + classes, + match, + tenant, + logEnabled, + monitoringEnabled, + encryptionEnabled, + adEnabled, + oicEnabled, + loadingTenant, + setErrorSnackMessage, +}: ITenantsSummary) => { + const [capacity, setCapacity] = useState(0); + const [poolCount, setPoolCount] = useState(0); + const [instances, setInstances] = useState(0); + const [volumes, setVolumes] = useState(0); + const [loadingUsage, setLoadingUsage] = useState(true); + const [usageError, setUsageError] = useState(""); + const [usage, setUsage] = useState(0); + const [updateMinioVersion, setUpdateMinioVersion] = useState(false); + + const tenantName = match.params["tenantName"]; + const tenantNamespace = match.params["tenantNamespace"]; + + const healthStatusToClass = (health_status: string) => { + return health_status === "red" + ? classes.redState + : health_status === "yellow" + ? classes.yellowState + : health_status === "green" + ? classes.greenState + : classes.greyState; + }; + + useEffect(() => { + if (loadingUsage) { + api + .invoke( + "GET", + `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/usage` + ) + .then((result: ITenantUsage) => { + const usage = get(result, "disk_used", "0"); + setUsage(parseInt(usage)); + setUsageError(""); + setLoadingUsage(false); + }) + .catch((err) => { + setUsageError(err); + setUsage(0); + setLoadingUsage(false); + }); + } + }, [tenantName, tenantNamespace, loadingUsage]); + + useEffect(() => { + if (tenant) { + const res = tenant; + + 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; + } + setCapacity(res.total_size || 0); + setPoolCount(resPools.length); + setVolumes(totalVolumes); + setInstances(totalInstances); + } + }, [tenant]); + + return ( + + {updateMinioVersion && ( + { + setUpdateMinioVersion(false); + }} + idTenant={tenantName} + namespace={tenantNamespace} + /> + )} +
+ + + + + + + + + {loadingTenant ? ( + + + + ) : ( + + + + + + + + + + + + + + + + + + + + {tenant?.endpoints && ( + + + + + + + )} + + + + + + )} + +
+

Details

+
+
+ +
Capacity:{niceBytes(capacity.toString(10))}MinIO: + +
Clusters:{poolCount}Console: + +
Instances:{instances}Volumes:{volumes}
Endpoint: + + {tenant?.endpoints.minio} + + Console: + + {tenant?.endpoints.console} + +
State:{tenant?.currentState}
+
+ + {loadingTenant ? ( +
+ ) : ( + + +

+ {tenant && tenant.status && ( + + ⬤  + + )} + Health +

+ + + + + + + + + + + + + + + +
Drives Online + {tenant?.status?.drives_online + ? tenant?.status?.drives_online + : 0} +
Drives Offline + {tenant?.status?.drives_offline + ? tenant?.status?.drives_offline + : 0} +
Write Quorum + {tenant?.status?.write_quorum + ? tenant?.status?.write_quorum + : 0} +
+
+ )} +
+
+
+
+
+ + + + + + + + + {loadingTenant ? ( + + + + ) : ( + + + + + + + + + + + + + + + {adEnabled || + (!adEnabled && !oicEnabled && ( + + + + + ))} + {oicEnabled || + (!oicEnabled && !adEnabled && ( + + + + + ))} + + + )} + +
+

Features

+
+
+ +
Logs: + + Monitoring: + +
Encryption: + +
+ Active Directory: + + + OpenID: + +
+
+
+
+
+ ); +}; + +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(TenantSummary)); diff --git a/portal-ui/src/screens/Console/Tenants/actions.ts b/portal-ui/src/screens/Console/Tenants/actions.ts index 86ca0499b..8ebc824bb 100644 --- a/portal-ui/src/screens/Console/Tenants/actions.ts +++ b/portal-ui/src/screens/Console/Tenants/actions.ts @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +import { ITenant } from "./ListTenants/types"; import { Opts } from "./ListTenants/utils"; import { ADD_TENANT_SET_ADVANCED_MODE, @@ -35,6 +36,10 @@ import { ADD_TENANT_ENCRYPTION_VAULT_CA, ADD_TENANT_ENCRYPTION_GEMALTO_CA, ADD_TENANT_RESET_FORM, + TENANT_DETAILS_SET_LOADING, + TENANT_DETAILS_SET_TENANT, + TENANT_DETAILS_SET_CURRENT_TENANT, + TENANT_DETAILS_SET_TAB, } from "./types"; // Basic actions @@ -220,3 +225,32 @@ export const resetAddTenantForm = () => { type: ADD_TENANT_RESET_FORM, }; }; + +export const setTenantDetailsLoad = (loading: boolean) => { + return { + type: TENANT_DETAILS_SET_LOADING, + state: loading, + } +}; + +export const setTenantName = (tenantName: string, tenantNamespace: string) => { + return { + type: TENANT_DETAILS_SET_CURRENT_TENANT, + name: tenantName, + namespace: tenantNamespace, + } +}; + +export const setTenantInfo = (tenant: ITenant | null) => { + return { + type: TENANT_DETAILS_SET_TENANT, + tenant + } +}; + +export const setTenantTab = (tab: string) => { + return { + type: TENANT_DETAILS_SET_TAB, + tab, + } +}; diff --git a/portal-ui/src/screens/Console/Tenants/reducer.ts b/portal-ui/src/screens/Console/Tenants/reducer.ts index c5f259958..e47b33b99 100644 --- a/portal-ui/src/screens/Console/Tenants/reducer.ts +++ b/portal-ui/src/screens/Console/Tenants/reducer.ts @@ -36,6 +36,10 @@ import { ADD_TENANT_ENCRYPTION_VAULT_CA, ADD_TENANT_ENCRYPTION_GEMALTO_CA, ADD_TENANT_RESET_FORM, + TENANT_DETAILS_SET_LOADING, + TENANT_DETAILS_SET_CURRENT_TENANT, + TENANT_DETAILS_SET_TENANT, + TENANT_DETAILS_SET_TAB, } from "./types"; import { KeyPair } from "./ListTenants/utils"; import { getRandomString } from "./utils"; @@ -227,6 +231,13 @@ const initialState: ITenantState = { }, }, }, + tenantDetails: { + currentTenant: "", + currentNamespace: "", + loadingTenant: false, + tenantInfo: null, + currentTab: "summary", + }, }; export function tenantsReducer( @@ -624,6 +635,49 @@ export function tenantsReducer( }, }, }; + case TENANT_DETAILS_SET_LOADING: + const tenantDetails = { + ...state.tenantDetails, + loadingTenant: action.state, + }; + return { + ...state, + tenantDetails: { + ...tenantDetails, + }, + }; + case TENANT_DETAILS_SET_CURRENT_TENANT: + const currentTenant = { + ...state.tenantDetails, + currentTenant: action.name, + currentNamespace: action.namespace, + }; + return { + ...state, + tenantDetails: { + ...currentTenant, + }, + }; + case TENANT_DETAILS_SET_TENANT: + let tenantData = null; + if (action.tenant) { + tenantData = { tenantInfo: { ...action.tenant } }; + } + const setTenant = { ...state.tenantDetails, ...tenantData }; + return { + ...state, + tenantDetails: { + ...setTenant, + }, + }; + case TENANT_DETAILS_SET_TAB: + const newTab = { ...state.tenantDetails, currentTab: action.tab }; + return { + ...state, + tenantDetails: { + ...newTab, + }, + }; default: return state; } diff --git a/portal-ui/src/screens/Console/Tenants/types.ts b/portal-ui/src/screens/Console/Tenants/types.ts index e25b4acb3..6fc656c6d 100644 --- a/portal-ui/src/screens/Console/Tenants/types.ts +++ b/portal-ui/src/screens/Console/Tenants/types.ts @@ -15,7 +15,7 @@ // along with this program. If not, see . import { IErasureCodeCalc } from "../../../common/types"; -import { IMemorySize } from "./ListTenants/types"; +import { IMemorySize, ITenant } from "./ListTenants/types"; import { KeyPair, Opts } from "./ListTenants/utils"; export const ADD_TENANT_SET_CURRENT_PAGE = "ADD_TENANT/SET_CURRENT_PAGE"; @@ -52,6 +52,13 @@ export const ADD_TENANT_ENCRYPTION_VAULT_CA = "ADD_TENANT/ENCRYPTION_VAULT_CA"; export const ADD_TENANT_ENCRYPTION_GEMALTO_CA = "ADD_TENANT/ENCRYPTION_GEMALTO_CA"; +// Tenant Details +export const TENANT_DETAILS_SET_LOADING = "TENANT_DETAILS/SET_LOADING"; +export const TENANT_DETAILS_SET_CURRENT_TENANT = + "TENANT_DETAILS/SET_CURRENT_TENANT"; +export const TENANT_DETAILS_SET_TENANT = "TENANT_DETAILS/SET_TENANT"; +export const TENANT_DETAILS_SET_TAB = "TENANT_DETAILS/SET_TAB"; + export interface ICreateTenant { page: number; validPages: string[]; @@ -188,8 +195,17 @@ export interface ITenantAffinity { withPodAntiAffinity: boolean; } +export interface ITenantDetails { + currentTenant: string; + currentNamespace: string; + loadingTenant: boolean; + tenantInfo: ITenant | null; + currentTab: string; +} + export interface ITenantState { createTenant: ICreateTenant; + tenantDetails: ITenantDetails; } export interface ILabelKeyPair { @@ -308,6 +324,27 @@ interface ResetForm { type: typeof ADD_TENANT_RESET_FORM; } +interface SetLoadingTenant { + type: typeof TENANT_DETAILS_SET_LOADING; + state: boolean; +} + +interface SetTenantName { + type: typeof TENANT_DETAILS_SET_CURRENT_TENANT; + name: string; + namespace: string; +} + +interface SetTenantDetails { + type: typeof TENANT_DETAILS_SET_TENANT; + tenant: ITenant | null; +} + +interface SetTenantTab { + type: typeof TENANT_DETAILS_SET_TAB; + tab: string; +} + export type FieldsToHandle = INameTenantFields; export type TenantsManagementTypes = @@ -329,4 +366,8 @@ export type TenantsManagementTypes = | AddFileVaultCert | AddFileVaultCa | AddFileGemaltoCa - | ResetForm; + | ResetForm + | SetLoadingTenant + | SetTenantName + | SetTenantDetails + | SetTenantTab; diff --git a/portal-ui/src/screens/Console/Users/ListUsers.tsx b/portal-ui/src/screens/Console/Users/ListUsers.tsx index a27ec04ca..59d217748 100644 --- a/portal-ui/src/screens/Console/Users/ListUsers.tsx +++ b/portal-ui/src/screens/Console/Users/ListUsers.tsx @@ -91,7 +91,6 @@ const ListUsers = ({ classes, setErrorSnackMessage }: IUsersProps) => { const [filter, setFilter] = useState(""); const [checkedUsers, setCheckedUsers] = useState([]); const [policyOpen, setPolicyOpen] = useState(false); - const [resetPWOpen, setResetPWOpen] = useState(false); const [ChangeUserPasswordModalOpen, setChangeUserPasswordModalOpen] = useState(false); diff --git a/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx b/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx index 3a4f46bfc..8aa86044b 100644 --- a/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx +++ b/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx @@ -66,8 +66,6 @@ const UserServiceAccountsPanel = ({ const [showNewCredentials, setShowNewCredentials] = useState(false); const [newServiceAccount, setNewServiceAccount] = useState(null); - const [changePasswordModalOpen, setChangePasswordModalOpen] = - useState(false); useEffect(() => { fetchRecords(); @@ -88,7 +86,7 @@ const UserServiceAccountsPanel = ({ setLoading(false); }); } - }, [loading, setLoading, setRecords, setErrorSnackMessage]); + }, [loading, setLoading, setRecords, setErrorSnackMessage, user]); const fetchRecords = () => { setLoading(true);