github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/BucketDetails/BucketEventsPanel.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 get from "lodash/get";
    19  import { useSelector } from "react-redux";
    20  import { useParams } from "react-router-dom";
    21  import {
    22    AddIcon,
    23    Button,
    24    HelpBox,
    25    LambdaIcon,
    26    DataTable,
    27    Grid,
    28    SectionTitle,
    29    HelpTip,
    30  } from "mds";
    31  import { api } from "api";
    32  import { NotificationConfig } from "api/consoleApi";
    33  import { errorToHandler } from "api/errors";
    34  import {
    35    hasPermission,
    36    SecureComponent,
    37  } from "../../../../common/SecureComponent";
    38  import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
    39  import { setErrorSnackMessage, setHelpName } from "../../../../systemSlice";
    40  import { selBucketDetailsLoading } from "./bucketDetailsSlice";
    41  import { useAppDispatch } from "../../../../store";
    42  import withSuspense from "../../Common/Components/withSuspense";
    43  import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
    44  
    45  const DeleteEvent = withSuspense(React.lazy(() => import("./DeleteEvent")));
    46  const AddEvent = withSuspense(React.lazy(() => import("./AddEvent")));
    47  
    48  const BucketEventsPanel = () => {
    49    const dispatch = useAppDispatch();
    50    const params = useParams();
    51  
    52    const loadingBucket = useSelector(selBucketDetailsLoading);
    53  
    54    const [addEventScreenOpen, setAddEventScreenOpen] = useState<boolean>(false);
    55    const [loadingEvents, setLoadingEvents] = useState<boolean>(true);
    56    const [records, setRecords] = useState<NotificationConfig[]>([]);
    57    const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
    58    const [selectedEvent, setSelectedEvent] = useState<NotificationConfig | null>(
    59      null,
    60    );
    61  
    62    const bucketName = params.bucketName || "";
    63  
    64    const displayEvents = hasPermission(bucketName, [
    65      IAM_SCOPES.S3_GET_BUCKET_NOTIFICATIONS,
    66      IAM_SCOPES.S3_GET_ACTIONS,
    67    ]);
    68  
    69    useEffect(() => {
    70      if (loadingBucket) {
    71        setLoadingEvents(true);
    72      }
    73    }, [loadingBucket, setLoadingEvents]);
    74  
    75    useEffect(() => {
    76      dispatch(setHelpName("bucket_detail_events"));
    77      // eslint-disable-next-line react-hooks/exhaustive-deps
    78    }, []);
    79  
    80    useEffect(() => {
    81      if (loadingEvents) {
    82        if (displayEvents) {
    83          api.buckets
    84            .listBucketEvents(bucketName)
    85            .then((res) => {
    86              const events = get(res.data, "events", []);
    87              setLoadingEvents(false);
    88              setRecords(events || []);
    89            })
    90            .catch((err) => {
    91              setLoadingEvents(false);
    92              dispatch(setErrorSnackMessage(errorToHandler(err.error)));
    93            });
    94        } else {
    95          setLoadingEvents(false);
    96        }
    97      }
    98    }, [loadingEvents, dispatch, bucketName, displayEvents]);
    99  
   100    const eventsDisplay = (events: string[] | null) => {
   101      if (!events) {
   102        return "other";
   103      }
   104  
   105      const cleanEvents = events.reduce((acc: string[], read: string) => {
   106        if (!acc.includes(read)) {
   107          return [...acc, read];
   108        }
   109        return acc;
   110      }, []);
   111  
   112      return <Fragment>{cleanEvents.join(", ")}</Fragment>;
   113    };
   114  
   115    const confirmDeleteEvent = (evnt: NotificationConfig) => {
   116      setDeleteOpen(true);
   117      setSelectedEvent(evnt);
   118    };
   119  
   120    const closeAddEventAndRefresh = () => {
   121      setAddEventScreenOpen(false);
   122      setLoadingEvents(true);
   123    };
   124  
   125    const closeDeleteModalAndRefresh = (refresh: boolean) => {
   126      setDeleteOpen(false);
   127      if (refresh) {
   128        setLoadingEvents(true);
   129      }
   130    };
   131  
   132    const tableActions = [{ type: "delete", onClick: confirmDeleteEvent }];
   133  
   134    return (
   135      <Fragment>
   136        {deleteOpen && (
   137          <DeleteEvent
   138            deleteOpen={deleteOpen}
   139            selectedBucket={bucketName}
   140            bucketEvent={selectedEvent}
   141            closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
   142          />
   143        )}
   144        {addEventScreenOpen && (
   145          <AddEvent
   146            open={addEventScreenOpen}
   147            selectedBucket={bucketName}
   148            closeModalAndRefresh={closeAddEventAndRefresh}
   149          />
   150        )}
   151  
   152        <SectionTitle
   153          separator
   154          sx={{ marginBottom: 15 }}
   155          actions={
   156            <SecureComponent
   157              scopes={[
   158                IAM_SCOPES.S3_PUT_BUCKET_NOTIFICATIONS,
   159                IAM_SCOPES.S3_PUT_ACTIONS,
   160                IAM_SCOPES.ADMIN_SERVER_INFO,
   161              ]}
   162              resource={bucketName}
   163              matchAll
   164              errorProps={{ disabled: true }}
   165            >
   166              <TooltipWrapper tooltip={"Subscribe to Event"}>
   167                <Button
   168                  id={"Subscribe-bucket-event"}
   169                  onClick={() => {
   170                    setAddEventScreenOpen(true);
   171                  }}
   172                  label={"Subscribe to Event"}
   173                  icon={<AddIcon />}
   174                  variant={"callAction"}
   175                />
   176              </TooltipWrapper>
   177            </SecureComponent>
   178          }
   179        >
   180          <HelpTip
   181            content={
   182              <Fragment>
   183                MinIO{" "}
   184                <a
   185                  target="blank"
   186                  href="https://min.io/docs/minio/kubernetes/upstream/administration/monitoring.html"
   187                >
   188                  bucket notifications
   189                </a>{" "}
   190                allow administrators to send notifications to supported external
   191                services on certain object or bucket events.
   192              </Fragment>
   193            }
   194            placement="right"
   195          >
   196            Events
   197          </HelpTip>
   198        </SectionTitle>
   199  
   200        <Grid container>
   201          <Grid item xs={12}>
   202            <SecureComponent
   203              scopes={[
   204                IAM_SCOPES.S3_GET_BUCKET_NOTIFICATIONS,
   205                IAM_SCOPES.S3_GET_ACTIONS,
   206              ]}
   207              resource={bucketName}
   208              errorProps={{ disabled: true }}
   209            >
   210              <DataTable
   211                itemActions={tableActions}
   212                columns={[
   213                  { label: "SQS", elementKey: "arn" },
   214                  {
   215                    label: "Events",
   216                    elementKey: "events",
   217                    renderFunction: eventsDisplay,
   218                  },
   219                  { label: "Prefix", elementKey: "prefix" },
   220                  { label: "Suffix", elementKey: "suffix" },
   221                ]}
   222                isLoading={loadingEvents}
   223                records={records}
   224                entityName="Events"
   225                idField="id"
   226                customPaperHeight={"400px"}
   227              />
   228            </SecureComponent>
   229          </Grid>
   230          {!loadingEvents && (
   231            <Grid item xs={12}>
   232              <br />
   233              <HelpBox
   234                title={"Event Notifications"}
   235                iconComponent={<LambdaIcon />}
   236                help={
   237                  <Fragment>
   238                    MinIO bucket notifications allow administrators to send
   239                    notifications to supported external services on certain object
   240                    or bucket events. MinIO supports bucket and object-level S3
   241                    events similar to the Amazon S3 Event Notifications.
   242                    <br />
   243                    <br />
   244                    You can learn more at our{" "}
   245                    <a
   246                      href="https://min.io/docs/minio/linux/administration/monitoring/bucket-notifications.html?ref=con"
   247                      target="_blank"
   248                      rel="noopener"
   249                    >
   250                      documentation
   251                    </a>
   252                    .
   253                  </Fragment>
   254                }
   255              />
   256            </Grid>
   257          )}
   258        </Grid>
   259      </Fragment>
   260    );
   261  };
   262  
   263  export default BucketEventsPanel;