Configuration List Forms (#83)

Created Lists & forms for configurations in mcs
This commit is contained in:
Alex
2020-04-30 00:00:02 -05:00
committed by GitHub
parent b85712e29e
commit 9df9309c66
16 changed files with 1724 additions and 731 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,151 @@
// This file is part of MinIO Console Server
// Copyright (c) 2019 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, {
useState,
useEffect,
createRef,
ChangeEvent,
useCallback
} from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import get from "lodash/get";
import InputBoxWrapper from "../InputBoxWrapper/InputBoxWrapper";
import { InputLabel, Tooltip } from "@material-ui/core";
import { fieldBasic } from "../common/styleLibrary";
import HelpIcon from "@material-ui/icons/Help";
interface ICSVMultiSelector {
elements: string;
name: string;
label: string;
tooltip?: string;
classes: any;
onChange: (elements: string) => void;
}
const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
inputLabel: {
...fieldBasic.inputLabel,
marginBottom: 10
},
inputContainer: {
height: 150,
overflowY: "auto",
padding: 15,
position: "relative",
border: "1px solid #c4c4c4",
marginBottom: 10
},
labelContainer: {
display: "flex"
}
});
const CSVMultiSelector = ({
elements,
name,
label,
tooltip,
onChange,
classes
}: ICSVMultiSelector) => {
const [currentElements, setCurrentElements] = useState<string[]>([""]);
const bottomList = createRef<HTMLDivElement>();
// Use effect to get the initial values from props
useCallback(() => {
if (currentElements.length === 1 && currentElements[0] === "") {
const elementsSplitted = elements.split(",");
if (elementsSplitted[elementsSplitted.length - 1].trim() !== "") {
elementsSplitted.push("");
}
setCurrentElements(elementsSplitted);
}
}, [elements, setCurrentElements, currentElements]);
// Use effect to send new values to onChange
useEffect(() => {
const elementsString = currentElements
.filter(element => element.trim() !== "")
.join(",");
onChange(elementsString);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentElements]);
// If the last input is not empty, we add a new one
const addEmptyLine = (elementsUp: string[]) => {
if (elementsUp[elementsUp.length - 1].trim() !== "") {
elementsUp.push("");
const refScroll = bottomList.current;
if (refScroll) {
refScroll.scrollIntoView(false);
}
}
return elementsUp;
};
// Onchange function for input box, we get the dataset-index & only update that value in the array
const onChangeElement = (e: ChangeEvent<HTMLInputElement>) => {
e.persist();
let updatedElement = [...currentElements];
const index = get(e.target, "dataset.index", 0);
updatedElement[index] = e.target.value;
updatedElement = addEmptyLine(updatedElement);
setCurrentElements(updatedElement);
};
const inputs = currentElements.map((element, index) => {
return (
<InputBoxWrapper
id={`${name}-${index.toString()}`}
label={""}
name={`${name}-${index.toString()}`}
value={currentElements[index]}
onChange={onChangeElement}
index={index}
key={`csv-${name}-${index.toString()}`}
/>
);
});
return (
<React.Fragment>
<Grid item xs={12} className={classes.fieldContainer}>
<InputLabel className={classes.inputLabel}>{label}</InputLabel>
<Grid item xs={12} className={classes.inputContainer}>
{inputs}
<div ref={bottomList} />
</Grid>
{tooltip !== "" && (
<div className={classes.tooltipContainer}>
<Tooltip title={tooltip} placement="left">
<HelpIcon />
</Tooltip>
</div>
)}
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(CSVMultiSelector);

View File

@@ -43,6 +43,7 @@ interface InputBoxProps {
type?: string;
tooltip?: string;
autoComplete?: string;
index?: number;
}
const styles = (theme: Theme) =>
@@ -89,14 +90,17 @@ const InputBoxWrapper = ({
disabled = false,
multiline = false,
tooltip = "",
index = 0,
classes
}: InputBoxProps) => {
return (
<React.Fragment>
<Grid item xs={12} className={classes.fieldContainer}>
<InputLabel htmlFor={id} className={classes.inputLabel}>
{label}
</InputLabel>
{label !== "" && (
<InputLabel htmlFor={id} className={classes.inputLabel}>
{label}
</InputLabel>
)}
<div className={classes.textBoxContainer}>
<InputField
className={classes.boxDesign}
@@ -110,10 +114,11 @@ const InputBoxWrapper = ({
type={type}
multiline={multiline}
autoComplete={autoComplete}
inputProps={{ "data-index": index }}
/>
</div>
{tooltip !== "" && (
<div>
<div className={classes.tooltipContainer}>
<Tooltip title={tooltip} placement="left">
<HelpIcon />
</Tooltip>

View File

@@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// This object contains variables that will be used across form components.
export const fieldBasic = {
inputLabel: {
fontWeight: 500,
@@ -27,5 +28,8 @@ export const fieldBasic = {
display: "flex",
alignItems: "center",
marginBottom: 10
},
tooltipContainer: {
marginLeft: 5
}
};

View File

@@ -19,10 +19,11 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import { KVField } from "./types";
import { IElementValue, KVField } from "./types";
import CSVMultiSelector from "../Common/FormComponents/CSVMultiSelector/CSVMultiSelector";
interface IConfGenericProps {
onChange: (newValue: Map<string, string>) => void;
onChange: (newValue: IElementValue[]) => void;
fields: KVField[];
classes: any;
}
@@ -34,100 +35,92 @@ const ConfTargetGeneric = ({
fields,
classes
}: IConfGenericProps) => {
//Local States
const [valueHolder, setValueHolder] = useState<IElementValue[]>([]);
const fieldsElements = !fields ? [] : fields;
const [keyValues, setKeyValues] = useState<Map<string, string>>(new Map());
// Effect to create all the values to hold
useEffect(() => {
const values: IElementValue[] = [];
fields.forEach(field => {
const stateInsert: IElementValue = {
key: field.name,
value: field.type === "on|off" ? "false" : ""
};
values.push(stateInsert);
});
setValueHolder(values);
}, [fields]);
useEffect(() => {
if (keyValues.size > 0) {
onChange(keyValues);
}
}, [keyValues, onChange]);
onChange(valueHolder);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [valueHolder]);
const val = (key: string): string => {
return keyValues.get(key) === undefined ? "" : keyValues.get(key) + "";
const setValueElement = (key: string, value: string, index: number) => {
const valuesDup = [...valueHolder];
valuesDup[index] = { key, value };
setValueHolder(valuesDup);
};
const valFall = (key: string, fallback: string): string => {
return keyValues.get(key) === undefined
? fallback
: keyValues.get(key) + "";
const fieldDefinition = (field: KVField, item: number) => {
switch (field.type) {
case "on|off":
return (
<RadioGroupSelector
currentSelection={valueHolder[item] ? valueHolder[item].value : ""}
id={field.name}
name={field.name}
label={field.label}
tooltip={field.tooltip}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValueElement(field.name, e.target.value, item)
}
selectorOptions={[
{ label: "On", value: "true" },
{ label: "Off", value: "false" }
]}
/>
);
case "csv":
return (
<CSVMultiSelector
elements={valueHolder[item] ? valueHolder[item].value : ""}
label={field.label}
name={field.name}
onChange={(value: string) =>
setValueElement(field.name, value, item)
}
tooltip={field.tooltip}
/>
);
default:
return (
<InputBoxWrapper
id={field.name}
name={field.name}
label={field.label}
tooltip={field.tooltip}
value={valueHolder[item] ? valueHolder[item].value : ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValueElement(field.name, e.target.value, item)
}
multiline={!!field.multiline}
/>
);
}
};
return (
<Grid container>
{fields.map(field => (
{fieldsElements.map((field, item) => (
<React.Fragment key={field.name}>
{field.type === "on|off" ? (
<Grid item xs={12}>
<RadioGroupSelector
currentSelection={valFall(field.name, "false")}
id={field.name}
name={field.name}
label={field.label}
tooltip={field.tooltip}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setKeyValues(keyValues.set(field.name, e.target.value));
}}
selectorOptions={[
{ label: "On", value: "true" },
{ label: "Off", value: "false" }
]}
/>
</Grid>
) : (
<Grid item xs={12}>
<InputBoxWrapper
id={field.name}
name={field.name}
label={field.label}
tooltip={field.tooltip}
value={val(field.name)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setKeyValues(keyValues.set(field.name, e.target.value));
}}
/>
</Grid>
)}
<Grid item xs={12}>
{fieldDefinition(field, item)}
</Grid>
</React.Fragment>
))}
<Grid item xs={12}>
<InputBoxWrapper
id="queue-dir"
name="queue_dir"
label="Queue Dir"
value={val("queue_dir")}
tooltip="staging dir for undelivered messages e.g. '/home/events'"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setKeyValues(keyValues.set("queue_dir", e.target.value));
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="queue-limit"
name="queue_limit"
label="Queue Limit"
type="number"
value={val("queue_limit")}
tooltip="maximum limit for undelivered messages, defaults to '10000'"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setKeyValues(keyValues.set("queue_limit", e.target.value));
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="comment"
name="comment"
label="Comment"
multiline={true}
value={val("comment")}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setKeyValues(keyValues.set("comment", e.target.value));
}}
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>

View File

@@ -0,0 +1,155 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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, { useState } from "react";
import get from "lodash/get";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { TextField } from "@material-ui/core";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import history from "../../../../history";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import { configurationElements } from "../utils";
import { IConfigurationElement } from "../types";
import EditConfiguration from "../CustomForms/EditConfiguration";
interface IListConfiguration {
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
},
strongText: {
fontWeight: 700
},
keyName: {
marginLeft: 5
},
actionsTray: {
textAlign: "right",
"& button": {
marginLeft: 10
}
},
searchField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012"
},
iconText: {
lineHeight: "24px"
}
});
const ConfigurationsList = ({ classes }: IListConfiguration) => {
const [editScreenOpen, setEditScreenOpen] = useState(false);
const [selectedConfiguration, setSelectedConfiguration] = useState({
configuration_id: "",
configuration_label: ""
});
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
const [filter, setFilter] = useState("");
const tableActions = [
{
type: "edit",
onClick: (element: IConfigurationElement) => {
const url = get(element, "url", "");
if (url !== "") {
// We redirect Browser
history.push(url);
} else {
setSelectedConfiguration(element);
setEditScreenOpen(true);
}
}
}
];
const filteredRecords: IConfigurationElement[] = configurationElements.filter(
elementItem =>
elementItem.configuration_id
.toLocaleLowerCase()
.includes(filter.toLocaleLowerCase())
);
return (
<React.Fragment>
{editScreenOpen && (
<EditConfiguration
open={editScreenOpen}
closeModalAndRefresh={() => {
setIsLoading(true);
setEditScreenOpen(false);
}}
selectedConfiguration={selectedConfiguration}
/>
)}
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Configurations List</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
{error !== "" && <Grid container>{error}</Grid>}
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Filter"
className={classes.searchField}
id="search-resource"
label=""
onChange={event => {
setFilter(event.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "Configuration", elementKey: "configuration_id" }
]}
isLoading={isLoading}
records={filteredRecords}
entityName="Configurations"
idField="configuration_id"
/>
</Grid>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(ConfigurationsList);

View File

@@ -0,0 +1,188 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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, { useState } from "react";
import get from "lodash/get";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { Button, TextField } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import AddIcon from "@material-ui/icons/Add";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import EditConfiguration from "../CustomForms/EditConfiguration";
interface IMatchParams {
isExact: boolean;
params: any;
path: string;
}
interface IWebhookPanel {
match: IMatchParams;
classes: any;
}
interface IWebhook {
name: string;
}
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
},
strongText: {
fontWeight: 700
},
keyName: {
marginLeft: 5
},
actionsTray: {
textAlign: "right",
"& button": {
marginLeft: 10
}
},
searchField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012"
},
iconText: {
lineHeight: "24px"
}
});
const panels = {
logger: {
main: "logger",
title: "Logger Webhook Configuration",
modalTitle: "Logger Webhook",
apiURL: "",
configuration: {
configuration_id: "logger_webhook",
configuration_label: "Logger Webhook"
}
},
audit: {
main: "audit",
title: "Audit Webhook Configuration",
modalTitle: "Audit Webhook",
apiURL: "",
configuration: {
configuration_id: "audit_webhook",
configuration_label: "Audit Webhook"
}
}
};
const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
const [addWebhookOpen, setAddWebhookOpen] = useState<boolean>(false);
const [error, setError] = useState<string>("");
const [filter, setFilter] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
const [webhooks, setWebhooks] = useState<IWebhook[]>([]);
const pathIn = get(match, "path", "");
const panelToDisplay = pathIn.split("/");
const panelData = get(panels, panelToDisplay[2], false);
if (!panelData) {
return null;
}
const filteredRecords: IWebhook[] = webhooks.filter(elementItem =>
elementItem.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())
);
const tableActions = [
{
type: "edit",
onClick: () => {}
}
];
return (
<React.Fragment>
{addWebhookOpen && (
<EditConfiguration
open={addWebhookOpen}
closeModalAndRefresh={() => {
setIsLoading(true);
setAddWebhookOpen(false);
}}
selectedConfiguration={panelData.configuration}
/>
)}
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">{panelData.title}</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
{error !== "" && <Grid container>{error}</Grid>}
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Filter"
className={classes.searchField}
id="search-resource"
label=""
onChange={event => {
setFilter(event.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
)
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<AddIcon />}
onClick={() => {
setAddWebhookOpen(true);
}}
>
Add Webhook Configuration
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={isLoading}
records={filteredRecords}
entityName="Webhook Configurations"
idField="name"
/>
</Grid>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(WebhookPanel);

View File

@@ -18,11 +18,12 @@ import React, { useCallback, useEffect, useState } from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { FormControlLabel, Switch } from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import { IElementValue } from "../types";
interface IConfMySqlProps {
onChange: (newValue: Map<string, string>) => void;
onChange: (newValue: IElementValue[]) => void;
classes: any;
}
@@ -81,32 +82,16 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
useEffect(() => {
if (dsnString !== "") {
let values: Map<string, string> = new Map();
const formValues = [
{ key: "dsn_string", value: dsnString },
{ key: "table", value: table },
{ key: "format", value: format },
{ key: "queue_dir", value: queueDir },
{ key: "queue_limit", value: queueLimit },
{ key: "comment", value: comment }
];
if (dsnString !== "") {
values.set("dsn_string", dsnString);
}
if (table !== "") {
values.set("table", table);
}
if (format !== "") {
values.set("format", format);
}
if (queueDir !== "") {
values.set("queue_dir", queueDir);
}
if (queueLimit !== "") {
values.set("queue_limit", queueLimit);
}
if (comment !== "") {
values.set("comment", comment);
}
onChange(values);
onChange(formValues);
}
}, [dsnString, table, format, queueDir, queueLimit, comment, onChange]);

View File

@@ -18,12 +18,13 @@ import React, { useCallback, useEffect, useState } from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { FormControlLabel, Switch } from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import SelectWrapper from "../Common/FormComponents/SelectWrapper/SelectWrapper";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import { IElementValue } from "../types";
interface IConfPostgresProps {
onChange: (newValue: Map<string, string>) => void;
onChange: (newValue: IElementValue[]) => void;
classes: any;
}
@@ -129,32 +130,16 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
useEffect(() => {
if (connectionString !== "") {
let values: Map<string, string> = new Map();
const formValues = [
{ key: "connection_string", value: connectionString },
{ key: "table", value: table },
{ key: "format", value: format },
{ key: "queue_dir", value: queueDir },
{ key: "queue_limit", value: queueLimit },
{ key: "comment", value: comment }
];
if (connectionString !== "") {
values.set("connection_string", connectionString);
}
if (table !== "") {
values.set("table", table);
}
if (format !== "") {
values.set("format", format);
}
if (queueDir !== "") {
values.set("queue_dir", queueDir);
}
if (queueLimit !== "") {
values.set("queue_limit", queueLimit);
}
if (comment !== "") {
values.set("comment", comment);
}
onChange(values);
onChange(formValues);
}
}, [
connectionString,

View File

@@ -0,0 +1,160 @@
// This file is part of MinIO Console Server
// Copyright (c) 2019 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, { useCallback, useEffect, useState } from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button } from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import api from "../../../../common/api";
import { serverNeedsRestart } from "../../../../actions";
import { connect } from "react-redux";
import ConfTargetGeneric from "../ConfTargetGeneric";
import { fieldsConfigurations, removeEmptyFields } from "../utils";
import { IConfigurationElement, IElementValue } from "../types";
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
},
strongText: {
fontWeight: 700
},
keyName: {
marginLeft: 5
},
buttonContainer: {
textAlign: "right"
},
logoButton: {
height: "80px"
}
});
interface IAddNotificationEndpointProps {
open: boolean;
closeModalAndRefresh: any;
serverNeedsRestart: typeof serverNeedsRestart;
selectedConfiguration: IConfigurationElement;
classes: any;
}
const EditConfiguration = ({
open,
closeModalAndRefresh,
serverNeedsRestart,
selectedConfiguration,
classes
}: IAddNotificationEndpointProps) => {
//Local States
const [valuesObj, setValueObj] = useState<IElementValue[]>([]);
const [saving, setSaving] = useState<boolean>(false);
const [addError, setError] = useState<string>("");
//Effects
useEffect(() => {
if (saving) {
const payload = {
key_values: removeEmptyFields(valuesObj)
};
api
.invoke(
"PUT",
`/api/v1/configs/${selectedConfiguration.configuration_id}`,
payload
)
.then(res => {
setSaving(false);
setError("");
serverNeedsRestart(true);
closeModalAndRefresh();
})
.catch(err => {
setSaving(false);
setError(err);
});
}
}, [
saving,
serverNeedsRestart,
selectedConfiguration,
valuesObj,
closeModalAndRefresh
]);
//Fetch Actions
const submitForm = (event: React.FormEvent) => {
event.preventDefault();
setSaving(true);
};
const onValueChange = useCallback(
newValue => {
setValueObj(newValue);
},
[setValueObj]
);
return (
<ModalWrapper
modalOpen={open}
onClose={closeModalAndRefresh}
title={selectedConfiguration.configuration_label}
>
<React.Fragment>
{addError !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{addError}
</Typography>
</Grid>
)}
<form noValidate onSubmit={submitForm}>
<ConfTargetGeneric
fields={
fieldsConfigurations[selectedConfiguration.configuration_id]
}
onChange={onValueChange}
/>
<Grid item xs={3} className={classes.buttonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
fullWidth
disabled={saving}
>
Save
</Button>
</Grid>
<Grid item xs={9} />
</form>
</React.Fragment>
</ModalWrapper>
);
};
const connector = connect(null, { serverNeedsRestart });
export default connector(withStyles(styles)(EditConfiguration));

View File

@@ -26,7 +26,8 @@ export type KVFieldType =
| "address"
| "duration"
| "uri"
| "sentence";
| "sentence"
| "csv";
export interface KVField {
name: string;
@@ -35,4 +36,16 @@ export interface KVField {
required?: boolean;
type: KVFieldType;
options?: SelectorTypes[];
multiline?: boolean;
}
export interface IConfigurationElement {
configuration_id: string;
configuration_label: string;
url?: string;
}
export interface IElementValue {
key: string;
value: string;
}

View File

@@ -0,0 +1,771 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 { IConfigurationElement, IElementValue } from "./types";
export const notifyPostgres = "notify_postgres";
export const notifyMysql = "notify_mysql";
export const notifyKafka = "notify_kafka";
export const notifyAmqp = "notify_amqp";
export const notifyMqtt = "notify_mqtt";
export const notifyRedis = "notify_redis";
export const notifyNats = "notify_nats";
export const notifyElasticsearch = "notify_elasticsearch";
export const notifyWebhooks = "notify_webhooks";
export const notifyNsq = "notify_nsq";
export const configurationElements: IConfigurationElement[] = [
{ configuration_id: "region", configuration_label: "Region Configuration" },
{ configuration_id: "cache", configuration_label: "Cache Configuration" },
{
configuration_id: "compression",
configuration_label: "Compression Configuration"
},
{ configuration_id: "etcd", configuration_label: "Etcd Configuration" },
{
configuration_id: "identity_openid",
configuration_label: "Identity Openid Configuration"
},
{
configuration_id: "identity_ldap",
configuration_label: "Identity LDAP Configuration"
},
{
configuration_id: "policy_opa",
configuration_label: "Policy OPA Configuration"
},
{
configuration_id: "kms_vault",
configuration_label: "KMS Vault Configuration"
},
{ configuration_id: "kms_kes", configuration_label: "KMS KES Configuration" },
{
configuration_id: "logger_webhook",
configuration_label: "Logger Webhook Configuration",
url: "/webhook/logger"
},
{
configuration_id: "audit_webhook",
configuration_label: "Audit Webhook Configuration",
url: "/webhook/audit"
}
];
export const fieldsConfigurations: any = {
region: [
{
name: "name",
required: true,
label: "name",
tooltip: 'Name of the location of the server e.g. "us-west-rack2"',
type: "string"
},
{
name: "comment",
required: false,
label: "comment",
tooltip: "You can add a comment to this setting",
type: "string",
multiline: true
}
],
cache: [
{
name: "drives",
required: true,
label: "Drives",
tooltip:
'Mountpoints e.g. "/optane1" or "/optane2", you can write one per field',
type: "csv"
},
{
name: "expiry",
required: false,
label: "Expiry",
tooltip: 'Cache expiry duration in days e.g. "90"',
type: "number"
},
{
name: "quota",
required: false,
label: "Quota",
tooltip: 'Limit cache drive usage in percentage e.g. "90"',
type: "number"
},
{
name: "exclude",
required: false,
label: "Exclude",
tooltip:
'Wildcard exclusion patterns e.g. "bucket/*.tmp" or "*.exe", you can write one per field',
type: "csv"
},
{
name: "after",
required: false,
label: "After",
tooltip: "Minimum number of access before caching an object",
type: "number"
},
{
name: "comment",
required: false,
label: "Comment",
tooltip: "You can add a comment to this setting",
type: "string",
multiline: true
}
],
compression: [
{
name: "minio_compress",
required: true,
label: "MinIO Compress",
type: "on|off"
},
{
name: "extensions",
required: false,
label: "Extensions",
tooltip:
'Extensions to compress e.g. ".txt",".log" or ".csv", you can write one per field',
type: "csv"
},
{
name: "mime_types",
required: false,
label: "Mime Types",
tooltip:
'Mime types e.g. "text/*","application/json" or "application/xml", you can write one per field',
type: "csv"
}
],
etcd: [
{
name: "endpoints",
required: true,
label: "Endpoints",
tooltip:
'List of etcd endpoints e.g. "http://localhost:2379", you can write one per field',
type: "csv"
},
{
name: "path_prefix",
required: false,
label: "Path Prefix",
tooltip: 'namespace prefix to isolate tenants e.g. "customer1/"',
type: "string"
},
{
name: "coredns_path",
required: false,
label: "Coredns Path",
tooltip: 'Shared bucket DNS records, default is "/skydns"',
type: "string"
},
{
name: "client_cert",
required: false,
label: "Client Cert",
tooltip: "Client cert for mTLS authentication",
type: "string"
},
{
name: "client_cert_key",
required: false,
label: "Client Cert Key",
tooltip: "Client cert key for mTLS authentication",
type: "string"
},
{
name: "comment",
required: false,
label: "Comment",
tooltip: "You can add a comment to this setting",
type: "string",
multiline: true
}
],
identity_openid: [
{
name: "client_id",
required: false,
label: "Client ID",
type: "string"
},
{
name: "config_url",
required: false,
label: "Config URL",
tooltip: "Config URL for Client ID configuration",
type: "string"
}
],
identity_ldap: [
{
name: "server_addr",
required: true,
label: "Server ADDR",
tooltip: 'AD/LDAP server address e.g. "myldapserver.com:636"',
type: "string"
},
{
name: "username_format",
required: true,
label: "Username Format",
tooltip:
'List of username bind DNs e.g. "uid=%s","cn=accounts","dc=myldapserver" or "dc=com", you can write one per field',
type: "csv"
},
{
name: "username_search_filter",
required: true,
label: "Username Search Filter",
tooltip:
'User search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"',
type: "string"
},
{
name: "group_search_filter",
required: true,
label: "Group Search Filter",
tooltip:
'Search filter for groups e.g. "(&(objectclass=groupOfNames)(memberUid=%s))"',
type: "string"
},
{
name: "username_search_base_dn",
required: false,
label: "Username Search Base DN",
tooltip: "List of username search DNs, you can write one per field",
type: "csv"
},
{
name: "group_name_attribute",
required: false,
label: "Group Name Attribute",
tooltip: 'Search attribute for group name e.g. "cn"',
type: "string"
},
{
name: "sts_expiry",
required: false,
label: "STS Expiry",
tooltip:
'temporary credentials validity duration in s,m,h,d. Default is "1h"',
type: "string"
},
{
name: "tls_skip_verify",
required: false,
label: "TLS Skip Verify",
tooltip:
'Trust server TLS without verification, defaults to "off" (verify)',
type: "on|off"
},
{
name: "server_insecure",
required: false,
label: "Server Insecure",
tooltip:
'Allow plain text connection to AD/LDAP server, defaults to "off"',
type: "on|off"
},
{
name: "comment",
required: false,
label: "Comment",
tooltip: "Optionally add a comment to this setting",
type: "string",
multiline: true
}
],
policy_opa: [
{
name: "opa_url",
required: true,
label: "OPA URL",
type: "string"
}
],
kms_vault: [],
kms_kes: [],
logger_webhook: [
{
name: "name",
required: true,
label: "Name",
tooltip: "Name of the webhook",
type: "string"
},
{
name: "auth_token",
required: true,
label: "Auth Token",
type: "string"
},
{
name: "endpoint",
required: true,
label: "Endpoint",
type: "string"
}
],
audit_webhook: [
{
name: "name",
required: true,
label: "Name",
tooltip: "Name of the webhook",
type: "string"
},
{
name: "auth_token",
required: true,
label: "Auth Token",
type: "string"
},
{
name: "endpoint",
required: true,
label: "Endpoint",
type: "string"
}
]
};
const commonFields = [
{
name: "queue-dir",
label: "Queue Dir",
required: true,
tooltip: "staging dir for undelivered messages e.g. '/home/events'",
type: "string"
},
{
name: "queue-limit",
label: "Queue Limit",
required: false,
tooltip: "maximum limit for undelivered messages, defaults to '10000'",
type: "number"
},
{
name: "comment",
label: "Comment",
required: false,
type: "string",
multiline: true
}
];
export const notificationEndpointsFields: any = {
[notifyKafka]: [
{
name: "brokers",
label: "Brokers",
required: true,
tooltip: "comma separated list of Kafka broker addresses",
type: "string"
},
{
name: "topic",
label: "Topic",
tooltip: "Kafka topic used for bucket notifications",
type: "string"
},
{
name: "sasl_username",
label: "SASL Username",
tooltip: "username for SASL/PLAIN or SASL/SCRAM authentication",
type: "string"
},
{
name: "sasl_password",
label: "SASL Password",
tooltip: "password for SASL/PLAIN or SASL/SCRAM authentication",
type: "string"
},
{
name: "sasl_mechanism",
label: "SASL Mechanism",
tooltip: "sasl authentication mechanism, default 'PLAIN'",
type: "string"
},
{
name: "tls_client_auth",
label: "TLS Client Auth",
tooltip:
"clientAuth determines the Kafka server's policy for TLS client auth",
type: "string"
},
{
name: "sasl",
label: "SASL",
tooltip: "set to 'on' to enable SASL authentication",
type: "on|off"
},
{
name: "tls",
label: "TLS",
tooltip: "set to 'on' to enable TLS",
type: "on|off"
},
{
name: "tls_skip_verify",
label: "TLS skip verify",
tooltip:
'trust server TLS without verification, defaults to "on" (verify)',
type: "on|off"
},
{
name: "client_tls_cert",
label: "client TLS cert",
tooltip: "path to client certificate for mTLS auth",
type: "path"
},
{
name: "client_tls_key",
label: "client TLS key",
tooltip: "path to client key for mTLS auth",
type: "path"
},
{
name: "version",
label: "Version",
tooltip: "specify the version of the Kafka cluster e.g '2.2.0'",
type: "string"
},
...commonFields
],
[notifyAmqp]: [
{
name: "url",
required: true,
label: "url",
tooltip:
"AMQP server endpoint e.g. `amqp://myuser:mypassword@localhost:5672`",
type: "url"
},
{
name: "exchange",
label: "exchange",
tooltip: "name of the AMQP exchange",
type: "string"
},
{
name: "exchange_type",
label: "exchange_type",
tooltip: "AMQP exchange type",
type: "string"
},
{
name: "routing_key",
label: "routing_key",
tooltip: "routing key for publishing",
type: "string"
},
{
name: "mandatory",
label: "mandatory",
tooltip:
"quietly ignore undelivered messages when set to 'off', default is 'on'",
type: "on|off"
},
{
name: "durable",
label: "durable",
tooltip:
"persist queue across broker restarts when set to 'on', default is 'off'",
type: "on|off"
},
{
name: "no_wait",
label: "no_wait",
tooltip:
"non-blocking message delivery when set to 'on', default is 'off'",
type: "on|off"
},
{
name: "internal",
label: "internal",
tooltip:
"set to 'on' for exchange to be not used directly by publishers, but only when bound to other exchanges",
type: "on|off"
},
{
name: "auto_deleted",
label: "auto_deleted",
tooltip:
"auto delete queue when set to 'on', when there are no consumers",
type: "on|off"
},
{
name: "delivery_mode",
label: "delivery_mode",
tooltip: "set to '1' for non-persistent or '2' for persistent queue",
type: "number"
},
...commonFields
],
[notifyRedis]: [
{
name: "address",
required: true,
label: "address",
tooltip: "Redis server's address. For example: `localhost:6379`",
type: "address"
},
{
name: "key",
required: true,
label: "key",
tooltip: "Redis key to store/update events, key is auto-created",
type: "string"
},
{
name: "password",
label: "password",
tooltip: "Redis server password",
type: "string"
},
...commonFields
],
[notifyMqtt]: [
{
name: "broker",
required: true,
label: "broker",
tooltip: "MQTT server endpoint e.g. `tcp://localhost:1883`",
type: "uri"
},
{
name: "topic",
required: true,
label: "topic",
tooltip: "name of the MQTT topic to publish",
type: "string"
},
{
name: "username",
label: "username",
tooltip: "MQTT username",
type: "string"
},
{
name: "password",
label: "password",
tooltip: "MQTT password",
type: "string"
},
{
name: "qos",
label: "qos",
tooltip: "set the quality of service priority, defaults to '0'",
type: "number"
},
{
name: "keep_alive_interval",
label: "keep_alive_interval",
tooltip: "keep-alive interval for MQTT connections in s,m,h,d",
type: "duration"
},
{
name: "reconnect_interval",
label: "reconnect_interval",
tooltip: "reconnect interval for MQTT connections in s,m,h,d",
type: "duration"
},
...commonFields
],
[notifyNats]: [
{
name: "address",
required: true,
label: "address",
tooltip: "NATS server address e.g. '0.0.0.0:4222'",
type: "address"
},
{
name: "subject",
required: true,
label: "subject",
tooltip: "NATS subscription subject",
type: "string"
},
{
name: "username",
label: "username",
tooltip: "NATS username",
type: "string"
},
{
name: "password",
label: "password",
tooltip: "NATS password",
type: "string"
},
{
name: "token",
label: "token",
tooltip: "NATS token",
type: "string"
},
{
name: "tls",
label: "tls",
tooltip: "set to 'on' to enable TLS",
type: "on|off"
},
{
name: "tls_skip_verify",
label: "tls_skip_verify",
tooltip:
'trust server TLS without verification, defaults to "on" (verify)',
type: "on|off"
},
{
name: "ping_interval",
label: "ping_interval",
tooltip: "client ping commands interval in s,m,h,d. Disabled by default",
type: "duration"
},
{
name: "streaming",
label: "streaming",
tooltip: "set to 'on', to use streaming NATS server",
type: "on|off"
},
{
name: "streaming_async",
label: "streaming_async",
tooltip: "set to 'on', to enable asynchronous publish",
type: "on|off"
},
{
name: "streaming_max_pub_acks_in_flight",
label: "streaming_max_pub_acks_in_flight",
tooltip: "number of messages to publish without waiting for ACKs",
type: "number"
},
{
name: "streaming_cluster_id",
label: "streaming_cluster_id",
tooltip: "unique ID for NATS streaming cluster",
type: "string"
},
{
name: "cert_authority",
label: "cert_authority",
tooltip: "path to certificate chain of the target NATS server",
type: "string"
},
{
name: "client_cert",
label: "client_cert",
tooltip: "client cert for NATS mTLS auth",
type: "string"
},
{
name: "client_key",
label: "client_key",
tooltip: "client cert key for NATS mTLS auth",
type: "string"
},
...commonFields
],
[notifyElasticsearch]: [
{
name: "url",
required: true,
label: "url",
tooltip:
"Elasticsearch server's address, with optional authentication info",
type: "url"
},
{
name: "index",
required: true,
label: "index",
tooltip:
"Elasticsearch index to store/update events, index is auto-created",
type: "string"
},
{
name: "format",
required: true,
label: "format",
tooltip:
"'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'",
type: "enum"
},
...commonFields
],
[notifyWebhooks]: [
{
name: "endpoint",
required: true,
label: "endpoint",
tooltip:
"webhook server endpoint e.g. http://localhost:8080/minio/events",
type: "url"
},
{
name: "auth_token",
label: "auth_token",
tooltip: "opaque string or JWT authorization token",
type: "string"
},
...commonFields
],
[notifyNsq]: [
{
name: "nsqd_address",
required: true,
label: "nsqd_address",
tooltip: "NSQ server address e.g. '127.0.0.1:4150'",
type: "address"
},
{
name: "topic",
required: true,
label: "topic",
tooltip: "NSQ topic",
type: "string"
},
{
name: "tls",
label: "tls",
tooltip: "set to 'on' to enable TLS",
type: "on|off"
},
{
name: "tls_skip_verify",
label: "tls_skip_verify",
tooltip:
'trust server TLS without verification, defaults to "on" (verify)',
type: "on|off"
},
...commonFields
]
};
export const removeEmptyFields = (formFields: IElementValue[]) => {
const nonEmptyFields = formFields.filter(field => field.value !== "");
return nonEmptyFields;
};

View File

@@ -58,7 +58,9 @@ import ServiceAccounts from "./ServiceAccounts/ServiceAccounts";
import Users from "./Users/Users";
import Groups from "./Groups/Groups";
import ListNotificationEndpoints from "./NotificationEndopoints/ListNotificationEndpoints";
import ConfigurationsList from "./Configurations/ConfigurationPanels/ConfigurationsList";
import { Button, LinearProgress } from "@material-ui/core";
import WebhookPanel from "./Configurations/ConfigurationPanels/WebhookPanel";
function Copyright() {
return (
@@ -289,10 +291,17 @@ class Console extends React.Component<
<Route exact path="/dashboard" component={Dashboard} />
<Route exct path="/groups" component={Groups} />
<Route
exct
exact
path="/notification-endpoints"
component={ListNotificationEndpoints}
/>
<Route
exact
path="/configurations-list"
component={ConfigurationsList}
/>
<Route exact path="/webhook/logger" component={WebhookPanel} />
<Route exact path="/webhook/audit" component={WebhookPanel} />
<Route exact path="/">
<Redirect to="/dashboard" />
</Route>

View File

@@ -38,6 +38,7 @@ import { createStyles, Theme } from "@material-ui/core/styles";
import PersonIcon from "@material-ui/icons/Person";
import api from "../../common/api";
import NotificationsIcon from "@material-ui/icons/Notifications";
import ListAltIcon from "@material-ui/icons/ListAlt";
const styles = (theme: Theme) =>
createStyles({
@@ -156,6 +157,12 @@ class Menu extends React.Component<MenuProps> {
</ListItemIcon>
<ListItemText primary="Lambda Notifications" />
</ListItem>
<ListItem button component={NavLink} to="/configurations-list">
<ListItemIcon>
<ListAltIcon />
</ListItemIcon>
<ListItemText primary="Configurations List" />
</ListItem>
<Divider />
<ListItem
button

View File

@@ -15,18 +15,33 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useCallback, useEffect, useState } from "react";
import get from "lodash/get";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button, LinearProgress } from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import ConfPostgres from "../Configurations/ConfPostgres";
import ConfPostgres from "../Configurations/CustomForms/ConfPostgres";
import api from "../../../common/api";
import { serverNeedsRestart } from "../../../actions";
import { connect } from "react-redux";
import ConfMySql from "../Configurations/ConfMySql";
import ConfMySql from "../Configurations/CustomForms/ConfMySql";
import ConfTargetGeneric from "../Configurations/ConfTargetGeneric";
import { KVField } from "../Configurations/types";
import {
notificationEndpointsFields,
notifyPostgres,
notifyMysql,
notifyKafka,
notifyAmqp,
notifyMqtt,
notifyRedis,
notifyNats,
notifyElasticsearch,
notifyWebhooks,
notifyNsq,
removeEmptyFields
} from "../Configurations/utils";
import { IElementValue } from "../Configurations/types";
const styles = (theme: Theme) =>
createStyles({
@@ -47,17 +62,6 @@ const styles = (theme: Theme) =>
}
});
const notifyPostgres = "notify_postgres";
const notifyMysql = "notify_mysql";
const notifyKafka = "notify_kafka";
const notifyAmqp = "notify_amqp";
const notifyMqtt = "notify_mqtt";
const notifyRedis = "notify_redis";
const notifyNats = "notify_nats";
const notifyElasticsearch = "notify_elasticsearch";
const notifyWebhooks = "notify_webhooks";
const notifyNsq = "notify_nsq";
interface IAddNotificationEndpointProps {
open: boolean;
closeModalAndRefresh: any;
@@ -73,7 +77,7 @@ const AddNotificationEndpoint = ({
}: IAddNotificationEndpointProps) => {
//Local States
const [service, setService] = useState<string>("");
const [valuesObj, setValueObj] = useState<Map<string, string>>(new Map());
const [valuesArr, setValueArr] = useState<IElementValue[]>([]);
const [saving, setSaving] = useState<boolean>(false);
const [addError, setError] = useState<string>("");
@@ -81,13 +85,8 @@ const AddNotificationEndpoint = ({
useEffect(() => {
if (saving) {
let keyValues: Array<object> = new Array<object>();
valuesObj.forEach((value: string, key: string) => {
keyValues.push({ key: key, value: value });
});
let payload = {
key_values: keyValues
const payload = {
key_values: removeEmptyFields(valuesArr)
};
api
.invoke("PUT", `/api/v1/configs/${service}`, payload)
@@ -103,7 +102,7 @@ const AddNotificationEndpoint = ({
setError(err);
});
}
}, [saving, serverNeedsRestart, service, valuesObj, closeModalAndRefresh]);
}, [saving, serverNeedsRestart, service, valuesArr, closeModalAndRefresh]);
//Fetch Actions
const submitForm = (event: React.FormEvent) => {
@@ -113,9 +112,9 @@ const AddNotificationEndpoint = ({
const onValueChange = useCallback(
newValue => {
setValueObj(newValue);
setValueArr(newValue);
},
[setValueObj]
[setValueArr]
);
let srvComponent = <React.Fragment />;
@@ -128,444 +127,12 @@ const AddNotificationEndpoint = ({
srvComponent = <ConfMySql onChange={onValueChange} />;
break;
}
case notifyKafka: {
const fields: KVField[] = [
{
name: "brokers",
label: "Brokers",
required: true,
tooltip: "comma separated list of Kafka broker addresses",
type: "string"
},
{
name: "topic",
label: "Topic",
tooltip: "Kafka topic used for bucket notifications",
type: "string"
},
{
name: "sasl_username",
label: "SASL Username",
tooltip: "username for SASL/PLAIN or SASL/SCRAM authentication",
type: "string"
},
{
name: "sasl_password",
label: "SASL Password",
tooltip: "password for SASL/PLAIN or SASL/SCRAM authentication",
type: "string"
},
{
name: "sasl_mechanism",
label: "SASL Mechanism",
tooltip: "sasl authentication mechanism, default 'PLAIN'",
type: "string"
},
{
name: "tls_client_auth",
label: "TLS Client Auth",
tooltip:
"clientAuth determines the Kafka server's policy for TLS client auth",
type: "string"
},
{
name: "sasl",
label: "SASL",
tooltip: "set to 'on' to enable SASL authentication",
type: "on|off"
},
{
name: "tls",
label: "TLS",
tooltip: "set to 'on' to enable TLS",
type: "on|off"
},
{
name: "tls_skip_verify",
label: "TLS skip verify",
tooltip:
'trust server TLS without verification, defaults to "on" (verify)',
type: "on|off"
},
{
name: "client_tls_cert",
label: "client TLS cert",
tooltip: "path to client certificate for mTLS auth",
type: "path"
},
{
name: "client_tls_key",
label: "client TLS key",
tooltip: "path to client key for mTLS auth",
type: "path"
},
{
name: "version",
label: "Version",
tooltip: "specify the version of the Kafka cluster e.g '2.2.0'",
type: "string"
}
];
default: {
const fields = get(notificationEndpointsFields, service, []);
srvComponent = (
<ConfTargetGeneric fields={fields} onChange={onValueChange} />
);
break;
}
case notifyAmqp: {
const fields: KVField[] = [
{
name: "url",
required: true,
label: "url",
tooltip:
"AMQP server endpoint e.g. `amqp://myuser:mypassword@localhost:5672`",
type: "url"
},
{
name: "exchange",
label: "exchange",
tooltip: "name of the AMQP exchange",
type: "string"
},
{
name: "exchange_type",
label: "exchange_type",
tooltip: "AMQP exchange type",
type: "string"
},
{
name: "routing_key",
label: "routing_key",
tooltip: "routing key for publishing",
type: "string"
},
{
name: "mandatory",
label: "mandatory",
tooltip:
"quietly ignore undelivered messages when set to 'off', default is 'on'",
type: "on|off"
},
{
name: "durable",
label: "durable",
tooltip:
"persist queue across broker restarts when set to 'on', default is 'off'",
type: "on|off"
},
{
name: "no_wait",
label: "no_wait",
tooltip:
"non-blocking message delivery when set to 'on', default is 'off'",
type: "on|off"
},
{
name: "internal",
label: "internal",
tooltip:
"set to 'on' for exchange to be not used directly by publishers, but only when bound to other exchanges",
type: "on|off"
},
{
name: "auto_deleted",
label: "auto_deleted",
tooltip:
"auto delete queue when set to 'on', when there are no consumers",
type: "on|off"
},
{
name: "delivery_mode",
label: "delivery_mode",
tooltip: "set to '1' for non-persistent or '2' for persistent queue",
type: "number"
}
];
srvComponent = (
<ConfTargetGeneric fields={fields} onChange={onValueChange} />
);
break;
}
case notifyRedis: {
const fields: KVField[] = [
{
name: "address",
required: true,
label: "address",
tooltip: "Redis server's address. For example: `localhost:6379`",
type: "address"
},
{
name: "key",
required: true,
label: "key",
tooltip: "Redis key to store/update events, key is auto-created",
type: "string"
},
{
name: "password",
label: "password",
tooltip: "Redis server password",
type: "string"
}
];
srvComponent = (
<ConfTargetGeneric fields={fields} onChange={onValueChange} />
);
break;
}
case notifyMqtt: {
const fields: KVField[] = [
{
name: "broker",
required: true,
label: "broker",
tooltip: "MQTT server endpoint e.g. `tcp://localhost:1883`",
type: "uri"
},
{
name: "topic",
required: true,
label: "topic",
tooltip: "name of the MQTT topic to publish",
type: "string"
},
{
name: "username",
label: "username",
tooltip: "MQTT username",
type: "string"
},
{
name: "password",
label: "password",
tooltip: "MQTT password",
type: "string"
},
{
name: "qos",
label: "qos",
tooltip: "set the quality of service priority, defaults to '0'",
type: "number"
},
{
name: "keep_alive_interval",
label: "keep_alive_interval",
tooltip: "keep-alive interval for MQTT connections in s,m,h,d",
type: "duration"
},
{
name: "reconnect_interval",
label: "reconnect_interval",
tooltip: "reconnect interval for MQTT connections in s,m,h,d",
type: "duration"
}
];
srvComponent = (
<ConfTargetGeneric fields={fields} onChange={onValueChange} />
);
break;
}
case notifyNats: {
const fields: KVField[] = [
{
name: "address",
required: true,
label: "address",
tooltip: "NATS server address e.g. '0.0.0.0:4222'",
type: "address"
},
{
name: "subject",
required: true,
label: "subject",
tooltip: "NATS subscription subject",
type: "string"
},
{
name: "username",
label: "username",
tooltip: "NATS username",
type: "string"
},
{
name: "password",
label: "password",
tooltip: "NATS password",
type: "string"
},
{
name: "token",
label: "token",
tooltip: "NATS token",
type: "string"
},
{
name: "tls",
label: "tls",
tooltip: "set to 'on' to enable TLS",
type: "on|off"
},
{
name: "tls_skip_verify",
label: "tls_skip_verify",
tooltip:
'trust server TLS without verification, defaults to "on" (verify)',
type: "on|off"
},
{
name: "ping_interval",
label: "ping_interval",
tooltip:
"client ping commands interval in s,m,h,d. Disabled by default",
type: "duration"
},
{
name: "streaming",
label: "streaming",
tooltip: "set to 'on', to use streaming NATS server",
type: "on|off"
},
{
name: "streaming_async",
label: "streaming_async",
tooltip: "set to 'on', to enable asynchronous publish",
type: "on|off"
},
{
name: "streaming_max_pub_acks_in_flight",
label: "streaming_max_pub_acks_in_flight",
tooltip: "number of messages to publish without waiting for ACKs",
type: "number"
},
{
name: "streaming_cluster_id",
label: "streaming_cluster_id",
tooltip: "unique ID for NATS streaming cluster",
type: "string"
},
{
name: "cert_authority",
label: "cert_authority",
tooltip: "path to certificate chain of the target NATS server",
type: "string"
},
{
name: "client_cert",
label: "client_cert",
tooltip: "client cert for NATS mTLS auth",
type: "string"
},
{
name: "client_key",
label: "client_key",
tooltip: "client cert key for NATS mTLS auth",
type: "string"
}
];
srvComponent = (
<ConfTargetGeneric fields={fields} onChange={onValueChange} />
);
break;
}
case notifyElasticsearch: {
const fields: KVField[] = [
{
name: "url",
required: true,
label: "url",
tooltip:
"Elasticsearch server's address, with optional authentication info",
type: "url"
},
{
name: "index",
required: true,
label: "index",
tooltip:
"Elasticsearch index to store/update events, index is auto-created",
type: "string"
},
{
name: "format",
required: true,
label: "format",
tooltip:
"'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'",
type: "enum"
}
];
srvComponent = (
<ConfTargetGeneric fields={fields} onChange={onValueChange} />
);
break;
}
case notifyWebhooks: {
const fields: KVField[] = [
{
name: "endpoint",
required: true,
label: "endpoint",
tooltip:
"webhook server endpoint e.g. http://localhost:8080/minio/events",
type: "url"
},
{
name: "auth_token",
label: "auth_token",
tooltip: "opaque string or JWT authorization token",
type: "string"
}
];
srvComponent = (
<ConfTargetGeneric fields={fields} onChange={onValueChange} />
);
break;
}
case notifyNsq: {
const fields: KVField[] = [
{
name: "nsqd_address",
required: true,
label: "nsqd_address",
tooltip: "NSQ server address e.g. '127.0.0.1:4150'",
type: "address"
},
{
name: "topic",
required: true,
label: "topic",
tooltip: "NSQ topic",
type: "string"
},
{
name: "tls",
label: "tls",
tooltip: "set to 'on' to enable TLS",
type: "on|off"
},
{
name: "tls_skip_verify",
label: "tls_skip_verify",
tooltip:
'trust server TLS without verification, defaults to "on" (verify)',
type: "on|off"
}
];
srvComponent = (
<ConfTargetGeneric fields={fields} onChange={onValueChange} />
);
break;
}
}

View File

@@ -65,15 +65,15 @@ const styles = (theme: Theme) =>
borderTopLeftRadius: "3px",
borderBottomLeftRadius: "3px",
background:
"transparent linear-gradient(333deg, #281B6F 1%, #271260 13%, #120D53 83%) 0% 0% no-repeat padding-box;",
"transparent linear-gradient(333deg, #281B6F 1%, #271260 13%, #120D53 83%) 0% 0% no-repeat padding-box;"
},
oceanBg: {
backgroundImage: "url(/images/BG_Illustration.svg)",
backgroundRepeat: "no-repeat",
backgroundPosition: "bottom left",
height: "100%",
width: "100%"
},
oceanBg:{
backgroundImage:'url(/images/BG_Illustration.svg)',
backgroundRepeat:'no-repeat',
backgroundPosition:'bottom left',
height:'100%',
width:'100%',
},
theLogin: {
padding: "76px 62px 20px 62px"
}
@@ -140,7 +140,7 @@ class Login extends React.Component<LoginProps> {
<Paper className={classes.paper}>
<Grid container className={classes.mainContainer}>
<Grid item xs={7} className={classes.theOcean}>
<div className={classes.oceanBg}></div>
<div className={classes.oceanBg}></div>
</Grid>
<Grid item xs={5} className={classes.theLogin}>
<Typography component="h1" variant="h6">