Moved notifications lambda pages to settings (#496)

This commit is contained in:
Alex
2020-12-10 13:59:45 -06:00
committed by GitHub
parent 90c54221d6
commit 6512a51119
19 changed files with 866 additions and 797 deletions

View File

@@ -28,7 +28,6 @@ var (
iamPolicies = "/policies"
dashboard = "/dashboard"
profiling = "/profiling"
notifications = "/notification-endpoints"
buckets = "/buckets"
bucketsDetail = "/buckets/:bucketName"
serviceAccounts = "/account"
@@ -124,18 +123,6 @@ var usersActionSet = ConfigurationActionSet{
),
}
// notificationsActionSet contains the list of admin actions required for this endpoint to work
var notificationsActionSet = ConfigurationActionSet{
actionTypes: iampolicy.NewActionSet(
iampolicy.AllActions,
),
actions: iampolicy.NewActionSet(
iampolicy.ListenBucketNotificationAction,
iampolicy.PutBucketNotificationAction,
iampolicy.GetBucketNotificationAction,
),
}
// bucketsActionSet contains the list of admin actions required for this endpoint to work
var bucketsActionSet = ConfigurationActionSet{
actionTypes: iampolicy.NewActionSet(
@@ -252,7 +239,6 @@ var endpointRules = map[string]ConfigurationActionSet{
iamPolicies: iamPoliciesActionSet,
dashboard: dashboardActionSet,
profiling: profilingActionSet,
notifications: notificationsActionSet,
buckets: bucketsActionSet,
bucketsDetail: bucketsActionSet,
serviceAccounts: serviceAccountsActionSet,

View File

@@ -81,7 +81,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"s3:*",
},
},
want: 8,
want: 7,
},
{
name: "all admin and s3 endpoints",
@@ -91,7 +91,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"s3:*",
},
},
want: 20,
want: 19,
},
{
name: "Console User - default endpoints",

File diff suppressed because one or more lines are too long

View File

@@ -263,4 +263,32 @@ export const settingsCommon = {
padding: "15px 38px",
textAlign: "right" as const,
},
settingsOptionsContainer: {
height: "calc(100vh - 244px)",
backgroundColor: "#fff",
border: "#EAEDEE 1px solid",
borderRadius: 3,
marginTop: 15,
},
backButton: {
cursor: "pointer",
fontSize: 10,
fontWeight: 600,
color: "#000",
backgroundColor: "transparent",
border: 0,
padding: 0,
display: "flex",
alignItems: "center",
"&:active, &:focus": {
outline: 0,
},
"& svg": {
width: 10,
marginRight: 4,
},
},
backContainer: {
margin: "20px 38px 0",
},
};

View File

