Operator improvements (#1798)
Added new design to Tenants page list Added Pool details initial page Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
2
go.mod
2
go.mod
@@ -22,7 +22,7 @@ require (
|
||||
github.com/minio/madmin-go v1.3.5
|
||||
github.com/minio/mc v0.0.0-20220302011226-f13defa54577
|
||||
github.com/minio/minio-go/v7 v7.0.23
|
||||
github.com/minio/operator v0.0.0-20220322220228-ae7a32a7c19e
|
||||
github.com/minio/operator v0.0.0-20220401213108-1e35dbf22c40
|
||||
github.com/minio/pkg v1.1.20
|
||||
github.com/minio/selfupdate v0.4.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
|
||||
@@ -24,7 +24,9 @@ package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
@@ -70,6 +72,9 @@ type TenantList struct {
|
||||
// pool count
|
||||
PoolCount int64 `json:"pool_count,omitempty"`
|
||||
|
||||
// tiers
|
||||
Tiers []*TenantTierElement `json:"tiers"`
|
||||
|
||||
// total size
|
||||
TotalSize int64 `json:"total_size,omitempty"`
|
||||
|
||||
@@ -79,11 +84,75 @@ type TenantList struct {
|
||||
|
||||
// Validate validates this tenant list
|
||||
func (m *TenantList) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateTiers(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this tenant list based on context it is used
|
||||
func (m *TenantList) validateTiers(formats strfmt.Registry) error {
|
||||
if swag.IsZero(m.Tiers) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.Tiers); i++ {
|
||||
if swag.IsZero(m.Tiers[i]) { // not required
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Tiers[i] != nil {
|
||||
if err := m.Tiers[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("tiers" + "." + strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("tiers" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validate this tenant list based on the context it is used
|
||||
func (m *TenantList) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.contextValidateTiers(ctx, formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *TenantList) contextValidateTiers(ctx context.Context, formats strfmt.Registry) error {
|
||||
|
||||
for i := 0; i < len(m.Tiers); i++ {
|
||||
|
||||
if m.Tiers[i] != nil {
|
||||
if err := m.Tiers[i].ContextValidate(ctx, formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("tiers" + "." + strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("tiers" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
73
models/tenant_tier_element.go
Normal file
73
models/tenant_tier_element.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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/>.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// TenantTierElement tenant tier element
|
||||
//
|
||||
// swagger:model tenantTierElement
|
||||
type TenantTierElement struct {
|
||||
|
||||
// name
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// size
|
||||
Size int64 `json:"size,omitempty"`
|
||||
|
||||
// type
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this tenant tier element
|
||||
func (m *TenantTierElement) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this tenant tier element based on context it is used
|
||||
func (m *TenantTierElement) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *TenantTierElement) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *TenantTierElement) UnmarshalBinary(b []byte) error {
|
||||
var res TenantTierElement
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
@@ -3454,6 +3454,12 @@ func init() {
|
||||
"pool_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"tiers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/tenantTierElement"
|
||||
}
|
||||
},
|
||||
"total_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -3696,6 +3702,21 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"tenantTierElement": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tenantUsage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -8044,6 +8065,12 @@ func init() {
|
||||
"pool_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"tiers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/tenantTierElement"
|
||||
}
|
||||
},
|
||||
"total_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -8286,6 +8313,21 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"tenantTierElement": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tenantUsage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -808,6 +808,18 @@ func listTenants(ctx context.Context, operatorClient OperatorClientI, namespace
|
||||
deletion = tenant.ObjectMeta.DeletionTimestamp.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
var tiers []*models.TenantTierElement
|
||||
|
||||
for _, tier := range tenant.Status.Usage.Tiers {
|
||||
tierItem := &models.TenantTierElement{
|
||||
Name: tier.Name,
|
||||
Type: tier.Type,
|
||||
Size: tier.TotalSize,
|
||||
}
|
||||
|
||||
tiers = append(tiers, tierItem)
|
||||
}
|
||||
|
||||
tenants = append(tenants, &models.TenantList{
|
||||
CreationDate: tenant.ObjectMeta.CreationTimestamp.Format(time.RFC3339),
|
||||
DeletionDate: deletion,
|
||||
@@ -823,6 +835,7 @@ func listTenants(ctx context.Context, operatorClient OperatorClientI, namespace
|
||||
CapacityRawUsage: tenant.Status.Usage.RawUsage,
|
||||
Capacity: tenant.Status.Usage.Capacity,
|
||||
CapacityUsage: tenant.Status.Usage.Usage,
|
||||
Tiers: tiers,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
35
portal-ui/src/icons/EditTenantIcon.tsx
Normal file
35
portal-ui/src/icons/EditTenantIcon.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 * as React from "react";
|
||||
import { SVGProps } from "react";
|
||||
|
||||
const EditTenantIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`min-icon`}
|
||||
fill={"currentcolor"}
|
||||
viewBox="0 0 256 256"
|
||||
{...props}
|
||||
>
|
||||
<g transform="translate(0 -0.853)">
|
||||
<path d="M89.25,173.48c-2.67-.25-5.25-1.12-7.54-2.52-2.52-2.16-3.51-5.62-2.52-8.78l7.55-35.2L204.84,8.87C210.17,4.17,216.73,1.09,223.76,0c7.06-.19,13.88,2.53,18.86,7.54,10.33,11.14,9.77,28.52-1.26,38.97l-116.9,118.1-33.94,7.55-1.26,1.25v.07Zm12.58-37.71l-5.04,20.12,20.13-5.03L231.28,36.46c4.78-4.21,5.34-11.46,1.26-16.35-2.52-2.52-5.03-3.77-7.54-2.52-3.34-.09-6.56,1.3-8.8,3.78l-114.39,114.39h.01Z" />
|
||||
<path d="M179.76,227.54H23.88C10.69,227.54,0,216.84,0,203.65V47.78c0-13.19,10.69-23.88,23.88-23.88H108.1v15.07H23.88c-4.46,.46-7.77,4.34-7.54,8.81V203.65c-.24,4.47,3.08,8.34,7.54,8.8H179.76c4.75,.12,8.69-3.63,8.81-8.38,0-.14,0-.28,0-.42v-49.03h16.33v49.03c-1.03,13.25-11.92,23.57-25.21,23.88h.07Z" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default EditTenantIcon;
|
||||
@@ -182,3 +182,4 @@ export { default as SelectAllIcon } from "./SelectAllIcon";
|
||||
export { default as BackIcon } from "./BackIcon";
|
||||
export { default as DeleteNonCurrentIcon } from "./DeleteNonCurrentIcon";
|
||||
export { default as FilterIcon } from "./FilterIcon";
|
||||
export { default as EditTenantIcon } from "./EditTenantIcon";
|
||||
|
||||
@@ -403,6 +403,12 @@ const IconsScreen = ({ classes }: IIconsScreenSimple) => {
|
||||
EditIcon
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={3} sm={2} md={1}>
|
||||
<cicons.EditTenantIcon />
|
||||
<br />
|
||||
EditTenantIcon
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={3} sm={2} md={1}>
|
||||
<cicons.EditYamlIcon />
|
||||
<br />
|
||||
@@ -1075,7 +1081,8 @@ const IconsScreen = ({ classes }: IIconsScreenSimple) => {
|
||||
WatchIcon
|
||||
</Grid>
|
||||
<Grid item xs={3} sm={2} md={1}>
|
||||
<cicons.AlertCloseIcon /> <br />
|
||||
<cicons.AlertCloseIcon />
|
||||
<br />
|
||||
AlertCloseIcon
|
||||
</Grid>
|
||||
<Grid item xs={3} sm={2} md={1}>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 } from "react";
|
||||
|
||||
interface IInformationItemProps {
|
||||
label: string;
|
||||
value: string;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
const InformationItem = ({ label, value, unit }: IInformationItemProps) => {
|
||||
return (
|
||||
<div style={{ margin: "0px 20px" }}>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<span style={{ fontSize: 18, color: "#000", fontWeight: 400 }}>
|
||||
{value}
|
||||
</span>
|
||||
{unit && (
|
||||
<Fragment>
|
||||
{" "}
|
||||
<span
|
||||
style={{ fontSize: 12, color: "#8F9090", fontWeight: "bold" }}
|
||||
>
|
||||
{unit}
|
||||
</span>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
color: "#767676",
|
||||
fontSize: 12,
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InformationItem;
|
||||
@@ -98,11 +98,6 @@ const styles = (theme: Theme) =>
|
||||
marginTop: 25,
|
||||
height: "calc(100vh - 195px)",
|
||||
},
|
||||
searchField: {
|
||||
...searchField.searchField,
|
||||
marginRight: "auto",
|
||||
maxWidth: 380,
|
||||
},
|
||||
});
|
||||
|
||||
const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
|
||||
@@ -191,22 +186,17 @@ const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
|
||||
)}
|
||||
<PageHeader
|
||||
label="Tenants"
|
||||
middleComponent={
|
||||
<SearchBox
|
||||
placeholder={"Filter Tenants"}
|
||||
onChange={(val) => {
|
||||
setFilterTenants(val);
|
||||
}}
|
||||
value={filterTenants}
|
||||
/>
|
||||
}
|
||||
actions={
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={classes.actionsTray}
|
||||
marginRight={"30px"}
|
||||
>
|
||||
<SearchBox
|
||||
placeholder={"Filter Tenants"}
|
||||
onChange={(val) => {
|
||||
setFilterTenants(val);
|
||||
}}
|
||||
overrideClass={classes.searchField}
|
||||
value={filterTenants}
|
||||
/>
|
||||
|
||||
<Grid item xs={12} marginRight={"30px"}>
|
||||
<RBIconButton
|
||||
id={"refresh-tenant-list"}
|
||||
tooltip={"Refresh Tenant List"}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 from "react";
|
||||
import { Cell, Pie, PieChart } from "recharts";
|
||||
import { CapacityValue, CapacityValues } from "./types";
|
||||
import { niceBytesInt } from "../../../../common/utils";
|
||||
import { CircleIcon } from "../../../../icons";
|
||||
|
||||
interface ITenantCapacity {
|
||||
totalCapacity: number;
|
||||
usedSpaceVariants: CapacityValues[];
|
||||
statusClass: string;
|
||||
}
|
||||
|
||||
const TenantCapacity = ({
|
||||
totalCapacity,
|
||||
usedSpaceVariants,
|
||||
statusClass,
|
||||
}: ITenantCapacity) => {
|
||||
const colors = [
|
||||
"#C4D4E9",
|
||||
"#DCD1EE",
|
||||
"#D1EEE7",
|
||||
"#EEDED1",
|
||||
"#AAF38F",
|
||||
"#F9E6C5",
|
||||
"#71ACCB",
|
||||
"#F4CECE",
|
||||
"#D6D6D6",
|
||||
"#2781B0",
|
||||
];
|
||||
|
||||
const totalUsedSpace = usedSpaceVariants.reduce((acc, currValue) => {
|
||||
return acc + currValue.value;
|
||||
}, 0);
|
||||
|
||||
const emptySpace = totalCapacity - totalUsedSpace;
|
||||
|
||||
let tiersList: CapacityValue[] = [];
|
||||
|
||||
const standardTier = usedSpaceVariants.find(
|
||||
(tier) => tier.variant === "STANDARD"
|
||||
) || {
|
||||
value: 0,
|
||||
variant: "empty",
|
||||
};
|
||||
|
||||
if (usedSpaceVariants.length > 10) {
|
||||
const totalUsedByTiers = totalUsedSpace - standardTier.value;
|
||||
|
||||
tiersList = [
|
||||
{ value: totalUsedByTiers, color: "#2781B0", label: "Total Tiers Space" },
|
||||
];
|
||||
} else {
|
||||
tiersList = usedSpaceVariants
|
||||
.filter((variant) => variant.variant !== "STANDARD")
|
||||
.map((variant, index) => {
|
||||
return {
|
||||
value: variant.value,
|
||||
color: colors[index],
|
||||
label: `Tier - ${variant.variant}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let standardTierColor = "#07193E";
|
||||
|
||||
const usedPercentage = (standardTier.value * 100) / totalCapacity;
|
||||
|
||||
if (usedPercentage >= 90) {
|
||||
standardTierColor = "#C83B51";
|
||||
} else if (usedPercentage >= 75) {
|
||||
standardTierColor = "#FFAB0F";
|
||||
}
|
||||
|
||||
const plotValues: CapacityValue[] = [
|
||||
{ value: emptySpace, color: "#D6D6D6", label: "Empty Space" },
|
||||
{
|
||||
value: standardTier.value,
|
||||
color: standardTierColor,
|
||||
label: "Used Space by Tenant",
|
||||
},
|
||||
...tiersList,
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ position: "relative", width: 110, height: 110 }}>
|
||||
<div
|
||||
style={{ position: "absolute", right: -5, top: 15, zIndex: 400 }}
|
||||
className={statusClass}
|
||||
>
|
||||
<CircleIcon
|
||||
style={{
|
||||
border: "#fff 2px solid",
|
||||
borderRadius: "100%",
|
||||
width: 20,
|
||||
height: 20,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
fontWeight: "bold",
|
||||
color: "#000",
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{niceBytesInt(totalUsedSpace)}
|
||||
</span>
|
||||
<div>
|
||||
<PieChart width={110} height={110}>
|
||||
<Pie
|
||||
data={plotValues}
|
||||
cx={"50%"}
|
||||
cy={"50%"}
|
||||
dataKey="value"
|
||||
outerRadius={50}
|
||||
innerRadius={40}
|
||||
>
|
||||
{plotValues.map((entry, index) => (
|
||||
<Cell key={`cellCapacity-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TenantCapacity;
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
// Copyright (c) 2022 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
|
||||
@@ -15,20 +15,19 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment } from "react";
|
||||
import { ITenant, ValueUnit } from "./types";
|
||||
import { CapacityValues, ITenant, ValueUnit } from "./types";
|
||||
import { connect } from "react-redux";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { ArrowRightIcon, CircleIcon, SettingsIcon } from "../../../../icons";
|
||||
import history from "../../../../history";
|
||||
import TenantsIcon from "../../../../icons/TenantsIcon";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { niceBytes } from "../../../../common/utils";
|
||||
import UsageBarWrapper from "../../Common/UsageBarWrapper/UsageBarWrapper";
|
||||
import {niceBytes, niceBytesInt} from "../../../../common/utils";
|
||||
import { tenantIsOnline } from "./utils";
|
||||
import RBIconButton from "../../Buckets/BucketDetails/SummaryItems/RBIconButton";
|
||||
import { Button } from "@mui/material";
|
||||
import InformationItem from "./InformationItem";
|
||||
import TenantCapacity from "./TenantCapacity";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -77,15 +76,22 @@ const styles = (theme: Theme) =>
|
||||
height: 10,
|
||||
},
|
||||
tenantItem: {
|
||||
border: "1px solid #dedede",
|
||||
border: "1px solid #EAEDEE",
|
||||
borderRadius: 3,
|
||||
marginBottom: 20,
|
||||
paddingLeft: 40,
|
||||
paddingRight: 40,
|
||||
paddingTop: 30,
|
||||
paddingBottom: 30,
|
||||
padding: "15px 30px",
|
||||
"&:hover": {
|
||||
backgroundColor: "#FAFAFA",
|
||||
cursor: "pointer",
|
||||
},
|
||||
},
|
||||
titleContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
},
|
||||
title: {
|
||||
fontSize: 22,
|
||||
fontSize: 18,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
titleSubKey: {
|
||||
@@ -112,6 +118,18 @@ const styles = (theme: Theme) =>
|
||||
marginRight: 8,
|
||||
textTransform: "initial",
|
||||
},
|
||||
namespaceLabel: {
|
||||
display: "inline-flex",
|
||||
backgroundColor: "#EAEDEF",
|
||||
borderRadius: 2,
|
||||
padding: "4px 8px",
|
||||
fontSize: 10,
|
||||
marginRight: 20,
|
||||
},
|
||||
status: {
|
||||
fontSize: 12,
|
||||
color: "#8F9090",
|
||||
},
|
||||
});
|
||||
|
||||
interface ITenantListItem {
|
||||
@@ -133,9 +151,9 @@ const TenantListItem = ({ tenant, classes }: ITenantListItem) => {
|
||||
}
|
||||
};
|
||||
|
||||
var raw: ValueUnit = { value: "n/a", unit: "" };
|
||||
var capacity: ValueUnit = { value: "n/a", unit: "" };
|
||||
var used: ValueUnit = { value: "n/a", unit: "" };
|
||||
let raw: ValueUnit = { value: "n/a", unit: "" };
|
||||
let capacity: ValueUnit = { value: "n/a", unit: "" };
|
||||
let used: ValueUnit = { value: "n/a", unit: "" };
|
||||
|
||||
if (tenant.capacity_raw) {
|
||||
const b = niceBytes(`${tenant.capacity_raw}`, true);
|
||||
@@ -150,126 +168,128 @@ const TenantListItem = ({ tenant, classes }: ITenantListItem) => {
|
||||
capacity.unit = parts[1];
|
||||
}
|
||||
if (tenant.capacity_usage) {
|
||||
const usageProportion =
|
||||
(tenant.capacity! * tenant.capacity_raw_usage!) / tenant.capacity_raw!;
|
||||
const b = niceBytes(`${usageProportion}`, true);
|
||||
const b = niceBytesInt(tenant.capacity_usage, true);
|
||||
const parts = b.split(" ");
|
||||
used.value = parts[0];
|
||||
used.unit = parts[1];
|
||||
}
|
||||
|
||||
let spaceVariants: CapacityValues[] = [];
|
||||
|
||||
if (!tenant.tiers || tenant.tiers.length === 0) {
|
||||
spaceVariants = [{ value: tenant.capacity_usage || 0, variant: "STANDARD" }];
|
||||
} else {
|
||||
spaceVariants = tenant.tiers.map((itemTenant) => {
|
||||
return { value: itemTenant.size, variant: itemTenant.name };
|
||||
});
|
||||
}
|
||||
|
||||
const openTenantDetails = () => {
|
||||
history.push(`/namespaces/${tenant.namespace}/tenants/${tenant.name}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.tenantItem} id={`list-tenant-${tenant.name}`}>
|
||||
<div
|
||||
className={classes.tenantItem}
|
||||
id={`list-tenant-${tenant.name}`}
|
||||
onClick={openTenantDetails}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item xs={8}>
|
||||
<div className={classes.title}>{tenant.name}</div>
|
||||
<Grid item xs={12} className={classes.titleContainer}>
|
||||
<div className={classes.title}>
|
||||
<span>{tenant.name}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className={classes.titleSubKey}>Namespace:</span>
|
||||
<span className={classes.titleSubValue}>{tenant.namespace}</span>
|
||||
<span className={classes.titleSubKey}>Pools:</span>
|
||||
<span className={classes.titleSubValue}>{tenant.pool_count}</span>
|
||||
<span className={classes.titleSubKey}>State:</span>
|
||||
<span className={classes.titleSubValue}>
|
||||
{tenant.currentState}
|
||||
<span className={classes.namespaceLabel}>
|
||||
Namespace: {tenant.namespace}
|
||||
</span>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={4} textAlign={"end"}>
|
||||
<RBIconButton
|
||||
id={"manage-tenant-" + tenant.name}
|
||||
tooltip={"Manage Tenant"}
|
||||
text={"Manage"}
|
||||
disabled={!tenantIsOnline(tenant)}
|
||||
onClick={() => {
|
||||
history.push(
|
||||
`/namespaces/${tenant.namespace}/tenants/${tenant.name}/hop`
|
||||
);
|
||||
}}
|
||||
icon={<SettingsIcon />}
|
||||
color="primary"
|
||||
variant={"outlined"}
|
||||
/>
|
||||
<RBIconButton
|
||||
id={"view-tenant-" + tenant.name}
|
||||
tooltip={"View Tenant"}
|
||||
text={"View"}
|
||||
onClick={() => {
|
||||
history.push(
|
||||
`/namespaces/${tenant.namespace}/tenants/${tenant.name}`
|
||||
);
|
||||
}}
|
||||
icon={<ArrowRightIcon />}
|
||||
color="primary"
|
||||
variant={"contained"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<hr />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container alignItems={"center"}>
|
||||
<Grid item xs={7}>
|
||||
<Grid container>
|
||||
<Grid item xs={3} style={{ textAlign: "center" }}>
|
||||
<div className={classes.tenantIcon}>
|
||||
<TenantsIcon style={{ height: 40, width: 40 }} />
|
||||
<div className={classes.healthStatusIcon}>
|
||||
<span
|
||||
className={healthStatusToClass(tenant.health_status)}
|
||||
>
|
||||
<CircleIcon />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.boxyTitle}>
|
||||
Raw Capacity
|
||||
</Grid>
|
||||
<Grid item className={classes.boxyValue}>
|
||||
{raw.value}
|
||||
<span className={classes.boxyUnit}>{raw.unit}</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.boxyTitle}>
|
||||
Capacity
|
||||
</Grid>
|
||||
<Grid item className={classes.boxyValue}>
|
||||
{capacity.value}
|
||||
<span className={classes.boxyUnit}>
|
||||
{capacity.unit}
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.boxyTitle}>
|
||||
Usage
|
||||
</Grid>
|
||||
<Grid item className={classes.boxyValue}>
|
||||
{used.value}
|
||||
<span className={classes.boxyUnit}>{used.unit}</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ marginTop: 2 }}>
|
||||
<Grid container>
|
||||
<Grid item xs={2}>
|
||||
<TenantCapacity
|
||||
totalCapacity={tenant.capacity_raw || 0}
|
||||
usedSpaceVariants={spaceVariants}
|
||||
statusClass={healthStatusToClass(tenant.health_status)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
<Grid
|
||||
item
|
||||
xs
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
marginTop: "10px",
|
||||
}}
|
||||
>
|
||||
<InformationItem
|
||||
label={"Raw Capacity"}
|
||||
value={raw.value}
|
||||
unit={raw.unit}
|
||||
/>
|
||||
<InformationItem
|
||||
label={"Usable Capacity"}
|
||||
value={capacity.value}
|
||||
unit={capacity.unit}
|
||||
/>
|
||||
<InformationItem
|
||||
label={"Usage"}
|
||||
value={used.value}
|
||||
unit={used.unit}
|
||||
/>
|
||||
<InformationItem
|
||||
label={"Pools"}
|
||||
value={tenant.pool_count.toString()}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sx={{ paddingLeft: "20px", marginTop: "15px" }}
|
||||
>
|
||||
<span className={classes.status}>
|
||||
<strong>State:</strong> {tenant.currentState}
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={5}>
|
||||
<UsageBarWrapper
|
||||
currValue={tenant.capacity_raw_usage ?? 0}
|
||||
maxValue={tenant.capacity_raw ?? 1}
|
||||
label={""}
|
||||
renderFunction={niceBytes}
|
||||
error={""}
|
||||
loading={false}
|
||||
labels={false}
|
||||
/>
|
||||
<Grid
|
||||
item
|
||||
xs={2}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
id={"manage-tenant-" + tenant.name}
|
||||
disabled={!tenantIsOnline(tenant)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
history.push(
|
||||
`/namespaces/${tenant.namespace}/tenants/${tenant.name}/hop`
|
||||
);
|
||||
}}
|
||||
disableTouchRipple
|
||||
disableRipple
|
||||
focusRipple={false}
|
||||
sx={{
|
||||
color: "#5E5E5E",
|
||||
border: "#5E5E5E 1px solid",
|
||||
whiteSpace: "nowrap",
|
||||
paddingLeft: 4.5,
|
||||
paddingRight: 4.5,
|
||||
}}
|
||||
variant={"outlined"}
|
||||
>
|
||||
Manage
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -35,6 +35,15 @@ export interface IEvent {
|
||||
reason: string;
|
||||
}
|
||||
|
||||
interface IRequestResource {
|
||||
cpu: number;
|
||||
memory: number;
|
||||
}
|
||||
|
||||
export interface IResources {
|
||||
requests: IRequestResource;
|
||||
}
|
||||
|
||||
export interface IPool {
|
||||
name: string;
|
||||
servers: number;
|
||||
@@ -44,6 +53,7 @@ export interface IPool {
|
||||
capacity: string;
|
||||
volumes: number;
|
||||
label?: string;
|
||||
resources?: IResources;
|
||||
}
|
||||
|
||||
export interface IPodListElement {
|
||||
@@ -85,6 +95,7 @@ export interface ITenantStatusUsage {
|
||||
capacity: number;
|
||||
capacity_usage: number;
|
||||
}
|
||||
|
||||
export interface ITenantStatus {
|
||||
write_quorum: string;
|
||||
drives_online: string;
|
||||
@@ -101,24 +112,30 @@ export interface ITenantEncryptionResponse {
|
||||
server: ICertificateInfo[];
|
||||
client: ICertificateInfo[];
|
||||
/*
|
||||
gemalto:
|
||||
type: object
|
||||
$ref: "#/definitions/gemaltoConfiguration"
|
||||
aws:
|
||||
type: object
|
||||
$ref: "#/definitions/awsConfiguration"
|
||||
vault:
|
||||
type: object
|
||||
$ref: "#/definitions/vaultConfiguration"
|
||||
gcp:
|
||||
type: object
|
||||
$ref: "#/definitions/gcpConfiguration"
|
||||
azure:
|
||||
type: object
|
||||
$ref: "#/definitions/azureConfiguration"
|
||||
securityContext:
|
||||
type: object
|
||||
$ref: "#/definitions/securityContext"*/
|
||||
gemalto:
|
||||
type: object
|
||||
$ref: "#/definitions/gemaltoConfiguration"
|
||||
aws:
|
||||
type: object
|
||||
$ref: "#/definitions/awsConfiguration"
|
||||
vault:
|
||||
type: object
|
||||
$ref: "#/definitions/vaultConfiguration"
|
||||
gcp:
|
||||
type: object
|
||||
$ref: "#/definitions/gcpConfiguration"
|
||||
azure:
|
||||
type: object
|
||||
$ref: "#/definitions/azureConfiguration"
|
||||
securityContext:
|
||||
type: object
|
||||
$ref: "#/definitions/securityContext"*/
|
||||
}
|
||||
|
||||
export interface ITenantTier {
|
||||
name: string;
|
||||
type: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface ITenant {
|
||||
@@ -129,7 +146,7 @@ export interface ITenant {
|
||||
pool_count: number;
|
||||
currentState: string;
|
||||
instance_count: 4;
|
||||
creation_date: Date;
|
||||
creation_date: string;
|
||||
volume_size: number;
|
||||
volume_count: number;
|
||||
volumes_per_server: number;
|
||||
@@ -149,6 +166,7 @@ export interface ITenant {
|
||||
capacity_raw_usage?: number;
|
||||
capacity?: number;
|
||||
capacity_usage?: number;
|
||||
tiers?: ITenantTier[];
|
||||
// computed
|
||||
total_capacity: string;
|
||||
subnet_license: SubnetInfo;
|
||||
@@ -224,3 +242,14 @@ export interface ValueUnit {
|
||||
value: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface CapacityValues {
|
||||
value: number;
|
||||
variant: string;
|
||||
}
|
||||
|
||||
export interface CapacityValue {
|
||||
value: number;
|
||||
label: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import {
|
||||
containerForHeader,
|
||||
spacingUtils,
|
||||
tableStyles,
|
||||
tenantDetailsStyles,
|
||||
textStyleUtils,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { setErrorSnackMessage } from "../../../../../../actions";
|
||||
import { AppState } from "../../../../../../store";
|
||||
import { setTenantDetailsLoad } from "../../../actions";
|
||||
import { Box } from "@mui/material";
|
||||
import { ITenant } from "../../../ListTenants/types";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import LabelValuePair from "../../../../Common/UsageBarWrapper/LabelValuePair";
|
||||
import { niceBytesInt } from "../../../../../../common/utils";
|
||||
|
||||
interface IPoolDetails {
|
||||
classes: any;
|
||||
loadingTenant: boolean;
|
||||
tenant: ITenant | null;
|
||||
selectedPool: string | null;
|
||||
closeDetailsView: () => void;
|
||||
setTenantDetailsLoad: typeof setTenantDetailsLoad;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...spacingUtils,
|
||||
...textStyleUtils,
|
||||
...tenantDetailsStyles,
|
||||
...tableStyles,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const twoColCssGridLayoutConfig = {
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", sm: "2fr 1fr" },
|
||||
gridAutoFlow: { xs: "dense", sm: "row" },
|
||||
gap: 2,
|
||||
};
|
||||
|
||||
const PoolDetails = ({
|
||||
closeDetailsView,
|
||||
tenant,
|
||||
selectedPool,
|
||||
}: IPoolDetails) => {
|
||||
const poolInformation =
|
||||
tenant?.pools.find((pool) => pool.name === selectedPool) || null;
|
||||
|
||||
if (poolInformation === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Grid container>
|
||||
<Grid item xs={8}>
|
||||
<h4>Pool Configuration</h4>
|
||||
</Grid>
|
||||
<Grid item xs={4} />
|
||||
</Grid>
|
||||
<Box sx={{ ...twoColCssGridLayoutConfig }}>
|
||||
<LabelValuePair label={"Pool Name"} value={poolInformation.label} />
|
||||
<LabelValuePair
|
||||
label={"Total Volumes"}
|
||||
value={poolInformation.volumes}
|
||||
/>
|
||||
<LabelValuePair
|
||||
label={"Volumes per server"}
|
||||
value={poolInformation.volumes_per_server}
|
||||
/>
|
||||
<LabelValuePair label={"Capacity"} value={poolInformation.capacity} />
|
||||
</Box>
|
||||
<Grid container>
|
||||
<Grid item xs={8}>
|
||||
<h4>Resources</h4>
|
||||
</Grid>
|
||||
<Grid item xs={4} />
|
||||
</Grid>
|
||||
<Box sx={{ ...twoColCssGridLayoutConfig }}>
|
||||
{poolInformation.resources && (
|
||||
<Fragment>
|
||||
<LabelValuePair
|
||||
label={"CPU"}
|
||||
value={poolInformation.resources.requests.cpu}
|
||||
/>
|
||||
<LabelValuePair
|
||||
label={"Memory"}
|
||||
value={niceBytesInt(poolInformation.resources.requests.memory)}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
<LabelValuePair
|
||||
label={"Volume Size"}
|
||||
value={niceBytesInt(poolInformation.volume_configuration.size)}
|
||||
/>
|
||||
<LabelValuePair
|
||||
label={"Storage Class Name"}
|
||||
value={poolInformation.volume_configuration.storage_class_name}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
loadingTenant: state.tenants.tenantDetails.loadingTenant,
|
||||
selectedTenant: state.tenants.tenantDetails.currentTenant,
|
||||
tenant: state.tenants.tenantDetails.tenantInfo,
|
||||
selectedPool: state.tenants.tenantDetails.selectedPool,
|
||||
});
|
||||
const connector = connect(mapState, {
|
||||
setErrorSnackMessage,
|
||||
setTenantDetailsLoad,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(PoolDetails));
|
||||
@@ -0,0 +1,166 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 withStyles from "@mui/styles/withStyles";
|
||||
import { AppState } from "../../../../../../store";
|
||||
import { connect } from "react-redux";
|
||||
import { setErrorSnackMessage } from "../../../../../../actions";
|
||||
import { setSelectedPool } from "../../../actions";
|
||||
import { IPool, ITenant } from "../../../ListTenants/types";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { TextField } from "@mui/material";
|
||||
import InputAdornment from "@mui/material/InputAdornment";
|
||||
import SearchIcon from "../../../../../../icons/SearchIcon";
|
||||
import RBIconButton from "../../../../Buckets/BucketDetails/SummaryItems/RBIconButton";
|
||||
import { AddIcon } from "../../../../../../icons";
|
||||
import TableWrapper from "../../../../Common/TableWrapper/TableWrapper";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
tableStyles,
|
||||
tenantDetailsStyles,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
interface IPoolsSummary {
|
||||
classes: any;
|
||||
tenant: ITenant | null;
|
||||
loadingTenant: boolean;
|
||||
history: any;
|
||||
setPoolDetailsView: () => void;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
setSelectedPool: typeof setSelectedPool;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...tenantDetailsStyles,
|
||||
...actionsTray,
|
||||
...tableStyles,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const PoolsListing = ({
|
||||
classes,
|
||||
tenant,
|
||||
loadingTenant,
|
||||
setSelectedPool,
|
||||
history,
|
||||
setPoolDetailsView,
|
||||
}: IPoolsSummary) => {
|
||||
const [pools, setPools] = useState<IPool[]>([]);
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
if (tenant) {
|
||||
const resPools = !tenant.pools ? [] : tenant.pools;
|
||||
setPools(resPools);
|
||||
}
|
||||
}, [tenant]);
|
||||
|
||||
const filteredPools = pools.filter((pool) => {
|
||||
if (pool.name.toLowerCase().includes(filter.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const listActions = [
|
||||
{
|
||||
type: "view",
|
||||
onClick: (selectedValue: IPool) => {
|
||||
setSelectedPool(selectedValue.name);
|
||||
setPoolDetailsView();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<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>
|
||||
),
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
|
||||
<RBIconButton
|
||||
tooltip={"Expand Tenant"}
|
||||
text={"Expand Tenant"}
|
||||
onClick={() => {
|
||||
history.push(
|
||||
`/namespaces/${tenant?.namespace || ""}/tenants/${
|
||||
tenant?.name || ""
|
||||
}/add-pool`
|
||||
);
|
||||
}}
|
||||
icon={<AddIcon />}
|
||||
color="primary"
|
||||
variant={"contained"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
<TableWrapper
|
||||
itemActions={listActions}
|
||||
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>
|
||||
</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,
|
||||
setSelectedPool,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(PoolsListing));
|
||||
@@ -14,7 +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 React, { Fragment, useEffect, useState } from "react";
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
@@ -25,25 +25,23 @@ import {
|
||||
tableStyles,
|
||||
tenantDetailsStyles,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { TextField } from "@mui/material";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { AddIcon } from "../../../../icons";
|
||||
import { IPool, ITenant } from "../ListTenants/types";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
|
||||
import InputAdornment from "@mui/material/InputAdornment";
|
||||
import { AppState } from "../../../../store";
|
||||
import { setTenantDetailsLoad } from "../actions";
|
||||
import SearchIcon from "../../../../icons/SearchIcon";
|
||||
import RBIconButton from "../../Buckets/BucketDetails/SummaryItems/RBIconButton";
|
||||
import { setSelectedPool, setTenantDetailsLoad } from "../actions";
|
||||
import PoolsListing from "./Pools/Details/PoolsListing";
|
||||
import PoolDetails from "./Pools/Details/PoolDetails";
|
||||
import BackLink from "../../../../common/BackLink";
|
||||
|
||||
interface IPoolsSummary {
|
||||
classes: any;
|
||||
tenant: ITenant | null;
|
||||
loadingTenant: boolean;
|
||||
history: any;
|
||||
match: any;
|
||||
selectedPool: string | null;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
setTenantDetailsLoad: typeof setTenantDetailsLoad;
|
||||
setSelectedPool: typeof setSelectedPool;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -56,88 +54,42 @@ const styles = (theme: Theme) =>
|
||||
|
||||
const PoolsSummary = ({
|
||||
classes,
|
||||
tenant,
|
||||
loadingTenant,
|
||||
setTenantDetailsLoad,
|
||||
history,
|
||||
selectedPool,
|
||||
match,
|
||||
}: IPoolsSummary) => {
|
||||
const [pools, setPools] = useState<IPool[]>([]);
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
if (tenant) {
|
||||
const resPools = !tenant.pools ? [] : tenant.pools;
|
||||
setPools(resPools);
|
||||
}
|
||||
}, [tenant]);
|
||||
|
||||
const filteredPools = pools.filter((pool) => {
|
||||
if (pool.name.toLowerCase().includes(filter.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const [poolDetailsOpen, setPoolDetailsOpen] = useState<boolean>(false);
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 className={classes.sectionTitle}>Pools</h1>
|
||||
<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>
|
||||
),
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
|
||||
<RBIconButton
|
||||
tooltip={"Expand Tenant"}
|
||||
text={"Expand Tenant"}
|
||||
onClick={() => {
|
||||
history.push(
|
||||
`/namespaces/${tenant?.namespace || ""}/tenants/${
|
||||
tenant?.name || ""
|
||||
}/add-pool`
|
||||
);
|
||||
}}
|
||||
icon={<AddIcon />}
|
||||
color="primary"
|
||||
variant={"contained"}
|
||||
/>
|
||||
</Grid>
|
||||
{poolDetailsOpen && (
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
<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"
|
||||
<BackLink
|
||||
executeOnClick={() => {
|
||||
setPoolDetailsOpen(false);
|
||||
}}
|
||||
label={"Back to Pools list"}
|
||||
to={match.url}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<h1 className={classes.sectionTitle}>
|
||||
{poolDetailsOpen ? `Pool Details - ${selectedPool || ""}` : "Pools"}
|
||||
</h1>
|
||||
<Grid container>
|
||||
{poolDetailsOpen ? (
|
||||
<PoolDetails
|
||||
closeDetailsView={() => {
|
||||
setPoolDetailsOpen(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<PoolsListing
|
||||
setPoolDetailsView={() => {
|
||||
setPoolDetailsOpen(true);
|
||||
}}
|
||||
history={history}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
@@ -146,12 +98,14 @@ const PoolsSummary = ({
|
||||
const mapState = (state: AppState) => ({
|
||||
loadingTenant: state.tenants.tenantDetails.loadingTenant,
|
||||
selectedTenant: state.tenants.tenantDetails.currentTenant,
|
||||
selectedPool: state.tenants.tenantDetails.selectedPool,
|
||||
tenant: state.tenants.tenantDetails.tenantInfo,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
setErrorSnackMessage,
|
||||
setTenantDetailsLoad,
|
||||
setSelectedPool,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(PoolsSummary));
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
ADD_POOL_ADD_NEW_TOLERATION,
|
||||
ADD_POOL_REMOVE_TOLERATION_ROW,
|
||||
ADD_POOL_SET_KEY_PAIR_VALUE,
|
||||
POOL_DETAILS_SET_SELECTED_POOL,
|
||||
} from "./types";
|
||||
import { ITolerationModel } from "../../../common/types";
|
||||
|
||||
@@ -407,3 +408,10 @@ export const setPoolKeyValuePairs = (newArray: LabelKeyPair[]) => {
|
||||
newArray,
|
||||
};
|
||||
};
|
||||
|
||||
export const setSelectedPool = (newPool: string | null) => {
|
||||
return {
|
||||
type: POOL_DETAILS_SET_SELECTED_POOL,
|
||||
pool: newPool,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
ADD_POOL_SET_TOLERATION_VALUE,
|
||||
ADD_POOL_REMOVE_TOLERATION_ROW,
|
||||
ADD_POOL_SET_KEY_PAIR_VALUE,
|
||||
POOL_DETAILS_SET_SELECTED_POOL,
|
||||
} from "./types";
|
||||
import { KeyPair } from "./ListTenants/utils";
|
||||
import { getRandomString } from "./utils";
|
||||
@@ -363,6 +364,7 @@ const initialState: ITenantState = {
|
||||
loadingTenant: false,
|
||||
tenantInfo: null,
|
||||
currentTab: "summary",
|
||||
selectedPool: null,
|
||||
},
|
||||
addPool: {
|
||||
addPoolLoading: false,
|
||||
@@ -1163,6 +1165,14 @@ export function tenantsReducer(
|
||||
},
|
||||
},
|
||||
};
|
||||
case POOL_DETAILS_SET_SELECTED_POOL:
|
||||
return {
|
||||
...state,
|
||||
tenantDetails: {
|
||||
...state.tenantDetails,
|
||||
selectedPool: action.pool,
|
||||
},
|
||||
};
|
||||
case ADD_POOL_RESET_FORM:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -98,6 +98,9 @@ export const ADD_POOL_SET_TOLERATION_VALUE = "ADD_POOL/SET_TOLERATION_VALUE";
|
||||
export const ADD_POOL_ADD_NEW_TOLERATION = "ADD_POOL/ADD_NEW_TOLERATION";
|
||||
export const ADD_POOL_REMOVE_TOLERATION_ROW = "ADD_POOL/REMOVE_TOLERATION_ROW";
|
||||
|
||||
// Pool Details
|
||||
export const POOL_DETAILS_SET_SELECTED_POOL = "POOL_DETAILS/SET_SELECTED_POOL";
|
||||
|
||||
export interface ICertificateInfo {
|
||||
name: string;
|
||||
serialNumber: string;
|
||||
@@ -364,6 +367,7 @@ export interface ITenantDetails {
|
||||
loadingTenant: boolean;
|
||||
tenantInfo: ITenant | null;
|
||||
currentTab: string;
|
||||
selectedPool: string | null;
|
||||
}
|
||||
|
||||
export interface ITenantState {
|
||||
@@ -635,6 +639,11 @@ interface SetPoolSelectorKeyPairValueArray {
|
||||
newArray: LabelKeyPair[];
|
||||
}
|
||||
|
||||
interface SetSelectedPool {
|
||||
type: typeof POOL_DETAILS_SET_SELECTED_POOL;
|
||||
pool: string | null;
|
||||
}
|
||||
|
||||
export type FieldsToHandle = INameTenantFields;
|
||||
|
||||
export type TenantsManagementTypes =
|
||||
@@ -676,4 +685,5 @@ export type TenantsManagementTypes =
|
||||
| SetPoolTolerationValue
|
||||
| AddNewPoolToleration
|
||||
| RemovePoolTolerationRow
|
||||
| SetPoolSelectorKeyPairValueArray;
|
||||
| SetPoolSelectorKeyPairValueArray
|
||||
| SetSelectedPool;
|
||||
|
||||
@@ -1420,6 +1420,10 @@ definitions:
|
||||
capacity_usage:
|
||||
type: integer
|
||||
format: int64
|
||||
tiers:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/tenantTierElement"
|
||||
|
||||
listTenantsResponse:
|
||||
type: object
|
||||
@@ -2857,3 +2861,14 @@ definitions:
|
||||
type: string
|
||||
latest_version:
|
||||
type: string
|
||||
|
||||
tenantTierElement:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
size:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
Reference in New Issue
Block a user