Updated styles & behavior for settings page (#334)

Updated styles & behavior for settings page, also implemented a couple of performance improvements on some fields

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2020-10-20 18:31:08 -05:00
committed by GitHub
parent c928972137
commit 7e9d581277
11 changed files with 323 additions and 151 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,34 @@
// 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 { SvgIcon } from "@material-ui/core";
class AddIcon extends React.Component {
render() {
return (
<SvgIcon viewBox="0 0 12 12">
<path
fill="#081c42"
className="a"
d="M-13160.269,1885.114h-3.235v-4.381h-4.382V1877.5h4.382v-4.381h3.235v4.381h4.383v3.238h-4.383v4.38Z"
transform="translate(13167.886 -1873.114)"
/>
</SvgIcon>
);
}
}
export default AddIcon;

View File

@@ -0,0 +1,33 @@
// 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 { SvgIcon } from "@material-ui/core";
class RemoveIcon extends React.Component {
render() {
return (
<SvgIcon viewBox="0 0 11.656 3.101">
<path
fill="#081c42"
d="M-13157.172,1879.551h-11.656v-3.1h11.656v3.1Z"
transform="translate(13168.828 -1876.449)"
/>
</SvgIcon>
);
}
}
export default RemoveIcon;

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
@@ -13,21 +13,32 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useState, useEffect, createRef, ChangeEvent } from "react";
import React, {
useState,
useEffect,
createRef,
useLayoutEffect,
ChangeEvent,
useRef,
} from "react";
import get from "lodash/get";
import debounce from "lodash/debounce";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import get from "lodash/get";
import InputBoxWrapper from "../InputBoxWrapper/InputBoxWrapper";
import HelpIcon from "@material-ui/icons/Help";
import { InputLabel, Tooltip } from "@material-ui/core";
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
import HelpIcon from "@material-ui/icons/Help";
import InputBoxWrapper from "../InputBoxWrapper/InputBoxWrapper";
import AddIcon from "../../../../../icons/AddIcon";
interface ICSVMultiSelector {
elements: string;
name: string;
label: string;
tooltip?: string;
commonPlaceholder?: string;
classes: any;
withBorder?: boolean;
onChange: (elements: string) => void;
}
@@ -35,16 +46,13 @@ const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
...tooltipHelper,
inputLabel: {
...fieldBasic.inputLabel,
width: 116,
},
inputContainer: {
inputWithBorder: {
border: "1px solid #EAEAEA",
padding: 15,
height: 150,
overflowY: "auto",
padding: 15,
position: "relative",
border: "1px solid #c4c4c4",
marginTop: 15,
},
labelContainer: {
display: "flex",
@@ -56,7 +64,9 @@ const CSVMultiSelector = ({
name,
label,
tooltip = "",
commonPlaceholder = "",
onChange,
withBorder = false,
classes,
}: ICSVMultiSelector) => {
const [currentElements, setCurrentElements] = useState<string[]>([""]);
@@ -75,29 +85,37 @@ const CSVMultiSelector = ({
setCurrentElements(elementsSplit);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [elements, currentElements]);
// Use effect to send new values to onChange
useEffect(() => {
const elementsString = currentElements
.filter((element) => element.trim() !== "")
.join(",");
onChange(elementsString);
const refScroll = bottomList.current;
if (refScroll) {
refScroll.scrollIntoView(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentElements]);
// We avoid multiple re-renders / hang issue typing too fast
const firstUpdate = useRef(true);
useLayoutEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
return;
}
debouncedOnChange();
}, [currentElements]);
// If the last input is not empty, we add a new one
const addEmptyLine = (elementsUp: string[]) => {
if (elementsUp[elementsUp.length - 1].trim() !== "") {
elementsUp.push("");
const refScroll = bottomList.current;
if (refScroll) {
refScroll.scrollIntoView(false);
}
const cpList = [...elementsUp];
cpList.push("");
setCurrentElements(cpList);
}
return elementsUp;
};
// Onchange function for input box, we get the dataset-index & only update that value in the array
@@ -108,10 +126,18 @@ const CSVMultiSelector = ({
const index = get(e.target, "dataset.index", 0);
updatedElement[index] = e.target.value;
updatedElement = addEmptyLine(updatedElement);
setCurrentElements(updatedElement);
};
// Debounce for On Change
const debouncedOnChange = debounce(() => {
const elementsString = currentElements
.filter((element) => element.trim() !== "")
.join(",");
onChange(elementsString);
}, 500);
const inputs = currentElements.map((element, index) => {
return (
<InputBoxWrapper
@@ -122,6 +148,11 @@ const CSVMultiSelector = ({
onChange={onChangeElement}
index={index}
key={`csv-${name}-${index.toString()}`}
placeholder={commonPlaceholder}
overlayIcon={index === currentElements.length - 1 ? <AddIcon /> : null}
overlayAction={() => {
addEmptyLine(currentElements);
}}
/>
);
});
@@ -139,7 +170,11 @@ const CSVMultiSelector = ({
</div>
)}
</InputLabel>
<Grid item xs={12} className={classes.inputContainer}>
<Grid
item
xs={12}
className={`${withBorder ? classes.inputWithBorder : ""}`}
>
{inputs}
<div ref={bottomList} />
</Grid>
@@ -147,5 +182,4 @@ const CSVMultiSelector = ({
</React.Fragment>
);
};
export default withStyles(styles)(CSVMultiSelector);

View File

@@ -68,11 +68,13 @@ const styles = (theme: Theme) =>
},
cssOutlinedInput: {
borderColor: "#9C9C9C",
padding: 16,
},
rootTest: {
rootContainer: {
"& .MuiOutlinedInput-inputMultiline": {
...fieldBasic.inputLabel,
fontSize: 13,
minHeight: 150,
},
},
});
@@ -142,7 +144,7 @@ const CommentBoxWrapper = ({
InputProps={{
classes: {
notchedOutline: classes.cssOutlinedInput,
root: classes.rootTest,
root: classes.rootContainer,
},
}}
variant="outlined"

View File

@@ -16,6 +16,7 @@
import React from "react";
import {
Grid,
IconButton,
InputLabel,
TextField,
TextFieldProps,
@@ -49,6 +50,8 @@ interface InputBoxProps {
placeholder?: string;
min?: string;
max?: string;
overlayIcon?: any;
overlayAction?: () => void;
}
const styles = (theme: Theme) =>
@@ -57,7 +60,10 @@ const styles = (theme: Theme) =>
...tooltipHelper,
textBoxContainer: {
flexGrow: 1,
},
textBoxWithIcon: {
position: "relative",
paddingRight: 25,
},
errorState: {
color: "#b53b4b",
@@ -66,6 +72,15 @@ const styles = (theme: Theme) =>
top: 7,
right: 7,
},
overlayAction: {
position: "absolute",
right: 0,
top: 15,
"& svg": {
maxWidth: 15,
maxHeight: 15,
},
},
});
const inputStyles = makeStyles((theme: Theme) =>
@@ -83,7 +98,7 @@ const inputStyles = makeStyles((theme: Theme) =>
},
},
input: {
padding: "15px 5px 10px",
padding: "15px 30px 10px 5px",
color: "#393939",
fontSize: 13,
fontWeight: 600,
@@ -127,6 +142,8 @@ const InputBoxWrapper = ({
placeholder = "",
min,
max,
overlayIcon = null,
overlayAction,
classes,
}: InputBoxProps) => {
let inputProps: any = { "data-index": index };
@@ -184,12 +201,28 @@ const InputBoxWrapper = ({
error={error !== ""}
helperText={error}
placeholder={placeholder}
InputLabelProps={{
shrink: true,
}}
className={classes.inputRebase}
/>
</div>
{overlayIcon && (
<div className={classes.overlayAction}>
<IconButton
onClick={
overlayAction
? () => {
overlayAction();
}
: () => null
}
size={"small"}
disableFocusRipple={false}
disableRipple={false}
disableTouchRipple={false}
>
{overlayIcon}
</IconButton>
</div>
)}
</Grid>
</React.Fragment>
);

View File

@@ -35,6 +35,7 @@ export const fieldBasic = {
},
fieldContainer: {
marginBottom: 20,
position: "relative" as const,
},
tooltipContainer: {
marginLeft: 5,

View File

@@ -123,6 +123,8 @@ const ConfTargetGeneric = ({
setValueElement(field.name, value, item)
}
tooltip={field.tooltip}
commonPlaceholder={field.placeholder}
withBorder={!!field.withBorder}
/>
);
case "comment":

View File

@@ -40,6 +40,7 @@ export interface KVField {
options?: SelectorTypes[];
multiline?: boolean;
placeholder?: string;
withBorder?: boolean;
}
export interface IConfigurationElement {

View File

@@ -27,33 +27,34 @@ export const notifyWebhooks = "notify_webhooks";
export const notifyNsq = "notify_nsq";
export const configurationElements: IConfigurationElement[] = [
{ configuration_id: "region", configuration_label: "Region Configuration" },
{ configuration_id: "cache", configuration_label: "Cache Configuration" },
{
configuration_id: "region",
configuration_label: "Edit Region Configuration",
},
{
configuration_id: "cache",
configuration_label: "Edit Cache Configuration",
},
{
configuration_id: "compression",
configuration_label: "Compression Configuration",
configuration_label: "Edit Compression Configuration",
},
{ configuration_id: "etcd", configuration_label: "Etcd Configuration" },
{ configuration_id: "etcd", configuration_label: "Edit Etcd Configuration" },
{
configuration_id: "identity_openid",
configuration_label: "Identity Openid Configuration",
configuration_label: "Edit Identity Openid Configuration",
},
{
configuration_id: "identity_ldap",
configuration_label: "Identity LDAP Configuration",
configuration_label: "Edit Identity LDAP Configuration",
},
{
configuration_id: "kms_vault",
configuration_label: "KMS Vault Configuration",
},
{ configuration_id: "kms_kes", configuration_label: "KMS KES Configuration" },
{
configuration_id: "logger_webhook",
configuration_label: "Logger Webhook Configuration",
configuration_label: "Edit Logger Webhook Configuration",
},
{
configuration_id: "audit_webhook",
configuration_label: "Audit Webhook Configuration",
configuration_label: "Edit Audit Webhook Configuration",
},
];
@@ -62,17 +63,18 @@ export const fieldsConfigurations: any = {
{
name: "name",
required: true,
label: "name",
label: "Server Location",
tooltip: 'Name of the location of the server e.g. "us-west-rack2"',
type: "string",
placeholder: "e.g. us-west-rack-2",
},
{
name: "comment",
required: false,
label: "comment",
label: "Comment",
tooltip: "You can add a comment to this setting",
type: "comment",
multiline: true,
placeholder: "Enter Comment",
},
],
cache: [
@@ -83,6 +85,7 @@ export const fieldsConfigurations: any = {
tooltip:
'Mountpoints e.g. "/optane1" or "/optane2", you can write one per field',
type: "csv",
placeholder: "Enter Mount Point",
},
{
name: "expiry",
@@ -90,6 +93,7 @@ export const fieldsConfigurations: any = {
label: "Expiry",
tooltip: 'Cache expiry duration in days e.g. "90"',
type: "number",
placeholder: "Enter Number of Days",
},
{
name: "quota",
@@ -97,6 +101,7 @@ export const fieldsConfigurations: any = {
label: "Quota",
tooltip: 'Limit cache drive usage in percentage e.g. "90"',
type: "number",
placeholder: "Enter in %",
},
{
name: "exclude",
@@ -105,6 +110,7 @@ export const fieldsConfigurations: any = {
tooltip:
'Wildcard exclusion patterns e.g. "bucket/*.tmp" or "*.exe", you can write one per field',
type: "csv",
placeholder: "Enter Wildcard Exclusion Patterns",
},
{
name: "after",
@@ -112,6 +118,7 @@ export const fieldsConfigurations: any = {
label: "After",
tooltip: "Minimum number of access before caching an object",
type: "number",
placeholder: "Enter Number of Attempts",
},
{
name: "watermark_low",
@@ -119,6 +126,7 @@ export const fieldsConfigurations: any = {
label: "Watermark Low",
tooltip: "Watermark Low",
type: "number",
placeholder: "Enter Watermark Low",
},
{
name: "watermark_high",
@@ -126,6 +134,7 @@ export const fieldsConfigurations: any = {
label: "Watermark High",
tooltip: "Watermark High",
type: "number",
placeholder: "Enter Watermark High",
},
{
name: "comment",
@@ -134,6 +143,7 @@ export const fieldsConfigurations: any = {
tooltip: "You can add a comment to this setting",
type: "comment",
multiline: true,
placeholder: "Enter Comment",
},
],
compression: [
@@ -144,6 +154,8 @@ export const fieldsConfigurations: any = {
tooltip:
'Extensions to compress e.g. ".txt",".log" or ".csv", you can write one per field',
type: "csv",
placeholder: "Enter an Extension",
withBorder: true,
},
{
name: "mime_types",
@@ -152,6 +164,8 @@ export const fieldsConfigurations: any = {
tooltip:
'Mime types e.g. "text/*","application/json" or "application/xml", you can write one per field',
type: "csv",
placeholder: "Enter a Mime Type",
withBorder: true,
},
],
etcd: [
@@ -162,6 +176,7 @@ export const fieldsConfigurations: any = {
tooltip:
'List of etcd endpoints e.g. "http://localhost:2379", you can write one per field',
type: "csv",
placeholder: "Enter Endpoint",
},
{
name: "path_prefix",
@@ -169,6 +184,7 @@ export const fieldsConfigurations: any = {
label: "Path Prefix",
tooltip: 'namespace prefix to isolate tenants e.g. "customer1/"',
type: "string",
placeholder: "Enter Path Prefix",
},
{
name: "coredns_path",
@@ -176,6 +192,7 @@ export const fieldsConfigurations: any = {
label: "Coredns Path",
tooltip: 'Shared bucket DNS records, default is "/skydns"',
type: "string",
placeholder: "Enter Coredns Path",
},
{
name: "client_cert",
@@ -183,6 +200,7 @@ export const fieldsConfigurations: any = {
label: "Client Cert",
tooltip: "Client cert for mTLS authentication",
type: "string",
placeholder: "Enter Client Cert",
},
{
name: "client_cert_key",
@@ -190,6 +208,7 @@ export const fieldsConfigurations: any = {
label: "Client Cert Key",
tooltip: "Client cert key for mTLS authentication",
type: "string",
placeholder: "Enter Client Cert Key",
},
{
name: "comment",
@@ -198,6 +217,7 @@ export const fieldsConfigurations: any = {
tooltip: "You can add a comment to this setting",
type: "comment",
multiline: true,
placeholder: "Enter Comment",
},
],
identity_openid: [
@@ -207,12 +227,14 @@ export const fieldsConfigurations: any = {
label: "Config URL",
tooltip: "Config URL for Client ID configuration",
type: "string",
placeholder: "Enter Config URL",
},
{
name: "client_id",
required: false,
label: "Client ID",
type: "string",
placeholder: "Enter Client ID",
},
{
name: "claim_name",
@@ -220,6 +242,7 @@ export const fieldsConfigurations: any = {
label: "Claim Name",
tooltip: "Claim Name",
type: "string",
placeholder: "Enter Claim Name",
},
{
name: "claim_prefix",
@@ -227,15 +250,17 @@ export const fieldsConfigurations: any = {
label: "Claim Prefix",
tooltip: "Claim Prefix",
type: "string",
placeholder: "Enter Claim Prefix",
},
],
identity_ldap: [
{
name: "server_addr",
required: true,
label: "Server ADDR",
label: "Server Addr",
tooltip: 'AD/LDAP server address e.g. "myldapserver.com:636"',
type: "string",
placeholder: "Enter Server Address",
},
{
name: "username_format",
@@ -244,6 +269,7 @@ export const fieldsConfigurations: any = {
tooltip:
'List of username bind DNs e.g. "uid=%s","cn=accounts","dc=myldapserver" or "dc=com", you can write one per field',
type: "csv",
placeholder: "Enter Username Format",
},
{
name: "username_search_filter",
@@ -252,6 +278,7 @@ export const fieldsConfigurations: any = {
tooltip:
'User search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"',
type: "string",
placeholder: "Enter Username Search Filter",
},
{
name: "group_search_filter",
@@ -260,6 +287,7 @@ export const fieldsConfigurations: any = {
tooltip:
'Search filter for groups e.g. "(&(objectclass=groupOfNames)(memberUid=%s))"',
type: "string",
placeholder: "Enter Group Search Filter",
},
{
name: "username_search_base_dn",
@@ -267,6 +295,7 @@ export const fieldsConfigurations: any = {
label: "Username Search Base DN",
tooltip: "List of username search DNs, you can write one per field",
type: "csv",
placeholder: "Enter Username Search Base DN",
},
{
name: "group_name_attribute",
@@ -274,6 +303,7 @@ export const fieldsConfigurations: any = {
label: "Group Name Attribute",
tooltip: 'Search attribute for group name e.g. "cn"',
type: "string",
placeholder: "Enter Group Name Attribute",
},
{
name: "sts_expiry",
@@ -282,6 +312,7 @@ export const fieldsConfigurations: any = {
tooltip:
'temporary credentials validity duration in s,m,h,d. Default is "1h"',
type: "string",
placeholder: "Enter STS Expiry",
},
{
name: "tls_skip_verify",
@@ -305,23 +336,23 @@ export const fieldsConfigurations: any = {
label: "Comment",
tooltip: "Optionally add a comment to this setting",
type: "comment",
multiline: true,
placeholder: "Enter Comment",
},
],
kms_vault: [],
kms_kes: [],
logger_webhook: [
{
name: "endpoint",
required: true,
label: "Endpoint",
type: "string",
placeholder: "Enter Endpoint",
},
{
name: "auth_token",
required: true,
label: "Auth Token",
type: "string",
placeholder: "Enter Auth Token",
},
],
audit_webhook: [
@@ -330,12 +361,14 @@ export const fieldsConfigurations: any = {
required: true,
label: "Endpoint",
type: "string",
placeholder: "Enter Endpoint",
},
{
name: "auth_token",
required: true,
label: "Auth Token",
type: "string",
placeholder: "Enter Auth Token",
},
],
};

View File

@@ -210,7 +210,6 @@ class AddUserContent extends React.Component<
const sendEnabled =
accessKey.trim() !== "" &&
selectedGroups.length > 0 &&
((secretKey.trim() !== "" && selectedUser === null) ||
selectedUser !== null);
return (