Redesigned Logger Webhooks & Audit Webhooks pages (#2620)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2023-02-02 23:22:43 -06:00
committed by GitHub
parent 606a69b92c
commit 58b45b8ebc
8 changed files with 912 additions and 155 deletions

View File

@@ -54,6 +54,11 @@ export interface IElementValue {
value: string;
}
export interface IConfigurationSys {
name?: string;
key_values: IElementValue[];
}
export interface IElement {
configuration_id: string;
configuration_label: string;

View File

@@ -15,7 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { Button } from "mds";
import { Button, Loader } from "mds";
import { useLocation, useNavigate } from "react-router-dom";
import get from "lodash/get";
import { Theme } from "@mui/material/styles";
@@ -36,18 +36,20 @@ import {
} from "../../Configurations/utils";
import {
IConfigurationElement,
IConfigurationSys,
IElementValue,
} from "../../Configurations/types";
import { ErrorResponseHandler } from "../../../../common/types";
import ResetConfigurationModal from "./ResetConfigurationModal";
import {
configurationIsLoading,
setErrorSnackMessage,
setServerNeedsRestart,
setSnackBarMessage,
} from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import { Loader } from "mds";
import EndpointDisplay from "./EndpointDisplay";
import { AppState, useAppDispatch } from "../../../../store";
import WebhookSettings from "../WebhookSettings/WebhookSettings";
import { useSelector } from "react-redux";
const styles = (theme: Theme) =>
createStyles({
@@ -81,15 +83,20 @@ const EditConfiguration = ({
//Local States
const [valuesObj, setValueObj] = useState<IElementValue[]>([]);
const [saving, setSaving] = useState<boolean>(false);
const [loadingConfig, setLoadingConfig] = useState<boolean>(true);
const [configValues, setConfigValues] = useState<IElementValue[]>([]);
const [configSubsysList, setConfigSubsysList] = useState<any>([]);
const [configSubsysList, setConfigSubsysList] = useState<IConfigurationSys[]>(
[]
);
const [resetConfigurationOpen, setResetConfigurationOpen] =
useState<boolean>(false);
const loadingConfig = useSelector(
(state: AppState) => state.system.loadingConfigurations
);
useEffect(() => {
setLoadingConfig(true);
}, [selConfigTab]);
dispatch(configurationIsLoading(true));
}, [selConfigTab, dispatch]);
useEffect(() => {
if (loadingConfig) {
@@ -102,16 +109,16 @@ const EditConfiguration = ({
setConfigSubsysList(res);
const keyVals = get(res[0], "key_values", []);
setConfigValues(keyVals);
setLoadingConfig(false);
dispatch(configurationIsLoading(false));
})
.catch((err: ErrorResponseHandler) => {
setLoadingConfig(false);
dispatch(configurationIsLoading(false));
dispatch(setErrorSnackMessage(err));
});
return;
}
setLoadingConfig(false);
dispatch(configurationIsLoading(false));
}
}, [loadingConfig, selectedConfiguration, dispatch]);
@@ -129,6 +136,7 @@ const EditConfiguration = ({
.then((res) => {
setSaving(false);
dispatch(setServerNeedsRestart(res.restart));
dispatch(configurationIsLoading(true));
if (!res.restart) {
dispatch(setSnackBarMessage("Configuration saved successfully"));
}
@@ -157,10 +165,14 @@ const EditConfiguration = ({
setResetConfigurationOpen(false);
dispatch(setServerNeedsRestart(restart));
if (restart) {
setLoadingConfig(true);
dispatch(configurationIsLoading(true));
}
};
const resetConfigurationMOpen = () => {
setResetConfigurationOpen(true);
};
return (
<Fragment>
{resetConfigurationOpen && (
@@ -181,62 +193,66 @@ const EditConfiguration = ({
height: "100%",
}}
>
<form
noValidate
onSubmit={submitForm}
className={className}
style={{
height: "100%",
display: "flex",
flexFlow: "column",
}}
>
<Grid item xs={12} className={classes.settingsFormContainer}>
<ConfTargetGeneric
fields={
fieldsConfigurations[selectedConfiguration.configuration_id]
}
onChange={onValueChange}
defaultVals={configValues}
/>
{(selectedConfiguration.configuration_id === "logger_webhook" ||
selectedConfiguration.configuration_id === "audit_webhook") && (
<EndpointDisplay
classes={classes}
configSubsysList={configSubsysList}
/>
)}
</Grid>
<Grid
item
xs={12}
sx={{
paddingTop: "15px ",
textAlign: "right" as const,
maxHeight: "60px",
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<Button
id={"restore-defaults"}
variant="secondary"
onClick={() => {
setResetConfigurationOpen(true);
{selectedConfiguration.configuration_id === "logger_webhook" ||
selectedConfiguration.configuration_id === "audit_webhook" ? (
<WebhookSettings
WebhookSettingslist={configSubsysList}
setResetConfigurationOpen={resetConfigurationMOpen}
type={selectedConfiguration.configuration_id}
/>
) : (
<Fragment>
<form
noValidate
onSubmit={submitForm}
className={className}
style={{
height: "100%",
display: "flex",
flexFlow: "column",
}}
label={"Restore Defaults"}
/>
&nbsp; &nbsp;
<Button
id={"save"}
type="submit"
variant="callAction"
disabled={saving}
label={"Save"}
/>
</Grid>
</form>
>
<Grid item xs={12} className={classes.settingsFormContainer}>
<ConfTargetGeneric
fields={
fieldsConfigurations[
selectedConfiguration.configuration_id
]
}
onChange={onValueChange}
defaultVals={configValues}
/>
</Grid>
<Grid
item
xs={12}
sx={{
paddingTop: "15px ",
textAlign: "right" as const,
maxHeight: "60px",
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<Button
id={"restore-defaults"}
variant="secondary"
onClick={resetConfigurationMOpen}
label={"Restore Defaults"}
/>
&nbsp; &nbsp;
<Button
id={"save"}
type="submit"
variant="callAction"
disabled={saving}
label={"Save"}
/>
</Grid>
</form>
</Fragment>
)}
</Box>
)}
</Fragment>

View File

@@ -1,88 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import {
fieldBasic,
settingsCommon,
} from "../../Common/FormComponents/common/styleLibrary";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
...settingsCommon,
});
interface IEndpointDisplayProps {
// selectedConfiguration: IConfigurationElement;
classes: any;
configSubsysList: any[];
className?: string;
}
const EndpointDisplay = ({
// selectedConfiguration,
classes,
configSubsysList,
className = "",
}: IEndpointDisplayProps) => {
const [configRecords, setConfigRecords] = useState<any>([]);
useEffect(() => {
let records: any[] = [];
if (configSubsysList !== null) {
configSubsysList.forEach((config) => {
if (config.name !== null && config.key_values !== null) {
records.push({
name: config.name,
endpoint: config.key_values[0]["value"],
});
if (config.key_values[0]["value"] === "off") {
records = [];
}
}
});
setConfigRecords(records);
}
}, [configSubsysList]);
return (
<Fragment>
<h3>Currently Configured Endpoints</h3>
<TableWrapper
columns={[
{ label: "Name", elementKey: "name" },
{ label: "Endpoint", elementKey: "endpoint" },
]}
idField="config-id"
isLoading={false}
records={configRecords}
classes={classes}
entityName="endpoints"
/>
</Fragment>
);
};
export default withStyles(styles)(EndpointDisplay);

View File

@@ -0,0 +1,251 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useState } from "react";
import { Button, Grid } from "mds";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import { Webhook } from "@mui/icons-material";
import { formFieldStyles } from "../../Common/FormComponents/common/styleLibrary";
import CallToActionIcon from "@mui/icons-material/CallToAction";
import PendingActionsIcon from "@mui/icons-material/PendingActions";
import api from "../../../../common/api";
import {
configurationIsLoading,
setErrorSnackMessage,
setServerNeedsRestart,
setSnackBarMessage,
} from "../../../../systemSlice";
import { ErrorResponseHandler } from "../../../../common/types";
import { useAppDispatch } from "../../../../store";
import { LinearProgress } from "@mui/material";
interface IEndpointModal {
open: boolean;
type: string;
onCloseEndpoint: () => void;
}
const AddEndpointModal = ({ open, type, onCloseEndpoint }: IEndpointModal) => {
const [name, setName] = useState<string>("");
const [endpoint, setEndpoint] = useState<string>("");
const [authToken, setAuthToken] = useState<string>("");
const [saving, setSaving] = useState<boolean>(false);
const [invalidInputs, setInvalidInput] = useState<string[]>([
"name",
"endpoint",
]);
const [initialInputs, setInitialInputs] = useState<string[]>([
"name",
"endpoint",
"auth-token",
]);
const dispatch = useAppDispatch();
const saveWebhook = () => {
if (saving) {
return;
}
if (invalidInputs.length !== 0) {
return;
}
if (name.trim() === "") {
setInvalidInput([...invalidInputs, "name"]);
return;
}
if (endpoint.trim() === "") {
setInvalidInput([...invalidInputs, "endpoint"]);
return;
}
setSaving(true);
const payload = {
key_values: [
{
key: "endpoint",
value: endpoint,
},
{
key: "auth_token",
value: authToken,
},
],
arn_resource_id: name,
};
api
.invoke("PUT", `/api/v1/configs/${type}`, payload)
.then((res) => {
setSaving(false);
dispatch(setServerNeedsRestart(res.restart));
if (!res.restart) {
dispatch(setSnackBarMessage("Configuration saved successfully"));
}
onCloseEndpoint();
dispatch(configurationIsLoading(true));
})
.catch((err: ErrorResponseHandler) => {
setSaving(false);
dispatch(setErrorSnackMessage(err));
});
};
const initializeInput = (name: string) => {
setInitialInputs(initialInputs.filter((item) => item !== name));
};
const validateInput = (name: string, valid: boolean) => {
if (invalidInputs.includes(name) && valid) {
setInvalidInput(invalidInputs.filter((item) => item !== name));
return;
}
if (!valid && !invalidInputs.includes(name)) {
setInvalidInput([...invalidInputs, name]);
}
};
let title = "Add new Webhook";
let icon = <Webhook />;
switch (type) {
case "logger_webhook":
title = "New Logger Webhook";
icon = <CallToActionIcon />;
break;
case "audit_webhook":
title = "New Audit Webhook";
icon = <PendingActionsIcon />;
break;
}
return (
<Fragment>
<ModalWrapper
modalOpen={open}
title={title}
onClose={onCloseEndpoint}
titleIcon={icon}
>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
id="name"
name="name"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
initializeInput("name");
setName(event.target.value);
validateInput("name", event.target.validity.valid);
}}
error={
invalidInputs.includes("name") && !initialInputs.includes("name")
? "Invalid Name"
: ""
}
label="Name"
value={name}
pattern={"^(?=.*[a-zA-Z0-9]).{1,}$"}
required
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
id="endpoint"
name="endpoint"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
initializeInput("endpoint");
setEndpoint(event.target.value);
validateInput("endpoint", event.target.validity.valid);
}}
error={
invalidInputs.includes("endpoint") &&
!initialInputs.includes("endpoint")
? "Invalid Endpoint set"
: ""
}
label="Endpoint"
value={endpoint}
pattern={
"^(https?):\\/\\/([a-zA-Z0-9\\-.]+)(:[0-9]+)?(\\/[a-zA-Z0-9\\-.\\/]*)?$"
}
required
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
id="auth-token"
name="auth-token"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
initializeInput("auth-token");
setAuthToken(event.target.value);
}}
label="Auth Token"
value={authToken}
/>
</Grid>
{saving && (
<Grid
item
xs={12}
sx={{
marginBottom: 10,
}}
>
<LinearProgress />
</Grid>
)}
<Grid
item
xs={12}
sx={{
display: "flex",
justifyContent: "flex-end",
}}
>
<Button
id={"reset"}
type="button"
variant="regular"
disabled={saving}
onClick={onCloseEndpoint}
label={"Cancel"}
sx={{
marginRight: 10,
}}
/>
<Button
id={"save-lifecycle"}
type="submit"
variant="callAction"
color="primary"
disabled={saving || invalidInputs.length !== 0}
label={"Save"}
onClick={saveWebhook}
/>
</Grid>
</ModalWrapper>
</Fragment>
);
};
export default AddEndpointModal;

View File

@@ -0,0 +1,96 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import { ConfirmDeleteIcon } from "mds";
import { DialogContentText } from "@mui/material";
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
import api from "../../../../common/api";
import {
configurationIsLoading,
setErrorSnackMessage,
setServerNeedsRestart,
} from "../../../../systemSlice";
import { ErrorResponseHandler } from "../../../../common/types";
import { useAppDispatch } from "../../../../store";
interface IDeleteWebhookEndpoint {
modalOpen: boolean;
onClose: () => void;
selectedARN: string;
type: string;
}
const DeleteWebhookEndpoint = ({
modalOpen,
onClose,
selectedARN,
type,
}: IDeleteWebhookEndpoint) => {
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
const dispatch = useAppDispatch();
useEffect(() => {
if (deleteLoading) {
api
.invoke("POST", `/api/v1/configs/${selectedARN}/reset`)
.then(() => {
setDeleteLoading(false);
dispatch(setServerNeedsRestart(true));
dispatch(configurationIsLoading(true));
onClose();
})
.catch((err: ErrorResponseHandler) => {
setDeleteLoading(false);
dispatch(setErrorSnackMessage(err));
});
}
}, [deleteLoading, dispatch, onClose, selectedARN]);
const onConfirmDelete = () => {
setDeleteLoading(true);
};
const defaultWH = !selectedARN.includes(":");
let message = "Are you sure you want to delete the Configured Endpoint";
// Main webhook, we just reset
if (defaultWH) {
message = "Are you sure you want to reset the Default";
}
return (
<ConfirmDialog
title={defaultWH ? `Reset Default Webhook` : `Delete Webhook`}
confirmText={defaultWH ? "Reset" : "Delete"}
isOpen={modalOpen}
isLoading={deleteLoading}
onConfirm={onConfirmDelete}
titleIcon={<ConfirmDeleteIcon />}
onClose={onClose}
confirmationContent={
<DialogContentText>
{`${message} `}
<strong>{selectedARN}</strong>?
</DialogContentText>
}
/>
);
};
export default DeleteWebhookEndpoint;

View File

@@ -0,0 +1,280 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import { Button, Grid } from "mds";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import { Webhook } from "@mui/icons-material";
import { formFieldStyles } from "../../Common/FormComponents/common/styleLibrary";
import CallToActionIcon from "@mui/icons-material/CallToAction";
import PendingActionsIcon from "@mui/icons-material/PendingActions";
import api from "../../../../common/api";
import {
configurationIsLoading,
setErrorSnackMessage,
setServerNeedsRestart,
setSnackBarMessage,
} from "../../../../systemSlice";
import { ErrorResponseHandler } from "../../../../common/types";
import { useAppDispatch } from "../../../../store";
import { LinearProgress } from "@mui/material";
import { IConfigurationSys } from "../../Configurations/types";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
interface IEndpointModal {
open: boolean;
type: string;
endpointInfo: IConfigurationSys;
onCloseEndpoint: () => void;
}
const EditEndpointModal = ({
open,
type,
endpointInfo,
onCloseEndpoint,
}: IEndpointModal) => {
const [name, setName] = useState<string>("");
const [endpoint, setEndpoint] = useState<string>("");
const [authToken, setAuthToken] = useState<string>("");
const [endpointState, setEndpointState] = useState<string>("on");
const [saving, setSaving] = useState<boolean>(false);
const [invalidInputs, setInvalidInput] = useState<string[]>([]);
const dispatch = useAppDispatch();
useEffect(() => {
if (endpointInfo) {
const endpointLocate = endpointInfo.key_values.find(
(key) => key.key === "endpoint"
);
const tokenLocate = endpointInfo.key_values.find(
(key) => key.key === "auth_token"
);
const enable = endpointInfo.key_values.find(
(key) => key.key === "enable"
);
let invalidInputs: string[] = [];
if (endpointLocate) {
const endpointValue = endpointLocate.value;
if (endpointValue === "") {
invalidInputs.push("endpoint");
} else {
setEndpoint(endpointValue);
}
}
if (tokenLocate) {
const tokenValue = tokenLocate.value;
if (tokenValue === "") {
invalidInputs.push("auth-token");
} else {
setAuthToken(tokenValue);
}
}
if (enable) {
if (enable.value === "off") {
setEndpointState(enable.value);
}
}
setName(endpointInfo.name || "");
setInvalidInput(invalidInputs);
}
}, [endpointInfo]);
const updateWebhook = () => {
if (saving) {
return;
}
if (invalidInputs.length !== 0) {
return;
}
if (!endpoint || endpoint.trim() === "") {
setInvalidInput([...invalidInputs, "endpoint"]);
return;
}
setSaving(true);
const payload = {
key_values: [
{
key: "endpoint",
value: endpoint,
},
{
key: "auth_token",
value: authToken,
},
{
key: "enable",
value: endpointState,
},
],
};
api
.invoke("PUT", `/api/v1/configs/${name}`, payload)
.then((res) => {
setSaving(false);
dispatch(setServerNeedsRestart(res.restart));
if (!res.restart) {
dispatch(setSnackBarMessage("Configuration saved successfully"));
}
onCloseEndpoint();
dispatch(configurationIsLoading(true));
})
.catch((err: ErrorResponseHandler) => {
setSaving(false);
dispatch(setErrorSnackMessage(err));
});
};
const validateInput = (name: string, valid: boolean) => {
if (invalidInputs.includes(name) && valid) {
setInvalidInput(invalidInputs.filter((item) => item !== name));
return;
}
if (!valid && !invalidInputs.includes(name)) {
setInvalidInput([...invalidInputs, name]);
}
};
const defaultWH = !name.includes(":");
let title = "Edit Webhook";
let icon = <Webhook />;
switch (type) {
case "logger_webhook":
title = `Edit ${defaultWH ? " the Default " : ""}Logger Webhook`;
icon = <CallToActionIcon />;
break;
case "audit_webhook":
title = `Edit ${defaultWH ? " the Default " : ""}Audit Webhook`;
icon = <PendingActionsIcon />;
break;
}
return (
<Fragment>
<ModalWrapper
modalOpen={open}
title={`${title}${defaultWH ? "" : ` - ${name}`}`}
onClose={onCloseEndpoint}
titleIcon={icon}
>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<FormSwitchWrapper
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.checked ? "on" : "off";
setEndpointState(value);
}}
id={"endpoint_enabled"}
name={"endpoint_enabled"}
label={"Enabled"}
value={"switch_on"}
checked={endpointState === "on"}
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
id="endpoint"
name="endpoint"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setEndpoint(event.target.value);
validateInput("endpoint", event.target.validity.valid);
}}
error={
invalidInputs.includes("endpoint") ? "Invalid Endpoint set" : ""
}
label="Endpoint"
value={endpoint}
pattern={
"^(https?):\\/\\/([a-zA-Z0-9\\-.]+)(:[0-9]+)?(\\/[a-zA-Z0-9\\-.\\/]*)?$"
}
required
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
id="auth-token"
name="auth-token"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setAuthToken(event.target.value);
}}
label="Auth Token"
value={authToken}
/>
</Grid>
{saving && (
<Grid
item
xs={12}
sx={{
marginBottom: 10,
}}
>
<LinearProgress />
</Grid>
)}
<Grid
item
xs={12}
sx={{
display: "flex",
justifyContent: "flex-end",
}}
>
<Button
id={"reset"}
type="button"
variant="regular"
disabled={saving}
onClick={onCloseEndpoint}
label={"Cancel"}
sx={{
marginRight: 10,
}}
/>
<Button
id={"save-lifecycle"}
type="submit"
variant="callAction"
color="primary"
disabled={saving || invalidInputs.length !== 0}
label={"Update"}
onClick={updateWebhook}
/>
</Grid>
</ModalWrapper>
</Fragment>
);
};
export default EditEndpointModal;

