github.com/minio/console@v1.4.1/web-app/src/screens/Console/Logs/ErrorLogs/ErrorLogs.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  
    17  import React, { Fragment, useEffect, useState } from "react";
    18  import { Box, Button, Grid, PageLayout, Select, Table, TableBody } from "mds";
    19  import { useSelector } from "react-redux";
    20  import { ErrorResponseHandler } from "../../../../common/types";
    21  import { AppState, useAppDispatch } from "../../../../store";
    22  import { wsProtocol } from "../../../../utils/wsUtils";
    23  import {
    24    logMessageReceived,
    25    logResetMessages,
    26    setLogsStarted,
    27  } from "../logsSlice";
    28  import { setHelpName } from "../../../../systemSlice";
    29  import SearchBox from "../../Common/SearchBox";
    30  import api from "../../../../../src/common/api";
    31  import LogLine from "./LogLine";
    32  import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
    33  import HelpMenu from "../../HelpMenu";
    34  
    35  var socket: any = null;
    36  
    37  const ErrorLogs = () => {
    38    const dispatch = useAppDispatch();
    39  
    40    const messages = useSelector((state: AppState) => state.logs.logMessages);
    41    const logsStarted = useSelector((state: AppState) => state.logs.logsStarted);
    42  
    43    const [filter, setFilter] = useState<string>("");
    44    const [nodes, setNodes] = useState<string[]>([""]);
    45    const [selectedNode, setSelectedNode] = useState<string>("all");
    46    const [selectedUserAgent, setSelectedUserAgent] =
    47      useState<string>("Select user agent");
    48    const [userAgents, setUserAgents] = useState<string[]>(["All User Agents"]);
    49    const [logType, setLogType] = useState<string>("all");
    50    const [loadingNodes, setLoadingNodes] = useState<boolean>(false);
    51  
    52    const startLogs = () => {
    53      dispatch(logResetMessages());
    54      const url = new URL(window.location.toString());
    55      const isDev = process.env.NODE_ENV === "development";
    56      const port = isDev ? "9090" : url.port;
    57  
    58      const wsProt = wsProtocol(url.protocol);
    59      // check if we are using base path, if not this always is `/`
    60      const baseLocation = new URL(document.baseURI);
    61      const baseUrl = baseLocation.pathname;
    62  
    63      socket = new WebSocket(
    64        `${wsProt}://${
    65          url.hostname
    66        }:${port}${baseUrl}ws/console/?logType=${logType}&node=${
    67          selectedNode === "Select node" ? "" : selectedNode
    68        }`,
    69      );
    70      let interval: any | null = null;
    71      if (socket !== null) {
    72        socket.onopen = () => {
    73          console.log("WebSocket Client Connected");
    74          dispatch(setLogsStarted(true));
    75          socket.send("ok");
    76          interval = setInterval(() => {
    77            socket.send("ok");
    78          }, 10 * 1000);
    79        };
    80        socket.onmessage = (message: MessageEvent) => {
    81          // console.log(message.data.toString())
    82          // FORMAT: 00:35:17 UTC 01/01/2021
    83  
    84          let m: any = JSON.parse(message.data.toString());
    85          let isValidEntry = true;
    86          if (
    87            m.level === "" &&
    88            m.errKind === "" &&
    89            //@ts-ignore
    90            m.time === "00:00:00 UTC 01/01/0001" &&
    91            m.ConsoleMsg === "" &&
    92            m.node === ""
    93          ) {
    94            isValidEntry = false;
    95          }
    96  
    97          m.key = Math.random();
    98          if (userAgents.indexOf(m.userAgent) < 0 && m.userAgent !== undefined) {
    99            userAgents.push(m.userAgent);
   100            setUserAgents(userAgents);
   101          }
   102          if (isValidEntry) {
   103            dispatch(logMessageReceived(m));
   104          }
   105        };
   106        socket.onclose = () => {
   107          clearInterval(interval);
   108          console.log("connection closed by server");
   109          dispatch(setLogsStarted(false));
   110        };
   111        return () => {
   112          socket.close(1000);
   113          clearInterval(interval);
   114          console.log("closing websockets");
   115          dispatch(setLogsStarted(false));
   116        };
   117      }
   118    };
   119  
   120    const stopLogs = () => {
   121      if (socket !== null && socket !== undefined) {
   122        socket.close(1000);
   123        dispatch(setLogsStarted(false));
   124      }
   125    };
   126  
   127    const filtLow = filter.toLowerCase();
   128    let filteredMessages = messages.filter((m) => {
   129      if (
   130        m.userAgent === selectedUserAgent ||
   131        selectedUserAgent === "All User Agents" ||
   132        selectedUserAgent === "Select user agent"
   133      ) {
   134        if (filter !== "") {
   135          if (m.ConsoleMsg.toLowerCase().indexOf(filtLow) >= 0) {
   136            return true;
   137          } else if (
   138            m.error &&
   139            m.error.source &&
   140            m.error.source.filter((x) => {
   141              return x.toLowerCase().indexOf(filtLow) >= 0;
   142            }).length > 0
   143          ) {
   144            return true;
   145          } else if (
   146            m.error &&
   147            m.error.message.toLowerCase().indexOf(filtLow) >= 0
   148          ) {
   149            return true;
   150          } else if (m.api && m.api.name.toLowerCase().indexOf(filtLow) >= 0) {
   151            return true;
   152          }
   153          return false;
   154        }
   155        return true;
   156      } else return false;
   157    });
   158  
   159    useEffect(() => {
   160      setLoadingNodes(true);
   161      api
   162        .invoke("GET", `/api/v1/nodes`)
   163        .then((res: string[]) => {
   164          setNodes(res);
   165          setLoadingNodes(false);
   166        })
   167        .catch((err: ErrorResponseHandler) => {
   168          setLoadingNodes(false);
   169        });
   170    }, []);
   171  
   172    useEffect(() => {
   173      dispatch(setHelpName("error_logs"));
   174      // eslint-disable-next-line react-hooks/exhaustive-deps
   175    }, []);
   176  
   177    return (
   178      <Fragment>
   179        <PageHeaderWrapper label="Logs" actions={<HelpMenu />} />
   180  
   181        <PageLayout>
   182          <Grid container sx={{ gap: 15 }}>
   183            <Grid item xs={3}>
   184              {!loadingNodes ? (
   185                <Select
   186                  id="node-selector"
   187                  name="node"
   188                  data-test-id="node-selector"
   189                  value={selectedNode}
   190                  onChange={(value) => {
   191                    setSelectedNode(value as string);
   192                  }}
   193                  disabled={loadingNodes || logsStarted}
   194                  options={[
   195                    { label: "All Nodes", value: "all" },
   196                    ...nodes.map((aNode) => ({ label: aNode, value: aNode })),
   197                  ]}
   198                />
   199              ) : (
   200                <h3> Loading nodes</h3>
   201              )}
   202            </Grid>
   203  
   204            <Grid item xs={3}>
   205              <Select
   206                id="logType"
   207                name="logType"
   208                data-test-id="log-type"
   209                value={logType}
   210                onChange={(value) => {
   211                  setLogType(value as string);
   212                }}
   213                disabled={loadingNodes || logsStarted}
   214                options={[
   215                  { value: "all", label: "All Log Types" },
   216                  {
   217                    value: "minio",
   218                    label: "MinIO",
   219                  },
   220                  { value: "application", label: "Application" },
   221                ]}
   222              />
   223            </Grid>
   224            <Grid item xs={3}>
   225              {userAgents.length > 1 && (
   226                <Select
   227                  id="userAgent"
   228                  name="userAgent"
   229                  data-test-id="user-agent"
   230                  value={selectedUserAgent}
   231                  onChange={(value) => {
   232                    setSelectedUserAgent(value as string);
   233                  }}
   234                  disabled={userAgents.length < 1 || logsStarted}
   235                  options={userAgents.map((anAgent) => ({
   236                    label: anAgent,
   237                    value: anAgent,
   238                  }))}
   239                />
   240              )}
   241            </Grid>
   242            <Grid
   243              item
   244              xs={2}
   245              sx={{ display: "flex", justifyContent: "flex-end" }}
   246            >
   247              {!logsStarted && (
   248                <Button
   249                  id={"start-logs"}
   250                  type="submit"
   251                  variant="callAction"
   252                  disabled={false}
   253                  onClick={startLogs}
   254                  label={"Start Logs"}
   255                />
   256              )}
   257              {logsStarted && (
   258                <Button
   259                  id={"stop-logs"}
   260                  type="button"
   261                  variant="callAction"
   262                  onClick={stopLogs}
   263                  label={"Stop Logs"}
   264                />
   265              )}
   266            </Grid>
   267            <Grid
   268              item
   269              xs={12}
   270              sx={{
   271                display: "flex" as const,
   272                justifyContent: "space-between" as const,
   273                alignItems: "center",
   274                marginBottom: "1rem",
   275                "& button": {
   276                  flexGrow: 0,
   277                  marginLeft: 8,
   278                  marginBottom: 0,
   279                },
   280              }}
   281            >
   282              <SearchBox
   283                placeholder="Filter"
   284                onChange={(e) => {
   285                  setFilter(e);
   286                }}
   287                value={filter}
   288              />
   289            </Grid>
   290            <Grid item xs={12}>
   291              <Box
   292                id="logs-container"
   293                data-test-id="logs-list-container"
   294                sx={{
   295                  minHeight: 400,
   296                  height: "calc(100vh - 200px)",
   297                  overflow: "auto",
   298                  fontSize: 13,
   299                  borderRadius: 4,
   300                }}
   301              >
   302                <Box withBorders customBorderPadding={"0px"} useBackground>
   303                  <Table aria-label="collapsible table">
   304                    <TableBody>
   305                      {filteredMessages.map((m) => {
   306                        return <LogLine log={m} />;
   307                      })}
   308                    </TableBody>
   309                  </Table>
   310                  {filteredMessages.length === 0 && (
   311                    <Box sx={{ padding: 20, textAlign: "center" }}>
   312                      No logs to display
   313                    </Box>
   314                  )}
   315                </Box>
   316              </Box>
   317            </Grid>
   318          </Grid>
   319        </PageLayout>
   320      </Fragment>
   321    );
   322  };
   323  
   324  export default ErrorLogs;