Added URL navigation to tenant details (#795)
Added URL navigation to tenant details and fixed a couple of warnings Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -65,7 +65,7 @@ const LicenseModal = ({ classes, open, closeModal }: ILicenseModalProps) => {
|
||||
<Grid item className={classes.subnetLicenseKey} xs={12}>
|
||||
<p>
|
||||
Copyright © 2007 Free Software Foundation, Inc. <
|
||||
<a target="_blank" href="https://fsf.org/">
|
||||
<a target="_blank" href="https://fsf.org/" rel="noreferrer">
|
||||
https://fsf.org/
|
||||
</a>
|
||||
>
|
||||
@@ -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 <
|
||||
<a target="_blank" href="https://www.gnu.org/licenses/">
|
||||
<a target="_blank" href="https://www.gnu.org/licenses/" rel="noreferrer">
|
||||
https://www.gnu.org/licenses/
|
||||
</a>
|
||||
>.
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<IPodListElement[]>([]);
|
||||
const [loadingPods, setLoadingPods] = useState<boolean>(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 (
|
||||
<Fragment>
|
||||
<br />
|
||||
<TableWrapper
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Status", elementKey: "status" },
|
||||
{ label: "Age", elementKey: "time" },
|
||||
{ label: "Pod IP", elementKey: "podIP" },
|
||||
{
|
||||
label: "Restarts",
|
||||
elementKey: "restarts",
|
||||
renderFunction: (input) => {
|
||||
return input != null ? input : 0;
|
||||
},
|
||||
},
|
||||
{ label: "Node", elementKey: "node" },
|
||||
]}
|
||||
isLoading={loadingPods}
|
||||
records={pods}
|
||||
itemActions={podTableActions}
|
||||
entityName="Servers"
|
||||
idField="name"
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const connector = connect(null, {
|
||||
setErrorSnackMessage,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(PodsSummary));
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<IPool[]>([]);
|
||||
const [addPoolOpen, setAddPool] = useState<boolean>(false);
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
|
||||
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 (
|
||||
<Fragment>
|
||||
{addPoolOpen && tenant !== null && (
|
||||
<AddPoolModal
|
||||
open={addPoolOpen}
|
||||
onClosePoolAndReload={onClosePoolAndRefresh}
|
||||
tenant={tenant}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Filter"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(event) => {
|
||||
setFilter(event.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setAddPool(true);
|
||||
}}
|
||||
>
|
||||
Expand Tenant
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={[]}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Capacity", elementKey: "capacity" },
|
||||
{ label: "# of Instances", elementKey: "servers" },
|
||||
{ label: "# of Drives", elementKey: "volumes" },
|
||||
]}
|
||||
isLoading={loadingTenant}
|
||||
records={filteredPools}
|
||||
entityName="Servers"
|
||||
idField="name"
|
||||
customEmptyMessage="No Pools found"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
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));
|
||||
@@ -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<number>(0);
|
||||
const [capacity, setCapacity] = useState<number>(0);
|
||||
const [poolCount, setPoolCount] = useState<number>(0);
|
||||
const [pools, setPools] = useState<IPool[]>([]);
|
||||
const [pods, setPods] = useState<IPodListElement[]>([]);
|
||||
const [instances, setInstances] = useState<number>(0);
|
||||
const [volumes, setVolumes] = useState<number>(0);
|
||||
const [addPoolOpen, setAddPool] = useState<boolean>(false);
|
||||
const [addBucketOpen, setAddBucketOpen] = useState<boolean>(false);
|
||||
const [addReplicationOpen, setAddReplicationOpen] = useState<boolean>(false);
|
||||
const [tenant, setTenant] = useState<ITenant | null>(null);
|
||||
const [loadingUsage, setLoadingUsage] = useState<boolean>(true);
|
||||
const [usageError, setUsageError] = useState<string>("");
|
||||
const [usage, setUsage] = useState<number>(0);
|
||||
const [updateMinioVersion, setUpdateMinioVersion] = useState<boolean>(false);
|
||||
const [yamlScreenOpen, setYamlScreenOpen] = useState<boolean>(false);
|
||||
const [licenseInfo, setLicenseInfo] = useState<LicenseInfo>();
|
||||
const [loadingLicenseInfo, setLoadingLicenseInfo] = useState<boolean>(true);
|
||||
const [loadingActivateProduct, setLoadingActivateProduct] =
|
||||
useState<boolean>(false);
|
||||
const [logEnabled, setLogEnabled] = useState<boolean>(false);
|
||||
const [monitoringEnabled, setMonitoringEnabled] = useState<boolean>(false);
|
||||
const [encryptionEnabled, setEncryptionEnabled] = useState<boolean>(false);
|
||||
const [adEnabled, setAdEnabled] = useState<boolean>(false);
|
||||
const [oicEnabled, setOicEnabled] = useState<boolean>(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 (
|
||||
<React.Fragment>
|
||||
{addPoolOpen && tenant !== null && (
|
||||
<AddPoolModal
|
||||
open={addPoolOpen}
|
||||
onClosePoolAndReload={onClosePoolAndRefresh}
|
||||
tenant={tenant}
|
||||
/>
|
||||
)}
|
||||
{addBucketOpen && (
|
||||
<AddBucket
|
||||
open={addBucketOpen}
|
||||
closeModalAndRefresh={closeBucketsAndRefresh}
|
||||
/>
|
||||
)}
|
||||
{addReplicationOpen && (
|
||||
<ReplicationSetup
|
||||
open={addReplicationOpen}
|
||||
closeModalAndRefresh={closeReplicationAndRefresh}
|
||||
/>
|
||||
)}
|
||||
{updateMinioVersion && (
|
||||
<UpdateTenantModal
|
||||
open={updateMinioVersion}
|
||||
closeModalAndRefresh={() => {
|
||||
setUpdateMinioVersion(false);
|
||||
}}
|
||||
idTenant={tenantName}
|
||||
namespace={tenantNamespace}
|
||||
/>
|
||||
)}
|
||||
<Fragment>
|
||||
{yamlScreenOpen && (
|
||||
<TenantYAML
|
||||
open={yamlScreenOpen}
|
||||
@@ -452,347 +218,73 @@ const TenantDetails = ({
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
value={currentTab}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
onChange={(_, newValue: number) => {
|
||||
setSelectedTab(newValue);
|
||||
onChange={(_, newValue: string) => {
|
||||
setTenantTab(newValue);
|
||||
history.push(
|
||||
`/namespaces/${tenantNamespace}/tenants/${tenantName}/${newValue}`
|
||||
);
|
||||
}}
|
||||
aria-label="tenant-tabs"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
>
|
||||
<Tab label="Summary" />
|
||||
<Tab label="Pools" />
|
||||
<Tab label="Pods" />
|
||||
<Tab label="License" />
|
||||
<Tab value="summary" label="Summary" />
|
||||
<Tab value="pools" label="Pools" />
|
||||
<Tab value="pods" label="Pods" />
|
||||
<Tab value="license" label="License" />
|
||||
</Tabs>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
{selectedTab === 0 && (
|
||||
<React.Fragment>
|
||||
<br />
|
||||
<Paper className={classes.paperContainer}>
|
||||
<Grid container>
|
||||
<Grid item xs={8}>
|
||||
<table width={"100%"}>
|
||||
<tr>
|
||||
<td colSpan={4}>
|
||||
<h2>Details</h2>
|
||||
<hr className={classes.hrClass} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Capacity:</td>
|
||||
<td>{niceBytes(capacity.toString(10))}</td>
|
||||
<td className={classes.titleCol}>MinIO:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
onClick={() => {
|
||||
setUpdateMinioVersion(true);
|
||||
}}
|
||||
>
|
||||
{tenant ? tenant.image : ""}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Clusters:</td>
|
||||
<td>{poolCount}</td>
|
||||
<td className={classes.titleCol}>Console:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
onClick={() => {
|
||||
setUpdateMinioVersion(true);
|
||||
}}
|
||||
>
|
||||
{tenant ? tenant.console_image : ""}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Instances:</td>
|
||||
<td>{instances}</td>
|
||||
<td className={classes.titleCol}>Volumes:</td>
|
||||
<td>{volumes}</td>
|
||||
</tr>
|
||||
{tenant?.endpoints && (
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Endpoint:</td>
|
||||
<td>
|
||||
<a
|
||||
href={tenant?.endpoints.minio}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{tenant?.endpoints.minio}
|
||||
</a>
|
||||
</td>
|
||||
<td className={classes.titleCol}>Console:</td>
|
||||
<td>
|
||||
<a
|
||||
href={tenant?.endpoints.console}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{tenant?.endpoints.console}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td className={classes.titleCol}>State:</td>
|
||||
<td colSpan={3}>{tenant?.currentState}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<UsageBarWrapper
|
||||
currValue={usage}
|
||||
maxValue={tenant ? tenant.total_size : 0}
|
||||
label={"Storage"}
|
||||
renderFunction={niceBytes}
|
||||
error={usageError}
|
||||
loading={loadingUsage}
|
||||
/>
|
||||
<h4>
|
||||
{tenant && tenant.status && (
|
||||
<span
|
||||
className={healthStatusToClass(
|
||||
tenant.status.health_status
|
||||
)}
|
||||
>
|
||||
⬤
|
||||
</span>
|
||||
)}
|
||||
Health
|
||||
</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<td className={classes.healthCol}>Drives Online</td>
|
||||
<td>
|
||||
{tenant?.status?.drives_online
|
||||
? tenant?.status?.drives_online
|
||||
: 0}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.healthCol}>Drives Offline</td>
|
||||
<td>
|
||||
{tenant?.status?.drives_offline
|
||||
? tenant?.status?.drives_offline
|
||||
: 0}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.healthCol}>Write Quorum</td>
|
||||
<td>
|
||||
{tenant?.status?.write_quorum
|
||||
? tenant?.status?.write_quorum
|
||||
: 0}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
<br />
|
||||
<br />
|
||||
<Paper className={classes.paperContainer}>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<table width={"100%"}>
|
||||
<tr>
|
||||
<td colSpan={4}>
|
||||
<h2>Features</h2>
|
||||
<hr className={classes.hrClass} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Logs:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
>
|
||||
{logEnabled ? "Enabled" : "Disabled"}
|
||||
</Button>
|
||||
</td>
|
||||
<td className={classes.titleCol}>Monitoring:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
>
|
||||
{monitoringEnabled ? "Enabled" : "Disabled"}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Encryption:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
>
|
||||
{encryptionEnabled ? "Enabled" : "Disabled"}
|
||||
</Button>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
{adEnabled ||
|
||||
(!adEnabled && !oicEnabled && (
|
||||
<React.Fragment>
|
||||
<td className={classes.titleCol}>
|
||||
Active Directory:
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
>
|
||||
{adEnabled ? "Enabled" : "Disabled"}
|
||||
</Button>
|
||||
</td>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{oicEnabled ||
|
||||
(!oicEnabled && !adEnabled && (
|
||||
<React.Fragment>
|
||||
<td className={classes.titleCol}>OpenID:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
>
|
||||
{oicEnabled ? "Enabled" : "Disabled"}
|
||||
</Button>
|
||||
</td>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</tr>
|
||||
</table>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{selectedTab === 1 && (
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Filter"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(event) => {
|
||||
// setFilter(event.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setAddPool(true);
|
||||
}}
|
||||
>
|
||||
Expand Tenant
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={[
|
||||
{
|
||||
type: "delete",
|
||||
onClick: (element) => {
|
||||
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"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
{selectedTab === 2 && (
|
||||
<React.Fragment>
|
||||
<br />
|
||||
<TableWrapper
|
||||
itemActions={podTableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Status", elementKey: "status" },
|
||||
{ label: "Age", elementKey: "time" },
|
||||
{ label: "Pod IP", elementKey: "podIP" },
|
||||
{
|
||||
label: "Restarts",
|
||||
elementKey: "restarts",
|
||||
renderFunction: (input) => {
|
||||
return input != null ? input : 0;
|
||||
},
|
||||
},
|
||||
{ label: "Node", elementKey: "node" },
|
||||
]}
|
||||
isLoading={false}
|
||||
records={pods}
|
||||
entityName="Servers"
|
||||
idField="name"
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route
|
||||
path="/namespaces/:tenantNamespace/tenants/:tenantName/summary"
|
||||
component={TenantSummary}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{selectedTab === 3 && (
|
||||
<React.Fragment>
|
||||
<br />
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<SubnetLicenseTenant
|
||||
tenant={tenant}
|
||||
loadingLicenseInfo={loadingLicenseInfo}
|
||||
loadingActivateProduct={loadingActivateProduct}
|
||||
licenseInfo={licenseInfo}
|
||||
activateProduct={activateProduct}
|
||||
<Route
|
||||
path="/namespaces/:tenantNamespace/tenants/:tenantName/pools"
|
||||
component={PoolsSummary}
|
||||
/>
|
||||
<Route
|
||||
path="/namespaces/:tenantNamespace/tenants/:tenantName/pods"
|
||||
component={PodsSummary}
|
||||
/>
|
||||
<Route
|
||||
path="/namespaces/:tenantNamespace/tenants/:tenantName/license"
|
||||
component={TenantLicense}
|
||||
/>
|
||||
<Route
|
||||
path="/namespaces/:tenantNamespace/tenants/:tenantName"
|
||||
component={() => (
|
||||
<Redirect
|
||||
to={`/namespaces/${tenantNamespace}/tenants/${tenantName}/summary`}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
</Router>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
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));
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<LicenseInfo>();
|
||||
const [loadingLicenseInfo, setLoadingLicenseInfo] = useState<boolean>(true);
|
||||
const [loadingActivateProduct, setLoadingActivateProduct] =
|
||||
useState<boolean>(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 (
|
||||
<Fragment>
|
||||
<br />
|
||||
{loadingTenant ? (
|
||||
<div className={classes.loaderAlign}>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
) : (
|
||||
<Fragment>
|
||||
{tenant && (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<SubnetLicenseTenant
|
||||
tenant={tenant}
|
||||
loadingLicenseInfo={loadingLicenseInfo}
|
||||
loadingActivateProduct={loadingActivateProduct}
|
||||
licenseInfo={licenseInfo}
|
||||
activateProduct={activateProduct}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
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));
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<number>(0);
|
||||
const [poolCount, setPoolCount] = useState<number>(0);
|
||||
const [instances, setInstances] = useState<number>(0);
|
||||
const [volumes, setVolumes] = useState<number>(0);
|
||||
const [loadingUsage, setLoadingUsage] = useState<boolean>(true);
|
||||
const [usageError, setUsageError] = useState<string>("");
|
||||
const [usage, setUsage] = useState<number>(0);
|
||||
const [updateMinioVersion, setUpdateMinioVersion] = useState<boolean>(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 (
|
||||
<Fragment>
|
||||
{updateMinioVersion && (
|
||||
<UpdateTenantModal
|
||||
open={updateMinioVersion}
|
||||
closeModalAndRefresh={() => {
|
||||
setUpdateMinioVersion(false);
|
||||
}}
|
||||
idTenant={tenantName}
|
||||
namespace={tenantNamespace}
|
||||
/>
|
||||
)}
|
||||
<br />
|
||||
<Paper className={classes.paperContainer}>
|
||||
<Grid container>
|
||||
<Grid item xs={8}>
|
||||
<table width={"100%"}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={4}>
|
||||
<h2>Details</h2>
|
||||
<hr className={classes.hrClass} />
|
||||
</td>
|
||||
</tr>
|
||||
{loadingTenant ? (
|
||||
<tr>
|
||||
<td colSpan={4} className={classes.centerAlign}>
|
||||
<CircularProgress />
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
<Fragment>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Capacity:</td>
|
||||
<td>{niceBytes(capacity.toString(10))}</td>
|
||||
<td className={classes.titleCol}>MinIO:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
onClick={() => {
|
||||
setUpdateMinioVersion(true);
|
||||
}}
|
||||
>
|
||||
{tenant ? tenant.image : ""}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Clusters:</td>
|
||||
<td>{poolCount}</td>
|
||||
<td className={classes.titleCol}>Console:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
onClick={() => {
|
||||
setUpdateMinioVersion(true);
|
||||
}}
|
||||
>
|
||||
{tenant ? tenant.console_image : ""}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Instances:</td>
|
||||
<td>{instances}</td>
|
||||
<td className={classes.titleCol}>Volumes:</td>
|
||||
<td>{volumes}</td>
|
||||
</tr>
|
||||
{tenant?.endpoints && (
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Endpoint:</td>
|
||||
<td>
|
||||
<a
|
||||
href={tenant?.endpoints.minio}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{tenant?.endpoints.minio}
|
||||
</a>
|
||||
</td>
|
||||
<td className={classes.titleCol}>Console:</td>
|
||||
<td>
|
||||
<a
|
||||
href={tenant?.endpoints.console}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{tenant?.endpoints.console}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td className={classes.titleCol}>State:</td>
|
||||
<td colSpan={3}>{tenant?.currentState}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
{loadingTenant ? (
|
||||
<div className={classes.centerAlign}><CircularProgress /></div>
|
||||
) : (
|
||||
<Fragment>
|
||||
<UsageBarWrapper
|
||||
currValue={usage}
|
||||
maxValue={tenant ? tenant.total_size : 0}
|
||||
label={"Storage"}
|
||||
renderFunction={niceBytes}
|
||||
error={usageError}
|
||||
loading={loadingUsage}
|
||||
/>
|
||||
<h4>
|
||||
{tenant && tenant.status && (
|
||||
<span
|
||||
className={healthStatusToClass(
|
||||
tenant.status.health_status
|
||||
)}
|
||||
>
|
||||
⬤
|
||||
</span>
|
||||
)}
|
||||
Health
|
||||
</h4>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className={classes.healthCol}>Drives Online</td>
|
||||
<td>
|
||||
{tenant?.status?.drives_online
|
||||
? tenant?.status?.drives_online
|
||||
: 0}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.healthCol}>Drives Offline</td>
|
||||
<td>
|
||||
{tenant?.status?.drives_offline
|
||||
? tenant?.status?.drives_offline
|
||||
: 0}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.healthCol}>Write Quorum</td>
|
||||
<td>
|
||||
{tenant?.status?.write_quorum
|
||||
? tenant?.status?.write_quorum
|
||||
: 0}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
<br />
|
||||
<br />
|
||||
<Paper className={classes.paperContainer}>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<table width={"100%"}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={4}>
|
||||
<h2>Features</h2>
|
||||
<hr className={classes.hrClass} />
|
||||
</td>
|
||||
</tr>
|
||||
{loadingTenant ? (
|
||||
<tr>
|
||||
<td className={classes.centerAlign} colSpan={4}>
|
||||
<CircularProgress />
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
<Fragment>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Logs:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
>
|
||||
{logEnabled ? "Enabled" : "Disabled"}
|
||||
</Button>
|
||||
</td>
|
||||
<td className={classes.titleCol}>Monitoring:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
>
|
||||
{monitoringEnabled ? "Enabled" : "Disabled"}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Encryption:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
>
|
||||
{encryptionEnabled ? "Enabled" : "Disabled"}
|
||||
</Button>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
{adEnabled ||
|
||||
(!adEnabled && !oicEnabled && (
|
||||
<React.Fragment>
|
||||
<td className={classes.titleCol}>
|
||||
Active Directory:
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
>
|
||||
{adEnabled ? "Enabled" : "Disabled"}
|
||||
</Button>
|
||||
</td>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{oicEnabled ||
|
||||
(!oicEnabled && !adEnabled && (
|
||||
<React.Fragment>
|
||||
<td className={classes.titleCol}>OpenID:</td>
|
||||
<td>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.anchorButton}
|
||||
>
|
||||
{oicEnabled ? "Enabled" : "Disabled"}
|
||||
</Button>
|
||||
</td>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</tr>
|
||||
</Fragment>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
loadingTenant: state.tenants.tenantDetails.loadingTenant,
|
||||
selectedTenant: state.tenants.tenantDetails.currentTenant,
|
||||
tenant: state.tenants.tenantDetails.tenantInfo,
|
||||
logEnabled: get(state.tenants.tenantDetails.tenantInfo, "logEnabled", false),
|
||||
monitoringEnabled: get(
|
||||
state.tenants.tenantDetails.tenantInfo,
|
||||
"monitoringEnabled",
|
||||
false
|
||||
),
|
||||
encryptionEnabled: get(
|
||||
state.tenants.tenantDetails.tenantInfo,
|
||||
"encryptionEnabled",
|
||||
false
|
||||
),
|
||||
adEnabled: get(state.tenants.tenantDetails.tenantInfo, "idpAdEnabled", false),
|
||||
oicEnabled: get(
|
||||
state.tenants.tenantDetails.tenantInfo,
|
||||
"idpOicEnabled",
|
||||
false
|
||||
),
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
setErrorSnackMessage,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(TenantSummary));
|
||||
@@ -14,6 +14,7 @@
|
||||
// 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 { 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,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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;
|
||||
|
||||
@@ -91,7 +91,6 @@ const ListUsers = ({ classes, setErrorSnackMessage }: IUsersProps) => {
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const [checkedUsers, setCheckedUsers] = useState<string[]>([]);
|
||||
const [policyOpen, setPolicyOpen] = useState<boolean>(false);
|
||||
const [resetPWOpen, setResetPWOpen] = useState<boolean>(false);
|
||||
const [ChangeUserPasswordModalOpen, setChangeUserPasswordModalOpen] =
|
||||
useState<boolean>(false);
|
||||
|
||||
|
||||
@@ -66,8 +66,6 @@ const UserServiceAccountsPanel = ({
|
||||
const [showNewCredentials, setShowNewCredentials] = useState<boolean>(false);
|
||||
const [newServiceAccount, setNewServiceAccount] =
|
||||
useState<NewServiceAccount | null>(null);
|
||||
const [changePasswordModalOpen, setChangePasswordModalOpen] =
|
||||
useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRecords();
|
||||
@@ -88,7 +86,7 @@ const UserServiceAccountsPanel = ({
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
}, [loading, setLoading, setRecords, setErrorSnackMessage]);
|
||||
}, [loading, setLoading, setRecords, setErrorSnackMessage, user]);
|
||||
|
||||
const fetchRecords = () => {
|
||||
setLoading(true);
|
||||
|
||||
Reference in New Issue
Block a user