github.com/minio/console@v1.4.1/web-app/src/screens/Console/Watch/Watch.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, { useEffect, useState, Fragment } from "react";
    18  import { useSelector } from "react-redux";
    19  import {
    20    Box,
    21    Button,
    22    DataTable,
    23    Grid,
    24    InputBox,
    25    InputLabel,
    26    PageLayout,
    27    Select,
    28  } from "mds";
    29  import { AppState, useAppDispatch } from "../../../store";
    30  import { Bucket, BucketList, EventInfo } from "./types";
    31  import { niceBytes, timeFromDate } from "../../../common/utils";
    32  import { wsProtocol } from "../../../utils/wsUtils";
    33  import { ErrorResponseHandler } from "../../../common/types";
    34  import { watchMessageReceived, watchResetMessages } from "./watchSlice";
    35  import { setHelpName } from "../../../systemSlice";
    36  import api from "../../../common/api";
    37  import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
    38  import HelpMenu from "../HelpMenu";
    39  
    40  const Watch = () => {
    41    const dispatch = useAppDispatch();
    42    const messages = useSelector((state: AppState) => state.watch.messages);
    43  
    44    const [start, setStart] = useState(false);
    45    const [bucketName, setBucketName] = useState("Select Bucket");
    46    const [prefix, setPrefix] = useState("");
    47    const [suffix, setSuffix] = useState("");
    48    const [bucketList, setBucketList] = useState<Bucket[]>([]);
    49  
    50    const fetchBucketList = () => {
    51      api
    52        .invoke("GET", `/api/v1/buckets`)
    53        .then((res: BucketList) => {
    54          let buckets: Bucket[] = [];
    55          if (res.buckets !== null) {
    56            buckets = res.buckets;
    57          }
    58          setBucketList(buckets);
    59        })
    60        .catch((err: ErrorResponseHandler) => {
    61          console.error(err);
    62        });
    63    };
    64    useEffect(() => {
    65      fetchBucketList();
    66    }, []);
    67  
    68    useEffect(() => {
    69      dispatch(watchResetMessages());
    70      // begin watch if bucketName in bucketList and start pressed
    71      if (start && bucketList.some((bucket) => bucket.name === bucketName)) {
    72        const url = new URL(window.location.toString());
    73        const isDev = process.env.NODE_ENV === "development";
    74        const port = isDev ? "9090" : url.port;
    75  
    76        // check if we are using base path, if not this always is `/`
    77        const baseLocation = new URL(document.baseURI);
    78        const baseUrl = baseLocation.pathname;
    79  
    80        const wsProt = wsProtocol(url.protocol);
    81        const socket = new WebSocket(
    82          `${wsProt}://${url.hostname}:${port}${baseUrl}ws/watch/${bucketName}?prefix=${prefix}&suffix=${suffix}`,
    83        );
    84  
    85        let interval: any | null = null;
    86        if (socket !== null) {
    87          socket.onopen = () => {
    88            console.log("WebSocket Client Connected");
    89            socket.send("ok");
    90            interval = setInterval(() => {
    91              socket.send("ok");
    92            }, 10 * 1000);
    93          };
    94          socket.onmessage = (message: MessageEvent) => {
    95            let m: EventInfo = JSON.parse(message.data.toString());
    96            m.Time = new Date(m.Time.toString());
    97            m.key = Math.random();
    98            dispatch(watchMessageReceived(m));
    99          };
   100          socket.onclose = () => {
   101            clearInterval(interval);
   102            console.log("connection closed by server");
   103            // reset start status
   104            setStart(false);
   105          };
   106          return () => {
   107            // close websocket on useEffect cleanup
   108            socket.close(1000);
   109            clearInterval(interval);
   110            console.log("closing websockets");
   111          };
   112        }
   113      } else {
   114        // reset start status
   115        setStart(false);
   116      }
   117    }, [dispatch, start, bucketList, bucketName, prefix, suffix]);
   118  
   119    const bucketNames = bucketList.map((bucketName) => ({
   120      label: bucketName.name,
   121      value: bucketName.name,
   122    }));
   123  
   124    useEffect(() => {
   125      dispatch(setHelpName("watch"));
   126      // eslint-disable-next-line react-hooks/exhaustive-deps
   127    }, []);
   128  
   129    const optionsArray = bucketNames.map((option) => ({
   130      label: option.label,
   131      value: option.value,
   132    }));
   133  
   134    return (
   135      <Fragment>
   136        <PageHeaderWrapper label="Watch" actions={<HelpMenu />} />
   137        <PageLayout>
   138          <Grid container>
   139            <Grid
   140              item
   141              xs={12}
   142              sx={{
   143                display: "flex",
   144                gap: 10,
   145                marginBottom: 15,
   146                alignItems: "center",
   147              }}
   148            >
   149              <Box sx={{ flexGrow: 1 }}>
   150                <InputLabel>Bucket</InputLabel>
   151                <Select
   152                  id="bucket-name"
   153                  name="bucket-name"
   154                  value={bucketName}
   155                  onChange={(value) => {
   156                    setBucketName(value as string);
   157                  }}
   158                  disabled={start}
   159                  options={optionsArray}
   160                  placeholder={"Select Bucket"}
   161                />
   162              </Box>
   163              <Box sx={{ flexGrow: 1 }}>
   164                <InputLabel>Prefix</InputLabel>
   165                <InputBox
   166                  id="prefix-resource"
   167                  disabled={start}
   168                  onChange={(e) => {
   169                    setPrefix(e.target.value);
   170                  }}
   171                />
   172              </Box>
   173              <Box sx={{ flexGrow: 1 }}>
   174                <InputLabel>Suffix</InputLabel>
   175                <InputBox
   176                  id="suffix-resource"
   177                  disabled={start}
   178                  onChange={(e) => {
   179                    setSuffix(e.target.value);
   180                  }}
   181                />
   182              </Box>
   183              <Box sx={{ alignSelf: "flex-end", paddingBottom: 4 }}>
   184                {start ? (
   185                  <Button
   186                    id={"stop-watch"}
   187                    type="submit"
   188                    variant="callAction"
   189                    onClick={() => setStart(false)}
   190                    label={"Stop"}
   191                  />
   192                ) : (
   193                  <Button
   194                    id={"start-watch"}
   195                    type="submit"
   196                    variant="callAction"
   197                    onClick={() => setStart(true)}
   198                    label={"Start"}
   199                  />
   200                )}
   201              </Box>
   202            </Grid>
   203  
   204            <Grid item xs={12}>
   205              <DataTable
   206                columns={[
   207                  {
   208                    label: "Time",
   209                    elementKey: "Time",
   210                    renderFunction: timeFromDate,
   211                  },
   212                  {
   213                    label: "Size",
   214                    elementKey: "Size",
   215                    renderFunction: niceBytes,
   216                  },
   217                  { label: "Type", elementKey: "Type" },
   218                  { label: "Path", elementKey: "Path" },
   219                ]}
   220                records={messages}
   221                entityName={"Watch"}
   222                customEmptyMessage={"No Changes at this time"}
   223                idField={"watch_table"}
   224                isLoading={false}
   225                customPaperHeight={"calc(100vh - 270px)"}
   226              />
   227            </Grid>
   228          </Grid>
   229        </PageLayout>
   230      </Fragment>
   231    );
   232  };
   233  
   234  export default Watch;