diff --git a/models/tenant.go b/models/tenant.go index 164aadb00..5699c093e 100644 --- a/models/tenant.go +++ b/models/tenant.go @@ -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 diff --git a/models/tenant_list.go b/models/tenant_list.go index 82a6dd5fc..10c6a3bc3 100644 --- a/models/tenant_list.go +++ b/models/tenant_list.go @@ -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"` diff --git a/models/tenant_status.go b/models/tenant_status.go new file mode 100644 index 000000000..2349a63cc --- /dev/null +++ b/models/tenant_status.go @@ -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 . +// + +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 +} diff --git a/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx b/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx index 19f0b2a51..6fd98d0a8 100644 --- a/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx +++ b/portal-ui/src/screens/Console/Tenants/ListTenants/ListTenants.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -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 ( {deleteOpen && ( @@ -271,7 +293,25 @@ const ListTenants = ({ { + return ( + + + ⬤ + {" "} + {t.name} + + ); + }, + }, { label: "Namespace", elementKey: "namespace" }, { label: "Capacity", elementKey: "capacity" }, { label: "# of Pools", elementKey: "pool_count" }, diff --git a/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts b/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts index 43ef77014..08e8de419 100644 --- a/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts +++ b/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts @@ -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; diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx index afe6c1da3..eae5bbe46 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantDetails.tsx @@ -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 ( {addPoolOpen && tenant !== null && ( @@ -524,6 +550,44 @@ const TenantDetails = ({ error={usageError} loading={loadingUsage} /> +

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

+ + + + + + + + + + + + + +
Drives Online + {tenant?.status?.drives_online + ? tenant?.status?.drives_online + : 0} +
Drives Offline + {tenant?.status?.drives_offline + ? tenant?.status?.drives_offline + : 0} +
Write Quorum + {tenant?.status?.write_quorum + ? tenant?.status?.write_quorum + : 0} +
diff --git a/portal-ui/src/theme/newtheme.ts b/portal-ui/src/theme/newtheme.ts index 7f329a46d..fdd8ff85a 100644 --- a/portal-ui/src/theme/newtheme.ts +++ b/portal-ui/src/theme/newtheme.ts @@ -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(","), diff --git a/restapi/admin_tenants.go b/restapi/admin_tenants.go index 289503bfc..b32018480 100644 --- a/restapi/admin_tenants.go +++ b/restapi/admin_tenants.go @@ -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), }) } diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index edd6af714..f2fb068a3 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -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": { diff --git a/swagger.yml b/swagger.yml index 8eca7de02..cff446988 100644 --- a/swagger.yml +++ b/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