From 6cb8c51754ce2c280c03787103c608fede936e9f Mon Sep 17 00:00:00 2001 From: Alex <33497058+bexsoft@users.noreply.github.com> Date: Fri, 27 Aug 2021 12:59:41 -0500 Subject: [PATCH] Restructured basic dashboard elements (#988) Co-authored-by: Benjamin Perez --- models/server_drives.go | 70 ++++++ models/server_properties.go | 72 +++++- portal-ui/src/icons/DeleteIcon.tsx | 4 - .../Objects/ListObjects/ListObjects.tsx | 1 - .../Common/TableWrapper/TableActionButton.tsx | 2 - .../BasicDashboard/BasicDashboard.tsx | 95 +++++--- .../BasicDashboard/ServerInfoCard.tsx | 224 ++++++++++++------ .../screens/Console/Dashboard/Dashboard.tsx | 32 +-- .../src/screens/Console/Dashboard/types.tsx | 7 + .../src/screens/Console/License/License.tsx | 10 +- .../Users/UserServiceAccountsPanel.tsx | 1 - restapi/admin_info.go | 11 +- restapi/embedded_spec.go | 50 +++- swagger-console.yml | 17 +- 14 files changed, 449 insertions(+), 147 deletions(-) create mode 100644 models/server_drives.go diff --git a/models/server_drives.go b/models/server_drives.go new file mode 100644 index 000000000..0b60f5162 --- /dev/null +++ b/models/server_drives.go @@ -0,0 +1,70 @@ +// 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 ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ServerDrives server drives +// +// swagger:model serverDrives +type ServerDrives struct { + + // state + State string `json:"state,omitempty"` + + // uuid + UUID string `json:"uuid,omitempty"` +} + +// Validate validates this server drives +func (m *ServerDrives) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this server drives based on context it is used +func (m *ServerDrives) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ServerDrives) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ServerDrives) UnmarshalBinary(b []byte) error { + var res ServerDrives + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/server_properties.go b/models/server_properties.go index d119b9b24..17da3c613 100644 --- a/models/server_properties.go +++ b/models/server_properties.go @@ -24,7 +24,9 @@ package models import ( "context" + "strconv" + "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" ) @@ -37,9 +39,15 @@ type ServerProperties struct { // commit ID CommitID string `json:"commitID,omitempty"` + // drives + Drives []*ServerDrives `json:"drives"` + // endpoint Endpoint string `json:"endpoint,omitempty"` + // network + Network map[string]string `json:"network,omitempty"` + // pool number PoolNumber int64 `json:"poolNumber,omitempty"` @@ -47,7 +55,7 @@ type ServerProperties struct { State string `json:"state,omitempty"` // uptime - Uptime string `json:"uptime,omitempty"` + Uptime int64 `json:"uptime,omitempty"` // version Version string `json:"version,omitempty"` @@ -55,11 +63,71 @@ type ServerProperties struct { // Validate validates this server properties func (m *ServerProperties) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateDrives(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } return nil } -// ContextValidate validates this server properties based on context it is used +func (m *ServerProperties) validateDrives(formats strfmt.Registry) error { + if swag.IsZero(m.Drives) { // not required + return nil + } + + for i := 0; i < len(m.Drives); i++ { + if swag.IsZero(m.Drives[i]) { // not required + continue + } + + if m.Drives[i] != nil { + if err := m.Drives[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("drives" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this server properties based on the context it is used func (m *ServerProperties) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateDrives(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ServerProperties) contextValidateDrives(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Drives); i++ { + + if m.Drives[i] != nil { + if err := m.Drives[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("drives" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + return nil } diff --git a/portal-ui/src/icons/DeleteIcon.tsx b/portal-ui/src/icons/DeleteIcon.tsx index 867f0d206..1c63b782f 100644 --- a/portal-ui/src/icons/DeleteIcon.tsx +++ b/portal-ui/src/icons/DeleteIcon.tsx @@ -18,10 +18,6 @@ import React from "react"; import { SvgIcon } from "@material-ui/core"; import { IIcon } from "./props"; -interface IDeleteIcon { - width?: number; -} - const DeleteIcon = ({ width = 24 }: IIcon) => { return ( diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx index 3cb75b423..52e775e46 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx @@ -62,7 +62,6 @@ import { Route, } from "../../../../ObjectBrowser/reducers"; import CreateFolderModal from "./CreateFolderModal"; -import UploadFile from "../../../../../../icons/UploadFile"; import { download } from "../utils"; import { setErrorSnackMessage, diff --git a/portal-ui/src/screens/Console/Common/TableWrapper/TableActionButton.tsx b/portal-ui/src/screens/Console/Common/TableWrapper/TableActionButton.tsx index 822a21f6f..052ba7d16 100644 --- a/portal-ui/src/screens/Console/Common/TableWrapper/TableActionButton.tsx +++ b/portal-ui/src/screens/Console/Common/TableWrapper/TableActionButton.tsx @@ -20,8 +20,6 @@ import { createStyles, withStyles } from "@material-ui/core/styles"; import { IconButton } from "@material-ui/core"; import ViewIcon from "./TableActionIcons/ViewIcon"; import ShareIcon from "./TableActionIcons/ShareIcon"; -import DeleteIcon from "./TableActionIcons/DeleteIcon"; -import DescriptionIcon from "./TableActionIcons/DescriptionIcon"; import CloudIcon from "./TableActionIcons/CloudIcon"; import ConsoleIcon from "./TableActionIcons/ConsoleIcon"; import DownloadIcon from "./TableActionIcons/DownloadIcon"; diff --git a/portal-ui/src/screens/Console/Dashboard/BasicDashboard/BasicDashboard.tsx b/portal-ui/src/screens/Console/Dashboard/BasicDashboard/BasicDashboard.tsx index 64ac4ab17..96242943b 100644 --- a/portal-ui/src/screens/Console/Dashboard/BasicDashboard/BasicDashboard.tsx +++ b/portal-ui/src/screens/Console/Dashboard/BasicDashboard/BasicDashboard.tsx @@ -21,12 +21,12 @@ import Grid from "@material-ui/core/Grid"; import Paper from "@material-ui/core/Paper"; import Typography from "@material-ui/core/Typography"; import { Usage } from "../types"; -import { niceBytes, niceDays } from "../../../../common/utils"; +import { niceBytes } from "../../../../common/utils"; import DnsIcon from "@material-ui/icons/Dns"; import EgressIcon from "../../../../icons/EgressIcon"; import ReportedUsageIcon from "../../../../icons/ReportedUsageIcon"; +import ServerInfoCard from "./ServerInfoCard"; import { BucketsIcon } from "../../../../icons"; -import {ServerInfoCard} from "./ServerInfoCard" const styles = (theme: Theme) => createStyles({ @@ -38,17 +38,22 @@ const styles = (theme: Theme) => border: "#eaedee 1px solid", borderRadius: 5, boxShadow: "none", + marginBottom: 15, }, fixedHeight: { height: 165, minWidth: 247, marginRight: 20, padding: "25px 28px", - "& svg": { + "& svg:not(.computerIcon)": { maxHeight: 18, }, }, - infoHeight: { + serversContainer: { + height: 250, + overflow: "hidden" as const, + }, + infoHeight: { height: 180, minWidth: 247, marginRight: 20, @@ -67,7 +72,7 @@ const styles = (theme: Theme) => fontSize: "20px", fontWeight: "bold", }, - infoValue: { + infoValue: { fontWeight: 500, color: "#777777", fontSize: 14, @@ -80,12 +85,13 @@ const styles = (theme: Theme) => notationContainer: { display: "flex", flexWrap: "wrap", + marginTop: 20, }, dashboardBG: { width: 390, height: 255, - zIndex: 500, - position: "absolute", + zIndex: -1, + position: "fixed", backgroundSize: "fill", backgroundImage: "url(/images/BG_IllustrationDarker.svg)", backgroundPosition: "right bottom", @@ -93,10 +99,6 @@ const styles = (theme: Theme) => bottom: 0, backgroundRepeat: "no-repeat", }, - dashboardContainer: { - zIndex: 600, - position: "absolute", - }, elementTitle: { fontWeight: 500, color: "#777777", @@ -106,6 +108,19 @@ const styles = (theme: Theme) => smallUnit: { fontSize: 20, }, + serversListContainer: { + overflowY: "auto", + height: 200, + width: "100%", + }, + cardsContainer: { + display: "flex", + flexWrap: "wrap", + justifyContent: "center", + }, + serversAdj: { + maxWidth: 1380, + }, }); interface IDashboardProps { @@ -115,7 +130,11 @@ interface IDashboardProps { const BasicDashboard = ({ classes, usage }: IDashboardProps) => { const fixedHeightPaper = clsx(classes.paper, classes.fixedHeight); - + const serversPaperContainer = clsx( + classes.paper, + classes.fixedHeight, + classes.serversContainer + ); const prettyUsage = (usage: string | undefined) => { if (usage === undefined) { @@ -144,13 +163,11 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => { return usage.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); }; - const makeServerArray = (usage: Usage | null) => { if (usage != null) { - usage.servers.forEach((s) => (s.uptime = niceDays(s.uptime))); return usage.servers.sort(function (a, b) { - var nameA = a.endpoint.toUpperCase(); - var nameB = b.endpoint.toUpperCase(); + var nameA = a.endpoint.toLowerCase(); + var nameB = b.endpoint.toLowerCase(); if (nameA < nameB) { return -1; } @@ -169,7 +186,7 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => {
- + @@ -216,25 +233,33 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => { {usage ? prettyNumber(usage.objects) : 0} - - - - - - Servers - + + + +
+ + + + + + + {" "} + Servers + + - - {serverArray.map((server) => { - return( - - ) - })} - - - - - +
+
+
+ {serverArray.map((server, index) => ( + + ))} +
+
+
diff --git a/portal-ui/src/screens/Console/Dashboard/BasicDashboard/ServerInfoCard.tsx b/portal-ui/src/screens/Console/Dashboard/BasicDashboard/ServerInfoCard.tsx index 7c465c840..d5fe1a469 100644 --- a/portal-ui/src/screens/Console/Dashboard/BasicDashboard/ServerInfoCard.tsx +++ b/portal-ui/src/screens/Console/Dashboard/BasicDashboard/ServerInfoCard.tsx @@ -14,108 +14,174 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { Fragment } from "react"; +import React from "react"; import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; -import clsx from "clsx"; +import ComputerIcon from "@material-ui/icons/Computer"; import Grid from "@material-ui/core/Grid"; import Paper from "@material-ui/core/Paper"; -import Typography from "@material-ui/core/Typography"; -import { Usage, ServerInfo } from "../types"; -import { niceBytes, niceDays } from "../../../../common/utils"; -import DnsIcon from "@material-ui/icons/Dns"; -import EgressIcon from "../../../../icons/EgressIcon"; -import ReportedUsageIcon from "../../../../icons/ReportedUsageIcon"; -import { BucketsIcon } from "../../../../icons"; +import { ServerInfo } from "../types"; +import { niceDays } from "../../../../common/utils"; +import { Tooltip } from "@material-ui/core"; const styles = (theme: Theme) => createStyles({ - paper: { - padding: theme.spacing(2), + serverCard: { + padding: 15, + margin: 8, + width: "100%", + maxWidth: 620, + "& .computerIcon": { + marginRight: 10, + }, + }, + titleContainer: { display: "flex", - overflow: "auto", - flexDirection: "column", - border: "#eaedee 1px solid", - borderRadius: 5, - boxShadow: "none", }, - fixedHeight: { - height: 165, - minWidth: 247, - marginRight: 20, - padding: "25px 28px", - "& svg": { - maxHeight: 18, - }, - }, - infoHeight: { - height: 180, - minWidth: 247, - marginRight: 20, - padding: "25px 28px", - "& svg": { - maxHeight: 18, - }, - }, - consumptionValue: { - color: "#000000", - fontSize: "60px", - fontWeight: "bold", + cardIconContainer: { + display: "flex", + position: "relative", + alignItems: "center", }, endpoint: { color: "#000000", - fontSize: "20px", + fontSize: 20, fontWeight: "bold", + position: "relative" as const, + textOverflow: "ellipsis", + whiteSpace: "nowrap", + overflow: "hidden", }, - infoValue: { - fontWeight: 500, - color: "#777777", - fontSize: 14, - marginTop: 9, - }, - icon: { - marginRight: 10, - color: "#777777", - }, - notationContainer: { + stateContainer: { display: "flex", flexWrap: "wrap", + justifyContent: "space-between", }, - - elementTitle: { + infoValue: { fontWeight: 500, color: "#777777", fontSize: 14, - marginTop: -9, + margin: "5px 4px", + display: "inline-flex", + "& strong": { + marginRight: 4, + }, + }, + redState: { + color: theme.palette.error.main, + }, + greenState: { + color: theme.palette.success.main, + }, + yellowState: { + color: theme.palette.warning.main, + }, + greyState: { + color: "grey", + }, + healthStatusIcon: { + position: "absolute", + fontSize: 10, + left: 18, + height: 10, + bottom: 5, + }, + innerState: { + fontSize: 10, + marginLeft: 5, + display: "flex", + alignItems: "center", + marginTop: -3, }, }); - interface ICardProps { +interface ICardProps { classes: any; server: ServerInfo; } - export const ServerInfoCard = ({classes, server}: ICardProps) => { - return( - - - - - - {" "} - {server.endpoint} - - - Status: {server.state} - - - Uptime: {niceDays(server.uptime)} - - - Version: {server.version} - - - - - - ) - } \ No newline at end of file +const ServerInfoCard = ({ classes, server }: ICardProps) => { + console.log(server); + const serverStatusToClass = (health_status: string) => { + switch (health_status) { + case "offline": + return classes.redState; + case "online": + return classes.greenState; + default: + return classes.greyState; + } + }; + + const networkKeys = Object.keys(server.network); + + const networkTotal = networkKeys.length; + const totalDrives = server.drives.length; + + const activeNetwork = networkKeys.reduce((acc: number, currValue: string) => { + const item = server.network[currValue]; + if (item === "online") { + return acc + 1; + } + return acc; + }, 0); + + const activeDisks = server.drives.filter( + (element) => element.state === "ok" + ).length; + + return ( + + + +
+
+ +
+ {server.state && ( + + )} +
+
{" "} + +
{server.endpoint}
+
+
+
+ Version: {server.version} +
+
+ + + Drives: {activeDisks}/{totalDrives}{" "} + + ⬤ + + + + Network: {activeNetwork}/{networkTotal}{" "} + + ⬤ + + + + Uptime: {niceDays(server.uptime)} + + +
+
+ ); +}; + +export default withStyles(styles)(ServerInfoCard); diff --git a/portal-ui/src/screens/Console/Dashboard/Dashboard.tsx b/portal-ui/src/screens/Console/Dashboard/Dashboard.tsx index d7bb55dae..8ae8a282f 100644 --- a/portal-ui/src/screens/Console/Dashboard/Dashboard.tsx +++ b/portal-ui/src/screens/Console/Dashboard/Dashboard.tsx @@ -67,25 +67,25 @@ const Dashboard = ({ classes, displayErrorMessage }: IDashboardSimple) => { return ( - - {loading ? ( + {loading ? ( + - ) : ( - - {widgets !== null ? ( - - - - ) : ( - - - - )} - - )} - + + ) : ( + + {widgets !== null ? ( + + + + ) : ( + + + + )} + + )} ); }; diff --git a/portal-ui/src/screens/Console/Dashboard/types.tsx b/portal-ui/src/screens/Console/Dashboard/types.tsx index 69c314e6a..9d488a6df 100644 --- a/portal-ui/src/screens/Console/Dashboard/types.tsx +++ b/portal-ui/src/screens/Console/Dashboard/types.tsx @@ -29,4 +29,11 @@ export interface ServerInfo { version: string; commitID: string; poolNumber: number; + drives: IDriveInfo[], + network: any, +} + +export interface IDriveInfo { + state: string, + uuid: string, } diff --git a/portal-ui/src/screens/Console/License/License.tsx b/portal-ui/src/screens/Console/License/License.tsx index cc32f4cc0..4465ae241 100644 --- a/portal-ui/src/screens/Console/License/License.tsx +++ b/portal-ui/src/screens/Console/License/License.tsx @@ -271,6 +271,9 @@ const styles = (theme: Theme) => loadingLoginStrategy: { textAlign: "center", }, + clickableBlock: { + cursor: "pointer", + }, ...containerForHeader(theme.spacing(4)), }); @@ -486,7 +489,10 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
- setLicenseModal(true)} href="#"> + )} diff --git a/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx b/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx index 8e8bff8ad..ffb07c1fd 100644 --- a/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx +++ b/portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx @@ -17,7 +17,6 @@ import React, { useEffect, useState } from "react"; import { connect } from "react-redux"; import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; -import Grid from "@material-ui/core/Grid"; import { actionsTray, searchField, diff --git a/restapi/admin_info.go b/restapi/admin_info.go index 5d64e6e0e..d93d8cea6 100644 --- a/restapi/admin_info.go +++ b/restapi/admin_info.go @@ -24,7 +24,6 @@ import ( "net/http" "net/url" "regexp" - "strconv" "strings" "time" @@ -85,13 +84,21 @@ func GetAdminInfo(ctx context.Context, client MinioAdmin) (*UsageInfo, error) { //serverArray contains the serverProperties which describe the servers in the network var serverArray []*models.ServerProperties for _, serv := range serverInfo.Servers { + var drives = []*models.ServerDrives{} + + for _, drive := range serv.Disks { + drives = append(drives, &models.ServerDrives{State: drive.State, UUID: drive.UUID}) + } + var newServer = &models.ServerProperties{ State: serv.State, Endpoint: serv.Endpoint, - Uptime: strconv.Itoa(int(serv.Uptime)), + Uptime: serv.Uptime, Version: serv.Version, CommitID: serv.CommitID, PoolNumber: int64(serv.PoolNumber), + Network: serv.Network, + Drives: drives, } serverArray = append(serverArray, newServer) diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 4f3f2b9b3..4ae2bdaa4 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -4869,15 +4869,38 @@ func init() { } } }, + "serverDrives": { + "type": "object", + "properties": { + "state": { + "type": "string" + }, + "uuid": { + "type": "string" + } + } + }, "serverProperties": { "type": "object", "properties": { "commitID": { "type": "string" }, + "drives": { + "type": "array", + "items": { + "$ref": "#/definitions/serverDrives" + } + }, "endpoint": { "type": "string" }, + "network": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "poolNumber": { "type": "integer" }, @@ -4885,7 +4908,7 @@ func init() { "type": "string" }, "uptime": { - "type": "string" + "type": "integer" }, "version": { "type": "string" @@ -10324,15 +10347,38 @@ func init() { } } }, + "serverDrives": { + "type": "object", + "properties": { + "state": { + "type": "string" + }, + "uuid": { + "type": "string" + } + } + }, "serverProperties": { "type": "object", "properties": { "commitID": { "type": "string" }, + "drives": { + "type": "array", + "items": { + "$ref": "#/definitions/serverDrives" + } + }, "endpoint": { "type": "string" }, + "network": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "poolNumber": { "type": "integer" }, @@ -10340,7 +10386,7 @@ func init() { "type": "string" }, "uptime": { - "type": "string" + "type": "integer" }, "version": { "type": "string" diff --git a/swagger-console.yml b/swagger-console.yml index e19b7f49c..d028d63b6 100644 --- a/swagger-console.yml +++ b/swagger-console.yml @@ -3095,13 +3095,28 @@ definitions: endpoint: type: string uptime: - type: string + type: integer version: type: string commitID: type: string poolNumber: type: integer + network: + type: object + additionalProperties: + type: string + drives: + type: array + items: + $ref: "#/definitions/serverDrives" + serverDrives: + type: object + properties: + uuid: + type: string + state: + type: string arnsResponse: type: object properties: