Redesigned Logger Webhooks & Audit Webhooks pages (#2620)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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"}
|
||||
/>
|
||||
|
||||
<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"}
|
||||
/>
|
||||
|
||||
<Button
|
||||
id={"save"}
|
||||
type="submit"
|
||||
variant="callAction"
|
||||
disabled={saving}
|
||||
label={"Save"}
|
||||
/>
|
||||
</Grid>
|
||||
</form>
|
||||
</Fragment>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Fragment>
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user