UI Add Notification Targets (#73)
BIN
portal-ui/public/amqp.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
portal-ui/public/elasticsearch.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
portal-ui/public/kafka.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
portal-ui/public/mqtt.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
portal-ui/public/mysql.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
portal-ui/public/nats.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
portal-ui/public/postgres.png
Normal file
|
After Width: | Height: | Size: 301 KiB |
BIN
portal-ui/public/redis.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
@@ -14,7 +14,12 @@
|
||||
// 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 { MENU_OPEN, USER_LOGGED } from "./types";
|
||||
import {
|
||||
MENU_OPEN,
|
||||
SERVER_IS_LOADING,
|
||||
SERVER_NEEDS_RESTART,
|
||||
USER_LOGGED
|
||||
} from "./types";
|
||||
|
||||
export function userLoggedIn(loggedIn: boolean) {
|
||||
return {
|
||||
@@ -29,3 +34,17 @@ export function setMenuOpen(open: boolean) {
|
||||
open: open
|
||||
};
|
||||
}
|
||||
|
||||
export function serverNeedsRestart(needsRestart: boolean) {
|
||||
return {
|
||||
type: SERVER_NEEDS_RESTART,
|
||||
needsRestart: needsRestart
|
||||
};
|
||||
}
|
||||
|
||||
export function serverIsLoading(isLoading: boolean) {
|
||||
return {
|
||||
type: SERVER_IS_LOADING,
|
||||
isLoading: isLoading
|
||||
};
|
||||
}
|
||||
|
||||
BIN
portal-ui/src/icons/postgres.png
Normal file
|
After Width: | Height: | Size: 301 KiB |
@@ -14,13 +14,22 @@
|
||||
// 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 {MENU_OPEN, SystemActionTypes, SystemState, USER_LOGGED} from "./types";
|
||||
import {
|
||||
MENU_OPEN,
|
||||
SERVER_IS_LOADING,
|
||||
SERVER_NEEDS_RESTART,
|
||||
SystemActionTypes,
|
||||
SystemState,
|
||||
USER_LOGGED
|
||||
} from "./types";
|
||||
|
||||
const initialState: SystemState = {
|
||||
loggedIn: false,
|
||||
session: "",
|
||||
userName: "",
|
||||
sidebarOpen:true,
|
||||
sidebarOpen: true,
|
||||
serverNeedsRestart: false,
|
||||
serverIsLoading: false
|
||||
};
|
||||
|
||||
export function systemReducer(
|
||||
@@ -38,6 +47,17 @@ export function systemReducer(
|
||||
...state,
|
||||
sidebarOpen: action.open
|
||||
};
|
||||
case SERVER_NEEDS_RESTART:
|
||||
return {
|
||||
...state,
|
||||
serverNeedsRestart: action.needsRestart
|
||||
};
|
||||
|
||||
case SERVER_IS_LOADING:
|
||||
return {
|
||||
...state,
|
||||
serverIsLoading: action.isLoading
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,13 @@
|
||||
// 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 from "react";
|
||||
import { TextField, Grid, InputLabel, TextFieldProps } from "@material-ui/core";
|
||||
import {
|
||||
Grid,
|
||||
InputLabel,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
Tooltip
|
||||
} from "@material-ui/core";
|
||||
import { OutlinedInputProps } from "@material-ui/core/OutlinedInput";
|
||||
import {
|
||||
createStyles,
|
||||
@@ -23,16 +29,19 @@ import {
|
||||
withStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import { fieldBasic } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
interface InputBoxProps {
|
||||
label: string;
|
||||
classes: any;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
value: string;
|
||||
value: string | boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
multiline?: boolean;
|
||||
type?: string;
|
||||
tooltip?: string;
|
||||
autoComplete?: string;
|
||||
}
|
||||
|
||||
@@ -78,6 +87,8 @@ const InputBoxWrapper = ({
|
||||
type = "text",
|
||||
autoComplete = "off",
|
||||
disabled = false,
|
||||
multiline = false,
|
||||
tooltip = "",
|
||||
classes
|
||||
}: InputBoxProps) => {
|
||||
return (
|
||||
@@ -97,9 +108,17 @@ const InputBoxWrapper = ({
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
type={type}
|
||||
multiline={multiline}
|
||||
autoComplete={autoComplete}
|
||||
/>
|
||||
</div>
|
||||
{tooltip !== "" && (
|
||||
<div>
|
||||
<Tooltip title={tooltip} placement="left">
|
||||
<HelpIcon />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ import Grid from "@material-ui/core/Grid";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio, { RadioProps } from "@material-ui/core/Radio";
|
||||
import { InputLabel } from "@material-ui/core";
|
||||
import { InputLabel, Tooltip } from "@material-ui/core";
|
||||
import {
|
||||
createStyles,
|
||||
Theme,
|
||||
@@ -26,18 +26,20 @@ import {
|
||||
makeStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import { fieldBasic } from "../common/styleLibrary";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
|
||||
interface selectorTypes {
|
||||
export interface SelectorTypes {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface RadioGroupProps {
|
||||
selectorOptions: selectorTypes[];
|
||||
selectorOptions: SelectorTypes[];
|
||||
currentSelection: string;
|
||||
label: string;
|
||||
id: string;
|
||||
name: string;
|
||||
tooltip?: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
classes: any;
|
||||
displayInColumn?: boolean;
|
||||
@@ -106,6 +108,7 @@ export const RadioGroupSelector = ({
|
||||
id,
|
||||
name,
|
||||
onChange,
|
||||
tooltip = "",
|
||||
classes,
|
||||
displayInColumn = false
|
||||
}: RadioGroupProps) => {
|
||||
@@ -136,6 +139,13 @@ export const RadioGroupSelector = ({
|
||||
})}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{tooltip !== "" && (
|
||||
<div>
|
||||
<Tooltip title={tooltip} placement="left">
|
||||
<HelpIcon />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
307
portal-ui/src/screens/Console/Configurations/ConfMySql.tsx
Normal file
@@ -0,0 +1,307 @@
|
||||
// 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, { 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";
|
||||
|
||||
interface IConfMySqlProps {
|
||||
onChange: (newValue: Map<string, string>) => void;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
|
||||
//Local States
|
||||
const [useDsnString, setUseDsnString] = useState<boolean>(false);
|
||||
const [dsnString, setDsnString] = useState<string>("");
|
||||
const [host, setHostname] = useState<string>("");
|
||||
const [dbName, setDbName] = useState<string>("");
|
||||
const [port, setPort] = useState<string>("");
|
||||
const [user, setUser] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
|
||||
const [table, setTable] = useState<string>("");
|
||||
const [format, setFormat] = useState<string>("namespace");
|
||||
const [queueDir, setQueueDir] = useState<string>("");
|
||||
const [queueLimit, setQueueLimit] = useState<string>("");
|
||||
const [comment, setComment] = useState<string>("");
|
||||
|
||||
// dsn_string* (string) MySQL data-source-name connection string e.g. "<user>:<password>@tcp(<host>:<port>)/<database>"
|
||||
// table* (string) DB table name to store/update events, table is auto-created
|
||||
// format* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
|
||||
// queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
|
||||
// queue_limit (number) maximum limit for undelivered messages, defaults to '100000'
|
||||
// comment (sentence) optionally add a comment to this setting
|
||||
|
||||
const parseDsnString = (
|
||||
input: string,
|
||||
keys: string[]
|
||||
): Map<string, string> => {
|
||||
let kvFields: Map<string, string> = new Map();
|
||||
const regex = /(.*?):(.*?)@tcp\((.*?):(.*?)\)\/(.*?)$/gm;
|
||||
let m;
|
||||
|
||||
while ((m = regex.exec(input)) !== null) {
|
||||
// This is necessary to avoid infinite loops with zero-width matches
|
||||
if (m.index === regex.lastIndex) {
|
||||
regex.lastIndex++;
|
||||
}
|
||||
|
||||
kvFields.set("user", m[1]);
|
||||
kvFields.set("password", m[2]);
|
||||
kvFields.set("host", m[3]);
|
||||
kvFields.set("port", m[4]);
|
||||
kvFields.set("dbname", m[5]);
|
||||
}
|
||||
|
||||
return kvFields;
|
||||
};
|
||||
|
||||
const configToDsnString = useCallback((): string => {
|
||||
return `${user}:${password}@tcp(${host}:${port})/${dbName}`;
|
||||
}, [user, password, host, port, dbName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dsnString !== "") {
|
||||
let values: Map<string, string> = new Map();
|
||||
|
||||
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);
|
||||
}
|
||||
}, [dsnString, table, format, queueDir, queueLimit, comment, onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
const cs = configToDsnString();
|
||||
setDsnString(cs);
|
||||
}, [user, dbName, password, port, host, setDsnString, configToDsnString]);
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={useDsnString}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
// build dsn_string
|
||||
const cs = configToDsnString();
|
||||
setDsnString(cs);
|
||||
} else {
|
||||
// parse dsn_string
|
||||
const kv = parseDsnString(dsnString, [
|
||||
"host",
|
||||
"port",
|
||||
"dbname",
|
||||
"user",
|
||||
"password"
|
||||
]);
|
||||
setHostname(kv.get("host") ? kv.get("host") + "" : "");
|
||||
setPort(kv.get("port") ? kv.get("port") + "" : "");
|
||||
setDbName(kv.get("dbname") ? kv.get("dbname") + "" : "");
|
||||
setUser(kv.get("user") ? kv.get("user") + "" : "");
|
||||
setPassword(
|
||||
kv.get("password") ? kv.get("password") + "" : ""
|
||||
);
|
||||
}
|
||||
|
||||
setUseDsnString(event.target.checked);
|
||||
}}
|
||||
name="checkedB"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Enter DSN String"
|
||||
/>
|
||||
</Grid>
|
||||
{useDsnString ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="dsn-string"
|
||||
name="dsn_string"
|
||||
label="DSN String"
|
||||
value={dsnString}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDsnString(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="host"
|
||||
name="host"
|
||||
label="Host"
|
||||
value={host}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setHostname(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="db-name"
|
||||
name="db-name"
|
||||
label="DB Name"
|
||||
value={dbName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDbName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="port"
|
||||
name="port"
|
||||
label="Port"
|
||||
value={port}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPort(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="user"
|
||||
name="user"
|
||||
label="User"
|
||||
value={user}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUser(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="password"
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="table"
|
||||
name="table"
|
||||
label="Table"
|
||||
value={table}
|
||||
tooltip="DB table name to store/update events, table is auto-created"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTable(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroupSelector
|
||||
currentSelection={format}
|
||||
id="format"
|
||||
name="format"
|
||||
label="Format"
|
||||
onChange={e => {
|
||||
setFormat(e.target.value);
|
||||
}}
|
||||
tooltip="'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'"
|
||||
selectorOptions={[
|
||||
{ label: "Namespace", value: "namespace" },
|
||||
{ label: "Access", value: "access" }
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="queue-dir"
|
||||
name="queue_dir"
|
||||
label="Queue Dir"
|
||||
value={queueDir}
|
||||
tooltip="staging dir for undelivered messages e.g. '/home/events'"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQueueDir(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="queue-limit"
|
||||
name="queue_limit"
|
||||
label="Queue Limit"
|
||||
type="number"
|
||||
value={queueLimit}
|
||||
tooltip="maximum limit for undelivered messages, defaults to '10000'"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQueueLimit(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="comment"
|
||||
name="comment"
|
||||
label="Comment"
|
||||
multiline={true}
|
||||
value={comment}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setComment(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ConfMySql);
|
||||
388
portal-ui/src/screens/Console/Configurations/ConfPostgres.tsx
Normal file
@@ -0,0 +1,388 @@
|
||||
// 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, { 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";
|
||||
|
||||
interface IConfPostgresProps {
|
||||
onChange: (newValue: Map<string, string>) => void;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
|
||||
//Local States
|
||||
const [useConnectionString, setUseConnectionString] = useState<boolean>(
|
||||
false
|
||||
);
|
||||
const [connectionString, setConnectionString] = useState<string>("");
|
||||
const [host, setHostname] = useState<string>("");
|
||||
const [dbName, setDbName] = useState<string>("");
|
||||
const [port, setPort] = useState<string>("");
|
||||
const [user, setUser] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [sslMode, setSslMode] = useState<boolean>(true);
|
||||
|
||||
const [table, setTable] = useState<string>("");
|
||||
const [format, setFormat] = useState<string>("namespace");
|
||||
const [queueDir, setQueueDir] = useState<string>("");
|
||||
const [queueLimit, setQueueLimit] = useState<string>("");
|
||||
const [comment, setComment] = useState<string>("");
|
||||
|
||||
// connection_string* (string) Postgres server connection-string e.g. "host=localhost port=5432 dbname=minio_events user=postgres password=password sslmode=disable"
|
||||
|
||||
// "host=localhost
|
||||
// port=5432
|
||||
//dbname=minio_events
|
||||
//user=postgres
|
||||
//password=password
|
||||
//sslmode=disable"
|
||||
|
||||
// table* (string) DB table name to store/update events, table is auto-created
|
||||
// format* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
|
||||
// queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
|
||||
// queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
|
||||
// comment (sentence) optionally add a comment to this setting
|
||||
|
||||
const KvSeparator = "=";
|
||||
const parseConnectionString = (
|
||||
input: string,
|
||||
keys: string[]
|
||||
): Map<string, string> => {
|
||||
let valueIndexes: number[] = [];
|
||||
|
||||
for (const key of keys) {
|
||||
const i = input.indexOf(key + KvSeparator);
|
||||
if (i === -1) {
|
||||
continue;
|
||||
}
|
||||
valueIndexes.push(i);
|
||||
}
|
||||
valueIndexes.sort((n1, n2) => n1 - n2);
|
||||
|
||||
let kvFields = new Map<string, string>();
|
||||
let fields: string[] = new Array<string>(valueIndexes.length);
|
||||
for (let i = 0; i < valueIndexes.length; i++) {
|
||||
const j = i + 1;
|
||||
if (j < valueIndexes.length) {
|
||||
fields[i] = input.substr(
|
||||
valueIndexes[i],
|
||||
valueIndexes[j] - valueIndexes[i]
|
||||
);
|
||||
} else {
|
||||
fields[i] = input.substr(valueIndexes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (let field of fields) {
|
||||
if (field === undefined) {
|
||||
continue;
|
||||
}
|
||||
const key = field.substr(0, field.indexOf("="));
|
||||
const value = field.substr(field.indexOf("=") + 1).trim();
|
||||
kvFields.set(key, value);
|
||||
}
|
||||
return kvFields;
|
||||
};
|
||||
|
||||
const configToString = useCallback((): string => {
|
||||
let strValue = "";
|
||||
if (host !== "") {
|
||||
strValue = `${strValue} host=${host}`;
|
||||
}
|
||||
if (dbName !== "") {
|
||||
strValue = `${strValue} dbname=${dbName}`;
|
||||
}
|
||||
if (user !== "") {
|
||||
strValue = `${strValue} user=${user}`;
|
||||
}
|
||||
if (password !== "") {
|
||||
strValue = `${strValue} password=${password}`;
|
||||
}
|
||||
if (port !== "") {
|
||||
strValue = `${strValue} port=${port}`;
|
||||
}
|
||||
const sslModeVal = sslMode ? "enable" : "disable";
|
||||
strValue = `${strValue} sslmode=${sslModeVal}`;
|
||||
|
||||
return strValue.trim();
|
||||
}, [host, dbName, user, password, port, sslMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (connectionString !== "") {
|
||||
let values: Map<string, string> = new Map();
|
||||
|
||||
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);
|
||||
}
|
||||
}, [
|
||||
connectionString,
|
||||
table,
|
||||
format,
|
||||
queueDir,
|
||||
queueLimit,
|
||||
comment,
|
||||
onChange
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const cs = configToString();
|
||||
setConnectionString(cs);
|
||||
}, [
|
||||
user,
|
||||
dbName,
|
||||
password,
|
||||
port,
|
||||
sslMode,
|
||||
host,
|
||||
setConnectionString,
|
||||
configToString
|
||||
]);
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={useConnectionString}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
// build connection_string
|
||||
const cs = configToString();
|
||||
setConnectionString(cs);
|
||||
} else {
|
||||
// parse connection_string
|
||||
const kv = parseConnectionString(connectionString, [
|
||||
"host",
|
||||
"port",
|
||||
"dbname",
|
||||
"user",
|
||||
"password",
|
||||
"sslmode"
|
||||
]);
|
||||
setHostname(kv.get("host") ? kv.get("host") + "" : "");
|
||||
setPort(kv.get("port") ? kv.get("port") + "" : "");
|
||||
setDbName(kv.get("dbname") ? kv.get("dbname") + "" : "");
|
||||
setUser(kv.get("user") ? kv.get("user") + "" : "");
|
||||
setPassword(
|
||||
kv.get("password") ? kv.get("password") + "" : ""
|
||||
);
|
||||
setSslMode(kv.get("sslmode") === "true");
|
||||
}
|
||||
|
||||
setUseConnectionString(event.target.checked);
|
||||
}}
|
||||
name="checkedB"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Enter Connection String"
|
||||
/>
|
||||
</Grid>
|
||||
{useConnectionString ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="connection-string"
|
||||
name="connection_string"
|
||||
label="Connection String"
|
||||
value={connectionString}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setConnectionString(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="host"
|
||||
name="host"
|
||||
label="Host"
|
||||
value={host}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setHostname(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="db-name"
|
||||
name="db-name"
|
||||
label="DB Name"
|
||||
value={dbName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDbName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="port"
|
||||
name="port"
|
||||
label="Port"
|
||||
value={port}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPort(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<RadioGroupSelector
|
||||
currentSelection={sslMode + ""}
|
||||
id="sslmode"
|
||||
name="sslmode"
|
||||
label="SSL Mode"
|
||||
onChange={e => {
|
||||
setSslMode(e.target.value === "true");
|
||||
}}
|
||||
selectorOptions={[
|
||||
{ label: "Enabled", value: "true" },
|
||||
{ label: "Disabled", value: "false" }
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="user"
|
||||
name="user"
|
||||
label="User"
|
||||
value={user}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUser(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="password"
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="table"
|
||||
name="table"
|
||||
label="Table"
|
||||
value={table}
|
||||
tooltip="DB table name to store/update events, table is auto-created"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTable(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroupSelector
|
||||
currentSelection={format}
|
||||
id="format"
|
||||
name="format"
|
||||
label="Format"
|
||||
onChange={e => {
|
||||
setFormat(e.target.value);
|
||||
}}
|
||||
tooltip="'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'"
|
||||
selectorOptions={[
|
||||
{ label: "Namespace", value: "namespace" },
|
||||
{ label: "Access", value: "access" }
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="queue-dir"
|
||||
name="queue_dir"
|
||||
label="Queue Dir"
|
||||
value={queueDir}
|
||||
tooltip="staging dir for undelivered messages e.g. '/home/events'"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQueueDir(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="queue-limit"
|
||||
name="queue_limit"
|
||||
label="Queue Limit"
|
||||
type="number"
|
||||
value={queueLimit}
|
||||
tooltip="maximum limit for undelivered messages, defaults to '10000'"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQueueLimit(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="comment"
|
||||
name="comment"
|
||||
label="Comment"
|
||||
multiline={true}
|
||||
value={comment}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setComment(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ConfPostgres);
|
||||
@@ -0,0 +1,141 @@
|
||||
// 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, { useEffect, useState } from "react";
|
||||
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";
|
||||
|
||||
interface IConfGenericProps {
|
||||
onChange: (newValue: Map<string, string>) => void;
|
||||
fields: KVField[];
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const ConfTargetGeneric = ({
|
||||
onChange,
|
||||
fields,
|
||||
classes
|
||||
}: IConfGenericProps) => {
|
||||
//Local States
|
||||
|
||||
const [keyValues, setKeyValues] = useState<Map<string, string>>(new Map());
|
||||
|
||||
useEffect(() => {
|
||||
if (keyValues.size > 0) {
|
||||
onChange(keyValues);
|
||||
}
|
||||
}, [keyValues, onChange]);
|
||||
|
||||
const val = (key: string): string => {
|
||||
return keyValues.get(key) === undefined ? "" : keyValues.get(key) + "";
|
||||
};
|
||||
const valFall = (key: string, fallback: string): string => {
|
||||
return keyValues.get(key) === undefined
|
||||
? fallback
|
||||
: keyValues.get(key) + "";
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
{fields.map(field => (
|
||||
<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>
|
||||
)}
|
||||
</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>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ConfTargetGeneric);
|
||||
38
portal-ui/src/screens/Console/Configurations/types.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// 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 { SelectorTypes } from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
|
||||
export type KVFieldType =
|
||||
| "string"
|
||||
| "number"
|
||||
| "on|off"
|
||||
| "enum"
|
||||
| "path"
|
||||
| "url"
|
||||
| "address"
|
||||
| "duration"
|
||||
| "uri"
|
||||
| "sentence";
|
||||
|
||||
export interface KVField {
|
||||
name: string;
|
||||
label: string;
|
||||
tooltip: string;
|
||||
required?: boolean;
|
||||
type: KVFieldType;
|
||||
options?: SelectorTypes[];
|
||||
}
|
||||
@@ -40,7 +40,11 @@ import {
|
||||
} from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
import { AppState } from "../../store";
|
||||
import { setMenuOpen } from "../../actions";
|
||||
import {
|
||||
serverIsLoading,
|
||||
serverNeedsRestart,
|
||||
setMenuOpen
|
||||
} from "../../actions";
|
||||
import { ThemedComponentProps } from "@material-ui/core/styles/withTheme";
|
||||
import Buckets from "./Buckets/Buckets";
|
||||
import Policies from "./Policies/Policies";
|
||||
@@ -54,6 +58,7 @@ import ServiceAccounts from "./ServiceAccounts/ServiceAccounts";
|
||||
import Users from "./Users/Users";
|
||||
import Groups from "./Groups/Groups";
|
||||
import ListNotificationEndpoints from "./NotificationEndopoints/ListNotificationEndpoints";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
|
||||
function Copyright() {
|
||||
return (
|
||||
@@ -151,24 +156,42 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
fixedHeight: {
|
||||
minHeight: 240
|
||||
},
|
||||
warningBar: {
|
||||
background: theme.palette.primary.main,
|
||||
color: "white",
|
||||
heigh: "60px",
|
||||
widht: "100%",
|
||||
lineHeight: "60px",
|
||||
textAlign: "center"
|
||||
}
|
||||
});
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
open: state.system.sidebarOpen
|
||||
open: state.system.sidebarOpen,
|
||||
needsRestart: state.system.serverNeedsRestart,
|
||||
isServerLoading: state.system.serverIsLoading
|
||||
});
|
||||
|
||||
const connector = connect(mapState, { setMenuOpen });
|
||||
const connector = connect(mapState, {
|
||||
setMenuOpen,
|
||||
serverNeedsRestart,
|
||||
serverIsLoading
|
||||
});
|
||||
|
||||
interface ConsoleProps {
|
||||
interface IConsoleProps {
|
||||
open: boolean;
|
||||
needsRestart: boolean;
|
||||
isServerLoading: boolean;
|
||||
title: string;
|
||||
classes: any;
|
||||
setMenuOpen: typeof setMenuOpen;
|
||||
serverNeedsRestart: typeof serverNeedsRestart;
|
||||
serverIsLoading: typeof serverIsLoading;
|
||||
}
|
||||
|
||||
class Console extends React.Component<
|
||||
ConsoleProps & RouteComponentProps & StyledProps & ThemedComponentProps
|
||||
IConsoleProps & RouteComponentProps & StyledProps & ThemedComponentProps
|
||||
> {
|
||||
componentDidMount(): void {
|
||||
api
|
||||
@@ -182,8 +205,25 @@ class Console extends React.Component<
|
||||
});
|
||||
}
|
||||
|
||||
restartServer() {
|
||||
this.props.serverIsLoading(true);
|
||||
api
|
||||
.invoke("POST", "/api/v1/service/restart", {})
|
||||
.then(res => {
|
||||
console.log("success restarting service");
|
||||
console.log(res);
|
||||
this.props.serverIsLoading(false);
|
||||
this.props.serverNeedsRestart(false);
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.serverIsLoading(false);
|
||||
console.log("failure restarting service");
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, open } = this.props;
|
||||
const { classes, open, needsRestart, isServerLoading } = this.props;
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<CssBaseline />
|
||||
@@ -209,6 +249,30 @@ class Console extends React.Component<
|
||||
</Drawer>
|
||||
|
||||
<main className={classes.content}>
|
||||
{needsRestart && (
|
||||
<div className={classes.warningBar}>
|
||||
{isServerLoading ? (
|
||||
<React.Fragment>
|
||||
The server is restarting.
|
||||
<LinearProgress />
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
The instance needs to be restarted for configuration changes
|
||||
to take effect.{" "}
|
||||
<Button
|
||||
color="secondary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
this.restartServer();
|
||||
}}
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={classes.appBarSpacer} />
|
||||
<Container maxWidth="lg" className={classes.container}>
|
||||
<Router history={history}>
|
||||
|
||||
@@ -46,7 +46,7 @@ const styles = (theme: Theme) =>
|
||||
marginBottom: "20px",
|
||||
textAlign: "center",
|
||||
"& img": {
|
||||
width: "160px"
|
||||
width: "120px"
|
||||
}
|
||||
},
|
||||
menuList: {
|
||||
|
||||
@@ -0,0 +1,801 @@
|
||||
// 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, 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 api from "../../../common/api";
|
||||
import { serverNeedsRestart } from "../../../actions";
|
||||
import { connect } from "react-redux";
|
||||
import ConfMySql from "../Configurations/ConfMySql";
|
||||
import ConfTargetGeneric from "../Configurations/ConfTargetGeneric";
|
||||
import { KVField } from "../Configurations/types";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right"
|
||||
},
|
||||
logoButton: {
|
||||
height: "80px"
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
serverNeedsRestart: typeof serverNeedsRestart;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const AddNotificationEndpoint = ({
|
||||
open,
|
||||
closeModalAndRefresh,
|
||||
serverNeedsRestart,
|
||||
classes
|
||||
}: IAddNotificationEndpointProps) => {
|
||||
//Local States
|
||||
const [service, setService] = useState<string>("");
|
||||
const [valuesObj, setValueObj] = useState<Map<string, string>>(new Map());
|
||||
const [saving, setSaving] = useState<boolean>(false);
|
||||
const [addError, setError] = useState<string>("");
|
||||
|
||||
//Effects
|
||||
|
||||
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
|
||||
};
|
||||
api
|
||||
.invoke("PUT", `/api/v1/configs/${service}`, payload)
|
||||
.then(res => {
|
||||
setSaving(false);
|
||||
setError("");
|
||||
serverNeedsRestart(true);
|
||||
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
setSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
}
|
||||
}, [saving, serverNeedsRestart, service, valuesObj, closeModalAndRefresh]);
|
||||
|
||||
//Fetch Actions
|
||||
const submitForm = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
setSaving(true);
|
||||
};
|
||||
|
||||
const onValueChange = useCallback(
|
||||
newValue => {
|
||||
setValueObj(newValue);
|
||||
},
|
||||
[setValueObj]
|
||||
);
|
||||
|
||||
let srvComponent = <React.Fragment />;
|
||||
switch (service) {
|
||||
case notifyPostgres: {
|
||||
srvComponent = <ConfPostgres onChange={onValueChange} />;
|
||||
break;
|
||||
}
|
||||
case notifyMysql: {
|
||||
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"
|
||||
}
|
||||
];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
let targetTitle = "";
|
||||
switch (service) {
|
||||
case notifyNsq:
|
||||
targetTitle = "NSQ";
|
||||
break;
|
||||
case notifyWebhooks:
|
||||
targetTitle = "Webhooks";
|
||||
break;
|
||||
case notifyElasticsearch:
|
||||
targetTitle = "Elastic Search";
|
||||
break;
|
||||
case notifyNats:
|
||||
targetTitle = "NATS";
|
||||
break;
|
||||
case notifyMqtt:
|
||||
targetTitle = "MQTT";
|
||||
break;
|
||||
case notifyRedis:
|
||||
targetTitle = "Redis";
|
||||
break;
|
||||
case notifyKafka:
|
||||
targetTitle = "Kafka";
|
||||
break;
|
||||
case notifyPostgres:
|
||||
targetTitle = "Postgres";
|
||||
break;
|
||||
case notifyMysql:
|
||||
targetTitle = "Mysql";
|
||||
break;
|
||||
case notifyAmqp:
|
||||
targetTitle = "AMQP";
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={closeModalAndRefresh}
|
||||
title={`Add Lambda Notification Target ${targetTitle}`}
|
||||
>
|
||||
{service === "" && (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<p>Pick a supported service:</p>
|
||||
<table className={classes.chooseTable} style={{ width: "100%" }}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyPostgres);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/postgres.png"
|
||||
className={classes.logoButton}
|
||||
alt="postgres"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyKafka);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/kafka.png"
|
||||
className={classes.logoButton}
|
||||
alt="kafka"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyAmqp);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/amqp.png"
|
||||
className={classes.logoButton}
|
||||
alt="amqp"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyMqtt);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/mqtt.png"
|
||||
className={classes.logoButton}
|
||||
alt="mqtt"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyRedis);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/redis.png"
|
||||
className={classes.logoButton}
|
||||
alt="redis"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyNats);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/nats.png"
|
||||
className={classes.logoButton}
|
||||
alt="nats"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyMysql);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/mysql.png"
|
||||
className={classes.logoButton}
|
||||
alt="mysql"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyElasticsearch);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/elasticsearch.png"
|
||||
className={classes.logoButton}
|
||||
alt="elasticsearch"
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyWebhooks);
|
||||
}}
|
||||
>
|
||||
Webhook
|
||||
</Button>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setService(notifyNsq);
|
||||
}}
|
||||
>
|
||||
NSQ
|
||||
</Button>
|
||||
</td>
|
||||
<td />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
|
||||
{saving && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
{service !== "" && (
|
||||
<React.Fragment>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<form noValidate onSubmit={submitForm}>
|
||||
{srvComponent}
|
||||
|
||||
<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)(AddNotificationEndpoint));
|
||||
@@ -17,6 +17,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
@@ -27,6 +28,7 @@ import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import { CreateIcon } from "../../../icons";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
@@ -41,6 +43,7 @@ import { NotificationEndpointItem, NotificationEndpointsList } from "./types";
|
||||
import api from "../../../common/api";
|
||||
import FiberManualRecordIcon from "@material-ui/icons/FiberManualRecord";
|
||||
import { red } from "@material-ui/core/colors";
|
||||
import AddNotificationEndpoint from "./AddNotificationEndpoint";
|
||||
|
||||
interface IListNotificationEndpoints {
|
||||
classes: any;
|
||||
@@ -83,6 +86,7 @@ const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const [error, setError] = useState<string>("");
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
|
||||
|
||||
//Effects
|
||||
// load records on mount
|
||||
@@ -116,6 +120,15 @@ const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addScreenOpen && (
|
||||
<AddNotificationEndpoint
|
||||
open={addScreenOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
setIsLoading(true);
|
||||
setAddScreenOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Lambda Notification Targets</Typography>
|
||||
@@ -142,6 +155,16 @@ const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setAddScreenOpen(true);
|
||||
}}
|
||||
>
|
||||
Add Notification Target
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
|
||||
@@ -19,10 +19,14 @@ export interface SystemState {
|
||||
sidebarOpen: boolean;
|
||||
session: string;
|
||||
userName: string;
|
||||
serverNeedsRestart: boolean;
|
||||
serverIsLoading: boolean;
|
||||
}
|
||||
|
||||
export const USER_LOGGED = "USER_LOGGED";
|
||||
export const MENU_OPEN = "MENU_OPEN";
|
||||
export const SERVER_NEEDS_RESTART = "SERVER_NEEDS_RESTART";
|
||||
export const SERVER_IS_LOADING = "SERVER_IS_LOADING";
|
||||
|
||||
interface UserLoggedAction {
|
||||
type: typeof USER_LOGGED;
|
||||
@@ -34,4 +38,18 @@ interface SetMenuOpenAction {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
export type SystemActionTypes = UserLoggedAction | SetMenuOpenAction;
|
||||
interface ServerNeedsRestartAction {
|
||||
type: typeof SERVER_NEEDS_RESTART;
|
||||
needsRestart: boolean;
|
||||
}
|
||||
|
||||
interface ServerIsLoading {
|
||||
type: typeof SERVER_IS_LOADING;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export type SystemActionTypes =
|
||||
| UserLoggedAction
|
||||
| SetMenuOpenAction
|
||||
| ServerNeedsRestartAction
|
||||
| ServerIsLoading;
|
||||
|
||||
@@ -168,7 +168,7 @@ func setConfigWithARNAccountID(ctx context.Context, client MinioAdmin, configNam
|
||||
func buildConfig(configName *string, kvs []*models.ConfigurationKV) *string {
|
||||
configElements := []string{*configName}
|
||||
for _, kv := range kvs {
|
||||
configElements = append(configElements, kv.Key+"="+kv.Value)
|
||||
configElements = append(configElements, fmt.Sprintf("%s=%s", kv.Key, kv.Value))
|
||||
}
|
||||
config := strings.Join(configElements, " ")
|
||||
return &config
|
||||
|
||||
@@ -19,6 +19,7 @@ package restapi
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@@ -141,7 +142,14 @@ func TestAddPolicy(t *testing.T) {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
|
||||
} else {
|
||||
funcAssert.Equal(policy.Name, assertPolicy.Name)
|
||||
funcAssert.Equal(policy.Policy, assertPolicy.Policy)
|
||||
|
||||
var expectedPolicy iampolicy.Policy
|
||||
var actualPolicy iampolicy.Policy
|
||||
err1 := json.Unmarshal([]byte(policy.Policy), &expectedPolicy)
|
||||
funcAssert.NoError(err1)
|
||||
err2 := json.Unmarshal([]byte(assertPolicy.Policy), &actualPolicy)
|
||||
funcAssert.NoError(err2)
|
||||
funcAssert.Equal(expectedPolicy, actualPolicy)
|
||||
}
|
||||
// Test-2 : addPolicy() got an error while adding policy
|
||||
minioAddPolicyMock = func(name string, policy *iampolicy.Policy) error {
|
||||
|
||||