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

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2023 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    Button,
    20    ConsoleIcon,
    21    FormLayout,
    22    Grid,
    23    InputBox,
    24    PendingItemsIcon,
    25    ProgressBar,
    26    ReadBox,
    27    Switch,
    28    Tooltip,
    29    WebhookIcon,
    30  } from "mds";
    31  import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
    32  import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary";
    33  import {
    34    configurationIsLoading,
    35    setErrorSnackMessage,
    36    setServerNeedsRestart,
    37    setSnackBarMessage,
    38  } from "../../../../systemSlice";
    39  import { useAppDispatch } from "../../../../store";
    40  
    41  import { IConfigurationSys } from "../../Configurations/types";
    42  import { overrideFields } from "../../Configurations/utils";
    43  import { api } from "api";
    44  import { errorToHandler } from "api/errors";
    45  
    46  interface IEndpointModal {
    47    open: boolean;
    48    type: string;
    49    endpointInfo: IConfigurationSys;
    50    onCloseEndpoint: () => void;
    51  }
    52  
    53  const EditEndpointModal = ({
    54    open,
    55    type,
    56    endpointInfo,
    57    onCloseEndpoint,
    58  }: IEndpointModal) => {
    59    const [name, setName] = useState<string>("");
    60    const [endpoint, setEndpoint] = useState<string>("");
    61    const [authToken, setAuthToken] = useState<string>("");
    62    const [endpointState, setEndpointState] = useState<string>("on");
    63    const [saving, setSaving] = useState<boolean>(false);
    64    const [invalidInputs, setInvalidInput] = useState<string[]>([]);
    65  
    66    const dispatch = useAppDispatch();
    67  
    68    useEffect(() => {
    69      if (endpointInfo) {
    70        const endpointLocate = endpointInfo.key_values.find(
    71          (key) => key.key === "endpoint",
    72        );
    73        const tokenLocate = endpointInfo.key_values.find(
    74          (key) => key.key === "auth_token",
    75        );
    76        const enable = endpointInfo.key_values.find(
    77          (key) => key.key === "enable",
    78        );
    79  
    80        let invalidInputs: string[] = [];
    81  
    82        if (endpointLocate) {
    83          const endpointValue = endpointLocate.value;
    84  
    85          if (endpointValue === "") {
    86            invalidInputs.push("endpoint");
    87          } else {
    88            setEndpoint(endpointValue);
    89          }
    90        }
    91  
    92        if (tokenLocate) {
    93          const tokenValue = tokenLocate.value;
    94  
    95          if (tokenValue === "") {
    96            invalidInputs.push("auth-token");
    97          } else {
    98            setAuthToken(tokenValue);
    99          }
   100        }
   101  
   102        if (enable) {
   103          if (enable.value === "off") {
   104            setEndpointState(enable.value);
   105          }
   106        }
   107  
   108        setName(endpointInfo.name || "");
   109        setInvalidInput(invalidInputs);
   110      }
   111    }, [endpointInfo]);
   112  
   113    const updateWebhook = () => {
   114      if (saving) {
   115        return;
   116      }
   117  
   118      if (invalidInputs.length !== 0) {
   119        return;
   120      }
   121  
   122      if (!endpoint || endpoint.trim() === "") {
   123        setInvalidInput([...invalidInputs, "endpoint"]);
   124  
   125        return;
   126      }
   127  
   128      setSaving(true);
   129  
   130      const payload = {
   131        key_values: [
   132          {
   133            key: "endpoint",
   134            value: endpoint,
   135          },
   136          {
   137            key: "auth_token",
   138            value: authToken,
   139          },
   140          {
   141            key: "enable",
   142            value: endpointState,
   143          },
   144        ],
   145      };
   146  
   147      api.configs
   148        .setConfig(name, payload)
   149        .then((res) => {
   150          setSaving(false);
   151          dispatch(setServerNeedsRestart(res.data.restart || false));
   152          if (!res.data.restart) {
   153            dispatch(setSnackBarMessage("Configuration saved successfully"));
   154          }
   155  
   156          onCloseEndpoint();
   157          dispatch(configurationIsLoading(true));
   158        })
   159        .catch((err) => {
   160          setSaving(false);
   161          dispatch(setErrorSnackMessage(errorToHandler(err.error)));
   162        });
   163    };
   164  
   165    const validateInput = (name: string, valid: boolean) => {
   166      if (invalidInputs.includes(name) && valid) {
   167        setInvalidInput(invalidInputs.filter((item) => item !== name));
   168        return;
   169      }
   170  
   171      if (!valid && !invalidInputs.includes(name)) {
   172        setInvalidInput([...invalidInputs, name]);
   173      }
   174    };
   175  
   176    const defaultWH = !name.includes(":");
   177    const hasOverride = endpointInfo.key_values.filter(
   178      (itm) => !!itm.env_override,
   179    );
   180  
   181    const overrideValues = overrideFields(hasOverride);
   182  
   183    let title = "Edit Webhook";
   184    let icon = <WebhookIcon />;
   185  
   186    switch (type) {
   187      case "logger_webhook":
   188        title = `Edit ${defaultWH ? " the Default " : ""}Logger Webhook`;
   189        icon = <ConsoleIcon />;
   190        break;
   191      case "audit_webhook":
   192        title = `Edit ${defaultWH ? " the Default " : ""}Audit Webhook`;
   193        icon = <PendingItemsIcon />;
   194        break;
   195    }
   196  
   197    if (hasOverride.length > 0) {
   198      title = "View env variable Webhook";
   199    }
   200  
   201    return (
   202      <Fragment>
   203        <ModalWrapper
   204          modalOpen={open}
   205          title={`${title}${defaultWH ? "" : ` - ${name}`}`}
   206          onClose={onCloseEndpoint}
   207          titleIcon={icon}
   208        >
   209          <FormLayout withBorders={false} containerPadding={false}>
   210            {hasOverride.length > 0 ? (
   211              <Fragment>
   212                <ReadBox
   213                  label={"Enabled"}
   214                  sx={{ width: "100%" }}
   215                  actionButton={
   216                    <Grid
   217                      item
   218                      sx={{
   219                        display: "flex",
   220                        justifyContent: "flex-end",
   221                        paddingRight: "10px",
   222                      }}
   223                    >
   224                      <Tooltip
   225                        tooltip={
   226                          overrideValues.enable
   227                            ? `This value is set from the ${
   228                                overrideValues.enable?.overrideEnv || "N/A"
   229                              } environment variable`
   230                            : ""
   231                        }
   232                        placement={"left"}
   233                      >
   234                        <ConsoleIcon style={{ width: 20 }} />
   235                      </Tooltip>
   236                    </Grid>
   237                  }
   238                >
   239                  {overrideValues.enable?.value || "-"}
   240                </ReadBox>
   241                <ReadBox
   242                  label={"Endpoint"}
   243                  sx={{ width: "100%" }}
   244                  actionButton={
   245                    <Grid
   246                      item
   247                      sx={{
   248                        display: "flex",
   249                        justifyContent: "flex-end",
   250                        paddingRight: "10px",
   251                      }}
   252                    >
   253                      <Tooltip
   254                        tooltip={
   255                          overrideValues.enable
   256                            ? `This value is set from the ${
   257                                overrideValues.endpoint?.overrideEnv || "N/A"
   258                              } environment variable`
   259                            : ""
   260                        }
   261                        placement={"left"}
   262                      >
   263                        <ConsoleIcon style={{ width: 20 }} />
   264                      </Tooltip>
   265                    </Grid>
   266                  }
   267                >
   268                  {overrideValues.endpoint?.value || "-"}
   269                </ReadBox>
   270                <ReadBox
   271                  label={"Auth Token"}
   272                  sx={{ width: "100%" }}
   273                  actionButton={
   274                    <Grid
   275                      item
   276                      sx={{
   277                        display: "flex",
   278                        justifyContent: "flex-end",
   279                        paddingRight: "10px",
   280                      }}
   281                    >
   282                      <Tooltip
   283                        tooltip={
   284                          overrideValues.enable
   285                            ? `This value is set from the ${
   286                                overrideValues.auth_token?.overrideEnv || "N/A"
   287                              } environment variable`
   288                            : ""
   289                        }
   290                        placement={"left"}
   291                      >
   292                        <ConsoleIcon style={{ width: 20 }} />
   293                      </Tooltip>
   294                    </Grid>
   295                  }
   296                >
   297                  {overrideValues.auth_token?.value || "-"}
   298                </ReadBox>
   299              </Fragment>
   300            ) : (
   301              <Fragment>
   302                <Switch
   303                  onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   304                    const value = e.target.checked ? "on" : "off";
   305                    setEndpointState(value);
   306                  }}
   307                  id={"endpoint_enabled"}
   308                  name={"endpoint_enabled"}
   309                  label={"Enabled"}
   310                  value={"switch_on"}
   311                  checked={endpointState === "on"}
   312                />
   313                <InputBox
   314                  id="endpoint"
   315                  name="endpoint"
   316                  onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
   317                    setEndpoint(event.target.value);
   318                    validateInput("endpoint", event.target.validity.valid);
   319                  }}
   320                  error={
   321                    invalidInputs.includes("endpoint")
   322                      ? "Invalid Endpoint set"
   323                      : ""
   324                  }
   325                  label="Endpoint"
   326                  value={endpoint}
   327                  pattern={
   328                    "^(https?):\\/\\/([a-zA-Z0-9\\-.]+)(:[0-9]+)?(\\/[a-zA-Z0-9\\-.\\/]*)?$"
   329                  }
   330                  required
   331                />
   332                <InputBox
   333                  id="auth-token"
   334                  name="auth-token"
   335                  onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
   336                    setAuthToken(event.target.value);
   337                  }}
   338                  label="Auth Token"
   339                  value={authToken}
   340                />
   341                {saving && (
   342                  <Grid
   343                    item
   344                    xs={12}
   345                    sx={{
   346                      marginBottom: 10,
   347                    }}
   348                  >
   349                    <ProgressBar />
   350                  </Grid>
   351                )}
   352                <Grid item sx={modalStyleUtils.modalButtonBar}>
   353                  <Button
   354                    id={"reset"}
   355                    type="button"
   356                    variant="regular"
   357                    disabled={saving}
   358                    onClick={onCloseEndpoint}
   359                    label={"Cancel"}
   360                  />
   361                  <Button
   362                    id={"save-lifecycle"}
   363                    type="submit"
   364                    variant="callAction"
   365                    color="primary"
   366                    disabled={saving || invalidInputs.length !== 0}
   367                    label={"Update"}
   368                    onClick={updateWebhook}
   369                  />
   370                </Grid>
   371              </Fragment>
   372            )}
   373          </FormLayout>
   374        </ModalWrapper>
   375      </Fragment>
   376    );
   377  };
   378  
   379  export default EditEndpointModal;