Added file name visualization in file select (#289)

* Added missing validations in add tenant modal

* Added file name visualization in file selector

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2020-09-28 12:36:31 -05:00
committed by GitHub
parent 858d363e97
commit 459e2bf61c
6 changed files with 524 additions and 43 deletions

View File

@@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import get from "lodash/get";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { NewServiceAccount } from "./types";
import { Button } from "@material-ui/core";
@@ -67,6 +68,8 @@ const CredentialsPrompt = ({
return null;
}
const consoleCreds = get(newServiceAccount, "console", null);
return (
<ModalWrapper
modalOpen={open}
@@ -87,6 +90,21 @@ const CredentialsPrompt = ({
<b>Secret Key:</b> {newServiceAccount.secretKey}
</li>
</ul>
{consoleCreds && (
<React.Fragment>
<Grid item xs={12}>
<strong>Console Credentials</strong>
<ul>
<li>
<b>Access Key:</b> {consoleCreds.accessKey}
</li>
<li>
<b>Secret Key:</b> {consoleCreds.secretKey}
</li>
</ul>
</Grid>
</React.Fragment>
)}
<Typography
component="p"
variant="body1"
@@ -99,11 +117,23 @@ const CredentialsPrompt = ({
<Grid item xs={12} className={classes.buttonContainer}>
<Button
onClick={() => {
let consoleExtras = {};
if (consoleCreds) {
consoleExtras = {
console: {
access_key: consoleCreds.accessKey,
secret_key: consoleCreds.secretKey,
},
};
}
download(
"credentials.json",
JSON.stringify({
access_key: newServiceAccount.accessKey,
secret_key: newServiceAccount.secretKey,
...consoleExtras,
})
);
}}

View File

@@ -17,4 +17,10 @@
export interface NewServiceAccount {
accessKey: string;
secretKey: string;
console?: ConsoleSA;
}
export interface ConsoleSA {
accessKey: string;
secretKey: string;
}

View File

@@ -14,8 +14,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import React, { useState } from "react";
import get from "lodash/get";
import { Grid, InputLabel, Tooltip } from "@material-ui/core";
import IconButton from "@material-ui/core/IconButton";
import AttachFileIcon from "@material-ui/icons/AttachFile";
import CancelIcon from "@material-ui/icons/Cancel";
import HelpIcon from "@material-ui/icons/Help";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
@@ -24,7 +28,7 @@ import { fileProcess } from "./utils";
interface InputBoxProps {
label: string;
classes: any;
onChange: (e: string) => void;
onChange: (e: string, i: string) => void;
id: string;
name: string;
disabled?: boolean;
@@ -32,6 +36,7 @@ interface InputBoxProps {
required?: boolean;
error?: string;
accept?: string;
value?: string;
}
const styles = (theme: Theme) =>
@@ -41,6 +46,7 @@ const styles = (theme: Theme) =>
textBoxContainer: {
flexGrow: 1,
position: "relative",
flexDirection: "column",
},
errorState: {
color: "#b53b4b",
@@ -49,6 +55,27 @@ const styles = (theme: Theme) =>
top: 7,
right: 7,
},
errorText: {
margin: "0",
fontSize: "0.75rem",
marginTop: 3,
textAlign: "left",
fontFamily: "Lato,sans-serif",
fontWeight: 400,
lineHeight: "1.66",
color: "#dc1f2e",
},
valueString: {
maxWidth: 350,
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
marginTop: 2,
},
fileReselect: {
display: "flex",
alignItems: "center",
},
});
const FileSelector = ({
@@ -62,7 +89,10 @@ const FileSelector = ({
required,
error = "",
accept = "",
value = "",
}: InputBoxProps) => {
const [showFileSelector, setShowSelector] = useState(false);
return (
<React.Fragment>
<Grid
@@ -92,19 +122,62 @@ const FileSelector = ({
)}
</InputLabel>
)}
<div className={classes.textBoxContainer}>
<input
type="file"
name={name}
onChange={(e) => {
fileProcess(e, (data: any) => {
onChange(data);
});
}}
accept={accept}
required
/>
</div>
{showFileSelector || value === "" ? (
<div className={classes.textBoxContainer}>
<input
type="file"
name={name}
onChange={(e) => {
const fileName = get(e, "target.files[0].name", "");
fileProcess(e, (data: any) => {
onChange(data, fileName);
});
}}
accept={accept}
required={required}
disabled={disabled}
/>
{value !== "" && (
<IconButton
color="primary"
aria-label="upload picture"
component="span"
onClick={() => {
setShowSelector(false);
}}
disableRipple={false}
disableFocusRipple={false}
>
<CancelIcon />
</IconButton>
)}
{error !== "" && (
<React.Fragment>
<br />
<span className={classes.errorText}>{error}</span>
</React.Fragment>
)}
</div>
) : (
<div className={classes.fileReselect}>
<div className={classes.valueString}>{value}</div>
<IconButton
color="primary"
aria-label="upload picture"
component="span"
onClick={() => {
setShowSelector(true);
}}
disableRipple={false}
disableFocusRipple={false}
>
<AttachFileIcon />
</IconButton>
</div>
)}
</Grid>
</React.Fragment>
);

View File

@@ -52,13 +52,12 @@ import {
ICapacity,
ITenantCreator,
} from "../../../../common/types";
import { NewTenantCredential } from "./TenantCredentialsPrompt/types";
interface IAddTenantProps {
open: boolean;
closeModalAndRefresh: (
reloadData: boolean,
res: NewTenantCredential | null
res: NewServiceAccount | null
) => any;
classes: any;
}
@@ -178,6 +177,9 @@ const AddTenant = ({
const [nameTenantValid, setNameTenantValid] = useState<boolean>(false);
const [configValid, setConfigValid] = useState<boolean>(false);
const [configureValid, setConfigureValid] = useState<boolean>(false);
const [idpValid, setIdpValid] = useState<boolean>(false);
const [securityValid, setSecurityValid] = useState<boolean>(false);
const [encryptionValid, setEncryptionValid] = useState<boolean>(false);
// Custom Elements
const [customDockerhub, setCustomDockerhub] = useState<boolean>(false);
@@ -198,6 +200,20 @@ const AddTenant = ({
gemaltoCA: "",
});
// Files States
const [tlsKeyVal, setTlsKeyVal] = useState<string>("");
const [tlsCertVal, setTlsCertVal] = useState<string>("");
const [consoleKeyVal, setConsoleKeyVal] = useState<string>("");
const [consoleCertVal, setConsoleCertVal] = useState<string>("");
const [serverKeyVal, setServerKeyVal] = useState<string>("");
const [serverCertVal, setServerCertVal] = useState<string>("");
const [clientKeyVal, setClientKeyVal] = useState<string>("");
const [clientCertVal, setClientCertVal] = useState<string>("");
const [vaultKeyVal, setVaultKeyVal] = useState<string>("");
const [vaultCertVal, setVaultCertVal] = useState<string>("");
const [vaultCAVal, setVaultCAVal] = useState<string>("");
const [gemaltoCAVal, setGemaltoCAVal] = useState<string>("");
/*Debounce functions*/
// Storage Quotas
@@ -351,6 +367,14 @@ const AddTenant = ({
pattern: /^((.*?)\/(.*?):(.+))$/,
customPatternMessage: "Format must be of form: 'minio/minio:VERSION'",
},
{
fieldKey: "consoleImage",
required: true,
value: consoleImage,
pattern: /^((.*?)\/(.*?):(.+))$/,
customPatternMessage:
"Format must be of form: 'minio/console:VERSION'",
},
];
}
@@ -359,7 +383,302 @@ const AddTenant = ({
setConfigureValid(Object.keys(commonVal).length === 0);
setValidationErrors(commonVal);
}, [customDockerhub, imageName]);
}, [customDockerhub, imageName, consoleImage]);
useEffect(() => {
let customIDPValidation: IValidation[] = [];
if (idpSelection === "none") {
setIdpValid(true);
setValidationErrors({});
return;
}
if (idpSelection === "OpenID") {
customIDPValidation = [
...customIDPValidation,
{
fieldKey: "openID_URL",
required: true,
value: openIDURL,
},
{
fieldKey: "openID_clientID",
required: true,
value: openIDClientID,
},
{
fieldKey: "openID_secretID",
required: true,
value: openIDSecretID,
},
];
}
if (idpSelection === "AD") {
customIDPValidation = [
...customIDPValidation,
{
fieldKey: "AD_URL",
required: true,
value: ADURL,
},
{
fieldKey: "ad_userNameFilter",
required: true,
value: ADUserNameFilter,
},
{
fieldKey: "ad_groupBaseDN",
required: true,
value: ADGroupBaseDN,
},
{
fieldKey: "ad_groupSearchFilter",
required: true,
value: ADGroupSearchFilter,
},
{
fieldKey: "ad_nameAttribute",
required: true,
value: ADNameAttribute,
},
];
}
const commonVal = commonFormValidation(customIDPValidation);
setIdpValid(Object.keys(commonVal).length === 0);
setValidationErrors(commonVal);
}, [
idpSelection,
openIDURL,
openIDClientID,
openIDSecretID,
ADURL,
ADUserNameFilter,
ADGroupBaseDN,
ADGroupSearchFilter,
ADNameAttribute,
]);
useEffect(() => {
let securityValidation: IValidation[] = [];
if (!enableTLS) {
setSecurityValid(true);
setValidationErrors({});
return;
}
if (tlsType === "autocert") {
setSecurityValid(true);
setValidationErrors({});
return;
}
securityValidation = [
...securityValidation,
{
fieldKey: "tlsKey",
required: true,
value: filesBase64.tlsKey,
},
{
fieldKey: "tlsCert",
required: true,
value: filesBase64.tlsCert,
},
{
fieldKey: "consoleKey",
required: true,
value: filesBase64.consoleKey,
},
{
fieldKey: "consoleCert",
required: true,
value: filesBase64.consoleCert,
},
];
const commonVal = commonFormValidation(securityValidation);
setSecurityValid(Object.keys(commonVal).length === 0);
setValidationErrors(commonVal);
}, [enableTLS, filesBase64, tlsType]);
useEffect(() => {
let encryptionValidation: IValidation[] = [];
if (enableEncryption) {
if (enableTLS && tlsType !== "autocert") {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "serverKey",
required: true,
value: filesBase64.serverKey,
},
{
fieldKey: "serverCert",
required: true,
value: filesBase64.serverCert,
},
{
fieldKey: "clientKey",
required: true,
value: filesBase64.clientKey,
},
{
fieldKey: "clientCert",
required: true,
value: filesBase64.clientCert,
},
];
}
if (encryptionType === "vault") {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "vault_endpoint",
required: true,
value: vaultEndpoint,
},
{
fieldKey: "vault_engine",
required: true,
value: vaultEngine,
},
{
fieldKey: "vault_id",
required: true,
value: vaultId,
},
{
fieldKey: "vault_secret",
required: true,
value: vaultSecret,
},
{
fieldKey: "vault_key",
required: true,
value: filesBase64.vaultKey,
},
{
fieldKey: "vault_cert",
required: true,
value: filesBase64.vaultCert,
},
{
fieldKey: "vault_ca",
required: true,
value: filesBase64.vaultCA,
},
{
fieldKey: "vault_ping",
required: true,
value: vaultPing,
customValidation: parseInt(vaultPing) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
{
fieldKey: "vault_retry",
required: true,
value: vaultRetry,
customValidation: parseInt(vaultRetry) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
];
}
if (encryptionType === "aws") {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "aws_endpoint",
required: true,
value: awsEndpoint,
},
{
fieldKey: "aws_region",
required: true,
value: awsRegion,
},
{
fieldKey: "aws_accessKey",
required: true,
value: awsAccessKey,
},
{
fieldKey: "aws_secretKey",
required: true,
value: awsSecretKey,
},
];
}
if (encryptionType === "gemalto") {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "gemalto_endpoint",
required: true,
value: gemaltoEndpoint,
},
{
fieldKey: "gemalto_token",
required: true,
value: gemaltoToken,
},
{
fieldKey: "gemalto_domain",
required: true,
value: gemaltoDomain,
},
{
fieldKey: "gemalto_retry",
required: true,
value: gemaltoRetry,
customValidation: parseInt(gemaltoRetry) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
{
fieldKey: "gemalto_ca",
required: true,
value: filesBase64.gemaltoCA,
},
];
}
}
const commonVal = commonFormValidation(encryptionValidation);
setEncryptionValid(Object.keys(commonVal).length === 0);
setValidationErrors(commonVal);
}, [
enableEncryption,
encryptionType,
filesBase64,
vaultEndpoint,
vaultEngine,
vaultId,
vaultSecret,
vaultPing,
vaultRetry,
awsEndpoint,
awsRegion,
awsSecretKey,
awsAccessKey,
gemaltoEndpoint,
gemaltoToken,
gemaltoDomain,
gemaltoRetry,
]);
const clearValidationError = (fieldKey: string) => {
const newValidationElement = { ...validationErrors };
@@ -583,7 +902,7 @@ const AddTenant = ({
api
.invoke("POST", `/api/v1/tenants`, dataSend)
.then((res) => {
const newSrvAcc: NewTenantCredential = {
const newSrvAcc: NewServiceAccount = {
accessKey: res.access_key,
secretKey: res.secret_key,
console: {
@@ -971,7 +1290,7 @@ const AddTenant = ({
buttons: [
cancelButton,
{ label: "Back", type: "back", enabled: true },
{ label: "Next", type: "next", enabled: true },
{ label: "Next", type: "next", enabled: idpValid },
],
},
{
@@ -1021,50 +1340,66 @@ const AddTenant = ({
<h5>MinIO TLS Certs</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("tlsKey", encodedValue);
setTlsKeyVal(fileName);
clearValidationError("tlsKey");
}}
accept=".key,.pem"
id="tlsKey"
name="tlsKey"
label="Key"
error={validationErrors["tlsKey"] || ""}
value={tlsKeyVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("tlsCert", encodedValue);
setTlsCertVal(fileName);
clearValidationError("tlsCert");
}}
accept=".cer,.crt,.cert,.pem"
id="tlsCert"
name="tlsCert"
label="Cert"
error={validationErrors["tlsCert"] || ""}
value={tlsCertVal}
required
/>
</Grid>
<h5>Console TLS Certs</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("consoleKey", encodedValue);
setConsoleKeyVal(fileName);
clearValidationError("consoleKey");
}}
accept=".key,.pem"
id="consoleKey"
name="consoleKey"
label="Key"
error={validationErrors["consoleKey"] || ""}
value={consoleKeyVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("consoleCert", encodedValue);
setConsoleCertVal(fileName);
clearValidationError("consoleCert");
}}
accept=".cer,.crt,.cert,.pem"
id="consoleCert"
name="consoleCert"
label="Cert"
error={validationErrors["consoleCert"] || ""}
value={consoleCertVal}
required
/>
</Grid>
@@ -1077,7 +1412,7 @@ const AddTenant = ({
buttons: [
cancelButton,
{ label: "Back", type: "back", enabled: true },
{ label: "Next", type: "next", enabled: true },
{ label: "Next", type: "next", enabled: securityValid },
],
},
{
@@ -1129,50 +1464,66 @@ const AddTenant = ({
<h5>Server</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("serverKey", encodedValue);
setServerKeyVal(fileName);
clearValidationError("serverKey");
}}
accept=".key,.pem"
id="serverKey"
name="serverKey"
label="Key"
error={validationErrors["serverKey"] || ""}
value={serverKeyVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("serverCert", encodedValue);
setServerCertVal(fileName);
clearValidationError("serverCert");
}}
accept=".cer,.crt,.cert,.pem"
id="serverCert"
name="serverCert"
label="Cert"
error={validationErrors["serverCert"] || ""}
value={serverCertVal}
required
/>
</Grid>
<h5>Client</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("clientKey", encodedValue);
setClientKeyVal(fileName);
clearValidationError("clientKey");
}}
accept=".key,.pem"
id="clientKey"
name="clientKey"
label="Key"
error={validationErrors["clientKey"] || ""}
value={clientKeyVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("clientCert", encodedValue);
setClientCertVal(fileName);
clearValidationError("clientCert");
}}
accept=".cer,.crt,.cert,.pem"
id="clientCert"
name="clientCert"
label="Cert"
error={validationErrors["clientCert"] || ""}
value={clientCertVal}
required
/>
</Grid>
@@ -1201,9 +1552,11 @@ const AddTenant = ({
name="vault_engine"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setVaultEngine(e.target.value);
clearValidationError("vault_engine");
}}
label="Engine"
value={vaultEngine}
error={validationErrors["vault_engine"] || ""}
required
/>
</Grid>
@@ -1263,7 +1616,7 @@ const AddTenant = ({
setVaultSecret(e.target.value);
clearValidationError("vault_secret");
}}
label="Id"
label="Secret"
value={vaultSecret}
error={validationErrors["vault_secret"] || ""}
required
@@ -1277,46 +1630,60 @@ const AddTenant = ({
name="vault_retry"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setVaultRetry(e.target.value);
clearValidationError("vault_retry");
}}
label="Retry"
value={vaultRetry}
error={validationErrors["vault_retry"] || ""}
required
/>
</Grid>
<h5>TLS</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("vaultKey", encodedValue);
setVaultKeyVal(fileName);
clearValidationError("vault_key");
}}
accept=".key,.pem"
id="vault_key"
name="vault_key"
label="Key"
error={validationErrors["vault_key"] || ""}
value={vaultKeyVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("vaultCert", encodedValue);
setVaultCertVal(fileName);
clearValidationError("vault_cert");
}}
accept=".cer,.crt,.cert,.pem"
id="vault_cert"
name="vault_cert"
label="Cert"
error={validationErrors["vault_cert"] || ""}
value={vaultCertVal}
required
/>
</Grid>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("vaultCA", encodedValue);
setVaultCAVal(fileName);
clearValidationError("vault_ca");
}}
accept=".cer,.crt,.cert,.pem"
id="vault_ca"
name="vault_ca"
label="CA"
error={validationErrors["vault_ca"] || ""}
value={vaultCAVal}
required
/>
</Grid>
@@ -1329,9 +1696,11 @@ const AddTenant = ({
name="vault_ping"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setVaultPing(e.target.value);
clearValidationError("vault_ping");
}}
label="Ping"
value={vaultPing}
error={validationErrors["vault_ping"] || ""}
required
/>
</Grid>
@@ -1443,11 +1812,11 @@ const AddTenant = ({
name="gemalto_token"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setGemaltoToken(e.target.value);
clearValidationError("gemalto_endpoint");
clearValidationError("gemalto_token");
}}
label="Token"
value={gemaltoToken}
error={validationErrors["gemalto_endpoint"] || ""}
error={validationErrors["gemalto_token"] || ""}
required
/>
</Grid>
@@ -1473,6 +1842,7 @@ const AddTenant = ({
name="gemalto_retry"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setGemaltoRetry(e.target.value);
clearValidationError("gemalto_retry");
}}
label="Domain"
value={gemaltoRetry}
@@ -1482,13 +1852,17 @@ const AddTenant = ({
<h5>TLS</h5>
<Grid item xs={12}>
<FileSelector
onChange={(encodedValue) => {
onChange={(encodedValue, fileName) => {
storeCertInObject("gemaltoCA", encodedValue);
setGemaltoCAVal(fileName);
clearValidationError("gemalto_ca");
}}
accept=".cer,.crt,.cert,.pem"
id="gemalto_ca"
name="gemalto_ca"
label="CA"
error={validationErrors["gemalto_ca"] || ""}
value={gemaltoCAVal}
required
/>
</Grid>
@@ -1501,7 +1875,7 @@ const AddTenant = ({
buttons: [
cancelButton,
{ label: "Back", type: "back", enabled: true },
{ label: "Next", type: "next", enabled: true },
{ label: "Next", type: "next", enabled: encryptionValid },
],
},
{

View File

@@ -34,8 +34,6 @@ import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
import history from "../../../../history";
import RefreshIcon from "@material-ui/icons/Refresh";
import TenantCredentialsPrompt from "./TenantCredentialsPrompt/TenantCredentialsPrompt";
import { NewTenantCredential } from "./TenantCredentialsPrompt/types";
interface ITenantsList {
classes: any;
@@ -100,11 +98,11 @@ const ListTenants = ({ classes }: ITenantsList) => {
const [
createdAccount,
setCreatedAccount,
] = useState<NewTenantCredential | null>(null);
] = useState<NewServiceAccount | null>(null);
const closeAddModalAndRefresh = (
reloadData: boolean,
res: NewTenantCredential | null
res: NewServiceAccount | null
) => {
setCreateTenantOpen(false);
@@ -238,7 +236,7 @@ const ListTenants = ({ classes }: ITenantsList) => {
/>
)}
{showNewCredentials && (
<TenantCredentialsPrompt
<CredentialsPrompt
newServiceAccount={createdAccount}
open={showNewCredentials}
closeModal={() => {

View File

@@ -19,7 +19,7 @@ export interface IValidation {
required: boolean;
pattern?: RegExp;
customPatternMessage?: string;
customValidation?: boolean;
customValidation?: boolean; // The validation to trigger the error
customValidationMessage?: string;
value: string;
}