Add Tenant Health Details (#780)
* Add Tenant Health Details Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> * Colors Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
@@ -80,6 +80,9 @@ type Tenant struct {
|
||||
// pools
|
||||
Pools []*Pool `json:"pools"`
|
||||
|
||||
// status
|
||||
Status *TenantStatus `json:"status,omitempty"`
|
||||
|
||||
// subnet license
|
||||
SubnetLicense *License `json:"subnet_license,omitempty"`
|
||||
|
||||
@@ -99,6 +102,10 @@ func (m *Tenant) Validate(formats strfmt.Registry) error {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateStatus(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateSubnetLicense(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
@@ -152,6 +159,24 @@ func (m *Tenant) validatePools(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Tenant) validateStatus(formats strfmt.Registry) error {
|
||||
|
||||
if swag.IsZero(m.Status) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.Status != nil {
|
||||
if err := m.Status.Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("status")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Tenant) validateSubnetLicense(formats strfmt.Registry) error {
|
||||
|
||||
if swag.IsZero(m.SubnetLicense) { // not required
|
||||
|
||||
@@ -41,6 +41,9 @@ type TenantList struct {
|
||||
// deletion date
|
||||
DeletionDate string `json:"deletion_date,omitempty"`
|
||||
|
||||
// health status
|
||||
HealthStatus string `json:"health_status,omitempty"`
|
||||
|
||||
// instance count
|
||||
InstanceCount int64 `json:"instance_count,omitempty"`
|
||||
|
||||
|
||||
72
models/tenant_status.go
Normal file
72
models/tenant_status.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// 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/>.
|
||||
//
|
||||
|
||||
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 (
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// TenantStatus tenant status
|
||||
//
|
||||
// swagger:model tenantStatus
|
||||
type TenantStatus struct {
|
||||
|
||||
// drives healing
|
||||
DrivesHealing int32 `json:"drives_healing,omitempty"`
|
||||
|
||||
// drives offline
|
||||
DrivesOffline int32 `json:"drives_offline,omitempty"`
|
||||
|
||||
// drives online
|
||||
DrivesOnline int32 `json:"drives_online,omitempty"`
|
||||
|
||||
// health status
|
||||
HealthStatus string `json:"health_status,omitempty"`
|
||||
|
||||
// write quorum
|
||||
WriteQuorum int32 `json:"write_quorum,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this tenant status
|
||||
func (m *TenantStatus) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *TenantStatus) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *TenantStatus) UnmarshalBinary(b []byte) error {
|
||||
var res TenantStatus
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
@@ -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, { useEffect, useState, Fragment } from "react";
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
@@ -93,6 +93,18 @@ const styles = (theme: Theme) =>
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
redState: {
|
||||
color: theme.palette.error.main,
|
||||
},
|
||||
yellowState: {
|
||||
color: theme.palette.warning.main,
|
||||
},
|
||||
greenState: {
|
||||
color: theme.palette.success.main,
|
||||
},
|
||||
greyState: {
|
||||
color: "grey",
|
||||
},
|
||||
});
|
||||
|
||||
const ListTenants = ({
|
||||
@@ -203,6 +215,16 @@ const ListTenants = ({
|
||||
setCurrentPanel(1);
|
||||
};
|
||||
|
||||
const healthStatusToClass = (health_status: string) => {
|
||||
return health_status == "red"
|
||||
? classes.redState
|
||||
: health_status == "yellow"
|
||||
? classes.yellowState
|
||||
: health_status == "green"
|
||||
? classes.greenState
|
||||
: classes.greyState;
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{deleteOpen && (
|
||||
@@ -271,7 +293,25 @@ const ListTenants = ({
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFullObject: true,
|
||||
renderFunction: (t) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span
|
||||
className={healthStatusToClass(
|
||||
t.health_status
|
||||
)}
|
||||
>
|
||||
⬤
|
||||
</span>{" "}
|
||||
{t.name}
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
{ label: "Namespace", elementKey: "namespace" },
|
||||
{ label: "Capacity", elementKey: "capacity" },
|
||||
{ label: "# of Pools", elementKey: "pool_count" },
|
||||
|
||||
@@ -57,6 +57,14 @@ export interface IEndpoints {
|
||||
console: string;
|
||||
}
|
||||
|
||||
export interface ITenantStatus {
|
||||
write_quorum: string;
|
||||
drives_online: string;
|
||||
drives_offline: string;
|
||||
drives_healing: string;
|
||||
health_status: string;
|
||||
}
|
||||
|
||||
export interface ITenant {
|
||||
total_size: number;
|
||||
name: string;
|
||||
@@ -77,6 +85,8 @@ export interface ITenant {
|
||||
encryptionEnabled: boolean;
|
||||
idpAdEnabled: boolean;
|
||||
idpOicEnabled: boolean;
|
||||
health_status: string;
|
||||
status?: ITenantStatus;
|
||||
// computed
|
||||
capacity: string;
|
||||
subnet_license: LicenseInfo;
|
||||
|
||||
@@ -133,6 +133,22 @@ const styles = (theme: Theme) =>
|
||||
textDecoration: "none",
|
||||
color: "black",
|
||||
},
|
||||
redState: {
|
||||
color: theme.palette.error.main,
|
||||
},
|
||||
yellowState: {
|
||||
color: theme.palette.warning.main,
|
||||
},
|
||||
greenState: {
|
||||
color: theme.palette.success.main,
|
||||
},
|
||||
greyState: {
|
||||
color: "grey",
|
||||
},
|
||||
healthCol: {
|
||||
fontWeight: "bold",
|
||||
paddingRight: "10px",
|
||||
},
|
||||
...modalBasic,
|
||||
...actionsTray,
|
||||
...buttonsStyles,
|
||||
@@ -345,6 +361,16 @@ const TenantDetails = ({
|
||||
loadInfo();
|
||||
};
|
||||
|
||||
const healthStatusToClass = (health_status: string) => {
|
||||
return health_status == "red"
|
||||
? classes.redState
|
||||
: health_status == "yellow"
|
||||
? classes.yellowState
|
||||
: health_status == "green"
|
||||
? classes.greenState
|
||||
: classes.greyState;
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addPoolOpen && tenant !== null && (
|
||||
@@ -524,6 +550,44 @@ const TenantDetails = ({
|
||||
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>
|
||||
|
||||
@@ -14,11 +14,6 @@ const newTheme = createMuiTheme({
|
||||
dark: "#01262E",
|
||||
contrastText: "#000",
|
||||
},
|
||||
error: {
|
||||
light: "#e03a48",
|
||||
main: "#dc1f2e",
|
||||
contrastText: "#ffffff",
|
||||
},
|
||||
grey: {
|
||||
100: "#F7F7F7",
|
||||
200: "#D8DDDE",
|
||||
@@ -33,6 +28,17 @@ const newTheme = createMuiTheme({
|
||||
background: {
|
||||
default: "#F4F4F4",
|
||||
},
|
||||
success: {
|
||||
main: "#32c787",
|
||||
},
|
||||
warning: {
|
||||
main: "#ffb300",
|
||||
},
|
||||
error: {
|
||||
light: "#e03a48",
|
||||
main: "#dc1f2e",
|
||||
contrastText: "#ffffff",
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: ["Lato", "sans-serif"].join(","),
|
||||
|
||||
@@ -434,6 +434,15 @@ func getTenantInfoResponse(session *models.Principal, params admin_api.TenantInf
|
||||
}
|
||||
}
|
||||
|
||||
// attach status information
|
||||
info.Status = &models.TenantStatus{
|
||||
HealthStatus: string(minTenant.Status.HealthStatus),
|
||||
DrivesHealing: minTenant.Status.DrivesHealing,
|
||||
DrivesOffline: minTenant.Status.DrivesOffline,
|
||||
DrivesOnline: minTenant.Status.DrivesOnline,
|
||||
WriteQuorum: minTenant.Status.WriteQuorum,
|
||||
}
|
||||
|
||||
// get tenant service
|
||||
minTenant.EnsureDefaults()
|
||||
//minio service
|
||||
@@ -533,6 +542,7 @@ func listTenants(ctx context.Context, operatorClient OperatorClientI, namespace
|
||||
CurrentState: tenant.Status.CurrentState,
|
||||
Namespace: tenant.ObjectMeta.Namespace,
|
||||
TotalSize: totalSize,
|
||||
HealthStatus: string(tenant.Status.HealthStatus),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6699,6 +6699,9 @@ func init() {
|
||||
"$ref": "#/definitions/pool"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/tenantStatus"
|
||||
},
|
||||
"subnet_license": {
|
||||
"$ref": "#/definitions/license"
|
||||
},
|
||||
@@ -6720,6 +6723,9 @@ func init() {
|
||||
"deletion_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"health_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"instance_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -6777,6 +6783,30 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"tenantStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"drives_healing": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"drives_offline": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"drives_online": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"health_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"write_quorum": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tenantUsage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -14398,6 +14428,9 @@ func init() {
|
||||
"$ref": "#/definitions/pool"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/tenantStatus"
|
||||
},
|
||||
"subnet_license": {
|
||||
"$ref": "#/definitions/license"
|
||||
},
|
||||
@@ -14419,6 +14452,9 @@ func init() {
|
||||
"deletion_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"health_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"instance_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -14476,6 +14512,30 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"tenantStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"drives_healing": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"drives_offline": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"drives_online": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"health_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"write_quorum": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tenantUsage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
21
swagger.yml
21
swagger.yml
@@ -3659,6 +3659,23 @@ definitions:
|
||||
object_locking_enabled:
|
||||
type: boolean
|
||||
|
||||
tenantStatus:
|
||||
type: object
|
||||
properties:
|
||||
write_quorum:
|
||||
type: integer
|
||||
format: int32
|
||||
drives_online:
|
||||
type: integer
|
||||
format: int32
|
||||
drives_offline:
|
||||
type: integer
|
||||
format: int32
|
||||
drives_healing:
|
||||
type: integer
|
||||
format: int32
|
||||
health_status:
|
||||
type: string
|
||||
|
||||
tenant:
|
||||
type: object
|
||||
@@ -3705,6 +3722,8 @@ definitions:
|
||||
type: boolean
|
||||
encryptionEnabled:
|
||||
type: boolean
|
||||
status:
|
||||
$ref: "#/definitions/tenantStatus"
|
||||
|
||||
|
||||
tenantUsage:
|
||||
@@ -3738,6 +3757,8 @@ definitions:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
health_status:
|
||||
type: string
|
||||
|
||||
logSearchResponse:
|
||||
type: object
|
||||
|
||||
Reference in New Issue
Block a user