@@ -48,7 +48,6 @@ const SlideOptions = ({
return (
<AutoSizer>
{({ width, height }: any) => {
console.log(width, height);
const currentSliderPosition = currentSlide * width;
const containerSize = width * slideOptions.length;
return (
@@ -61,9 +60,13 @@ const SlideOptions = ({
width: `${containerSize}px`,
}}
>
{slideOptions.map((block: any) => {
{slideOptions.map((block: any, index: number) => {
return (
<div className={classes.slide} style={{ width }}>
<div
className={classes.slide}
style={{ width }}
key={`slide-panel-${index.toString()}`}
>
{block}
</div>
);

View File

@@ -6,6 +6,7 @@ import { containerForHeader } from "../Common/FormComponents/common/styleLibrary
import Tab from "@material-ui/core/Tab";
import Tabs from "@material-ui/core/Tabs";
import ConfigurationsList from "./ConfigurationPanels/ConfigurationsList";
import ListNotificationEndpoints from "./NotificationEndpoints/ListNotificationEndpoints";
interface IConfigurationMain {
classes: any;
@@ -50,7 +51,11 @@ const ConfigurationMain = ({ classes }: IConfigurationMain) => {
<ConfigurationsList />
</Grid>
)}
{selectedTab === 1 && <div>Lambda notifications</div>}
{selectedTab === 1 && (
<Grid item xs={12}>
<ListNotificationEndpoints />
</Grid>
)}
</Grid>
</Grid>
</Grid>

View File

@@ -18,9 +18,6 @@ import React, { useState, Fragment } from "react";
import get from "lodash/get";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
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";
@@ -60,34 +57,6 @@ const styles = (theme: Theme) =>
display: "none",
},
},
settingsOptionsContainer: {
height: "calc(100vh - 244px)",
backgroundColor: "#fff",
border: "#EAEDEE 1px solid",
borderRadius: 3,
marginTop: 15,
},
backButton: {
cursor: "pointer",
fontSize: 10,
fontWeight: 600,
color: "#000",
backgroundColor: "transparent",
border: 0,
padding: 0,
display: "flex",
alignItems: "center",
"&:active, &:focus": {
outline: 0,
},
"& svg": {
width: 10,
marginRight: 4,
},
},
backContainer: {
margin: "20px 38px 0",
},
...searchField,
...actionsTray,
...settingsCommon,
@@ -136,7 +105,7 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
};
return (
<React.Fragment>
<Fragment>
<Grid container>
<Grid item xs={12}>
<Grid item xs={12}>
@@ -191,7 +160,7 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
</Grid>
</Grid>
</Grid>
</React.Fragment>
</Fragment>
);
};

View File

@@ -131,7 +131,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
};
return (
<Grid container className={classes.formScrollable}>
<Grid container>
<Grid item xs={12}>
<FormSwitchWrapper
label={"Enter DNS String"}

View File

@@ -202,7 +202,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
}, [useConnectionString]);
return (
<Grid container className={classes.formScrollable}>
<Grid container>
<Grid item xs={12}>
<FormSwitchWrapper
label={"Manually Configure String"}

View File

@@ -0,0 +1,191 @@
// 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, { Fragment, useCallback, useEffect, useState } from "react";
import get from "lodash/get";
import Grid from "@material-ui/core/Grid";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Button } from "@material-ui/core";
import Typography from "@material-ui/core/Typography";
import ConfPostgres from "../CustomForms/ConfPostgres";
import api from "../../../../common/api";
import { serverNeedsRestart } from "../../../../actions";
import { connect } from "react-redux";
import ConfMySql from "../CustomForms/ConfMySql";
import ConfTargetGeneric from "../ConfTargetGeneric";
import {
notificationEndpointsFields,
notifyPostgres,
notifyMysql,
removeEmptyFields,
} from "../utils";
import { IElementValue } from "../types";
import {
modalBasic,
settingsCommon,
} from "../../Common/FormComponents/common/styleLibrary";
import { servicesList } from "./utils";
const styles = (theme: Theme) =>
createStyles({
...modalBasic,
...settingsCommon,
errorBlock: {
color: "red",
},
strongText: {
fontWeight: 700,
},
keyName: {
marginLeft: 5,
},
buttonContainer: {
textAlign: "right",
},
lambdaFormIndicator: {
display: "flex",
marginBottom: 40,
},
customTitle: {
...settingsCommon.customTitle,
marginTop: 0,
},
settingsFormContainer: {
...settingsCommon.settingsFormContainer,
height: "calc(100vh - 422px)",
},
});
interface IAddNotificationEndpointProps {
service: string;
saveAndRefresh: any;
serverNeedsRestart: typeof serverNeedsRestart;
classes: any;
}
const AddNotificationEndpoint = ({
service,
saveAndRefresh,
serverNeedsRestart,
classes,
}: IAddNotificationEndpointProps) => {
//Local States
const [valuesArr, setValueArr] = useState<IElementValue[]>([]);
const [saving, setSaving] = useState<boolean>(false);
const [addError, setError] = useState<string>("");
//Effects
useEffect(() => {
if (saving) {
const payload = {
key_values: removeEmptyFields(valuesArr),
};
api
.invoke("PUT", `/api/v1/configs/${service}`, payload)
.then(() => {
setSaving(false);
setError("");
serverNeedsRestart(true);
saveAndRefresh();
})
.catch((err) => {
setSaving(false);
setError(err);
});
}
}, [saving, serverNeedsRestart, service, valuesArr, saveAndRefresh]);
//Fetch Actions
const submitForm = (event: React.FormEvent) => {
event.preventDefault();
setSaving(true);
};
const onValueChange = useCallback(
(newValue) => {
setValueArr(newValue);
},
[setValueArr]
);
let srvComponent;
switch (service) {
case notifyPostgres: {
srvComponent = <ConfPostgres onChange={onValueChange} />;
break;
}
case notifyMysql: {
srvComponent = <ConfMySql onChange={onValueChange} />;
break;
}
default: {
const fields = get(notificationEndpointsFields, service, []);
srvComponent = (
<ConfTargetGeneric fields={fields} onChange={onValueChange} />
);
}
}
const targetElement = servicesList.find(
(element) => element.actionTrigger === service
);
return (
<Fragment>
{service !== "" && (
<Fragment>
<form noValidate onSubmit={submitForm}>
<Grid item xs={12} className={classes.customTitle}>
{targetElement ? targetElement.targetTitle : ""} - Add Lambda
Notification Target
</Grid>
<Grid item xs={12} className={classes.settingsFormContainer}>
{addError !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{addError}
</Typography>
</Grid>
)}
{srvComponent}
</Grid>
<Grid item xs={12} className={classes.settingsButtonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={saving}
>
Save
</Button>
</Grid>
<Grid item xs={9} />
</form>
</Fragment>
)}
</Fragment>
);
};
const connector = connect(null, { serverNeedsRestart });
export default connector(withStyles(styles)(AddNotificationEndpoint));

View File

@@ -0,0 +1,289 @@
// 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, Fragment } from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { TextField } from "@material-ui/core";
import { red } from "@material-ui/core/colors";
import Grid from "@material-ui/core/Grid";
import FiberManualRecordIcon from "@material-ui/icons/FiberManualRecord";
import Button from "@material-ui/core/Button";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import {
NotificationEndpointItem,
NotificationEndpointsList,
TransformedEndpointItem,
} from "./types";
import { notificationTransform } from "./utils";
import { CreateIcon } from "../../../../icons";
import api from "../../../../common/api";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import AddNotificationEndpoint from "./AddNotificationEndpoint";
import {
actionsTray,
containerForHeader,
searchField,
settingsCommon,
} from "../../Common/FormComponents/common/styleLibrary";
import SlideOptions from "../../Common/SlideOptions/SlideOptions";
import BackSettingsIcon from "../../../../icons/BackSettingsIcon";
import NotificationTypeSelector from "./NotificationTypeSelector";
interface IListNotificationEndpoints {
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
...actionsTray,
...searchField,
...settingsCommon,
...containerForHeader(theme.spacing(4)),
errorBlock: {
color: "red",
},
strongText: {
fontWeight: 700,
},
keyName: {
marginLeft: 5,
},
iconText: {
lineHeight: "24px",
},
customConfigurationPage: {
height: "calc(100vh - 410px)",
scrollbarWidth: "none" as const,
"&::-webkit-scrollbar": {
display: "none",
},
},
lambdaContainer: {
padding: "15px 0",
},
actionsTray: {
...actionsTray.actionsTray,
padding: "0 38px",
},
});
const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
//Local States
const [records, setRecords] = useState<TransformedEndpointItem[]>([]);
const [filter, setFilter] = useState<string>("");
const [error, setError] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
const [currentPanel, setCurrentPanel] = useState<number>(0);
const [service, setService] = useState<string>("");
//Effects
// load records on mount
useEffect(() => {
if (isLoading) {
const fetchRecords = () => {
api
.invoke("GET", `/api/v1/admin/notification_endpoints`)
.then((res: NotificationEndpointsList) => {
let resNotEndList: NotificationEndpointItem[] = [];
if (res.notification_endpoints !== null) {
resNotEndList = res.notification_endpoints;
}
setRecords(notificationTransform(resNotEndList));
setError("");
setIsLoading(false);
})
.catch((err) => {
setError(err);
setIsLoading(false);
});
};
fetchRecords();
}
}, [isLoading]);
useEffect(() => {
setIsLoading(true);
}, []);
const tableActions = [
{
type: "delete",
onClick: (row: any) => {
//confirmDeleteBucket(row.name);
},
},
];
const filteredRecords = records.filter((b: TransformedEndpointItem) => {
if (filter === "") {
return true;
} else {
if (b.service_name.indexOf(filter) >= 0) {
return true;
} else {
return false;
}
}
});
const statusDisplay = (status: string) => {
return (
<div
style={{
display: "flex",
alignItems: "center",
}}
>
<FiberManualRecordIcon
style={status === "Offline" ? { color: red[500] } : {}}
/>
{status}
</div>
);
};
const openNewLambdaSelector = () => {
setCurrentPanel(1);
};
const backClick = () => {
setService("");
setCurrentPanel(currentPanel - 1);
};
const saveAndRefresh = () => {
setIsLoading(true);
setCurrentPanel(0);
setService("");
};
return (
<Fragment>
<Grid container>
<Grid item xs={12}>
<Grid item xs={12}>
<div className={classes.settingsOptionsContainer}>
<SlideOptions
slideOptions={[
<Fragment>
<Grid item xs={12} className={classes.customTitle}>
Lambda Notification Targets
</Grid>
<Grid item xs={12} className={classes.lambdaContainer}>
{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={<CreateIcon />}
onClick={openNewLambdaSelector}
>
Add Notification Target
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{
label: "Status",
elementKey: "status",
renderFunction: statusDisplay,
width: 150,
},
{ label: "Service", elementKey: "service_name" },
]}
isLoading={isLoading}
records={filteredRecords}
entityName="Notification Endpoints"
idField="service_name"
customPaperHeight={classes.customConfigurationPage}
noBackground
/>
</Grid>
</Grid>
</Fragment>,
<Fragment>
<Grid item xs={12} className={classes.backContainer}>
<button
onClick={backClick}
className={classes.backButton}
>
<BackSettingsIcon />
Back To Lambda Notifications
</button>
</Grid>
<Grid item xs={12}>
<NotificationTypeSelector
setService={(serviceName: string) => {
setService(serviceName);
setCurrentPanel(2);
}}
/>
</Grid>
</Fragment>,
<Fragment>
<Grid item xs={12} className={classes.backContainer}>
<button
onClick={backClick}
className={classes.backButton}
>
<BackSettingsIcon />
Back To Supported Services
</button>
</Grid>
<Grid item xs={12}>
<AddNotificationEndpoint
service={service}
saveAndRefresh={saveAndRefresh}
/>
</Grid>
</Fragment>,
]}
currentSlide={currentPanel}
/>
</div>
</Grid>
</Grid>
</Grid>
</Fragment>
);
};
export default withStyles(styles)(ListNotificationEndpoints);

View File

@@ -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, { Fragment } from "react";
import Grid from "@material-ui/core/Grid";
import { Button } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { servicesList } from "./utils";
import { settingsCommon } from "../../Common/FormComponents/common/styleLibrary";
interface INotificationTypeSelector {
classes: any;
setService: (trigger: string) => any;
}
const nonLogos = servicesList.filter((elService) => elService.logo === "");
const withLogos = servicesList.filter((elService) => elService.logo !== "");
const styles = (theme: Theme) =>
createStyles({
...settingsCommon,
logoButton: {
height: "80px",
},
lambdaNotif: {
backgroundColor: "#fff",
border: "#393939 1px solid",
borderRadius: 5,
width: 101,
height: 91,
display: "flex",
alignItems: "center",
justifyContent: "center",
marginBottom: 16,
cursor: "pointer",
"& img": {
maxWidth: 71,
maxHeight: 71,
},
},
iconContainer: {
display: "flex",
flexDirection: "row",
maxWidth: 455,
justifyContent: "space-between",
flexWrap: "wrap",
},
nonIconContainer: {
marginBottom: 16,
width: 455,
marginTop: 15,
"& button": {
marginRight: 16,
},
},
pickTitle: {
fontWeight: 600,
color: "#393939",
fontSize: 14,
marginBottom: 16,
},
centerElements: {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
},
customTitle: {
...settingsCommon.customTitle,
marginTop: 0,
},
});
const NotificationTypeSelector = ({
classes,
setService,
}: INotificationTypeSelector) => {
return (
<Fragment>
<Grid container>
<Grid item xs={12}>
<Grid item xs={12} className={classes.customTitle}>
Pick a supported service
</Grid>
<Grid item xs={12} className={classes.centerElements}>
<div className={classes.nonIconContainer}>
{nonLogos.map((item) => {
return (
<Button
variant="contained"
color="primary"
key={`non-icon-${item.targetTitle}`}
onClick={() => {
setService(item.actionTrigger);
}}
>
{item.targetTitle.toUpperCase()}
</Button>
);
})}
</div>
<div className={classes.iconContainer}>
{withLogos.map((item) => {
return (
<button
key={`icon-${item.targetTitle}`}
className={classes.lambdaNotif}
onClick={() => {
setService(item.actionTrigger);
}}
>
<img
src={item.logo}
className={classes.logoButton}
alt={item.targetTitle}
/>
</button>
);
})}
</div>
</Grid>
</Grid>
</Grid>
</Fragment>
);
};
export default withStyles(styles)(NotificationTypeSelector);

View File

@@ -0,0 +1,93 @@
// 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 { NotificationEndpointItem } from "./types";
import {
notifyAmqp,
notifyElasticsearch,
notifyKafka,
notifyMqtt,
notifyMysql,
notifyNats,
notifyNsq,
notifyPostgres,
notifyRedis,
notifyWebhooks,
} from "../utils";
export const notificationTransform = (
notificationElements: NotificationEndpointItem[]
) => {
return notificationElements.map((element) => {
return {
service_name: `${element.service}:${element.account_id}`,
status: element.status,
};
});
};
export const servicesList = [
{
actionTrigger: notifyPostgres,
targetTitle: "Postgres SQL",
logo: "/postgres.png",
},
{
actionTrigger: notifyKafka,
targetTitle: "Kafka",
logo: "/kafka.png",
},
{
actionTrigger: notifyAmqp,
targetTitle: "AMQP",
logo: "/amqp.png",
},
{
actionTrigger: notifyMqtt,
targetTitle: "MQTT",
logo: "/mqtt.png",
},
{
actionTrigger: notifyRedis,
targetTitle: "Redis",
logo: "/redis.png",
},
{
actionTrigger: notifyNats,
targetTitle: "NATS",
logo: "/nats.png",
},
{
actionTrigger: notifyMysql,
targetTitle: "Mysql",
logo: "/mysql.png",
},
{
actionTrigger: notifyElasticsearch,
targetTitle: "Elastic Search",
logo: "/elasticsearch.png",
},
{
actionTrigger: notifyWebhooks,
targetTitle: "Webhook",
logo: "",
},
{
actionTrigger: notifyNsq,
targetTitle: "NSQ",
logo: "",
},
];

View File

@@ -39,7 +39,7 @@ import api from "../../common/api";
import Account from "./Account/Account";
import Users from "./Users/Users";
import Groups from "./Groups/Groups";
import ListNotificationEndpoints from "./NotificationEndopoints/ListNotificationEndpoints";
import ListNotificationEndpoints from "./Configurations/NotificationEndpoints/ListNotificationEndpoints";
import ConfigurationMain from "./Configurations/ConfigurationMain";
import WebhookPanel from "./Configurations/ConfigurationPanels/WebhookPanel";
import ListTenants from "./Tenants/ListTenants/ListTenants";
@@ -248,10 +248,6 @@ const Console = ({
component: Logs,
path: "/logs",
},
{
component: ListNotificationEndpoints,
path: "/notification-endpoints",
},
{
component: ConfigurationMain,
path: "/settings",

View File

@@ -274,14 +274,6 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => {
name: "Heal",
icon: <HealIcon />,
},
{
group: "Admin",
type: "item",
component: NavLink,
to: "/notification-endpoints",
name: "Lambda Notifications",
icon: <LambdaNotificationsIcon />,
},
{
group: "Admin",
type: "item",

View File

@@ -1,385 +0,0 @@
// 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 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/CustomForms/ConfPostgres";
import api from "../../../common/api";
import { serverNeedsRestart } from "../../../actions";
import { connect } from "react-redux";
import ConfMySql from "../Configurations/CustomForms/ConfMySql";
import ConfTargetGeneric from "../Configurations/ConfTargetGeneric";
import {
notificationEndpointsFields,
notifyPostgres,
notifyMysql,
notifyKafka,
notifyAmqp,
notifyMqtt,
notifyRedis,
notifyNats,
notifyElasticsearch,
notifyWebhooks,
notifyNsq,
removeEmptyFields,
} from "../Configurations/utils";
import { IElementValue } from "../Configurations/types";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red",
},
strongText: {
fontWeight: 700,
},
keyName: {
marginLeft: 5,
},
buttonContainer: {
textAlign: "right",
},
logoButton: {
height: "80px",
},
lambdaNotif: {
backgroundColor: "#fff",
border: "#393939 1px solid",
borderRadius: 5,
width: 101,
height: 91,
display: "flex",
alignItems: "center",
justifyContent: "center",
marginBottom: 16,
cursor: "pointer",
"& img": {
maxWidth: 71,
maxHeight: 71,
},
},
iconContainer: {
display: "flex",
flexDirection: "row",
width: 455,
justifyContent: "space-between",
flexWrap: "wrap",
},
nonIconContainer: {
marginBottom: 16,
"& button": {
marginRight: 16,
},
},
pickTitle: {
fontWeight: 600,
color: "#393939",
fontSize: 14,
marginBottom: 16,
},
lambdaFormIndicator: {
display: "flex",
marginBottom: 40,
},
lambdaName: {
fontSize: 18,
fontWeight: 700,
color: "#000",
marginBottom: 6,
},
lambdaSubname: {
fontSize: 12,
color: "#000",
fontWeight: 600,
},
lambdaIcon: {
borderRadius: 5,
border: "#393939 1px solid",
width: 53,
height: 48,
display: "flex",
justifyContent: "center",
alignItems: "center",
marginRight: 16,
"& img": {
width: 38,
},
},
...modalBasic,
});
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 [valuesArr, setValueArr] = useState<IElementValue[]>([]);
const [saving, setSaving] = useState<boolean>(false);
const [addError, setError] = useState<string>("");
//Effects
useEffect(() => {
if (saving) {
const payload = {
key_values: removeEmptyFields(valuesArr),
};
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, valuesArr, closeModalAndRefresh]);
//Fetch Actions
const submitForm = (event: React.FormEvent) => {
event.preventDefault();
setSaving(true);
};
const onValueChange = useCallback(
(newValue) => {
setValueArr(newValue);
},
[setValueArr]
);
let srvComponent = <React.Fragment />;
switch (service) {
case notifyPostgres: {
srvComponent = <ConfPostgres onChange={onValueChange} />;
break;
}
case notifyMysql: {
srvComponent = <ConfMySql onChange={onValueChange} />;
break;
}
default: {
const fields = get(notificationEndpointsFields, service, []);
srvComponent = (
<ConfTargetGeneric fields={fields} onChange={onValueChange} />
);
}
}
const servicesList = [
{
actionTrigger: notifyPostgres,
targetTitle: "Postgres SQL",
logo: "/postgres.png",
},
{
actionTrigger: notifyKafka,
targetTitle: "Kafka",
logo: "/kafka.png",
},
{
actionTrigger: notifyAmqp,
targetTitle: "AMQP",
logo: "/amqp.png",
},
{
actionTrigger: notifyMqtt,
targetTitle: "MQTT",
logo: "/mqtt.png",
},
{
actionTrigger: notifyRedis,
targetTitle: "Redis",
logo: "/redis.png",
},
{
actionTrigger: notifyNats,
targetTitle: "NATS",
logo: "/nats.png",
},
{
actionTrigger: notifyMysql,
targetTitle: "Mysql",
logo: "/mysql.png",
},
{
actionTrigger: notifyElasticsearch,
targetTitle: "Elastic Search",
logo: "/elasticsearch.png",
},
{
actionTrigger: notifyWebhooks,
targetTitle: "Webhook",
logo: "",
},
{
actionTrigger: notifyNsq,
targetTitle: "NSQ",
logo: "",
},
];
const nonLogos = servicesList.filter((elService) => elService.logo === "");
const withLogos = servicesList.filter((elService) => elService.logo !== "");
const targetElement = servicesList.find(
(element) => element.actionTrigger === service
);
const goBack = () => {
setService("");
};
return (
<ModalWrapper modalOpen={open} onClose={closeModalAndRefresh} title={""}>
{service === "" && (
<Grid container>
<Grid item xs={12}>
<div className={classes.pickTitle}>Pick a supported service:</div>
<div className={classes.nonIconContainer}>
{nonLogos.map((item) => {
return (
<Button
variant="contained"
color="primary"
key={`non-icon-${item.targetTitle}`}
onClick={() => {
setService(item.actionTrigger);
}}
>
{item.targetTitle.toUpperCase()}
</Button>
);
})}
</div>
<div className={classes.iconContainer}>
{withLogos.map((item) => {
return (
<button
key={`icon-${item.targetTitle}`}
className={classes.lambdaNotif}
onClick={() => {
setService(item.actionTrigger);
}}
>
<img
src={item.logo}
className={classes.logoButton}
alt={item.targetTitle}
/>
</button>
);
})}
</div>
</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}>
<Grid item xs={12} className={classes.lambdaFormIndicator}>
{targetElement && targetElement.logo !== "" && (
<div className={classes.lambdaIcon}>
<img
src={targetElement.logo}
alt={targetElement.targetTitle}
/>
</div>
)}
<div className={classes.lambdaTitle}>
<div className={classes.lambdaName}>
{targetElement ? targetElement.targetTitle : ""}
</div>
<div className={classes.lambdaSubname}>
Add Lambda Notification Target
</div>
</div>
</Grid>
<Grid item xs={12}>
{srvComponent}
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={goBack}
>
Back
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={saving}
>
Save
</Button>
</Grid>
<Grid item xs={9} />
</form>
</React.Fragment>
)}
</ModalWrapper>
);
};
const connector = connect(null, { serverNeedsRestart });
export default connector(withStyles(styles)(AddNotificationEndpoint));

View File

@@ -1,211 +0,0 @@
// 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 { TextField } from "@material-ui/core";
import { red } from "@material-ui/core/colors";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import {
NotificationEndpointItem,
NotificationEndpointsList,
TransformedEndpointItem,
} from "./types";
import { notificationTransform } from "./utils";
import { CreateIcon } from "../../../icons";
import api from "../../../common/api";
import FiberManualRecordIcon from "@material-ui/icons/FiberManualRecord";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import AddNotificationEndpoint from "./AddNotificationEndpoint";
import {
actionsTray,
containerForHeader,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import PageHeader from "../Common/PageHeader/PageHeader";
interface IListNotificationEndpoints {
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red",
},
strongText: {
fontWeight: 700,
},
keyName: {
marginLeft: 5,
},
iconText: {
lineHeight: "24px",
},
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
});
const ListNotificationEndpoints = ({ classes }: IListNotificationEndpoints) => {
//Local States
const [records, setRecords] = useState<TransformedEndpointItem[]>([]);
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
useEffect(() => {
if (isLoading) {
const fetchRecords = () => {
api
.invoke("GET", `/api/v1/admin/notification_endpoints`)
.then((res: NotificationEndpointsList) => {
let resNotEndList: NotificationEndpointItem[] = [];
if (res.notification_endpoints !== null) {
resNotEndList = res.notification_endpoints;
}
setRecords(notificationTransform(resNotEndList));
setError("");
setIsLoading(false);
})
.catch((err) => {
setError(err);
setIsLoading(false);
});
};
fetchRecords();
}
}, [isLoading]);
useEffect(() => {
setIsLoading(true);
}, []);
const tableActions = [
{ type: "view", to: "/notification-endpoints", sendOnlyId: true },
{
type: "delete",
onClick: (row: any) => {
//confirmDeleteBucket(row.name);
},
},
];
const filteredRecords = records.filter((b: TransformedEndpointItem) => {
if (filter === "") {
return true;
} else {
if (b.service_name.indexOf(filter) >= 0) {
return true;
} else {
return false;
}
}
});
const statusDisplay = (status: string) => {
return (
<div
style={{
display: "flex",
alignItems: "center",
}}
>
<FiberManualRecordIcon
style={status === "Offline" ? { color: red[500] } : {}}
/>
{status}
</div>
);
};
return (
<React.Fragment>
{addScreenOpen && (
<AddNotificationEndpoint
open={addScreenOpen}
closeModalAndRefresh={() => {
setIsLoading(true);
setAddScreenOpen(false);
}}
/>
)}
<PageHeader label="Lambda Notification Targets" />
<Grid container>
<Grid item xs={12} className={classes.container}>
{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={<CreateIcon />}
onClick={() => {
setAddScreenOpen(true);
}}
>
Add Notification Target
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "Service", elementKey: "service_name" },
{
label: "Status",
elementKey: "status",
renderFunction: statusDisplay,
},
]}
isLoading={isLoading}
records={filteredRecords}
entityName="Notification Endpoints"
idField="service_name"
/>
</Grid>
</Grid>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(ListNotificationEndpoints);

View File

@@ -1,28 +0,0 @@
// 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 { NotificationEndpointItem } from "./types";
export const notificationTransform = (
notificationElements: NotificationEndpointItem[]
) => {
return notificationElements.map(element => {
return {
service_name: `${element.service}:${element.account_id}`,
status: element.status
};
});
};