Modals UI style changes (#331)

Implements new input styles & adjusts information on modal boxes for console.

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2020-10-19 13:27:54 -05:00
committed by GitHub
parent e4510cbc18
commit e1fdf3fb28
38 changed files with 2372 additions and 1231 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -103,6 +103,7 @@ const AddBucket = ({
const [bName, setBName] = useState<string>(bucketName);
const [addLoading, setAddLoading] = useState<boolean>(false);
const [addError, setAddError] = useState<string>("");
const [sendEnabled, setSendEnabled] = useState<boolean>(false);
const addRecord = (event: React.FormEvent) => {
event.preventDefault();
@@ -141,10 +142,34 @@ const AddBucket = ({
const [value] = useDebounce(bName, 1000);
useEffect(() => {
console.log("called");
addBucketName(value);
}, [value]);
const resetForm = () => {
setBName("");
addBucketVersioned(false);
addBucketQuota(false);
addBucketQuotaType("hard");
addBucketQuotaSize("1");
addBucketQuotaUnit("TiB");
};
useEffect(() => {
let valid = false;
if (bName.trim() !== "") {
valid = true;
}
if (enableQuota && valid) {
if (quotaSize.trim() === "" || parseInt(quotaSize) === 0) {
valid = false;
}
}
setSendEnabled(valid);
}, [bName, versioned, quotaType, quotaSize, quotaUnit, enableQuota]);
return (
<ModalWrapper
title="Create Bucket"
@@ -196,7 +221,8 @@ const AddBucket = ({
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
addBucketVersioned(event.target.checked);
}}
label={"Turn On Versioning"}
label={"Versioning"}
indicatorLabel={"On"}
/>
</Grid>
<Grid item xs={12}>
@@ -209,6 +235,7 @@ const AddBucket = ({
addBucketQuota(event.target.checked);
}}
label={"Enable Bucket Quota"}
indicatorLabel={"On"}
/>
</Grid>
{enableQuota && (
@@ -264,11 +291,19 @@ const AddBucket = ({
)}
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetForm}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
disabled={addLoading || !sendEnabled}
>
Save
</Button>

View File

@@ -172,7 +172,7 @@ class ListObjects extends React.Component<
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
xhr.withCredentials = false;
xhr.onload = function(event) {
xhr.onload = function (event) {
// TODO: handle status
if (xhr.status == 401 || xhr.status == 403) {
listObjects.showSnackBarMessage(
@@ -227,7 +227,7 @@ class ListObjects extends React.Component<
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
xhr.responseType = "blob";
xhr.onload = function(e) {
xhr.onload = function (e) {
if (this.status == 200) {
var blob = new Blob([this.response], {
type: "octet/stream",

View File

@@ -0,0 +1,83 @@
// 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 from "react";
import HelpIcon from "@material-ui/icons/Help";
import Grid from "@material-ui/core/Grid";
import { Controlled as CodeMirror } from "react-codemirror2";
import { InputLabel, Tooltip } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { fieldBasic } from "../common/styleLibrary";
import "./ConsoleCodeMirror.css";
require("codemirror/mode/javascript/javascript");
interface ICodeWrapper {
value: string;
label?: string;
tooltip?: string;
classes: any;
onChange?: (editor: any, data: any, value: string) => any;
onBeforeChange: (editor: any, data: any, value: string) => any;
readOnly?: boolean;
}
const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
});
const CodeMirrorWrapper = ({
value,
label = "",
tooltip = "",
classes,
onChange = () => {},
onBeforeChange,
readOnly = false,
}: ICodeWrapper) => {
return (
<React.Fragment>
<InputLabel className={classes.inputLabel}>
<span>{label}</span>
{tooltip !== "" && (
<div className={classes.tooltipContainer}>
<Tooltip title={tooltip} placement="top-start">
<HelpIcon className={classes.tooltip} />
</Tooltip>
</div>
)}
</InputLabel>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<CodeMirror
value={value}
options={{
mode: "javascript",
lineNumbers: true,
readOnly,
}}
onBeforeChange={onBeforeChange}
onChange={onChange}
/>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(CodeMirrorWrapper);

View File

@@ -0,0 +1,353 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: #fff;
background: #081C42;
direction: ltr;
font-size: 13px;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: rgba(255,255,255,0.8); /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #ffffff80;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
white-space: nowrap;
color: #000;
font-size: 10px;
height: 18px;
line-height: 18px;
text-align: center;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid white;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, 0.5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: 0;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: #fff;}
.cm-s-default .cm-quote {color: #fff;}
.cm-negative {color: #fff;}
.cm-positive {color: #fff;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #fff;}
.cm-s-default .cm-atom {color: #fff;}
.cm-s-default .cm-number {color: #fff;}
.cm-s-default .cm-def {color: #fff;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #fff;}
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #fff;}
.cm-s-default .cm-comment {color: #fff;}
.cm-s-default .cm-string {color: #fff;}
.cm-s-default .cm-string-2 {color: #fff;}
.cm-s-default .cm-meta {color: #fff;}
.cm-s-default .cm-qualifier {color: #fff;}
.cm-s-default .cm-builtin {color: #fff;}
.cm-s-default .cm-bracket {color: #fff;}
.cm-s-default .cm-tag {color: #fff;}
.cm-s-default .cm-attribute {color: #fff;}
.cm-s-default .cm-hr {color: #fff;}
.cm-s-default .cm-link {color: #fff;}
.cm-s-default .cm-error {color: #fff;}
.cm-invalidchar {color: #fff;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #fff;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #fff;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 50px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -50px; margin-right: -50px;
padding-bottom: 50px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 50px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -50px;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre.CodeMirror-line,
.CodeMirror-wrap pre.CodeMirror-line-like {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-widget {}
.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background-color: #ffa;
background-color: rgba(255, 255, 0, .4);
}
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

View File

@@ -0,0 +1,156 @@
// 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 from "react";
import {
Grid,
InputLabel,
TextField,
TextFieldProps,
Tooltip,
} from "@material-ui/core";
import { OutlinedInputProps } from "@material-ui/core/OutlinedInput";
import {
createStyles,
makeStyles,
Theme,
withStyles,
} from "@material-ui/core/styles";
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
import HelpIcon from "@material-ui/icons/Help";
interface CommentBoxProps {
label: string;
classes: any;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
value: string | boolean;
id: string;
name: string;
disabled?: boolean;
tooltip?: string;
index?: number;
error?: string;
required?: boolean;
placeholder?: string;
}
const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
...tooltipHelper,
inputLabel: {
...fieldBasic.inputLabel,
marginBottom: 16,
fontSize: 14,
},
textBoxContainer: {
flexGrow: 1,
position: "relative",
},
errorState: {
color: "#b53b4b",
fontSize: 14,
position: "absolute",
top: 7,
right: 7,
},
cssOutlinedInput: {
borderColor: "#9C9C9C",
},
rootTest: {
"& .MuiOutlinedInput-inputMultiline": {
...fieldBasic.inputLabel,
fontSize: 13,
},
},
});
const CommentBoxWrapper = ({
label,
onChange,
value,
id,
name,
disabled = false,
tooltip = "",
index = 0,
error = "",
required = false,
placeholder = "",
classes,
}: CommentBoxProps) => {
let inputProps: any = { "data-index": index };
return (
<React.Fragment>
<Grid
item
xs={12}
className={`${classes.fieldContainer} ${
error !== "" ? classes.errorInField : ""
}`}
>
{label !== "" && (
<InputLabel
htmlFor={id}
className={`${error !== "" ? classes.fieldLabelError : ""} ${
classes.inputLabel
}`}
>
<span>
{label}
{required ? "*" : ""}
</span>
{tooltip !== "" && (
<div className={classes.tooltipContainer}>
<Tooltip title={tooltip} placement="top-start">
<HelpIcon className={classes.tooltip} />
</Tooltip>
</div>
)}
</InputLabel>
)}
<div className={classes.textBoxContainer}>
<TextField
id={id}
name={name}
fullWidth
value={value}
disabled={disabled}
onChange={onChange}
multiline
inputProps={inputProps}
error={error !== ""}
helperText={error}
placeholder={placeholder}
InputLabelProps={{
shrink: true,
}}
InputProps={{
classes: {
notchedOutline: classes.cssOutlinedInput,
root: classes.rootTest,
},
}}
variant="outlined"
/>
</div>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(CommentBoxWrapper);

View File

@@ -22,7 +22,7 @@ import { actionsTray, fieldBasic } from "../common/styleLibrary";
import HelpIcon from "@material-ui/icons/Help";
interface IFormSwitch {
label: string;
label?: string;
classes: any;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
value: string | boolean;
@@ -31,7 +31,9 @@ interface IFormSwitch {
disabled?: boolean;
tooltip?: string;
index?: number;
indicatorLabel?: string;
checked: boolean;
switchOnly?: boolean;
}
const styles = (theme: Theme) =>
@@ -82,7 +84,7 @@ const styles = (theme: Theme) =>
},
actionsTitle: {
fontWeight: 600,
color: "#000",
color: "#081C42",
fontSize: 16,
alignSelf: "center",
},
@@ -95,7 +97,7 @@ const styles = (theme: Theme) =>
"& .input": {
"&::placeholder": {
fontWeight: 600,
color: "#000",
color: "#081C42",
},
},
},
@@ -107,6 +109,15 @@ const styles = (theme: Theme) =>
paddingBottom: 14,
marginBottom: 20,
},
indicatorLabel: {
fontSize: 12,
fontWeight: 600,
color: "#081C42",
margin: "0 8px 0 10px",
},
switchContainer: {
display: "flex",
},
...actionsTray,
...fieldBasic,
});
@@ -127,7 +138,7 @@ const StyledSwitch = withStyles({
color: "#fff",
},
"&$checked + $track": {
backgroundColor: "#000",
backgroundColor: "#081C42",
opacity: 1,
height: 15,
},
@@ -135,14 +146,14 @@ const StyledSwitch = withStyles({
checked: {},
track: {
height: 15,
backgroundColor: "#000",
backgroundColor: "#081C42",
opacity: 1,
padding: 0,
marginTop: 1.5,
},
thumb: {
backgroundColor: "#fff",
border: "#000 1px solid",
border: "#081C42 1px solid",
boxShadow: "none",
width: 18,
height: 18,
@@ -152,16 +163,44 @@ const StyledSwitch = withStyles({
})(Switch);
const FormSwitchWrapper = ({
label,
label = "",
onChange,
value,
id,
name,
checked = false,
disabled = false,
switchOnly = false,
tooltip = "",
indicatorLabel = "",
classes,
}: IFormSwitch) => {
const switchComponent = (
<React.Fragment>
<div className={classes.switchContainer}>
<StyledSwitch
checked={checked}
onChange={onChange}
color="primary"
name={name}
inputProps={{ "aria-label": "primary checkbox" }}
disabled={disabled}
disableRipple
disableFocusRipple
disableTouchRipple
value={value}
/>
{indicatorLabel !== "" && (
<span className={classes.indicatorLabel}>{indicatorLabel}</span>
)}
</div>
</React.Fragment>
);
if (switchOnly) {
return switchComponent;
}
return (
<React.Fragment>
<Grid item xs={12} className={`${classes.wrapperContainer}`}>
@@ -177,21 +216,7 @@ const FormSwitchWrapper = ({
)}
</InputLabel>
)}
<div className={classes.textBoxContainer}>
<StyledSwitch
checked={checked}
onChange={onChange}
color="primary"
name={name}
inputProps={{ "aria-label": "primary checkbox" }}
disabled={disabled}
disableRipple
disableFocusRipple
disableTouchRipple
value={value}
/>
</div>
{switchComponent}
</Grid>
</React.Fragment>
);

View File

@@ -76,11 +76,21 @@ const inputStyles = makeStyles((theme: Theme) =>
borderColor: "#9c9c9c",
},
},
disabled: {
"&.MuiInput-underline::before": {
borderColor: "#eaeaea",
borderBottomStyle: "solid",
},
},
input: {
padding: "15px 5px 10px",
color: "#393939",
fontSize: 13,
fontWeight: 600,
"&:placeholder": {
color: "#393939",
opacity: 1,
},
},
error: {
color: "#b53b4b",

View File

@@ -25,7 +25,7 @@ import {
withStyles,
makeStyles,
} from "@material-ui/core/styles";
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
import { fieldBasic, radioIcons, tooltipHelper } from "../common/styleLibrary";
import HelpIcon from "@material-ui/icons/Help";
export interface SelectorTypes {
@@ -49,8 +49,20 @@ const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
...tooltipHelper,
radioBoxContainer: {
flexGrow: 1,
radioBoxContainer: {},
fieldContainer: {
...fieldBasic.fieldContainer,
display: "flex",
justifyContent: "space-between",
borderBottom: "#9c9c9c 1px solid",
paddingBottom: 10,
marginTop: 11,
},
checkedOption: {
"& .MuiFormControlLabel-label": {
color: "#000",
fontWeight: 700,
},
},
});
@@ -60,31 +72,7 @@ const radioStyles = makeStyles({
backgroundColor: "transparent",
},
},
icon: {
borderRadius: "100%",
width: 14,
height: 14,
border: "1px solid #000",
},
checkedIcon: {
borderRadius: "100%",
width: 14,
height: 14,
border: "1px solid #000",
padding: 4,
position: "relative",
"&::after": {
content: '" "',
width: 8,
height: 8,
borderRadius: "100%",
display: "block",
position: "absolute",
backgroundColor: "#000",
top: 2,
left: 2,
},
},
...radioIcons,
});
const RadioButton = (props: RadioProps) => {
@@ -95,8 +83,8 @@ const RadioButton = (props: RadioProps) => {
className={classes.root}
disableRipple
color="default"
checkedIcon={<span className={classes.checkedIcon} />}
icon={<span className={classes.icon} />}
checkedIcon={<span className={classes.radioSelectedIcon} />}
icon={<span className={classes.radioUnselectedIcon} />}
{...props}
/>
);
@@ -143,6 +131,11 @@ export const RadioGroupSelector = ({
value={selectorOption.value}
control={<RadioButton />}
label={selectorOption.label}
className={
selectorOption.value === currentSelection
? classes.checkedOption
: ""
}
/>
);
})}

View File

@@ -20,7 +20,6 @@ export const fieldBasic = {
inputLabel: {
fontWeight: 600,
marginRight: 10,
width: 160,
fontSize: 15,
color: "#000",
textAlign: "left" as const,
@@ -53,6 +52,30 @@ export const modalBasic = {
formSlider: {
marginLeft: 0,
},
clearButton: {
border: "0",
backgroundColor: "transparent",
color: "#393939",
fontWeight: 600,
fontSize: 14,
marginRight: 10,
outline: "0",
padding: "16px 25px 16px 25px",
cursor: "pointer",
},
floatingEnabled: {
position: "absolute" as const,
right: 58,
zIndex: 1000,
marginTop: -38,
},
configureString: {
border: "#EAEAEA 1px solid",
borderRadius: 4,
padding: "24px 50px",
overflowY: "auto" as const,
height: 170,
},
};
export const tooltipHelper = {
@@ -76,6 +99,21 @@ export const checkboxIcons = {
},
};
const radioBasic = {
width: 12,
height: 12,
borderRadius: "100%",
};
export const radioIcons = {
radioUnselectedIcon: { ...radioBasic, border: "1px solid #000" },
radioSelectedIcon: {
...radioBasic,
border: "1px solid #000",
backgroundColor: "#000",
},
};
export const containerForHeader = (bottomSpacing: any) => ({
container: {
padding: "110px 33px 30px",
@@ -137,5 +175,6 @@ export const predefinedList = {
color: "#393939",
fontSize: 12,
fontWeight: 600,
minHeight: 41,
},
};

View File

@@ -29,11 +29,12 @@ interface IModalProps {
modalOpen: boolean;
title: string;
children: any;
wideLimit?: boolean;
}
const baseCloseLine = {
content: '" "',
borderLeft: "2px solid #707070",
borderLeft: "2px solid #9C9C9C",
height: 33,
width: 1,
position: "absolute",
@@ -61,10 +62,10 @@ const styles = (theme: Theme) =>
},
modalCloseIcon: {
fontSize: 35,
color: "#707070",
color: "#9C9C9C",
fontWeight: 300,
"&:hover": {
color: "#000",
color: "#9C9C9C",
},
},
closeIcon: {
@@ -77,7 +78,7 @@ const styles = (theme: Theme) =>
transform: "rotate(-45deg)",
},
"&:hover::before, &:hover::after": {
borderColor: "#000",
borderColor: "#9C9C9C",
},
width: 24,
height: 24,
@@ -95,6 +96,10 @@ const styles = (theme: Theme) =>
modalContent: {
padding: "0 50px",
},
customDialogSize: {
width: "100%",
maxWidth: 765,
},
});
const ModalWrapper = ({
@@ -103,15 +108,22 @@ const ModalWrapper = ({
title,
children,
classes,
wideLimit = true,
}: IModalProps) => {
const customSize = wideLimit
? {
classes: {
paper: classes.customDialogSize,
},
}
: { maxWidth: "md" as const, fullWidth: true };
return (
<Dialog
open={modalOpen}
onClose={onClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
maxWidth={"md"}
fullWidth
{...customSize}
>
<div className={classes.dialogContainer}>
<div className={classes.closeContainer}>

View File

@@ -33,7 +33,10 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { TablePaginationActionsProps } from "@material-ui/core/TablePagination/TablePaginationActions";
import TableActionButton from "./TableActionButton";
import history from "../../../../history";
import { checkboxIcons } from "../FormComponents/common/styleLibrary";
import {
checkboxIcons,
radioIcons,
} from "../FormComponents/common/styleLibrary";
//Interfaces for table Items
@@ -80,6 +83,7 @@ interface TableWrapperProps {
entityName: string;
selectedItems?: string[];
stickyHeader?: boolean;
radioSelection?: boolean;
paginatorConfig?: IPaginatorConfig;
}
@@ -103,7 +107,8 @@ const styles = (theme: Theme) =>
padding: "19px 38px",
minHeight: "200px",
boxShadow: "none",
border: "#e7e7e7 1px solid",
border: "#EAEDEE 1px solid",
borderRadius: 3,
},
minTableHeader: {
color: "#393939",
@@ -159,6 +164,7 @@ const styles = (theme: Theme) =>
cursor: "pointer",
},
...checkboxIcons,
...radioIcons,
});
// Function that renders Title Columns
@@ -239,6 +245,7 @@ const TableWrapper = ({
idField,
classes,
stickyHeader = false,
radioSelection = false,
paginatorConfig,
}: TableWrapperProps) => {
const findView = itemActions
@@ -333,8 +340,24 @@ const TableWrapper = ({
e.stopPropagation();
e.preventDefault();
}}
checkedIcon={<span className={classes.checkedIcon} />}
icon={<span className={classes.unCheckedIcon} />}
checkedIcon={
<span
className={
radioSelection
? classes.radioSelectedIcon
: classes.checkedIcon
}
/>
}
icon={
<span
className={
radioSelection
? classes.radioUnselectedIcon
: classes.unCheckedIcon
}
/>
}
/>
</TableCell>
)}

View File

@@ -22,6 +22,8 @@ import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import CSVMultiSelector from "../Common/FormComponents/CSVMultiSelector/CSVMultiSelector";
import CommentBoxWrapper from "../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
interface IConfGenericProps {
onChange: (newValue: IElementValue[]) => void;
@@ -94,20 +96,21 @@ const ConfTargetGeneric = ({
const fieldDefinition = (field: KVField, item: number) => {
switch (field.type) {
case "on|off":
const value = valueHolder[item] ? valueHolder[item].value : "false";
return (
<RadioGroupSelector
currentSelection={valueHolder[item] ? valueHolder[item].value : ""}
<FormSwitchWrapper
indicatorLabel="On"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.checked ? "true" : "false";
setValueElement(field.name, value, item);
}}
id={field.name}
name={field.name}
label={field.label}
value={"switch_on"}
tooltip={field.tooltip}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValueElement(field.name, e.target.value, item)
}
selectorOptions={[
{ label: "On", value: "true" },
{ label: "Off", value: "false" },
]}
checked={value === "true"}
/>
);
case "csv":
@@ -122,6 +125,20 @@ const ConfTargetGeneric = ({
tooltip={field.tooltip}
/>
);
case "comment":
return (
<CommentBoxWrapper
id={field.name}
name={field.name}
label={field.label}
tooltip={field.tooltip}
value={valueHolder[item] ? valueHolder[item].value : ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValueElement(field.name, e.target.value, item)
}
placeholder={field.placeholder}
/>
);
default:
return (
<InputBoxWrapper
@@ -134,6 +151,7 @@ const ConfTargetGeneric = ({
setValueElement(field.name, e.target.value, item)
}
multiline={!!field.multiline}
placeholder={field.placeholder}
/>
);
}

View File

@@ -21,7 +21,12 @@ import Grid from "@material-ui/core/Grid";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import { IElementValue } from "../types";
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
import {
modalBasic,
predefinedList,
} from "../../Common/FormComponents/common/styleLibrary";
import CommentBoxWrapper from "../../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
interface IConfMySqlProps {
onChange: (newValue: IElementValue[]) => void;
@@ -31,6 +36,7 @@ interface IConfMySqlProps {
const styles = (theme: Theme) =>
createStyles({
...modalBasic,
...predefinedList,
});
const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
@@ -104,44 +110,41 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
setDsnString(cs);
}, [user, dbName, password, port, host, setDsnString, configToDsnString]);
const switcherChangeEvt = (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);
};
return (
<Grid container className={classes.formScrollable}>
<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"
className={classes.formSlider}
<FormSwitchWrapper
label={"Enter DNS String"}
checked={useDsnString}
id="checkedB"
name="checkedB"
onChange={switcherChangeEvt}
value={"dnsString"}
indicatorLabel={"On"}
/>
</Grid>
{useDsnString ? (
@@ -160,62 +163,78 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
</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} className={classes.configureString}>
<Grid item xs={12}>
<InputBoxWrapper
id="host"
name="host"
label=""
placeholder="Enter 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=""
placeholder="Enter DB Name"
value={dbName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setDbName(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="port"
name="port"
label=""
placeholder="Enter 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 item xs={12}>
<InputBoxWrapper
id="user"
name="user"
label=""
placeholder="Enter User"
value={user}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUser(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="password"
name="password"
label=""
placeholder="Enter Password"
type="password"
value={password}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
}}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.predefinedTitle}>
Connection String
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{dsnString}
</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);
}}
/>
<br />
</Grid>
</React.Fragment>
)}
@@ -224,6 +243,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
id="table"
name="table"
label="Table"
placeholder="Enter Table Name"
value={table}
tooltip="DB table name to store/update events, table is auto-created"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -252,6 +272,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
id="queue-dir"
name="queue_dir"
label="Queue Dir"
placeholder="Enter Queue Dir"
value={queueDir}
tooltip="staging dir for undelivered messages e.g. '/home/events'"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -264,6 +285,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
id="queue-limit"
name="queue_limit"
label="Queue Limit"
placeholder="Enter Queue Limit"
type="number"
value={queueLimit}
tooltip="maximum limit for undelivered messages, defaults to '10000'"
@@ -273,11 +295,11 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => {
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
<CommentBoxWrapper
id="comment"
name="comment"
label="Comment"
multiline={true}
placeholder="Enter Comment"
value={comment}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setComment(e.target.value);

View File

@@ -22,7 +22,12 @@ import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBo
import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import { IElementValue } from "../types";
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
import {
modalBasic,
predefinedList,
} from "../../Common/FormComponents/common/styleLibrary";
import CommentBoxWrapper from "../../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
interface IConfPostgresProps {
onChange: (newValue: IElementValue[]) => void;
@@ -32,6 +37,7 @@ interface IConfPostgresProps {
const styles = (theme: Theme) =>
createStyles({
...modalBasic,
...predefinedList,
});
const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
@@ -45,7 +51,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
const [port, setPort] = useState<string>("");
const [user, setUser] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [sslMode, setSslMode] = useState<string>("require");
const [sslMode, setSslMode] = useState<string>(" ");
const [table, setTable] = useState<string>("");
const [format, setFormat] = useState<string>("namespace");
@@ -126,8 +132,11 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
if (port !== "") {
strValue = `${strValue} port=${port}`;
}
if (sslMode !== " ") {
strValue = `${strValue} sslmode=${sslMode}`;
}
strValue = `${strValue} sslmode=${sslMode}`;
strValue = `${strValue} `;
return strValue.trim();
}, [host, dbName, user, password, port, sslMode]);
@@ -169,48 +178,44 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
configToString,
]);
useEffect(() => {
if (useConnectionString) {
// build connection_string
const cs = configToString();
setConnectionString(cs);
return;
}
// 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") ? kv.get("sslmode") + "" : " ");
}, [useConnectionString]);
return (
<Grid container className={classes.formScrollable}>
<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") ? kv.get("sslmode") + "" : "require"
);
}
setUseConnectionString(event.target.checked);
}}
name="checkedB"
color="primary"
/>
}
label="Enter Connection String"
className={classes.formSlider}
<FormSwitchWrapper
label={"Manually Configure String"}
checked={useConnectionString}
id="manualString"
name="manualString"
onChange={(e) => {
setUseConnectionString(e.target.checked);
}}
value={"manualString"}
indicatorLabel={"On"}
/>
</Grid>
{useConnectionString ? (
@@ -229,81 +234,97 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
</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 item xs={12} className={classes.configureString}>
<Grid item xs={12}>
<InputBoxWrapper
id="host"
name="host"
label=""
placeholder="Enter 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=""
placeholder="Enter DB Name"
value={dbName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setDbName(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="port"
name="port"
label=""
placeholder="Enter Port"
value={port}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPort(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<SelectWrapper
value={sslMode}
label=""
id="sslmode"
name="sslmode"
onChange={(e): void => {
if (e.target.value !== undefined) {
setSslMode(e.target.value + "");
}
}}
options={[
{ label: "Enter SSL Mode", value: " " },
{ label: "Require", value: "require" },
{ label: "Disable", value: "disable" },
{ label: "Verify CA", value: "verify-ca" },
{ label: "Verify Full", value: "verify-full" },
]}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="user"
name="user"
label=""
placeholder="Enter User"
value={user}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUser(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="password"
name="password"
label=""
type="password"
placeholder="Enter Password"
value={password}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
}}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.predefinedTitle}>
Connection String
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{connectionString}
</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}>
<SelectWrapper
value={sslMode}
label="SSL Mode"
id="sslmode"
name="sslmode"
onChange={(e): void => {
if (e.target.value !== undefined) {
setSslMode(e.target.value + "");
}
}}
options={[
{ label: "Require", value: "require" },
{ label: "Disable", value: "disable" },
{ label: "Verify CA", value: "verify-ca" },
{ label: "Verify Full", value: "verify-full" },
]}
/>
</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);
}}
/>
<br />
</Grid>
</React.Fragment>
)}
@@ -312,6 +333,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
id="table"
name="table"
label="Table"
placeholder={"Enter Table Name"}
value={table}
tooltip="DB table name to store/update events, table is auto-created"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -340,6 +362,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
id="queue-dir"
name="queue_dir"
label="Queue Dir"
placeholder="Enter Queue Directory"
value={queueDir}
tooltip="staging dir for undelivered messages e.g. '/home/events'"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -352,6 +375,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
id="queue-limit"
name="queue_limit"
label="Queue Limit"
placeholder="Enter Queue Limit"
type="number"
value={queueLimit}
tooltip="maximum limit for undelivered messages, defaults to '10000'"
@@ -361,11 +385,11 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => {
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
<CommentBoxWrapper
id="comment"
name="comment"
label="Comment"
multiline={true}
placeholder="Enter Comment"
value={comment}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setComment(e.target.value);

View File

@@ -27,7 +27,9 @@ export type KVFieldType =
| "duration"
| "uri"
| "sentence"
| "csv";
| "csv"
| "comment"
| "switch";
export interface KVField {
name: string;
@@ -37,6 +39,7 @@ export interface KVField {
type: KVFieldType;
options?: SelectorTypes[];
multiline?: boolean;
placeholder?: string;
}
export interface IConfigurationElement {

View File

@@ -71,7 +71,7 @@ export const fieldsConfigurations: any = {
required: false,
label: "comment",
tooltip: "You can add a comment to this setting",
type: "string",
type: "comment",
multiline: true,
},
],
@@ -132,7 +132,7 @@ export const fieldsConfigurations: any = {
required: false,
label: "Comment",
tooltip: "You can add a comment to this setting",
type: "string",
type: "comment",
multiline: true,
},
],
@@ -196,7 +196,7 @@ export const fieldsConfigurations: any = {
required: false,
label: "Comment",
tooltip: "You can add a comment to this setting",
type: "string",
type: "comment",
multiline: true,
},
],
@@ -304,7 +304,7 @@ export const fieldsConfigurations: any = {
required: false,
label: "Comment",
tooltip: "Optionally add a comment to this setting",
type: "string",
type: "comment",
multiline: true,
},
],
@@ -343,11 +343,12 @@ export const fieldsConfigurations: any = {
const commonFields = [
{
name: "queue-dir",
label: "Queue Dir",
label: "Queue Directory",
required: true,
tooltip: "staging dir for undelivered messages e.g. '/home/events'",
type: "string",
placeholder: "Enter Queue Directory",
},
{
name: "queue-limit",
@@ -356,13 +357,14 @@ const commonFields = [
tooltip: "maximum limit for undelivered messages, defaults to '10000'",
type: "number",
placeholder: "Enter Queue Limit",
},
{
name: "comment",
label: "Comment",
required: false,
type: "string",
multiline: true,
type: "comment",
placeholder: "Enter Comment",
},
];
@@ -373,46 +375,414 @@ export const notificationEndpointsFields: any = {
label: "Brokers",
required: true,
tooltip: "comma separated list of Kafka broker addresses",
tooltip: "Comma separated list of Kafka broker addresses",
type: "string",
placeholder: "Enter Brokers",
},
{
name: "topic",
label: "Topic",
tooltip: "Kafka topic used for bucket notifications",
type: "string",
placeholder: "Enter Topic",
},
{
name: "sasl_username",
label: "SASL Username",
tooltip: "username for SASL/PLAIN or SASL/SCRAM authentication",
tooltip: "Username for SASL/PLAIN or SASL/SCRAM authentication",
type: "string",
placeholder: "Enter SASL Username",
},
{
name: "sasl_password",
label: "SASL Password",
tooltip: "password for SASL/PLAIN or SASL/SCRAM authentication",
tooltip: "Password for SASL/PLAIN or SASL/SCRAM authentication",
type: "string",
placeholder: "Enter SASL Password",
},
{
name: "sasl_mechanism",
label: "SASL Mechanism",
tooltip: "sasl authentication mechanism, default 'PLAIN'",
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",
"Client Auth determines the Kafka server's policy for TLS client auth",
type: "string",
placeholder: "Enter TLS Client Auth",
},
{
name: "sasl",
label: "SASL",
tooltip: "set to 'on' to enable SASL authentication",
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",
placeholder: "Enter TLS Client Cert",
},
{
name: "client_tls_key",
label: "client TLS key",
tooltip: "Path to client key for mTLS auth",
type: "path",
placeholder: "Enter TLS Client Key",
},
{
name: "version",
label: "Version",
tooltip: "Specify the version of the Kafka cluster e.g '2.2.0'",
type: "string",
placeholder: "Enter Kafka Version",
},
...commonFields,
],
[notifyAmqp]: [
{
name: "url",
required: true,
label: "URL",
tooltip:
"AMQP server endpoint e.g. `amqp://myuser:mypassword@localhost:5672`",
type: "url",
},
{
name: "exchange",
label: "Exchange",
tooltip: "Name of the AMQP exchange",
type: "string",
placeholder: "Enter Exchange",
},
{
name: "exchange_type",
label: "Exchange Type",
tooltip: "AMQP exchange type",
type: "string",
placeholder: "Enter Exchange Type",
},
{
name: "routing_key",
label: "Routing Key",
tooltip: "Routing key for publishing",
type: "string",
placeholder: "Enter Routing Key",
},
{
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",
placeholder: "Enter Delivery Mode",
},
...commonFields,
],
[notifyRedis]: [
{
name: "address",
required: true,
label: "Address",
tooltip: "Redis server's address. For example: `localhost:6379`",
type: "address",
placeholder: "Enter Address",
},
{
name: "key",
required: true,
label: "Key",
tooltip: "Redis key to store/update events, key is auto-created",
type: "string",
placeholder: "Enter Key",
},
{
name: "password",
label: "Password",
tooltip: "Redis server password",
type: "string",
placeholder: "Enter Password",
},
...commonFields,
],
[notifyMqtt]: [
{
name: "broker",
required: true,
label: "Broker",
tooltip: "MQTT server endpoint e.g. `tcp://localhost:1883`",
type: "uri",
placeholder: "Enter Brokers",
},
{
name: "topic",
required: true,
label: "Topic",
tooltip: "name of the MQTT topic to publish",
type: "string",
placeholder: "Enter Topic",
},
{
name: "username",
label: "Username",
tooltip: "MQTT username",
type: "string",
placeholder: "Enter Username",
},
{
name: "password",
label: "Password",
tooltip: "MQTT password",
type: "string",
placeholder: "Enter Password",
},
{
name: "qos",
label: "QOS",
tooltip: "Set the quality of service priority, defaults to '0'",
type: "number",
placeholder: "Enter QOS",
},
{
name: "keep_alive_interval",
label: "Keep Alive Interval",
tooltip: "Keep-alive interval for MQTT connections in s,m,h,d",
type: "duration",
placeholder: "Enter Keep Alive Internal",
},
{
name: "reconnect_interval",
label: "Reconnect Interval",
tooltip: "Reconnect interval for MQTT connections in s,m,h,d",
type: "duration",
placeholder: "Enter Reconnect Interval",
},
...commonFields,
],
[notifyNats]: [
{
name: "address",
required: true,
label: "Address",
tooltip: "NATS server address e.g. '0.0.0.0:4222'",
type: "address",
placeholder: "Enter Address",
},
{
name: "subject",
required: true,
label: "Subject",
tooltip: "NATS subscription subject",
type: "string",
placeholder: "Enter NATS Subject",
},
{
name: "username",
label: "Username",
tooltip: "NATS username",
type: "string",
placeholder: "Enter NATS Username",
},
{
name: "password",
label: "Password",
tooltip: "NATS password",
type: "string",
placeholder: "Enter NATS password",
},
{
name: "token",
label: "Token",
tooltip: "NATS token",
type: "string",
placeholder: "Enter NATS token",
},
{
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",
placeholder: "Enter Ping Interval",
},
{
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 publish ACKS in flight",
tooltip: "Number of messages to publish without waiting for ACKs",
type: "number",
placeholder: "Enter Streaming in flight value",
},
{
name: "streaming_cluster_id",
label: "Streaming Cluster ID",
tooltip: "Unique ID for NATS streaming cluster",
type: "string",
placeholder: "Enter Streaming Cluster ID",
},
{
name: "cert_authority",
label: "Cert Authority",
tooltip: "Path to certificate chain of the target NATS server",
type: "string",
placeholder: "Enter Cert Authority",
},
{
name: "client_cert",
label: "Client Cert",
tooltip: "Client cert for NATS mTLS auth",
type: "string",
placeholder: "Enter Client Cert",
},
{
name: "client_key",
label: "Client Key",
tooltip: "Client cert key for NATS mTLS auth",
type: "string",
placeholder: "Enter Client Key",
},
...commonFields,
],
[notifyElasticsearch]: [
{
name: "url",
required: true,
label: "URL",
tooltip:
"Elasticsearch server's address, with optional authentication info",
type: "url",
placeholder: "Enter URL",
},
{
name: "index",
required: true,
label: "Index",
tooltip:
"Elasticsearch index to store/update events, index is auto-created",
type: "string",
placeholder: "Enter Index",
},
{
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",
placeholder: "Enter Format",
},
...commonFields,
],
[notifyWebhooks]: [
{
name: "endpoint",
required: true,
label: "Endpoint",
tooltip:
"webhook server endpoint e.g. http://localhost:8080/minio/events",
type: "url",
placeholder: "Enter Endpoint",
},
{
name: "auth_token",
label: "Auth Token",
tooltip: "opaque string or JWT authorization token",
type: "string",
placeholder: "Enter auth_token",
},
...commonFields,
],
[notifyNsq]: [
{
name: "nsqd_address",
required: true,
label: "NSQD Address",
tooltip: "NSQ server address e.g. '127.0.0.1:4150'",
type: "address",
placeholder: "Enter nsqd_address",
},
{
name: "topic",
required: true,
label: "Topic",
tooltip: "NSQ topic",
type: "string",
placeholder: "Enter Topic",
},
{
name: "tls",
label: "TLS",
@@ -421,335 +791,7 @@ export const notificationEndpointsFields: any = {
},
{
name: "tls_skip_verify",
label: "TLS skip verify",
tooltip:
'trust server TLS without verification, defaults to "on" (verify)',
type: "on|off",
},
{
name: "client_tls_cert",
label: "client TLS cert",
tooltip: "path to client certificate for mTLS auth",
type: "path",
},
{
name: "client_tls_key",
label: "client TLS key",
tooltip: "path to client key for mTLS auth",
type: "path",
},
{
name: "version",
label: "Version",
tooltip: "specify the version of the Kafka cluster e.g '2.2.0'",
type: "string",
},
...commonFields,
],
[notifyAmqp]: [
{
name: "url",
required: true,
label: "url",
tooltip:
"AMQP server endpoint e.g. `amqp://myuser:mypassword@localhost:5672`",
type: "url",
},
{
name: "exchange",
label: "exchange",
tooltip: "name of the AMQP exchange",
type: "string",
},
{
name: "exchange_type",
label: "exchange_type",
tooltip: "AMQP exchange type",
type: "string",
},
{
name: "routing_key",
label: "routing_key",
tooltip: "routing key for publishing",
type: "string",
},
{
name: "mandatory",
label: "mandatory",
tooltip:
"quietly ignore undelivered messages when set to 'off', default is 'on'",
type: "on|off",
},
{
name: "durable",
label: "durable",
tooltip:
"persist queue across broker restarts when set to 'on', default is 'off'",
type: "on|off",
},
{
name: "no_wait",
label: "no_wait",
tooltip:
"non-blocking message delivery when set to 'on', default is 'off'",
type: "on|off",
},
{
name: "internal",
label: "internal",
tooltip:
"set to 'on' for exchange to be not used directly by publishers, but only when bound to other exchanges",
type: "on|off",
},
{
name: "auto_deleted",
label: "auto_deleted",
tooltip:
"auto delete queue when set to 'on', when there are no consumers",
type: "on|off",
},
{
name: "delivery_mode",
label: "delivery_mode",
tooltip: "set to '1' for non-persistent or '2' for persistent queue",
type: "number",
},
...commonFields,
],
[notifyRedis]: [
{
name: "address",
required: true,
label: "address",
tooltip: "Redis server's address. For example: `localhost:6379`",
type: "address",
},
{
name: "key",
required: true,
label: "key",
tooltip: "Redis key to store/update events, key is auto-created",
type: "string",
},
{
name: "password",
label: "password",
tooltip: "Redis server password",
type: "string",
},
...commonFields,
],
[notifyMqtt]: [
{
name: "broker",
required: true,
label: "broker",
tooltip: "MQTT server endpoint e.g. `tcp://localhost:1883`",
type: "uri",
},
{
name: "topic",
required: true,
label: "topic",
tooltip: "name of the MQTT topic to publish",
type: "string",
},
{
name: "username",
label: "username",
tooltip: "MQTT username",
type: "string",
},
{
name: "password",
label: "password",
tooltip: "MQTT password",
type: "string",
},
{
name: "qos",
label: "qos",
tooltip: "set the quality of service priority, defaults to '0'",
type: "number",
},
{
name: "keep_alive_interval",
label: "keep_alive_interval",
tooltip: "keep-alive interval for MQTT connections in s,m,h,d",
type: "duration",
},
{
name: "reconnect_interval",
label: "reconnect_interval",
tooltip: "reconnect interval for MQTT connections in s,m,h,d",
type: "duration",
},
...commonFields,
],
[notifyNats]: [
{
name: "address",
required: true,
label: "address",
tooltip: "NATS server address e.g. '0.0.0.0:4222'",
type: "address",
},
{
name: "subject",
required: true,
label: "subject",
tooltip: "NATS subscription subject",
type: "string",
},
{
name: "username",
label: "username",
tooltip: "NATS username",
type: "string",
},
{
name: "password",
label: "password",
tooltip: "NATS password",
type: "string",
},
{
name: "token",
label: "token",
tooltip: "NATS token",
type: "string",
},
{
name: "tls",
label: "tls",
tooltip: "set to 'on' to enable TLS",
type: "on|off",
},
{
name: "tls_skip_verify",
label: "tls_skip_verify",
tooltip:
'trust server TLS without verification, defaults to "on" (verify)',
type: "on|off",
},
{
name: "ping_interval",
label: "ping_interval",
tooltip: "client ping commands interval in s,m,h,d. Disabled by default",
type: "duration",
},
{
name: "streaming",
label: "streaming",
tooltip: "set to 'on', to use streaming NATS server",
type: "on|off",
},
{
name: "streaming_async",
label: "streaming_async",
tooltip: "set to 'on', to enable asynchronous publish",
type: "on|off",
},
{
name: "streaming_max_pub_acks_in_flight",
label: "streaming_max_pub_acks_in_flight",
tooltip: "number of messages to publish without waiting for ACKs",
type: "number",
},
{
name: "streaming_cluster_id",
label: "streaming_cluster_id",
tooltip: "unique ID for NATS streaming cluster",
type: "string",
},
{
name: "cert_authority",
label: "cert_authority",
tooltip: "path to certificate chain of the target NATS server",
type: "string",
},
{
name: "client_cert",
label: "client_cert",
tooltip: "client cert for NATS mTLS auth",
type: "string",
},
{
name: "client_key",
label: "client_key",
tooltip: "client cert key for NATS mTLS auth",
type: "string",
},
...commonFields,
],
[notifyElasticsearch]: [
{
name: "url",
required: true,
label: "url",
tooltip:
"Elasticsearch server's address, with optional authentication info",
type: "url",
},
{
name: "index",
required: true,
label: "index",
tooltip:
"Elasticsearch index to store/update events, index is auto-created",
type: "string",
},
{
name: "format",
required: true,
label: "format",
tooltip:
"'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'",
type: "enum",
},
...commonFields,
],
[notifyWebhooks]: [
{
name: "endpoint",
required: true,
label: "endpoint",
tooltip:
"webhook server endpoint e.g. http://localhost:8080/minio/events",
type: "url",
},
{
name: "auth_token",
label: "auth_token",
tooltip: "opaque string or JWT authorization token",
type: "string",
},
...commonFields,
],
[notifyNsq]: [
{
name: "nsqd_address",
required: true,
label: "nsqd_address",
tooltip: "NSQ server address e.g. '127.0.0.1:4150'",
type: "address",
},
{
name: "topic",
required: true,
label: "topic",
tooltip: "NSQ topic",
type: "string",
},
{
name: "tls",
label: "tls",
tooltip: "set to 'on' to enable TLS",
type: "on|off",
},
{
name: "tls_skip_verify",
label: "tls_skip_verify",
label: "TLS Skip Verify",
tooltip:
'trust server TLS without verification, defaults to "on" (verify)',
type: "on|off",

View File

@@ -19,12 +19,16 @@ 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 { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import {
modalBasic,
predefinedList,
} from "../Common/FormComponents/common/styleLibrary";
import api from "../../../common/api";
import UsersSelectors from "./UsersSelectors";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
interface IGroupProps {
open: boolean;
@@ -54,6 +58,7 @@ const styles = (theme: Theme) =>
textAlign: "right",
},
...modalBasic,
...predefinedList,
});
const AddGroup = ({
@@ -64,11 +69,12 @@ const AddGroup = ({
}: IGroupProps) => {
//Local States
const [groupName, setGroupName] = useState<string>("");
const [groupEnabled, setGroupEnabled] = useState<string>("");
const [groupEnabled, setGroupEnabled] = useState<boolean>(false);
const [saving, isSaving] = useState<boolean>(false);
const [addError, setError] = useState<string>("");
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
const [loadingGroup, isLoadingGroup] = useState<boolean>(false);
const [validGroup, setValidGroup] = useState<boolean>(false);
//Effects
useEffect(() => {
@@ -80,6 +86,10 @@ const AddGroup = ({
}
}, [selectedGroup]);
useEffect(() => {
setValidGroup(groupName.trim() !== "");
}, [groupName, selectedUsers]);
useEffect(() => {
if (saving) {
const saveRecord = () => {
@@ -88,7 +98,7 @@ const AddGroup = ({
.invoke("PUT", `/api/v1/groups/${groupName}`, {
group: groupName,
members: selectedUsers,
status: groupEnabled,
status: groupEnabled ? "enabled" : "disabled",
})
.then((res) => {
isSaving(false);
@@ -133,7 +143,7 @@ const AddGroup = ({
api
.invoke("GET", `/api/v1/groups/${selectedGroup}`)
.then((res: MainGroupProps) => {
setGroupEnabled(res.status);
setGroupEnabled(res.status === "enabled");
setGroupName(res.name);
setSelectedUsers(res.members);
})
@@ -153,12 +163,35 @@ const AddGroup = ({
isSaving(true);
};
const resetForm = () => {
if (selectedGroup === null) {
setGroupName("");
}
setSelectedUsers([]);
};
return (
<ModalWrapper
modalOpen={open}
onClose={closeModalAndRefresh}
title={selectedGroup !== null ? `Group Edit - ${groupName}` : "Add Group"}
title={selectedGroup !== null ? `Edit Group` : "Create Group"}
>
{selectedGroup !== null && (
<div className={classes.floatingEnabled}>
<FormSwitchWrapper
indicatorLabel={"Enabled"}
checked={groupEnabled}
value={"group_enabled"}
id="group-status"
name="group-status"
onChange={(e) => {
setGroupEnabled(e.target.checked);
}}
switchOnly
/>
</div>
)}
<form noValidate autoComplete="off" onSubmit={setSaving}>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
@@ -174,31 +207,13 @@ const AddGroup = ({
</Grid>
)}
{selectedGroup !== null ? (
<React.Fragment>
<Grid item xs={12}>
<RadioGroupSelector
currentSelection={groupEnabled}
id="group-status"
name="group-status"
label="Status"
onChange={(e) => {
setGroupEnabled(e.target.value);
}}
selectorOptions={[
{ label: "Enabled", value: "enabled" },
{ label: "Disabled", value: "disabled" },
]}
/>
</Grid>
</React.Fragment>
) : (
{selectedGroup === null ? (
<React.Fragment>
<Grid item xs={12}>
<InputBoxWrapper
id="group-name"
name="group-name"
label="Name"
label="Group Name"
value={groupName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setGroupName(e.target.value);
@@ -206,23 +221,38 @@ const AddGroup = ({
/>
</Grid>
</React.Fragment>
) : (
<React.Fragment>
<Grid item xs={12} className={classes.predefinedTitle}>
Group Name
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{selectedGroup}
</Grid>
</React.Fragment>
)}
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<UsersSelectors
selectedUsers={selectedUsers}
setSelectedUsers={setSelectedUsers}
editMode={selectedGroup !== null}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetForm}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={saving}
disabled={saving || !validGroup}
>
Save
</Button>

View File

@@ -28,11 +28,16 @@ import TextField from "@material-ui/core/TextField";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import {
actionsTray,
predefinedList,
} from "../Common/FormComponents/common/styleLibrary";
interface IGroupsProps {
classes: any;
selectedUsers: string[];
setSelectedUsers: any;
editMode?: boolean;
}
const styles = (theme: Theme) =>
@@ -41,10 +46,11 @@ const styles = (theme: Theme) =>
marginTop: theme.spacing(3),
},
paper: {
// padding: theme.spacing(2),
display: "flex",
overflow: "auto",
flexDirection: "column",
paddingTop: 15,
boxShadow: "none",
},
addSideBar: {
width: "320px",
@@ -70,36 +76,43 @@ const styles = (theme: Theme) =>
},
},
},
actionsTray: {
textAlign: "left",
"& button": {
marginLeft: 10,
},
},
filterField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
width: "100%",
zIndex: 500,
},
noFound: {
textAlign: "center",
padding: "10px 0",
},
tableContainer: {
maxHeight: 250,
maxHeight: 200,
},
stickyHeader: {
backgroundColor: "#fff",
},
actionsTitle: {
fontWeight: 600,
color: "#000",
fontSize: 16,
alignSelf: "center",
},
tableBlock: {
marginTop: 15,
},
filterField: {
width: 375,
fontWeight: 600,
"& .input": {
"&::placeholder": {
fontWeight: 600,
color: "#000",
},
},
},
...actionsTray,
});
const UsersSelectors = ({
classes,
selectedUsers,
setSelectedUsers,
editMode = false,
}: IGroupsProps) => {
//Local States
const [records, setRecords] = useState<any[]>([]);
@@ -166,21 +179,22 @@ const UsersSelectors = ({
return (
<React.Fragment>
<Title>Assign Users</Title>
{error !== "" ? <div>{error}</div> : <React.Fragment />}
<Grid item xs={12}>
<Paper className={classes.paper}>
{loading && <LinearProgress />}
{error !== "" ? <div>{error}</div> : <React.Fragment />}
{records != null && records.length > 0 ? (
<React.Fragment>
<Grid item xs={12} className={classes.actionsTray}>
<span className={classes.actionsTitle}>
{editMode ? "Edit" : "Assign"} Members
</span>
<TextField
placeholder="Filter Groups"
className={classes.filterField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
@@ -192,7 +206,7 @@ const UsersSelectors = ({
}}
/>
</Grid>
<Grid item xs={12}>
<Grid item xs={12} className={classes.tableBlock}>
<TableWrapper
columns={[{ label: "Access Key", elementKey: "accessKey" }]}
onSelect={selectionChanged}

View File

@@ -42,6 +42,7 @@ import {
removeEmptyFields,
} from "../Configurations/utils";
import { IElementValue } from "../Configurations/types";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
const styles = (theme: Theme) =>
createStyles({
@@ -60,6 +61,69 @@ const styles = (theme: Theme) =>
logoButton: {
height: "80px",
},
lambdaNotif: {
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 {
@@ -136,186 +200,111 @@ const AddNotificationEndpoint = ({
}
}
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;
}
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={`Add Lambda Notification Target ${targetTitle}`}
>
<ModalWrapper modalOpen={open} onClose={closeModalAndRefresh} title={""}>
{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>
<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 (
<a
key={`icon-${item.targetTitle}`}
className={classes.lambdaNotif}
onClick={() => {
setService(item.actionTrigger);
}}
>
<img
src={item.logo}
className={classes.logoButton}
alt={item.targetTitle}
/>
</a>
);
})}
</div>
</Grid>
<Grid item xs={12}>
<br />
@@ -342,10 +331,37 @@ const AddNotificationEndpoint = ({
</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"

View File

@@ -16,19 +16,18 @@
import React from "react";
import Grid from "@material-ui/core/Grid";
import { UnControlled as CodeMirror } from "react-codemirror2";
import Typography from "@material-ui/core/Typography";
import { Button, LinearProgress } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import api from "../../../common/api";
import "codemirror/lib/codemirror.css";
import "codemirror/theme/material.css";
import { Policy } from "./types";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import {
fieldBasic,
modalBasic,
} from "../Common/FormComponents/common/styleLibrary";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
require("codemirror/mode/javascript/javascript");
import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
const styles = (theme: Theme) =>
createStyles({
@@ -39,26 +38,11 @@ const styles = (theme: Theme) =>
minHeight: 400,
width: "100%",
},
codeMirror: {
fontSize: 14,
"& .CodeMirror": {
color: "#fff",
backgroundColor: "#081C42",
},
"& .CodeMirror-gutter": {
backgroundColor: "#081C4280",
},
"& .CodeMirror-linenumber": {
color: "#000",
fontSize: 10,
height: 20,
lineHeight: "20px",
},
},
buttonContainer: {
textAlign: "right",
},
...modalBasic,
...fieldBasic,
});
interface IAddPolicyProps {
@@ -121,13 +105,26 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
if (policyEdit) {
this.setState({
policyName: policyEdit.name,
policyDefinition: policyEdit
? JSON.stringify(JSON.parse(policyEdit.policy), null, 4)
: "",
});
}
}
resetForm() {
this.setState({
policyName: "",
policyDefinition: "",
});
}
render() {
const { classes, open, policyEdit } = this.props;
const { addLoading, addError, policyName } = this.state;
const { addLoading, addError, policyName, policyDefinition } = this.state;
const validSave = policyName.trim() !== "";
return (
<ModalWrapper
modalOpen={open}
@@ -163,6 +160,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
id="policy-name"
name="policy-name"
label="Policy Name"
placeholder="Enter Policy Name"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ policyName: e.target.value });
}}
@@ -173,31 +171,32 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<CodeMirror
className={classes.codeMirror}
value={
policyEdit
? JSON.stringify(JSON.parse(policyEdit.policy), null, 4)
: ""
}
options={{
mode: "javascript",
lineNumbers: true,
}}
onChange={(editor, data, value) => {
this.setState({ policyDefinition: value });
}}
/>
</Grid>
<CodeMirrorWrapper
label="Write Policy"
value={policyDefinition}
onBeforeChange={(editor, data, value) => {
this.setState({ policyDefinition: value });
}}
readOnly={!!policyEdit}
/>
</Grid>
{!policyEdit && (
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={() => {
this.resetForm();
}}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
disabled={addLoading || !validSave}
>
Save
</Button>

View File

@@ -23,7 +23,7 @@ import {
DialogContent,
DialogContentText,
DialogTitle,
LinearProgress
LinearProgress,
} from "@material-ui/core";
import api from "../../../common/api";
import { PolicyList } from "./types";
@@ -32,8 +32,8 @@ import Typography from "@material-ui/core/Typography";
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
}
color: "red",
},
});
interface IDeletePolicyProps {
@@ -54,7 +54,7 @@ class DeletePolicy extends React.Component<
> {
state: IDeletePolicyState = {
deleteLoading: false,
deleteError: ""
deleteError: "",
};
removeRecord() {
const { deleteLoading } = this.state;
@@ -69,17 +69,17 @@ class DeletePolicy extends React.Component<
this.setState(
{
deleteLoading: false,
deleteError: ""
deleteError: "",
},
() => {
this.props.closeDeleteModalAndRefresh(true);
}
);
})
.catch(err => {
.catch((err) => {
this.setState({
deleteLoading: false,
deleteError: err
deleteError: err,
});
});
});
@@ -98,7 +98,7 @@ class DeletePolicy extends React.Component<
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Delete Bucket</DialogTitle>
<DialogTitle id="alert-dialog-title">Delete Policy</DialogTitle>
<DialogContent>
{deleteLoading && <LinearProgress />}
<DialogContentText id="alert-dialog-description">

View File

@@ -0,0 +1,204 @@
// This file is part of MinIO Kubernetes Cloud
// 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 { LinearProgress } from "@material-ui/core";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import TextField from "@material-ui/core/TextField";
import api from "../../../common/api";
import { policySort } from "../../../utils/sortFunctions";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import { actionsTray } from "../Common/FormComponents/common/styleLibrary";
import { PolicyList } from "./types";
interface ISelectPolicyProps {
classes: any;
selectedPolicy?: string;
setSelectedPolicy: any;
}
const styles = (theme: Theme) =>
createStyles({
seeMore: {
marginTop: theme.spacing(3),
},
paper: {
display: "flex",
overflow: "auto",
flexDirection: "column",
paddingTop: 15,
boxShadow: "none",
},
addSideBar: {
width: "320px",
padding: "20px",
},
errorBlock: {
color: "red",
},
tableToolbar: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(0),
},
wrapCell: {
maxWidth: "200px",
whiteSpace: "normal",
wordWrap: "break-word",
},
minTableHeader: {
color: "#393939",
"& tr": {
"& th": {
fontWeight: "bold",
},
},
},
noFound: {
textAlign: "center",
padding: "10px 0",
},
tableContainer: {
maxHeight: 200,
},
stickyHeader: {
backgroundColor: "#fff",
},
actionsTitle: {
fontWeight: 600,
color: "#000",
fontSize: 16,
alignSelf: "center",
},
tableBlock: {
marginTop: 15,
},
filterField: {
width: 375,
fontWeight: 600,
"& .input": {
"&::placeholder": {
fontWeight: 600,
color: "#000",
},
},
},
...actionsTray,
});
const PolicySelectors = ({
classes,
selectedPolicy = "",
setSelectedPolicy,
}: ISelectPolicyProps) => {
// Local State
const [records, setRecords] = useState<any[]>([]);
const [loading, isLoading] = useState<boolean>(false);
const [error, setError] = useState<string>("");
const [filter, setFilter] = useState<string>("");
//Effects
useEffect(() => {
isLoading(true);
}, []);
useEffect(() => {
if (loading) {
fetchPolicies();
}
}, [loading]);
const fetchPolicies = () => {
isLoading(true);
api
.invoke("GET", `/api/v1/policies?limit=1000`)
.then((res: PolicyList) => {
const policies = res.policies === null ? [] : res.policies;
isLoading(false);
setRecords(policies.sort(policySort));
setError("");
})
.catch((err) => {
isLoading(false);
setError(err);
});
};
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const targetD = e.target;
const value = targetD.value;
setSelectedPolicy(value);
};
const filteredRecords = records.filter((elementItem) =>
elementItem.name.includes(filter)
);
return (
<React.Fragment>
<Grid item xs={12}>
<Paper className={classes.paper}>
{loading && <LinearProgress />}
{error !== "" && <div>{error}</div>}
{records != null && records.length > 0 ? (
<React.Fragment>
<Grid item xs={12} className={classes.actionsTray}>
<span className={classes.actionsTitle}>Assign Policies</span>
<TextField
placeholder="Filter by Policy"
className={classes.filterField}
id="search-resource"
label=""
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
setFilter(e.target.value);
}}
/>
</Grid>
<Grid item xs={12} className={classes.tableBlock}>
<TableWrapper
columns={[{ label: "Policy", elementKey: "name" }]}
onSelect={selectionChanged}
selectedItems={[selectedPolicy]}
isLoading={loading}
records={filteredRecords}
entityName="Policies"
idField="name"
radioSelection
/>
</Grid>
</React.Fragment>
) : (
<div className={classes.noFound}>No Policies Available</div>
)}
</Paper>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(PolicySelectors);

View File

@@ -15,6 +15,7 @@
// 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,
@@ -28,13 +29,17 @@ import {
TableRow,
} from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import {
modalBasic,
predefinedList,
} from "../Common/FormComponents/common/styleLibrary";
import { User } from "../Users/types";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import { Policy, PolicyList } from "./types";
import api from "../../../common/api";
import { policySort } from "../../../utils/sortFunctions";
import { Group } from "../Groups/types";
import PolicySelectors from "./PolicySelectors";
interface ISetPolicyProps {
classes: any;
@@ -47,6 +52,7 @@ interface ISetPolicyProps {
const styles = (theme: Theme) =>
createStyles({
...modalBasic,
...predefinedList,
buttonContainer: {
textAlign: "right",
},
@@ -60,28 +66,12 @@ const SetPolicy = ({
open,
}: ISetPolicyProps) => {
//Local States
const [records, setRecords] = useState<Policy[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [actualPolicy, setActualPolicy] = useState<string>("");
const [selectedPolicy, setSelectedPolicy] = useState<string>("");
const [error, setError] = useState<string>("");
const fetchRecords = () => {
setLoading(true);
api
.invoke("GET", `/api/v1/policies?limit=1000`)
.then((res: PolicyList) => {
const policies = res.policies === null ? [] : res.policies;
setLoading(false);
setRecords(policies.sort(policySort));
setError("");
})
.catch((err) => {
setLoading(false);
setError(err);
});
};
const setPolicyAction = (policyName: string) => {
const setPolicyAction = () => {
let entity = "user";
let value = null;
if (selectedGroup !== null) {
@@ -96,7 +86,7 @@ const SetPolicy = ({
setLoading(true);
api
.invoke("PUT", `/api/v1/set-policy/${policyName}`, {
.invoke("PUT", `/api/v1/set-policy/${selectedPolicy}`, {
entityName: value,
entityType: entity,
})
@@ -111,71 +101,89 @@ const SetPolicy = ({
});
};
const fetchGroupInformation = () => {
if (selectedGroup) {
api
.invoke("GET", `/api/v1/groups/${selectedGroup}`)
.then((res: any) => {
const groupPolicy = get(res, "policy", "");
setActualPolicy(groupPolicy);
setSelectedPolicy(groupPolicy);
})
.catch((err) => {
setError(err);
setLoading(false);
});
}
};
const resetSelection = () => {
setSelectedPolicy(actualPolicy);
};
useEffect(() => {
if (open) {
fetchRecords();
if (selectedGroup !== null) {
fetchGroupInformation();
return;
}
const userPolicy = get(selectedUser, "policy", "");
setActualPolicy(userPolicy);
setSelectedPolicy(userPolicy);
}
}, [open]);
const userName = get(selectedUser, "accessKey", "");
return (
<ModalWrapper
onClose={() => {
closeModalAndRefresh();
}}
modalOpen={open}
title={
selectedUser !== null ? "Set Policy to User" : "Set Policy to Group"
}
title="Set Policies"
>
<Grid container className={classes.formScrollable}>
<Grid item xs={12}>
<TableContainer component={Paper}>
<Table
className={classes.table}
size="small"
aria-label="a dense table"
>
<TableHead>
<TableRow>
<TableCell>Policy</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{records.map((row) => (
<TableRow key={row.name}>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">
<Button
variant="contained"
color="primary"
size={"small"}
onClick={() => {
setPolicyAction(row.name);
}}
>
Set
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<Grid item xs={12}>
<Grid item xs={12} className={classes.predefinedTitle}>
Selected {selectedGroup !== null ? "Group" : "User"}
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{selectedGroup !== null ? selectedGroup : userName}
</Grid>
</Grid>
<Grid item xs={12}>
<Grid item xs={12} className={classes.predefinedTitle}>
Current Policy
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{actualPolicy}
</Grid>
</Grid>
<PolicySelectors
selectedPolicy={selectedPolicy}
setSelectedPolicy={setSelectedPolicy}
/>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetSelection}
>
Clear
</button>
<Button
type="submit"
type="button"
variant="contained"
color="primary"
onClick={() => {
closeModalAndRefresh();
}}
disabled={loading}
onClick={setPolicyAction}
>
Cancel
Save
</Button>
</Grid>
{loading && (

View File

@@ -16,19 +16,14 @@
import React, { useEffect, useState } from "react";
import Grid from "@material-ui/core/Grid";
import { UnControlled as CodeMirror } from "react-codemirror2";
import Typography from "@material-ui/core/Typography";
import { Button, LinearProgress, Tooltip } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import api from "../../../common/api";
import "codemirror/lib/codemirror.css";
import "codemirror/theme/material.css";
import { NewServiceAccount } from "../Common/CredentialsPrompt/types";
import HelpIcon from "@material-ui/icons/Help";
require("codemirror/mode/javascript/javascript");
import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
const styles = (theme: Theme) =>
createStyles({
@@ -39,9 +34,6 @@ const styles = (theme: Theme) =>
minHeight: 400,
width: "100%",
},
codeMirror: {
fontSize: 14,
},
buttonContainer: {
textAlign: "right",
},
@@ -92,6 +84,10 @@ const AddServiceAccount = ({
setAddSending(true);
};
const resetForm = () => {
setPolicyDefinition("");
};
return (
<ModalWrapper
modalOpen={open}
@@ -120,29 +116,24 @@ const AddServiceAccount = ({
</Typography>
</Grid>
)}
<Grid item xs={12}>
<Typography component="h5">
Optional Policy
<Tooltip
title="A policy that restricts this service account can be attached."
placement="top-start"
>
<HelpIcon />
</Tooltip>
</Typography>
<CodeMirror
className={classes.codeMirror}
options={{
mode: "javascript",
lineNumbers: true,
}}
onChange={(editor, data, value) => {
setPolicyDefinition(value);
}}
/>
</Grid>
<CodeMirrorWrapper
value={policyDefinition}
label="Optional Policy"
tooltip="A policy that restricts this service account can be attached."
onBeforeChange={(editor, data, value) => {
setPolicyDefinition(value);
}}
/>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetForm}
>
Clear
</button>
<Button
type="submit"
variant="contained"

View File

@@ -2113,6 +2113,7 @@ const AddTenant = ({
}}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
wideLimit={false}
>
{addSending && (
<Grid item xs={12}>

View File

@@ -60,6 +60,7 @@ const AddToGroup = ({
}: IAddToGroup) => {
//Local States
const [saving, isSaving] = useState<boolean>(false);
const [accepted, setAccepted] = useState<boolean>(false);
const [updatingError, setError] = useState<string>("");
const [selectedGroups, setSelectedGroups] = useState<string[]>([]);
@@ -75,7 +76,7 @@ const AddToGroup = ({
.then((res) => {
isSaving(false);
setError("");
closeModalAndRefresh(true);
setAccepted(true);
})
.catch((err) => {
isSaving(false);
@@ -102,62 +103,100 @@ const AddToGroup = ({
isSaving(true);
};
const resetForm = () => {
setSelectedGroups([]);
};
return (
<ModalWrapper
modalOpen={open}
onClose={() => {
closeModalAndRefresh(false);
closeModalAndRefresh(accepted);
}}
title="Add Users to Group"
title={
accepted
? "The selected users were added to the following groups."
: "Add Users to Group"
}
>
<form noValidate autoComplete="off" onSubmit={setSaving}>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
{updatingError !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{updatingError}
</Typography>
</Grid>
)}
{accepted ? (
<React.Fragment>
<Grid container>
<Grid item xs={12} className={classes.predefinedTitle}>
Selected Users
Groups
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{selectedGroups.join(", ")}
</Grid>
<Grid item xs={12} className={classes.predefinedTitle}>
Users
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{checkedUsers.join(", ")}
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<GroupsSelectors
selectedGroups={selectedGroups}
setSelectedGroups={setSelectedGroups}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={saving}
>
Save
</Button>
</Grid>
{saving && (
<Grid item xs={12}>
<LinearProgress />
<br />
<br />
<br />
</React.Fragment>
) : (
<form noValidate autoComplete="off" onSubmit={setSaving}>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
{updatingError !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{updatingError}
</Typography>
</Grid>
)}
<Grid item xs={12} className={classes.predefinedTitle}>
Selected Users
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{checkedUsers.join(", ")}
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<GroupsSelectors
selectedGroups={selectedGroups}
setSelectedGroups={setSelectedGroups}
/>
</Grid>
</Grid>
)}
</Grid>
</form>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetForm}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={saving || selectedGroups.length < 1}
>
Save
</Button>
</Grid>
{saving && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</Grid>
</form>
)}
</ModalWrapper>
);
};

View File

@@ -19,13 +19,17 @@ import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { Button, LinearProgress } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import {
modalBasic,
predefinedList,
} from "../Common/FormComponents/common/styleLibrary";
import { User } from "./types";
import api from "../../../common/api";
import GroupsSelectors from "./GroupsSelectors";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import RadioGroupSelector from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
const styles = (theme: Theme) =>
createStyles({
@@ -42,6 +46,7 @@ const styles = (theme: Theme) =>
textAlign: "right",
},
...modalBasic,
...predefinedList,
});
interface IAddUserContentProps {
@@ -57,7 +62,8 @@ interface IAddUserContentState {
accessKey: string;
secretKey: string;
selectedGroups: string[];
enabled: string;
currentGroups: string[];
enabled: boolean;
}
class AddUserContent extends React.Component<
@@ -69,8 +75,9 @@ class AddUserContent extends React.Component<
addError: "",
accessKey: "",
secretKey: "",
enabled: "enabled",
enabled: false,
selectedGroups: [],
currentGroups: [],
};
componentDidMount(): void {
@@ -103,7 +110,7 @@ class AddUserContent extends React.Component<
if (selectedUser !== null) {
api
.invoke("PUT", `/api/v1/users/${selectedUser.accessKey}`, {
status: enabled,
status: enabled ? "enabled" : "disabled",
groups: selectedGroups,
})
.then((res) => {
@@ -167,7 +174,8 @@ class AddUserContent extends React.Component<
addError: "",
accessKey: res.accessKey,
selectedGroups: res.memberOf || [],
enabled: res.status,
currentGroups: res.memberOf || [],
enabled: res.status === "enabled",
});
})
.catch((err) => {
@@ -178,6 +186,16 @@ class AddUserContent extends React.Component<
});
}
resetForm() {
if (this.props.selectedUser !== null) {
this.setState({ selectedGroups: [] });
return;
}
this.setState({ accessKey: "", secretKey: "", selectedGroups: [] });
}
render() {
const { classes, selectedUser } = this.props;
const {
@@ -186,9 +204,15 @@ class AddUserContent extends React.Component<
accessKey,
secretKey,
selectedGroups,
currentGroups,
enabled,
} = this.state;
const sendEnabled =
accessKey.trim() !== "" &&
selectedGroups.length > 0 &&
((secretKey.trim() !== "" && selectedUser === null) ||
selectedUser !== null);
return (
<ModalWrapper
onClose={() => {
@@ -197,6 +221,22 @@ class AddUserContent extends React.Component<
modalOpen={this.props.open}
title={selectedUser !== null ? "Edit User" : "Create User"}
>
{selectedUser !== null && (
<div className={classes.floatingEnabled}>
<FormSwitchWrapper
indicatorLabel={"Enabled"}
checked={enabled}
value={"user_enabled"}
id="user-status"
name="user-status"
onChange={(e) => {
this.setState({ enabled: e.target.checked });
}}
switchOnly
/>
</div>
)}
<React.Fragment>
<form
noValidate
@@ -231,19 +271,14 @@ class AddUserContent extends React.Component<
/>
{selectedUser !== null ? (
<RadioGroupSelector
currentSelection={enabled}
id="user-status"
name="user-status"
label="Status"
onChange={(e) => {
this.setState({ enabled: e.target.value });
}}
selectorOptions={[
{ label: "Enabled", value: "enabled" },
{ label: "Disabled", value: "disabled" },
]}
/>
<React.Fragment>
<Grid item xs={12} className={classes.predefinedTitle}>
Current Groups
</Grid>
<Grid item xs={12} className={classes.predefinedList}>
{currentGroups.join(", ")}
</Grid>
</React.Fragment>
) : (
<InputBoxWrapper
id="standard-multiline-static"
@@ -269,11 +304,21 @@ class AddUserContent extends React.Component<
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={() => {
this.resetForm();
}}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
disabled={addLoading || !sendEnabled}
>
Save
</Button>

View File

@@ -1,5 +1,5 @@
// This file is part of MinIO Console Server
// Copyright (c) 2019 MinIO, Inc.
// 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
@@ -283,6 +283,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
selectedGroup={null}
closeModalAndRefresh={() => {
this.setState({ setPolicyOpen: false });
this.fetchRecords();
}}
/>
)}

View File

@@ -22,6 +22,7 @@ export interface User {
enabled: boolean;
accessKey: string;
secretKey: string;
policy?: string;
}
export interface UsersList {