github.com/minio/console@v1.4.1/web-app/src/screens/Console/KMS/Status.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, { Fragment, useEffect, useState } from "react";
    18  import {
    19    Box,
    20    breakPoints,
    21    DisabledIcon,
    22    EnabledIcon,
    23    Grid,
    24    PageLayout,
    25    SectionTitle,
    26    Tabs,
    27    ValuePair,
    28  } from "mds";
    29  import {
    30    Bar,
    31    BarChart,
    32    CartesianGrid,
    33    Legend,
    34    Line,
    35    LineChart,
    36    Tooltip,
    37    XAxis,
    38    YAxis,
    39  } from "recharts";
    40  import { hasPermission } from "../../../common/SecureComponent";
    41  import {
    42    CONSOLE_UI_RESOURCE,
    43    IAM_SCOPES,
    44  } from "../../../common/SecureComponent/permissions";
    45  import { setErrorSnackMessage, setHelpName } from "../../../systemSlice";
    46  import { useAppDispatch } from "../../../store";
    47  import LabelWithIcon from "../Buckets/BucketDetails/SummaryItems/LabelWithIcon";
    48  import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
    49  import HelpMenu from "../HelpMenu";
    50  import { api } from "api";
    51  import { KmsStatusResponse } from "api/consoleApi";
    52  import { errorToHandler } from "api/errors";
    53  
    54  const Status = () => {
    55    const dispatch = useAppDispatch();
    56    const [curTab, setCurTab] = useState<string>("simple-tab-0");
    57  
    58    const [isKMSSecretKey, setIsKMSSecretKey] = useState<boolean>(true);
    59    const [status, setStatus] = useState<KmsStatusResponse | null>(null);
    60    const [loadingStatus, setLoadingStatus] = useState<boolean>(true);
    61    const [metrics, setMetrics] = useState<any | null>(null);
    62    const [loadingMetrics, setLoadingMetrics] = useState<boolean>(true);
    63    const [apis, setAPIs] = useState<any | null>(null);
    64    const [loadingAPIs, setLoadingAPIs] = useState<boolean>(true);
    65    const [version, setVersion] = useState<any | null>(null);
    66    const [loadingVersion, setLoadingVersion] = useState<boolean>(true);
    67  
    68    const displayStatus = hasPermission(CONSOLE_UI_RESOURCE, [
    69      IAM_SCOPES.KMS_STATUS,
    70    ]);
    71    const displayMetrics =
    72      hasPermission(CONSOLE_UI_RESOURCE, [IAM_SCOPES.KMS_METRICS]) &&
    73      !isKMSSecretKey;
    74    const displayAPIs =
    75      hasPermission(CONSOLE_UI_RESOURCE, [IAM_SCOPES.KMS_APIS]) &&
    76      !isKMSSecretKey;
    77    const displayVersion =
    78      hasPermission(CONSOLE_UI_RESOURCE, [IAM_SCOPES.KMS_Version]) &&
    79      !isKMSSecretKey;
    80  
    81    useEffect(() => {
    82      const loadStatus = () => {
    83        api.kms
    84          .kmsStatus()
    85          .then((result) => {
    86            if (result.data) {
    87              setStatus(result.data);
    88              setIsKMSSecretKey(result.data.name === "SecretKey");
    89            }
    90          })
    91          .catch((err) => {
    92            dispatch(setErrorSnackMessage(errorToHandler(err.error)));
    93          })
    94          .finally(() => setLoadingStatus(false));
    95      };
    96  
    97      const loadMetrics = () => {
    98        api.kms
    99          .kmsMetrics()
   100          .then((result) => {
   101            if (result.data) {
   102              setMetrics(result.data);
   103            }
   104          })
   105          .catch((err) => {
   106            dispatch(setErrorSnackMessage(errorToHandler(err.error)));
   107          })
   108          .finally(() => setLoadingMetrics(false));
   109      };
   110  
   111      const loadAPIs = () => {
   112        api.kms
   113          .kmsapIs()
   114          .then((result: any) => {
   115            if (result.data) {
   116              setAPIs(result.data);
   117            }
   118          })
   119          .catch((err) => {
   120            dispatch(setErrorSnackMessage(errorToHandler(err.error)));
   121          })
   122          .finally(() => setLoadingAPIs(false));
   123      };
   124  
   125      const loadVersion = () => {
   126        api.kms
   127          .kmsVersion()
   128          .then((result: any) => {
   129            if (result.data) {
   130              setVersion(result.data);
   131            }
   132          })
   133          .catch((err) => {
   134            dispatch(setErrorSnackMessage(errorToHandler(err.error)));
   135          })
   136          .finally(() => setLoadingVersion(false));
   137      };
   138  
   139      if (displayStatus && loadingStatus) {
   140        loadStatus();
   141      }
   142      if (displayMetrics && loadingMetrics) {
   143        loadMetrics();
   144      }
   145      if (displayAPIs && loadingAPIs) {
   146        loadAPIs();
   147      }
   148      if (displayVersion && loadingVersion) {
   149        loadVersion();
   150      }
   151    }, [
   152      dispatch,
   153      displayStatus,
   154      loadingStatus,
   155      displayMetrics,
   156      loadingMetrics,
   157      displayAPIs,
   158      loadingAPIs,
   159      displayVersion,
   160      loadingVersion,
   161    ]);
   162  
   163    const statusPanel = (
   164      <Fragment>
   165        <SectionTitle>Status</SectionTitle>
   166        <br />
   167        {status && (
   168          <Grid container>
   169            <Grid item xs={12}>
   170              <Box
   171                sx={{
   172                  display: "grid",
   173                  gap: 2,
   174                  gridTemplateColumns: "2fr 1fr",
   175                  gridAutoFlow: "row",
   176                  [`@media (max-width: ${breakPoints.sm}px)`]: {
   177                    gridTemplateColumns: "1fr",
   178                    gridAutoFlow: "dense",
   179                  },
   180                }}
   181              >
   182                <Box
   183                  sx={{
   184                    display: "grid",
   185                    gap: 2,
   186                    gridTemplateColumns: "2fr 1fr",
   187                    gridAutoFlow: "row",
   188                    [`@media (max-width: ${breakPoints.sm}px)`]: {
   189                      gridTemplateColumns: "1fr",
   190                      gridAutoFlow: "dense",
   191                    },
   192                  }}
   193                >
   194                  <ValuePair label={"Name:"} value={status.name} />
   195                  {version && (
   196                    <ValuePair label={"Version:"} value={version.version} />
   197                  )}
   198                  <ValuePair
   199                    label={"Default Key ID:"}
   200                    value={status.defaultKeyID}
   201                  />
   202                  <ValuePair
   203                    label={"Key Management Service Endpoints:"}
   204                    value={
   205                      <Fragment>
   206                        {status.endpoints?.map((e: any, i: number) => (
   207                          <LabelWithIcon
   208                            key={i}
   209                            icon={
   210                              e.status === "online" ? (
   211                                <EnabledIcon />
   212                              ) : (
   213                                <DisabledIcon />
   214                              )
   215                            }
   216                            label={e.url}
   217                          />
   218                        ))}
   219                      </Fragment>
   220                    }
   221                  />
   222                </Box>
   223              </Box>
   224            </Grid>
   225          </Grid>
   226        )}
   227      </Fragment>
   228    );
   229  
   230    const apisPanel = (
   231      <Fragment>
   232        <SectionTitle>Supported API endpoints</SectionTitle>
   233        <br />
   234        {apis && (
   235          <Grid container>
   236            <Grid item xs={12}>
   237              <ValuePair
   238                label={""}
   239                value={
   240                  <Box
   241                    sx={{
   242                      display: "grid",
   243                      gap: 2,
   244                      gridTemplateColumns: "2fr 1fr",
   245                      gridAutoFlow: "row",
   246                      [`@media (max-width: ${breakPoints.sm}px)`]: {
   247                        gridTemplateColumns: "1fr",
   248                        gridAutoFlow: "dense",
   249                      },
   250                    }}
   251                  >
   252                    {apis.results.map((e: any, i: number) => (
   253                      <LabelWithIcon
   254                        key={i}
   255                        icon={<EnabledIcon />}
   256                        label={`${e.path} - ${e.method}`}
   257                      />
   258                    ))}
   259                  </Box>
   260                }
   261              />
   262            </Grid>
   263          </Grid>
   264        )}
   265      </Fragment>
   266    );
   267  
   268    const getAPIRequestsData = () => {
   269      return [
   270        { label: "Success", success: metrics.requestOK },
   271        { label: "Failures", failures: metrics.requestFail },
   272        { label: "Errors", errors: metrics.requestErr },
   273        { label: "Active", active: metrics.requestActive },
   274      ];
   275    };
   276  
   277    const getEventsData = () => {
   278      return [
   279        { label: "Audit", audit: metrics.auditEvents },
   280        { label: "Errors", errors: metrics.errorEvents },
   281      ];
   282    };
   283  
   284    const getHistogramData = () => {
   285      return metrics.latencyHistogram.map((h: any) => {
   286        return {
   287          ...h,
   288          duration: `${h.duration / 1000000}ms`,
   289        };
   290      });
   291    };
   292  
   293    const metricsPanel = (
   294      <Fragment>
   295        {metrics && (
   296          <Fragment>
   297            <h3>API Requests</h3>
   298            <BarChart width={730} height={250} data={getAPIRequestsData()}>
   299              <CartesianGrid strokeDasharray="3 3" />
   300              <XAxis dataKey="label" />
   301              <YAxis />
   302              <Tooltip />
   303              <Legend />
   304              <Bar dataKey="success" fill="green" />
   305              <Bar dataKey="failures" fill="red" />
   306              <Bar dataKey="errors" fill="black" />
   307              <Bar dataKey="active" fill="#8884d8" />
   308            </BarChart>
   309  
   310            <h3>Events</h3>
   311            <BarChart width={730} height={250} data={getEventsData()}>
   312              <CartesianGrid strokeDasharray="3 3" />
   313              <XAxis dataKey="label" />
   314              <YAxis />
   315              <Tooltip />
   316              <Legend />
   317              <Bar dataKey="audit" fill="green" />
   318              <Bar dataKey="errors" fill="black" />
   319            </BarChart>
   320            <h3>Latency Histogram</h3>
   321            {metrics.latencyHistogram && (
   322              <LineChart
   323                width={730}
   324                height={250}
   325                data={getHistogramData()}
   326                margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
   327              >
   328                <CartesianGrid strokeDasharray="3 3" />
   329                <XAxis dataKey="duration" />
   330                <YAxis />
   331                <Tooltip />
   332                <Legend />
   333                <Line
   334                  type="monotone"
   335                  dataKey="total"
   336                  stroke="#8884d8"
   337                  name={"Requests that took T ms or less"}
   338                />
   339              </LineChart>
   340            )}
   341          </Fragment>
   342        )}
   343      </Fragment>
   344    );
   345  
   346    useEffect(() => {
   347      dispatch(setHelpName("kms_status"));
   348      // eslint-disable-next-line react-hooks/exhaustive-deps
   349    }, []);
   350  
   351    return (
   352      <Fragment>
   353        <PageHeaderWrapper
   354          label="Key Management Service"
   355          actions={<HelpMenu />}
   356        />
   357  
   358        <PageLayout>
   359          <Tabs
   360            currentTabOrPath={curTab}
   361            onTabClick={(newValue) => setCurTab(newValue)}
   362            options={[
   363              {
   364                tabConfig: { label: "Status", id: "simple-tab-0" },
   365                content: (
   366                  <Box
   367                    withBorders
   368                    sx={{
   369                      display: "flex",
   370                      flexFlow: "column",
   371                      padding: "43px",
   372                    }}
   373                  >
   374                    {statusPanel}
   375                  </Box>
   376                ),
   377              },
   378              {
   379                tabConfig: {
   380                  label: "APIs",
   381                  id: "simple-tab-1",
   382                  disabled: !displayAPIs,
   383                },
   384                content: (
   385                  <Box
   386                    withBorders
   387                    sx={{
   388                      display: "flex",
   389                      flexFlow: "column",
   390                      padding: "43px",
   391                    }}
   392                  >
   393                    {apisPanel}
   394                  </Box>
   395                ),
   396              },
   397              {
   398                tabConfig: {
   399                  label: "Metrics",
   400                  id: "simple-tab-2",
   401                  disabled: !displayMetrics,
   402                },
   403                content: (
   404                  <Box
   405                    withBorders
   406                    sx={{
   407                      display: "flex",
   408                      flexFlow: "column",
   409                      padding: "43px",
   410                    }}
   411                  >
   412                    {metricsPanel}
   413                  </Box>
   414                ),
   415              },
   416            ]}
   417          />
   418        </PageLayout>
   419      </Fragment>
   420    );
   421  };
   422  
   423  export default Status;