Fix tenant details screen (#277)

This commit is contained in:
Lenin Alevski
2020-09-16 23:01:28 -07:00
committed by GitHub
parent cb7513e9f0
commit 1768af9026
11 changed files with 222 additions and 297 deletions

View File

@@ -36,7 +36,7 @@ var (
bucketsDetail = "/buckets/:bucketName"
serviceAccounts = "/service-accounts"
tenants = "/tenants"
tenantsDetail = "/tenants/:tenantName"
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
heal = "/heal"
)

View File

@@ -107,7 +107,7 @@ export const getBytes = (
const powFactor = unitsTake.findIndex((element) => element === unit);
if (powFactor == -1) {
if (powFactor === -1) {
return "0";
}
const factor = Math.pow(1024, powFactor);

View File

@@ -311,7 +311,7 @@ const Console = ({
},
{
component: TenantDetails,
path: "/tenants/:tenantName",
path: "/namespaces/:tenantNamespace/tenants/:tenantName",
},
];
const allowedRoutes = routes.filter((route: any) => allowedPages[route.path]);

View File

@@ -40,7 +40,7 @@ import {
} from "../../../../common/utils";
import {
commonFormValidation,
IValidation,
IValidation
} from "../../../../utils/validationFunctions";
import GenericWizard from "../../Common/GenericWizard/GenericWizard";
import { IWizardElement } from "../../Common/GenericWizard/types";
@@ -65,19 +65,19 @@ interface IAddTenantProps {
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red",
color: "red"
},
buttonContainer: {
textAlign: "right",
textAlign: "right"
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const,
justifyContent: "flex-start" as const
},
sizeFactorContainer: {
marginLeft: 8,
alignSelf: "flex-start" as const,
alignSelf: "flex-start" as const
},
headerElement: {
position: "sticky",
@@ -85,16 +85,16 @@ const styles = (theme: Theme) =>
paddingTop: 5,
marginBottom: 10,
backgroundColor: "#fff",
zIndex: 500,
zIndex: 500
},
tableTitle: {
fontWeight: 700,
width: "30%",
width: "30%"
},
zoneError: {
color: "#dc1f2e",
fontSize: "0.75rem",
paddingLeft: 120,
paddingLeft: 120
},
error: {
color: "#dc1f2e",
@@ -111,7 +111,7 @@ interface Opts {
const AddTenant = ({
open,
closeModalAndRefresh,
classes,
classes
}: IAddTenantProps) => {
// Fields
const [addSending, setAddSending] = useState<boolean>(false);
@@ -301,8 +301,8 @@ const AddTenant = ({
setNameTenantValid(
!("tenant-name" in commonValidation) &&
!("namespace" in commonValidation) &&
storageClasses.length > 0
!("namespace" in commonValidation) &&
storageClasses.length > 0
);
setValidationErrors(commonValidation);
@@ -337,9 +337,9 @@ const AddTenant = ({
setConfigValid(
!("nodes" in commonValidation) &&
!("volume_size" in commonValidation) &&
!("memory_per_node" in commonValidation) &&
distribution.error === ""
!("volume_size" in commonValidation) &&
!("memory_per_node" in commonValidation) &&
distribution.error === ""
);
setValidationErrors(commonValidation);
@@ -356,8 +356,8 @@ const AddTenant = ({
required: true,
value: imageName,
pattern: /^((.*?)\/(.*?):(.+))$/,
customPatternMessage: "Format must be of form: 'minio/minio:VERSION'",
},
customPatternMessage: "Format must be of form: 'minio/minio:VERSION'"
}
];
}
@@ -620,7 +620,7 @@ const AddTenant = ({
enabled: true,
action: () => {
closeModalAndRefresh(false, null);
},
}
};
const wizardSteps: IWizardElement[] = [
@@ -690,7 +690,7 @@ const AddTenant = ({
id="adv_mode"
name="adv_mode"
checked={advancedMode}
onChange={(e) => {
onChange={e => {
const targetD = e.target;
const checked = targetD.checked;
@@ -703,8 +703,8 @@ const AddTenant = ({
),
buttons: [
cancelButton,
{ label: "Next", type: "next", enabled: nameTenantValid },
],
{ label: "Next", type: "next", enabled: nameTenantValid }
]
},
{
label: "Configure",
@@ -1080,8 +1080,8 @@ const AddTenant = ({
buttons: [
cancelButton,
{ label: "Back", type: "back", enabled: true },
{ label: "Next", type: "next", enabled: true },
],
{ label: "Next", type: "next", enabled: true }
]
},
{
label: "Encryption",
@@ -1504,8 +1504,8 @@ const AddTenant = ({
buttons: [
cancelButton,
{ label: "Back", type: "back", enabled: true },
{ label: "Next", type: "next", enabled: true },
],
{ label: "Next", type: "next", enabled: true }
]
},
{
label: "Tenant Size",
@@ -1720,16 +1720,16 @@ const AddTenant = ({
enabled: !addSending,
action: () => {
setAddSending(true);
},
},
],
},
}
}
]
}
];
let filteredWizardSteps = wizardSteps;
if (!advancedMode) {
filteredWizardSteps = wizardSteps.filter((step) => !step.advancedOnly);
filteredWizardSteps = wizardSteps.filter(step => !step.advancedOnly);
}
return (

View File

@@ -27,11 +27,12 @@ import {
import Typography from "@material-ui/core/Typography";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import api from "../../../../common/api";
import { ITenant } from "./types";
interface IDeleteTenant {
classes: any;
deleteOpen: boolean;
selectedTenant: string;
selectedTenant: ITenant;
closeDeleteModalAndRefresh: (refreshList: boolean) => any;
}
@@ -54,7 +55,10 @@ const DeleteTenant = ({
useEffect(() => {
if (deleteLoading) {
api
.invoke("DELETE", `/api/v1/tenants/${selectedTenant}`)
.invoke(
"DELETE",
`/api/v1/namespaces/${selectedTenant.namespace}/tenants/${selectedTenant.name}`
)
.then(() => {
setDeleteLoading(false);
setDeleteError("");
@@ -85,7 +89,7 @@ const DeleteTenant = ({
<DialogContent>
{deleteLoading && <LinearProgress />}
<DialogContentText id="alert-dialog-description">
Are you sure you want to delete tenant <b>{selectedTenant}</b>?
Are you sure you want to delete tenant <b>{selectedTenant.name}</b>?
{deleteError !== "" && (
<React.Fragment>
<br />

View File

@@ -32,6 +32,7 @@ import DeleteTenant from "./DeleteTenant";
import AddTenant from "./AddTenant";
import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
import history from "../../../../history";
interface ITenantsList {
classes: any;
@@ -122,11 +123,16 @@ const ListTenants = ({ classes }: ITenantsList) => {
}
};
const confirmDeleteTenant = (tenant: string) => {
const confirmDeleteTenant = (tenant: ITenant) => {
setSelectedTenant(tenant);
setDeleteOpen(true);
};
const redirectToTenantDetails = (tenant: ITenant) => {
history.push(`/namespaces/${tenant.namespace}/tenants/${tenant.name}`);
return;
};
const closeCredentialsModal = () => {
setShowNewCredentials(false);
setCreatedAccount(null);
@@ -149,8 +155,8 @@ const ListTenants = ({ classes }: ITenantsList) => {
};
const tableActions = [
{ type: "view", to: `/tenants`, sendOnlyId: true },
{ type: "delete", onClick: confirmDeleteTenant, sendOnlyId: true },
{ type: "view", onClick: redirectToTenantDetails },
{ type: "delete", onClick: confirmDeleteTenant },
];
const filteredRecords = records
@@ -187,9 +193,7 @@ const ListTenants = ({ classes }: ITenantsList) => {
}
for (let i = 0; i < resTenants.length; i++) {
const total =
resTenants[i].volume_count * resTenants[i].volume_size;
resTenants[i].capacity = niceBytes(total + "");
resTenants[i].capacity = niceBytes(resTenants[i].total_size + "");
}
setRecords(resTenants);

View File

@@ -86,7 +86,14 @@ const ZonesMultiSelector = ({
onChange,
classes,
}: IZonesMultiSelector) => {
const defaultZone: IZone = { name: "", servers: 0, capacity: "", volumes: 0 };
const defaultZone: IZone = {
name: "",
servers: 0,
capacity: "",
volumes: 0,
volumes_per_server: 0,
volume_configuration: { size: 0, storage_class: "", labels: null },
};
const [currentElements, setCurrentElements] = useState<IZone[]>([]);
const [internalCounter, setInternalCounter] = useState<number>(1);

View File

@@ -17,18 +17,32 @@
export interface IZone {
name: string;
servers: number;
volumes_per_server: number;
volume_configuration: IVolumeConfiguration;
// computed
capacity: string;
volumes: number;
}
export interface IAddZoneRequest {
name: string;
servers: number;
volumes_per_server: number;
volume_configuration: IVolumeConfiguration;
}
export interface IVolumeConfiguration {
size: number;
storage_class: string;
labels: { [key: string]: any } | null;
}
export interface ITenant {
total_size: number;
name: string;
namespace: string;
image: string;
console_image: string;
zone_count: number;
currentState: string;
instance_count: 4;

View File

@@ -8,33 +8,34 @@ import Grid from "@material-ui/core/Grid";
import {
factorForDropdown,
getTotalSize,
niceBytes,
niceBytes
} from "../../../../common/utils";
import { Button, LinearProgress } from "@material-ui/core";
import api from "../../../../common/api";
import { IAddZoneRequest, ITenant } from "../ListTenants/types";
interface IAddZoneProps {
tenant: ITenant;
classes: any;
open: boolean;
onCloseZoneAndReload: (shouldReload: boolean) => void;
volumesPerInstance: number;
volumeSize: number;
}
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red",
color: "red"
},
buttonContainer: {
textAlign: "right",
textAlign: "right"
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const,
justifyContent: "flex-start" as const
},
sizeFactorContainer: {
marginLeft: 8,
marginLeft: 8
},
bottomContainer: {
display: "flex",
@@ -42,39 +43,39 @@ const styles = (theme: Theme) =>
alignItems: "center",
"& div": {
flexGrow: 1,
width: "100%",
},
width: "100%"
}
},
factorElements: {
display: "flex",
justifyContent: "flex-start",
justifyContent: "flex-start"
},
sizeNumber: {
fontSize: 35,
fontWeight: 700,
textAlign: "center",
textAlign: "center"
},
sizeDescription: {
fontSize: 14,
color: "#777",
textAlign: "center",
textAlign: "center"
},
...modalBasic,
...modalBasic
});
const AddZoneModal = ({
tenant,
classes,
open,
onCloseZoneAndReload,
volumesPerInstance,
volumeSize,
onCloseZoneAndReload
}: IAddZoneProps) => {
const [addSending, setAddSending] = useState<boolean>(false);
const [zoneName, setZoneName] = useState<string>("");
const [numberOfInstances, setNumberOfInstances] = useState<number>(0);
const [numberOfNodes, setNumberOfNodes] = useState<number>(0);
const [volumeSize, setVolumeSize] = useState<number>(0);
const [volumesPerServer, setVolumesPerSever] = useState<number>(0);
const instanceCapacity: number = volumeSize * volumesPerInstance;
const totalCapacity: number = instanceCapacity * numberOfInstances;
const instanceCapacity: number = volumeSize * 1073741824 * volumesPerServer;
const totalCapacity: number = instanceCapacity * numberOfNodes;
return (
<ModalWrapper
@@ -88,30 +89,66 @@ const AddZoneModal = ({
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setAddSending(true);
const data: IAddZoneRequest = {
name: "",
servers: numberOfNodes,
volumes_per_server: volumesPerServer,
volume_configuration: {
size: volumeSize * 1073741824,
storage_class: "",
labels: null
}
};
api
.invoke(
"POST",
`/api/v1/namespaces/${tenant.namespace}/tenants/${tenant.name}/zones`,
data
)
.then(() => {
setAddSending(false);
onCloseZoneAndReload(true);
})
.catch(err => {
setAddSending(false);
// setDeleteError(err);
});
}}
>
<Grid item xs={12}>
<InputBoxWrapper
id="zone_name"
name="zone_name"
type="string"
id="number_of_nodes"
name="number_of_nodes"
type="number"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setZoneName(e.target.value);
setNumberOfNodes(parseInt(e.target.value));
}}
label="Name"
value={zoneName}
label="Number o Nodes"
value={numberOfNodes.toString(10)}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="number_instances"
name="number_instances"
id="zone_size"
name="zone_size"
type="number"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setNumberOfInstances(parseInt(e.target.value));
setVolumeSize(parseInt(e.target.value));
}}
label="Drives per Server"
value={numberOfInstances.toString(10)}
label="Volume Size (Gi)"
value={volumeSize.toString(10)}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="volumes_per_sever"
name="volumes_per_sever"
type="number"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setVolumesPerSever(parseInt(e.target.value));
}}
label="Volumes per Server"
value={volumesPerServer.toString(10)}
/>
</Grid>
<Grid item xs={12}>

View File

@@ -42,25 +42,25 @@ interface ITenantDetailsProps {
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red",
color: "red"
},
buttonContainer: {
textAlign: "right",
textAlign: "right"
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const,
justifyContent: "flex-start" as const
},
sizeFactorContainer: {
marginLeft: 8,
marginLeft: 8
},
containerHeader: {
display: "flex",
justifyContent: "space-between",
justifyContent: "space-between"
},
paperContainer: {
padding: "15px 15px 15px 50px",
padding: "15px 15px 15px 50px"
},
infoGrid: {
display: "grid",
@@ -68,27 +68,27 @@ const styles = (theme: Theme) =>
gridGap: 8,
"& div": {
display: "flex",
alignItems: "center",
alignItems: "center"
},
"& div:nth-child(odd)": {
justifyContent: "flex-end",
fontWeight: 700,
fontWeight: 700
},
"& div:nth-child(2n)": {
paddingRight: 35,
},
paddingRight: 35
}
},
masterActions: {
width: "25%",
minWidth: "120px",
"& div": {
margin: "5px 0px",
},
margin: "5px 0px"
}
},
actionsTray: {
textAlign: "right",
textAlign: "right"
},
...modalBasic,
...modalBasic
});
const mainPagination = {
@@ -99,9 +99,9 @@ const mainPagination = {
page: 0,
SelectProps: {
inputProps: { "aria-label": "rows per page" },
native: true,
native: true
},
ActionsComponent: MinTablePaginationActions,
ActionsComponent: MinTablePaginationActions
};
const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
@@ -142,31 +142,45 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
const loadInfo = () => {
const tenantName = match.params["tenantName"];
const tenantNamespace = match.params["tenantNamespace"];
setLoading(true);
api
.invoke("GET", `/api/v1/tenants/${tenantName}`)
.invoke(
"GET",
`/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}`
)
.then((res: ITenant) => {
const total = res.volume_count * res.volume_size;
setCapacity(total);
setZoneCount(res.zone_count);
setVolumes(res.volume_count);
setInstances(res.instance_count);
const resZones = !res.zones ? [] : res.zones;
const total = res.volume_count * res.volume_size;
let totalInstances = 0;
let totalVolumes = 0;
let count = 1;
for (let zone of resZones) {
zone.volumes = res.volumes_per_server;
const cap = res.volumes_per_server * res.volume_size * zone.servers;
const cap =
zone.volumes_per_server *
zone.servers *
zone.volume_configuration.size;
zone.name = `zone-${count}`;
zone.capacity = niceBytes(cap + "");
zone.volumes = zone.servers * zone.volumes_per_server;
totalInstances += zone.servers;
totalVolumes += zone.volumes;
count += 1;
}
setCapacity(res.total_size);
setZoneCount(resZones.length);
setVolumes(totalVolumes);
setInstances(totalInstances);
setZones(resZones);
setTenant(res);
setError("");
setLoading(false);
})
.catch((err) => {
.catch(err => {
setError(err);
setLoading(false);
});
@@ -182,8 +196,7 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
<AddZoneModal
open={addZoneOpen}
onCloseZoneAndReload={onCloseZoneAndRefresh}
volumeSize={tenant.volume_size}
volumesPerInstance={tenant.volumes_per_server}
tenant={tenant}
/>
)}
{addBucketOpen && (
@@ -212,40 +225,18 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
<div className={classes.infoGrid}>
<div>Capacity:</div>
<div>{niceBytes(capacity.toString(10))}</div>
<div>Minio:</div>
<div>{tenant ? tenant.image : ""}</div>
<div>Console:</div>
<div>{tenant ? tenant.console_image : ""}</div>
<div>Zones:</div>
<div>{zoneCount}</div>
<div>External IDP:</div>
<div>
{externalIDP ? "Yes" : "No"}&nbsp;&nbsp;
<Button
variant="contained"
color="primary"
size="small"
onClick={() => {}}
>
Edit
</Button>
</div>
<div>Instances:</div>
<div>{instances}</div>
<div>External KMS:</div>
<div>{externalKMS ? "Yes" : "No"}&nbsp;&nbsp;</div>
<div>Volumes:</div>
<div>{volumes}</div>
</div>
</Paper>
<div className={classes.masterActions}>
<div>
<Button
variant="contained"
color="primary"
fullWidth
onClick={() => {}}
>
Warp
</Button>
</div>
</div>
</Grid>
<Grid item xs={12}>
<br />
@@ -261,185 +252,50 @@ const TenantDetails = ({ classes, match }: ITenantDetailsProps) => {
aria-label="tenant-tabs"
>
<Tab label="Zones" />
<Tab label="Buckets" />
<Tab label="Replication" />
</Tabs>
</Grid>
<Grid item xs={6} className={classes.actionsTray}>
{selectedTab === 0 && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddZone(true);
}}
>
Add Zone
</Button>
)}
{selectedTab === 1 && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddBucketOpen(true);
}}
>
Create Bucket
</Button>
)}
{selectedTab === 2 && (
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddReplicationOpen(true);
}}
>
Add Replication
</Button>
)}
<Button
variant="contained"
color="primary"
startIcon={<CreateIcon />}
onClick={() => {
setAddZone(true);
}}
>
Add Zone
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
{selectedTab === 0 && (
<TableWrapper
itemActions={[
{
type: "view",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
<TableWrapper
itemActions={[
{
type: "delete",
onClick: element => {
console.log(element);
},
{
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={zones}
entityName="Zones"
idField="name"
paginatorConfig={{
...mainPagination,
onChangePage: () => {},
onChangeRowsPerPage: () => {},
}}
/>
)}
{selectedTab === 1 && (
<TableWrapper
itemActions={[
{
type: "view",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
},
{
type: "replicate",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
},
{
type: "mirror",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
},
{
type: "delete",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
},
]}
columns={[
{
label: "Status",
elementKey: "status",
},
{ label: "Name", elementKey: "name" },
{ label: "AccessPolicy", elementKey: "access_policy" },
]}
isLoading={false}
records={[]}
entityName="Buckets"
idField="name"
paginatorConfig={{
...mainPagination,
onChangePage: () => {},
onChangeRowsPerPage: () => {},
}}
/>
)}
{selectedTab === 2 && (
<TableWrapper
itemActions={[
{
type: "view",
onClick: (element) => {
console.log(element);
},
sendOnlyId: true,
},
]}
columns={[
{
label: "Source",
elementKey: "source",
},
{ label: "Source Bucket", elementKey: "source_bucket" },
{ label: "Destination", elementKey: "destination" },
{
label: "Destination Bucket",
elementKey: "destination_bucket",
},
]}
isLoading={false}
records={[]}
entityName="Replication"
idField="id"
paginatorConfig={{
rowsPerPageOptions: [5, 10, 25],
colSpan: 3,
count: 0,
rowsPerPage: 0,
page: 0,
SelectProps: {
inputProps: { "aria-label": "rows per page" },
native: true,
},
onChangePage: () => {},
onChangeRowsPerPage: () => {},
ActionsComponent: MinTablePaginationActions,
}}
/>
)}
sendOnlyId: true
}
]}
columns={[
{ label: "Name", elementKey: "name" },
{ label: "Capacity", elementKey: "capacity" },
{ label: "# of Instances", elementKey: "servers" },
{ label: "# of Drives", elementKey: "volumes" }
]}
isLoading={false}
records={zones}
entityName="Zones"
idField="name"
paginatorConfig={{
...mainPagination,
onChangePage: () => {},
onChangeRowsPerPage: () => {}
}}
/>
</Grid>
</Grid>
</React.Fragment>

View File

@@ -1531,6 +1531,9 @@ func updateTenantZones(
// replace zones array
minInst.Spec.Zones = newZoneArray
minInst = minInst.DeepCopy()
minInst.EnsureDefaults()
payloadBytes, err := json.Marshal(minInst)
if err != nil {
return nil, err