github.com/minio/console@v1.4.1/web-app/src/screens/Console/Dashboard/BasicDashboard/BasicDashboard.tsx (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2022 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  import React from "react";
    18  import {
    19    ArrowRightIcon,
    20    Box,
    21    breakPoints,
    22    BucketsIcon,
    23    Button,
    24    DiagnosticsMenuIcon,
    25    DrivesIcon,
    26    FormatDrivesIcon,
    27    HealIcon,
    28    HelpBox,
    29    PrometheusErrorIcon,
    30    ServersIcon,
    31    StorageIcon,
    32    TotalObjectsIcon,
    33    UptimeIcon,
    34  } from "mds";
    35  import { calculateBytes, representationNumber } from "../../../../common/utils";
    36  import StatusCountCard from "./StatusCountCard";
    37  import groupBy from "lodash/groupBy";
    38  import ServersList from "./ServersList";
    39  import CounterCard from "./CounterCard";
    40  import ReportedUsage from "./ReportedUsage";
    41  import { Link } from "react-router-dom";
    42  import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
    43  import TimeStatItem from "../TimeStatItem";
    44  import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
    45  import { AdminInfoResponse, ServerDrives } from "api/consoleApi";
    46  
    47  const BoxItem = ({ children }: { children: any }) => {
    48    return (
    49      <Box
    50        withBorders
    51        sx={{
    52          padding: 15,
    53          height: "136px",
    54          maxWidth: "100%",
    55          [`@media (max-width: ${breakPoints.sm}px)`]: {
    56            padding: 5,
    57            maxWidth: "initial",
    58          },
    59        }}
    60      >
    61        {children}
    62      </Box>
    63    );
    64  };
    65  
    66  interface IDashboardProps {
    67    usage: AdminInfoResponse | undefined;
    68  }
    69  
    70  const getServersList = (usage: AdminInfoResponse | undefined) => {
    71    if (usage && usage.servers) {
    72      return [...usage.servers].sort(function (a, b) {
    73        const nameA = a.endpoint?.toLowerCase() || "";
    74        const nameB = b.endpoint?.toLowerCase() || "";
    75        if (nameA < nameB) {
    76          return -1;
    77        }
    78        if (nameA > nameB) {
    79          return 1;
    80        }
    81        return 0;
    82      });
    83    }
    84  
    85    return [];
    86  };
    87  
    88  const prettyUsage = (usage: string | undefined) => {
    89    if (usage === undefined) {
    90      return { total: "0", unit: "Mi" };
    91    }
    92  
    93    return calculateBytes(usage);
    94  };
    95  
    96  const BasicDashboard = ({ usage }: IDashboardProps) => {
    97    const usageValue = usage && usage.usage ? usage.usage.toString() : "0";
    98    const usageToRepresent = prettyUsage(usageValue);
    99  
   100    const { lastScan = "n/a", lastHeal = "n/a", upTime = "n/a" } = {};
   101  
   102    const serverList = getServersList(usage);
   103  
   104    let allDrivesArray: ServerDrives[] = [];
   105  
   106    serverList.forEach((server) => {
   107      const drivesInput = server.drives?.map((drive) => {
   108        return drive;
   109      });
   110      if (drivesInput) {
   111        allDrivesArray = [...allDrivesArray, ...drivesInput];
   112      }
   113    });
   114  
   115    const serversGroup = groupBy(serverList, "state");
   116    const { offline: offlineServers = [], online: onlineServers = [] } =
   117      serversGroup;
   118    const drivesGroup = groupBy(allDrivesArray, "state");
   119    const { offline: offlineDrives = [], ok: onlineDrives = [] } = drivesGroup;
   120    return (
   121      <Box>
   122        <Box
   123          sx={{
   124            display: "grid",
   125            gridTemplateRows: "1fr",
   126            gridTemplateColumns: "1fr",
   127            gap: 27,
   128            marginBottom: 40,
   129          }}
   130        >
   131          <Box
   132            sx={{
   133              display: "grid",
   134              gridTemplateColumns: "1fr",
   135              gap: "40px",
   136            }}
   137          >
   138            <Box
   139              sx={{
   140                display: "grid",
   141                gridTemplateRows: "136px",
   142                gridTemplateColumns: "1fr 1fr 1fr",
   143                gap: 20,
   144                [`@media (max-width: ${breakPoints.sm}px)`]: {
   145                  gridTemplateColumns: "1fr",
   146                },
   147                [`@media (max-width: ${breakPoints.md}px)`]: {
   148                  marginBottom: 0,
   149                },
   150              }}
   151            >
   152              <BoxItem>
   153                <CounterCard
   154                  label={"Buckets"}
   155                  icon={<BucketsIcon />}
   156                  counterValue={usage ? representationNumber(usage.buckets) : 0}
   157                  actions={
   158                    <Link
   159                      to={IAM_PAGES.BUCKETS}
   160                      style={{
   161                        zIndex: 3,
   162                        textDecoration: "none",
   163                        top: "40px",
   164                        position: "relative",
   165                        marginRight: "75px",
   166                      }}
   167                    >
   168                      <TooltipWrapper tooltip={"Browse"}>
   169                        <Button
   170                          id={"browse-dashboard"}
   171                          onClick={() => {}}
   172                          label={"Browse"}
   173                          icon={<ArrowRightIcon />}
   174                          variant={"regular"}
   175                          style={{
   176                            padding: 5,
   177                            height: 30,
   178                            fontSize: 14,
   179                            marginTop: 20,
   180                          }}
   181                        />
   182                      </TooltipWrapper>
   183                    </Link>
   184                  }
   185                />
   186              </BoxItem>
   187              <BoxItem>
   188                <CounterCard
   189                  label={"Objects"}
   190                  icon={<TotalObjectsIcon />}
   191                  counterValue={usage ? representationNumber(usage.objects) : 0}
   192                />
   193              </BoxItem>
   194  
   195              <BoxItem>
   196                <StatusCountCard
   197                  onlineCount={onlineServers.length}
   198                  offlineCount={offlineServers.length}
   199                  label={"Servers"}
   200                  icon={<ServersIcon />}
   201                />
   202              </BoxItem>
   203              <BoxItem>
   204                <StatusCountCard
   205                  offlineCount={
   206                    usage?.backend?.offlineDrives || offlineDrives.length
   207                  }
   208                  onlineCount={
   209                    usage?.backend?.onlineDrives || onlineDrives.length
   210                  }
   211                  label={"Drives"}
   212                  icon={<DrivesIcon />}
   213                />
   214              </BoxItem>
   215  
   216              <Box
   217                withBorders
   218                sx={{
   219                  gridRowStart: "1",
   220                  gridRowEnd: "3",
   221                  gridColumnStart: "3",
   222                  padding: 15,
   223                  display: "grid",
   224                  justifyContent: "stretch",
   225                }}
   226              >
   227                <ReportedUsage
   228                  usageValue={usageValue}
   229                  total={usageToRepresent.total}
   230                  unit={usageToRepresent.unit}
   231                />
   232  
   233                <Box
   234                  sx={{
   235                    display: "flex",
   236                    flexFlow: "column",
   237                    gap: "14px",
   238                  }}
   239                >
   240                  <TimeStatItem
   241                    icon={<HealIcon />}
   242                    label={
   243                      <Box>
   244                        <Box
   245                          sx={{
   246                            display: "inline",
   247                            [`@media (max-width: ${breakPoints.sm}px)`]: {
   248                              display: "none",
   249                            },
   250                          }}
   251                        >
   252                          Time since last
   253                        </Box>{" "}
   254                        Heal Activity
   255                      </Box>
   256                    }
   257                    value={lastHeal}
   258                  />
   259                  <TimeStatItem
   260                    icon={<DiagnosticsMenuIcon />}
   261                    label={
   262                      <Box>
   263                        <Box
   264                          sx={{
   265                            display: "inline",
   266                            [`@media (max-width: ${breakPoints.sm}px)`]: {
   267                              display: "none",
   268                            },
   269                          }}
   270                        >
   271                          Time since last
   272                        </Box>{" "}
   273                        Scan Activity
   274                      </Box>
   275                    }
   276                    value={lastScan}
   277                  />
   278                  <TimeStatItem
   279                    icon={<UptimeIcon />}
   280                    label={"Uptime"}
   281                    value={upTime}
   282                  />
   283                </Box>
   284              </Box>
   285            </Box>
   286            <Box
   287              sx={{
   288                display: "grid",
   289                gridTemplateColumns: "1fr 1fr 1fr",
   290                gap: "14px",
   291                [`@media (max-width: ${breakPoints.lg}px)`]: {
   292                  gridTemplateColumns: "1fr",
   293                },
   294              }}
   295            >
   296              <TimeStatItem
   297                icon={<StorageIcon />}
   298                label={"Backend type"}
   299                value={usage?.backend?.backendType ?? "Unknown"}
   300              />
   301              <TimeStatItem
   302                icon={<FormatDrivesIcon />}
   303                label={"Standard storage class parity"}
   304                value={usage?.backend?.standardSCParity?.toString() ?? "n/a"}
   305              />
   306              <TimeStatItem
   307                icon={<FormatDrivesIcon />}
   308                label={"Reduced redundancy storage class parity"}
   309                value={usage?.backend?.rrSCParity?.toString() ?? "n/a"}
   310              />
   311            </Box>
   312  
   313            <Box
   314              sx={{
   315                display: "grid",
   316                gridTemplateRows: "auto",
   317                gridTemplateColumns: "1fr",
   318                gap: "auto",
   319              }}
   320            >
   321              <ServersList data={serverList} />
   322            </Box>
   323          </Box>
   324          {usage?.advancedMetricsStatus === "not configured" && (
   325            <Box>
   326              <HelpBox
   327                iconComponent={<PrometheusErrorIcon />}
   328                title={"We can’t retrieve advanced metrics at this time."}
   329                help={
   330                  <Box>
   331                    <Box
   332                      sx={{
   333                        fontSize: "14px",
   334                      }}
   335                    >
   336                      MinIO Dashboard will display basic metrics as we couldn’t
   337                      connect to Prometheus successfully. Please try again in a
   338                      few minutes. If the problem persists, you can review your
   339                      configuration and confirm that Prometheus server is up and
   340                      running.
   341                    </Box>
   342                    <Box
   343                      sx={{
   344                        paddingTop: 20,
   345                        fontSize: 14,
   346                      }}
   347                    >
   348                      <a
   349                        href="https://min.io/docs/minio/linux/operations/monitoring/collect-minio-metrics-using-prometheus.html"
   350                        target="_blank"
   351                        rel="noopener"
   352                      >
   353                        Read more about Prometheus on our Docs site.
   354                      </a>
   355                    </Box>
   356                  </Box>
   357                }
   358              />
   359            </Box>
   360          )}
   361        </Box>
   362      </Box>
   363    );
   364  };
   365  
   366  export default BasicDashboard;