View File

@@ -0,0 +1,191 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useState } from "react";
import { IConfigurationSys, IElementValue } from "../../Configurations/types";
import { Button, DataTable, Grid, TierOfflineIcon, TierOnlineIcon } from "mds";
import AddEndpointModal from "./AddEndpointModal";
import DeleteWebhookEndpoint from "./DeleteWebhookEndpoint";
import EditWebhookEndpoint from "./EditWebhookEndpoint";
interface WebhookSettingsProps {
WebhookSettingslist: IConfigurationSys[];
setResetConfigurationOpen: () => void;
type: string;
}
const WebhookSettings = ({
setResetConfigurationOpen,
WebhookSettingslist,
type,
}: WebhookSettingsProps) => {
const [newEndpointOpen, setNewEndpointOpen] = useState<boolean>(false);
const [deleteWebhookOpen, setDeleteWebhookOpen] = useState<boolean>(false);
const [editWebhookOpen, setEditWebhookOpen] = useState<boolean>(false);
const [selectedARN, setSelectedARN] = useState<string>("");
const [selectedEndpoint, setSelectedEndpoint] =
useState<IConfigurationSys | null>(null);
const renderEndpoint = (item: IElementValue[]) => {
const endpointFilter = item.find((itm) => itm.key === "endpoint");
if (endpointFilter) {
return endpointFilter.value;
}
return "";
};
const renderWebhookStatus = (item: IElementValue[]) => {
const EnableFilter = item.find((itm) => itm.key === "enable");
// If enable is not set, then enabled by default
if (!EnableFilter || EnableFilter.value === "on") {
return (
<Grid
container
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyItems: "start",
fontSize: "8px",
}}
>
<TierOnlineIcon style={{ fill: "#4CCB92" }} />
Enabled
</Grid>
);
}
return (
<Grid
container
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyItems: "start",
fontSize: "8px",
}}
>
<TierOfflineIcon style={{ fill: "#C83B51" }} />
Disabled
</Grid>
);
};
const onCloseDelete = () => {
setDeleteWebhookOpen(false);
setSelectedARN("");
};
const onCloseEditWebhook = () => {
setEditWebhookOpen(false);
setSelectedEndpoint(null);
};
const actions = [
{
type: "view",
onClick: (item: IConfigurationSys) => {
if (item.name) {
setEditWebhookOpen(true);
setSelectedEndpoint(item);
}
},
},
{
type: "delete",
onClick: (item: IConfigurationSys) => {
if (item.name) {
setDeleteWebhookOpen(true);
setSelectedARN(item.name);
}
},
},
];
return (
<Grid container>
{newEndpointOpen && (
<AddEndpointModal
open={newEndpointOpen}
type={type}
onCloseEndpoint={() => {
setNewEndpointOpen(false);
}}
/>
)}
{deleteWebhookOpen && (
<DeleteWebhookEndpoint
modalOpen={deleteWebhookOpen}
onClose={onCloseDelete}
selectedARN={selectedARN}
type={type}
/>
)}
{editWebhookOpen && selectedEndpoint && (
<EditWebhookEndpoint
open={editWebhookOpen}
type={type}
endpointInfo={selectedEndpoint}
onCloseEndpoint={onCloseEditWebhook}
/>
)}
<Grid item xs={12} sx={{ display: "flex", justifyContent: "flex-end" }}>
<Button
id={"newWebhook"}
variant="callAction"
onClick={() => {
setNewEndpointOpen(true);
}}
>
New Endpoint
</Button>
</Grid>
<Grid item xs={12} sx={{ padding: "0 10px 10px" }}>
<Fragment>
<h3>Currently Configured Endpoints</h3>
<DataTable
columns={[
{
label: "Status",
elementKey: "key_values",
renderFunction: renderWebhookStatus,
width: 50,
},
{ label: "Name", elementKey: "name" },
{
label: "Endpoint",
elementKey: "key_values",
renderFunction: renderEndpoint,
},
]}
itemActions={actions}
idField="name"
isLoading={false}
records={WebhookSettingslist}
entityName="endpoints"
customPaperHeight={"calc(100vh - 750px)"}
/>
</Fragment>
</Grid>
</Grid>
);
};
export default WebhookSettings;

View File

@@ -35,6 +35,7 @@ export interface SystemState {
userName: string;
serverNeedsRestart: boolean;
serverIsLoading: boolean;
loadingConfigurations: boolean;
loadingProgress: number;
snackBar: snackBarMessage;
modalSnackBar: snackBarMessage;
@@ -58,6 +59,7 @@ const initialState: SystemState = {
siteReplicationInfo: { siteName: "", curSite: false, enabled: false },
serverNeedsRestart: false,
serverIsLoading: false,
loadingConfigurations: true,
loadingProgress: 100,
snackBar: {
message: "",
@@ -106,6 +108,9 @@ export const systemSlice = createSlice({
serverIsLoading: (state, action: PayloadAction<boolean>) => {
state.serverIsLoading = action.payload;
},
configurationIsLoading: (state, action: PayloadAction<boolean>) => {
state.loadingConfigurations = action.payload;
},
setLoadingProgress: (state, action: PayloadAction<number>) => {
state.loadingProgress = action.payload;
},
@@ -192,6 +197,7 @@ export const {
setOverrideStyles,
setAnonymousMode,
resetSystem,
configurationIsLoading,
} = systemSlice.actions;
export const selDistSet = (state: AppState) => state.system.distributedSetup;