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

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 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  import React, { Fragment, useEffect, useState } from "react";
    17  import { useSelector } from "react-redux";
    18  import { useNavigate } from "react-router-dom";
    19  import { Box, Button, Grid, HelpBox, InfoIcon, Loader, PageLayout } from "mds";
    20  import {
    21    DiagStatError,
    22    DiagStatInProgress,
    23    DiagStatSuccess,
    24    HealthInfoMessage,
    25    ReportMessage,
    26  } from "./types";
    27  import { AppState, useAppDispatch } from "../../../store";
    28  import {
    29    WSCloseAbnormalClosure,
    30    WSCloseInternalServerErr,
    31    WSClosePolicyViolation,
    32    wsProtocol,
    33  } from "../../../utils/wsUtils";
    34  import { setHelpName, setServerDiagStat } from "../../../systemSlice";
    35  import {
    36    healthInfoMessageReceived,
    37    healthInfoResetMessage,
    38  } from "./healthInfoSlice";
    39  import { registeredCluster } from "../../../config";
    40  import TestWrapper from "../Common/TestWrapper/TestWrapper";
    41  import RegisterCluster from "../Support/RegisterCluster";
    42  import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
    43  import HelpMenu from "../HelpMenu";
    44  
    45  const HealthInfo = () => {
    46    const dispatch = useAppDispatch();
    47    const navigate = useNavigate();
    48  
    49    const message = useSelector((state: AppState) => state.healthInfo.message);
    50  
    51    const serverDiagnosticStatus = useSelector(
    52      (state: AppState) => state.system.serverDiagnosticStatus,
    53    );
    54    const [startDiagnostic, setStartDiagnostic] = useState(false);
    55  
    56    const [downloadDisabled, setDownloadDisabled] = useState(true);
    57    const [localMessage, setMessage] = useState<string>("");
    58    const [buttonStartText, setButtonStartText] = useState<string>(
    59      "Start Health Report",
    60    );
    61    const [title, setTitle] = useState<string>("Health Report");
    62    const [diagFileContent, setDiagFileContent] = useState<string>("");
    63    const [subnetResponse, setSubnetResponse] = useState<string>("");
    64    const clusterRegistered = registeredCluster();
    65  
    66    const download = () => {
    67      let element = document.createElement("a");
    68      element.setAttribute(
    69        "href",
    70        `data:application/gzip;base64,${diagFileContent}`,
    71      );
    72      element.setAttribute("download", "diagnostic.json.gz");
    73  
    74      element.style.display = "none";
    75      document.body.appendChild(element);
    76  
    77      element.click();
    78  
    79      document.body.removeChild(element);
    80    };
    81  
    82    useEffect(() => {
    83      if (serverDiagnosticStatus === DiagStatInProgress) {
    84        setTitle("Health Report in progress...");
    85        setMessage(
    86          "Health Report started. Please do not refresh page during diagnosis.",
    87        );
    88        return;
    89      }
    90  
    91      if (serverDiagnosticStatus === DiagStatSuccess) {
    92        setTitle("Health Report complete");
    93        setMessage("Health Report file is ready to be downloaded.");
    94        setButtonStartText("Start Health Report");
    95        return;
    96      }
    97  
    98      if (serverDiagnosticStatus === DiagStatError) {
    99        setTitle("Error");
   100        setMessage("An error occurred while getting the Health Report file.");
   101        setButtonStartText("Retry Health Report");
   102        return;
   103      }
   104    }, [serverDiagnosticStatus, startDiagnostic]);
   105  
   106    useEffect(() => {
   107      if (
   108        serverDiagnosticStatus === DiagStatSuccess &&
   109        message !== ({} as HealthInfoMessage)
   110      ) {
   111        // Allow download of diagnostics file only when
   112        // it succeded fetching all the results and info is not empty.
   113        setDownloadDisabled(false);
   114      }
   115      if (serverDiagnosticStatus === DiagStatInProgress) {
   116        // Disable Start Health Report and Disable Download buttons
   117        // if a Diagnosis is in progress.
   118        setDownloadDisabled(true);
   119      }
   120      setStartDiagnostic(false);
   121    }, [serverDiagnosticStatus, message]);
   122  
   123    useEffect(() => {
   124      if (startDiagnostic) {
   125        dispatch(healthInfoResetMessage());
   126        setDiagFileContent("");
   127        const url = new URL(window.location.toString());
   128        const isDev = process.env.NODE_ENV === "development";
   129        const port = isDev ? "9090" : url.port;
   130  
   131        const wsProt = wsProtocol(url.protocol);
   132  
   133        // check if we are using base path, if not this always is `/`
   134        const baseLocation = new URL(document.baseURI);
   135        const baseUrl = baseLocation.pathname;
   136  
   137        const socket = new WebSocket(
   138          `${wsProt}://${url.hostname}:${port}${baseUrl}ws/health-info?deadline=1h`,
   139        );
   140        let interval: any | null = null;
   141        if (socket !== null) {
   142          socket.onopen = () => {
   143            console.log("WebSocket Client Connected");
   144            socket.send("ok");
   145            interval = setInterval(() => {
   146              socket.send("ok");
   147            }, 10 * 1000);
   148            setMessage(
   149              "Health Report started. Please do not refresh page during diagnosis.",
   150            );
   151            dispatch(setServerDiagStat(DiagStatInProgress));
   152          };
   153          socket.onmessage = (message: MessageEvent) => {
   154            let m: ReportMessage = JSON.parse(message.data.toString());
   155            if (m.serverHealthInfo) {
   156              dispatch(healthInfoMessageReceived(m.serverHealthInfo));
   157            }
   158            if (m.encoded !== "") {
   159              setDiagFileContent(m.encoded);
   160            }
   161            if (m.subnetResponse) {
   162              setSubnetResponse(m.subnetResponse);
   163            }
   164          };
   165          socket.onerror = (error) => {
   166            console.error("error closing websocket:", error);
   167            socket.close(1000);
   168            clearInterval(interval);
   169            dispatch(setServerDiagStat(DiagStatError));
   170          };
   171          socket.onclose = (event: CloseEvent) => {
   172            clearInterval(interval);
   173            if (
   174              event.code === WSCloseInternalServerErr ||
   175              event.code === WSClosePolicyViolation ||
   176              event.code === WSCloseAbnormalClosure
   177            ) {
   178              // handle close with error
   179              console.log("connection closed by server with code:", event.code);
   180              setMessage(
   181                "An error occurred while getting the Health Report file.",
   182              );
   183              dispatch(setServerDiagStat(DiagStatError));
   184            } else {
   185              console.log("connection closed by server");
   186  
   187              setMessage("Health Report file is ready to be downloaded.");
   188              dispatch(setServerDiagStat(DiagStatSuccess));
   189            }
   190          };
   191        }
   192      } else {
   193        // reset start status
   194        setStartDiagnostic(false);
   195      }
   196    }, [startDiagnostic, dispatch]);
   197  
   198    const startDiagnosticAction = () => {
   199      if (!clusterRegistered) {
   200        navigate("/support/register");
   201        return;
   202      }
   203      setStartDiagnostic(true);
   204    };
   205  
   206    useEffect(() => {
   207      dispatch(setHelpName("health_info"));
   208    }, [dispatch]);
   209  
   210    return (
   211      <Fragment>
   212        <PageHeaderWrapper label="Health" actions={<HelpMenu />} />
   213  
   214        <PageLayout>
   215          {!clusterRegistered && <RegisterCluster compactMode />}
   216          <Box withBorders>
   217            <TestWrapper title={title}>
   218              <Grid
   219                container
   220                sx={{
   221                  justifyContent: "flex-start",
   222                  gap: 20,
   223                }}
   224              >
   225                <Grid
   226                  key="start-download"
   227                  item
   228                  xs={12}
   229                  sx={{
   230                    textAlign: "center",
   231                    marginBottom: 25,
   232                  }}
   233                >
   234                  <h2>{localMessage}</h2>
   235                  <Box
   236                    sx={{
   237                      textAlign: "center",
   238                      marginBottom: 25,
   239                    }}
   240                  >
   241                    {" "}
   242                    {subnetResponse !== "" &&
   243                      !subnetResponse.toLowerCase().includes("error") && (
   244                        <Grid item xs={12}>
   245                          <strong>
   246                            Health report uploaded to SUBNET successfully!
   247                          </strong>
   248                          &nbsp;{" "}
   249                          <strong>
   250                            See the results on your{" "}
   251                            <a href={subnetResponse}>SUBNET Dashboard</a>{" "}
   252                          </strong>
   253                        </Grid>
   254                      )}
   255                    {(subnetResponse === "" ||
   256                      subnetResponse.toLowerCase().includes("error")) &&
   257                      serverDiagnosticStatus === DiagStatSuccess && (
   258                        <Grid item xs={12}>
   259                          <strong>
   260                            Something went wrong uploading your Health report to
   261                            SUBNET.
   262                          </strong>
   263                          &nbsp;{" "}
   264                          <strong>
   265                            Log into your{" "}
   266                            <a href="https://subnet.min.io">SUBNET Account</a> to
   267                            manually upload your Health report.
   268                          </strong>
   269                        </Grid>
   270                      )}
   271                  </Box>
   272                  {serverDiagnosticStatus === DiagStatInProgress ? (
   273                    <Box
   274                      sx={{
   275                        paddingTop: 8,
   276                        paddingLeft: 40,
   277                      }}
   278                    >
   279                      <Loader style={{ width: 25, height: 25 }} />
   280                    </Box>
   281                  ) : (
   282                    <Fragment>
   283                      <Box
   284                        sx={{
   285                          display: "flex",
   286                          gap: 10,
   287                          alignItems: "center",
   288                          justifyContent: "center",
   289                        }}
   290                      >
   291                        <Box>
   292                          {serverDiagnosticStatus !== DiagStatError &&
   293                            !downloadDisabled && (
   294                              <Button
   295                                id={"download"}
   296                                type="submit"
   297                                variant="callAction"
   298                                onClick={() => download()}
   299                                disabled={downloadDisabled}
   300                                label={"Download"}
   301                              />
   302                            )}
   303                        </Box>
   304                        <Box>
   305                          <Button
   306                            id="start-new-diagnostic"
   307                            type="submit"
   308                            variant={
   309                              !clusterRegistered ? "regular" : "callAction"
   310                            }
   311                            disabled={startDiagnostic || !clusterRegistered}
   312                            onClick={startDiagnosticAction}
   313                            label={buttonStartText}
   314                          />
   315                        </Box>
   316                      </Box>
   317                    </Fragment>
   318                  )}
   319                </Grid>
   320              </Grid>
   321            </TestWrapper>
   322          </Box>
   323          {!startDiagnostic && clusterRegistered && (
   324            <Fragment>
   325              <br />
   326              <HelpBox
   327                title={
   328                  "Cluster Health Report will be uploaded to SUBNET, and is viewable from your SUBNET Diagnostics dashboard."
   329                }
   330                iconComponent={<InfoIcon />}
   331                help={
   332                  "If the Health report cannot be generated at this time, please wait a moment and try again."
   333                }
   334              />
   335            </Fragment>
   336          )}
   337        </PageLayout>
   338      </Fragment>
   339    );
   340  };
   341  
   342  export default HealthInfo;