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:
@@ -15,6 +15,7 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import get from "lodash/get";
|
||||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||||
import { NewServiceAccount } from "./types";
|
import { NewServiceAccount } from "./types";
|
||||||
import { Button } from "@material-ui/core";
|
import { Button } from "@material-ui/core";
|
||||||
@@ -67,6 +68,8 @@ const CredentialsPrompt = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const consoleCreds = get(newServiceAccount, "console", null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalWrapper
|
<ModalWrapper
|
||||||
modalOpen={open}
|
modalOpen={open}
|
||||||
@@ -87,6 +90,21 @@ const CredentialsPrompt = ({
|
|||||||
<b>Secret Key:</b> {newServiceAccount.secretKey}
|
<b>Secret Key:</b> {newServiceAccount.secretKey}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</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
|
<Typography
|
||||||
component="p"
|
component="p"
|
||||||
variant="body1"
|
variant="body1"
|
||||||
@@ -99,11 +117,23 @@ const CredentialsPrompt = ({
|
|||||||
<Grid item xs={12} className={classes.buttonContainer}>
|
<Grid item xs={12} className={classes.buttonContainer}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
let consoleExtras = {};
|
||||||
|
|
||||||
|
if (consoleCreds) {
|
||||||
|
consoleExtras = {
|
||||||
|
console: {
|
||||||
|
access_key: consoleCreds.accessKey,
|
||||||
|
secret_key: consoleCreds.secretKey,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
download(
|
download(
|
||||||
"credentials.json",
|
"credentials.json",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
access_key: newServiceAccount.accessKey,
|
access_key: newServiceAccount.accessKey,
|
||||||
secret_key: newServiceAccount.secretKey,
|
secret_key: newServiceAccount.secretKey,
|
||||||
|
...consoleExtras,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -17,4 +17,10 @@
|
|||||||
export interface NewServiceAccount {
|
export interface NewServiceAccount {
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
|
console?: ConsoleSA;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConsoleSA {
|
||||||
|
accessKey: string;
|
||||||
|
secretKey: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,12 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// 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/>.
|
// 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 { 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 HelpIcon from "@material-ui/icons/Help";
|
||||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||||
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
||||||
@@ -24,7 +28,7 @@ import { fileProcess } from "./utils";
|
|||||||
interface InputBoxProps {
|
interface InputBoxProps {
|
||||||
label: string;
|
label: string;
|
||||||
classes: any;
|
classes: any;
|
||||||
onChange: (e: string) => void;
|
onChange: (e: string, i: string) => void;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@@ -32,6 +36,7 @@ interface InputBoxProps {
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
accept?: string;
|
accept?: string;
|
||||||
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
@@ -41,6 +46,7 @@ const styles = (theme: Theme) =>
|
|||||||
textBoxContainer: {
|
textBoxContainer: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
flexDirection: "column",
|
||||||
},
|
},
|
||||||
errorState: {
|
errorState: {
|
||||||
color: "#b53b4b",
|
color: "#b53b4b",
|
||||||
@@ -49,6 +55,27 @@ const styles = (theme: Theme) =>
|
|||||||
top: 7,
|
top: 7,
|
||||||
right: 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 = ({
|
const FileSelector = ({
|
||||||
@@ -62,7 +89,10 @@ const FileSelector = ({
|
|||||||
required,
|
required,
|
||||||
error = "",
|
error = "",
|
||||||
accept = "",
|
accept = "",
|
||||||
|
value = "",
|
||||||
}: InputBoxProps) => {
|
}: InputBoxProps) => {
|
||||||
|
const [showFileSelector, setShowSelector] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Grid
|
<Grid
|
||||||
@@ -92,19 +122,62 @@ const FileSelector = ({
|
|||||||
)}
|
)}
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
)}
|
)}
|
||||||
<div className={classes.textBoxContainer}>
|
|
||||||
<input
|
{showFileSelector || value === "" ? (
|
||||||
type="file"
|
<div className={classes.textBoxContainer}>
|
||||||
name={name}
|
<input
|
||||||
onChange={(e) => {
|
type="file"
|
||||||
fileProcess(e, (data: any) => {
|
name={name}
|
||||||
onChange(data);
|
onChange={(e) => {
|
||||||
});
|
const fileName = get(e, "target.files[0].name", "");
|
||||||
}}
|
fileProcess(e, (data: any) => {
|
||||||
accept={accept}
|
onChange(data, fileName);
|
||||||
required
|
});
|
||||||
/>
|
}}
|
||||||
</div>
|
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>
|
</Grid>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -52,13 +52,12 @@ import {
|
|||||||
ICapacity,
|
ICapacity,
|
||||||
ITenantCreator,
|
ITenantCreator,
|
||||||
} from "../../../../common/types";
|
} from "../../../../common/types";
|
||||||
import { NewTenantCredential } from "./TenantCredentialsPrompt/types";
|
|
||||||
|
|
||||||
interface IAddTenantProps {
|
interface IAddTenantProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
closeModalAndRefresh: (
|
closeModalAndRefresh: (
|
||||||
reloadData: boolean,
|
reloadData: boolean,
|
||||||
res: NewTenantCredential | null
|
res: NewServiceAccount | null
|
||||||
) => any;
|
) => any;
|
||||||
classes: any;
|
classes: any;
|
||||||
}
|
}
|
||||||
@@ -178,6 +177,9 @@ const AddTenant = ({
|
|||||||
const [nameTenantValid, setNameTenantValid] = useState<boolean>(false);
|
const [nameTenantValid, setNameTenantValid] = useState<boolean>(false);
|
||||||
const [configValid, setConfigValid] = useState<boolean>(false);
|
const [configValid, setConfigValid] = useState<boolean>(false);
|
||||||
const [configureValid, setConfigureValid] = 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
|
// Custom Elements
|
||||||
const [customDockerhub, setCustomDockerhub] = useState<boolean>(false);
|
const [customDockerhub, setCustomDockerhub] = useState<boolean>(false);
|
||||||
@@ -198,6 +200,20 @@ const AddTenant = ({
|
|||||||
gemaltoCA: "",
|
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*/
|
/*Debounce functions*/
|
||||||
|
|
||||||
// Storage Quotas
|
// Storage Quotas
|
||||||
@@ -351,6 +367,14 @@ const AddTenant = ({
|
|||||||
pattern: /^((.*?)\/(.*?):(.+))$/,
|
pattern: /^((.*?)\/(.*?):(.+))$/,
|
||||||
customPatternMessage: "Format must be of form: 'minio/minio:VERSION'",
|
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);
|
setConfigureValid(Object.keys(commonVal).length === 0);
|
||||||
|
|
||||||
setValidationErrors(commonVal);
|
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 clearValidationError = (fieldKey: string) => {
|
||||||
const newValidationElement = { ...validationErrors };
|
const newValidationElement = { ...validationErrors };
|
||||||
@@ -583,7 +902,7 @@ const AddTenant = ({
|
|||||||
api
|
api
|
||||||
.invoke("POST", `/api/v1/tenants`, dataSend)
|
.invoke("POST", `/api/v1/tenants`, dataSend)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const newSrvAcc: NewTenantCredential = {
|
const newSrvAcc: NewServiceAccount = {
|
||||||
accessKey: res.access_key,
|
accessKey: res.access_key,
|
||||||
secretKey: res.secret_key,
|
secretKey: res.secret_key,
|
||||||
console: {
|
console: {
|
||||||
@@ -971,7 +1290,7 @@ const AddTenant = ({
|
|||||||
buttons: [
|
buttons: [
|
||||||
cancelButton,
|
cancelButton,
|
||||||
{ label: "Back", type: "back", enabled: true },
|
{ 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>
|
<h5>MinIO TLS Certs</h5>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("tlsKey", encodedValue);
|
storeCertInObject("tlsKey", encodedValue);
|
||||||
|
setTlsKeyVal(fileName);
|
||||||
|
clearValidationError("tlsKey");
|
||||||
}}
|
}}
|
||||||
accept=".key,.pem"
|
accept=".key,.pem"
|
||||||
id="tlsKey"
|
id="tlsKey"
|
||||||
name="tlsKey"
|
name="tlsKey"
|
||||||
label="Key"
|
label="Key"
|
||||||
|
error={validationErrors["tlsKey"] || ""}
|
||||||
|
value={tlsKeyVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("tlsCert", encodedValue);
|
storeCertInObject("tlsCert", encodedValue);
|
||||||
|
setTlsCertVal(fileName);
|
||||||
|
clearValidationError("tlsCert");
|
||||||
}}
|
}}
|
||||||
accept=".cer,.crt,.cert,.pem"
|
accept=".cer,.crt,.cert,.pem"
|
||||||
id="tlsCert"
|
id="tlsCert"
|
||||||
name="tlsCert"
|
name="tlsCert"
|
||||||
label="Cert"
|
label="Cert"
|
||||||
|
error={validationErrors["tlsCert"] || ""}
|
||||||
|
value={tlsCertVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<h5>Console TLS Certs</h5>
|
<h5>Console TLS Certs</h5>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("consoleKey", encodedValue);
|
storeCertInObject("consoleKey", encodedValue);
|
||||||
|
setConsoleKeyVal(fileName);
|
||||||
|
clearValidationError("consoleKey");
|
||||||
}}
|
}}
|
||||||
accept=".key,.pem"
|
accept=".key,.pem"
|
||||||
id="consoleKey"
|
id="consoleKey"
|
||||||
name="consoleKey"
|
name="consoleKey"
|
||||||
label="Key"
|
label="Key"
|
||||||
|
error={validationErrors["consoleKey"] || ""}
|
||||||
|
value={consoleKeyVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("consoleCert", encodedValue);
|
storeCertInObject("consoleCert", encodedValue);
|
||||||
|
setConsoleCertVal(fileName);
|
||||||
|
clearValidationError("consoleCert");
|
||||||
}}
|
}}
|
||||||
accept=".cer,.crt,.cert,.pem"
|
accept=".cer,.crt,.cert,.pem"
|
||||||
id="consoleCert"
|
id="consoleCert"
|
||||||
name="consoleCert"
|
name="consoleCert"
|
||||||
label="Cert"
|
label="Cert"
|
||||||
|
error={validationErrors["consoleCert"] || ""}
|
||||||
|
value={consoleCertVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -1077,7 +1412,7 @@ const AddTenant = ({
|
|||||||
buttons: [
|
buttons: [
|
||||||
cancelButton,
|
cancelButton,
|
||||||
{ label: "Back", type: "back", enabled: true },
|
{ 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>
|
<h5>Server</h5>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("serverKey", encodedValue);
|
storeCertInObject("serverKey", encodedValue);
|
||||||
|
setServerKeyVal(fileName);
|
||||||
|
clearValidationError("serverKey");
|
||||||
}}
|
}}
|
||||||
accept=".key,.pem"
|
accept=".key,.pem"
|
||||||
id="serverKey"
|
id="serverKey"
|
||||||
name="serverKey"
|
name="serverKey"
|
||||||
label="Key"
|
label="Key"
|
||||||
|
error={validationErrors["serverKey"] || ""}
|
||||||
|
value={serverKeyVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("serverCert", encodedValue);
|
storeCertInObject("serverCert", encodedValue);
|
||||||
|
setServerCertVal(fileName);
|
||||||
|
clearValidationError("serverCert");
|
||||||
}}
|
}}
|
||||||
accept=".cer,.crt,.cert,.pem"
|
accept=".cer,.crt,.cert,.pem"
|
||||||
id="serverCert"
|
id="serverCert"
|
||||||
name="serverCert"
|
name="serverCert"
|
||||||
label="Cert"
|
label="Cert"
|
||||||
|
error={validationErrors["serverCert"] || ""}
|
||||||
|
value={serverCertVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<h5>Client</h5>
|
<h5>Client</h5>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("clientKey", encodedValue);
|
storeCertInObject("clientKey", encodedValue);
|
||||||
|
setClientKeyVal(fileName);
|
||||||
|
clearValidationError("clientKey");
|
||||||
}}
|
}}
|
||||||
accept=".key,.pem"
|
accept=".key,.pem"
|
||||||
id="clientKey"
|
id="clientKey"
|
||||||
name="clientKey"
|
name="clientKey"
|
||||||
label="Key"
|
label="Key"
|
||||||
|
error={validationErrors["clientKey"] || ""}
|
||||||
|
value={clientKeyVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("clientCert", encodedValue);
|
storeCertInObject("clientCert", encodedValue);
|
||||||
|
setClientCertVal(fileName);
|
||||||
|
clearValidationError("clientCert");
|
||||||
}}
|
}}
|
||||||
accept=".cer,.crt,.cert,.pem"
|
accept=".cer,.crt,.cert,.pem"
|
||||||
id="clientCert"
|
id="clientCert"
|
||||||
name="clientCert"
|
name="clientCert"
|
||||||
label="Cert"
|
label="Cert"
|
||||||
|
error={validationErrors["clientCert"] || ""}
|
||||||
|
value={clientCertVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -1201,9 +1552,11 @@ const AddTenant = ({
|
|||||||
name="vault_engine"
|
name="vault_engine"
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setVaultEngine(e.target.value);
|
setVaultEngine(e.target.value);
|
||||||
|
clearValidationError("vault_engine");
|
||||||
}}
|
}}
|
||||||
label="Engine"
|
label="Engine"
|
||||||
value={vaultEngine}
|
value={vaultEngine}
|
||||||
|
error={validationErrors["vault_engine"] || ""}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -1263,7 +1616,7 @@ const AddTenant = ({
|
|||||||
setVaultSecret(e.target.value);
|
setVaultSecret(e.target.value);
|
||||||
clearValidationError("vault_secret");
|
clearValidationError("vault_secret");
|
||||||
}}
|
}}
|
||||||
label="Id"
|
label="Secret"
|
||||||
value={vaultSecret}
|
value={vaultSecret}
|
||||||
error={validationErrors["vault_secret"] || ""}
|
error={validationErrors["vault_secret"] || ""}
|
||||||
required
|
required
|
||||||
@@ -1277,46 +1630,60 @@ const AddTenant = ({
|
|||||||
name="vault_retry"
|
name="vault_retry"
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setVaultRetry(e.target.value);
|
setVaultRetry(e.target.value);
|
||||||
|
clearValidationError("vault_retry");
|
||||||
}}
|
}}
|
||||||
label="Retry"
|
label="Retry"
|
||||||
value={vaultRetry}
|
value={vaultRetry}
|
||||||
|
error={validationErrors["vault_retry"] || ""}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<h5>TLS</h5>
|
<h5>TLS</h5>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("vaultKey", encodedValue);
|
storeCertInObject("vaultKey", encodedValue);
|
||||||
|
setVaultKeyVal(fileName);
|
||||||
|
clearValidationError("vault_key");
|
||||||
}}
|
}}
|
||||||
accept=".key,.pem"
|
accept=".key,.pem"
|
||||||
id="vault_key"
|
id="vault_key"
|
||||||
name="vault_key"
|
name="vault_key"
|
||||||
label="Key"
|
label="Key"
|
||||||
|
error={validationErrors["vault_key"] || ""}
|
||||||
|
value={vaultKeyVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("vaultCert", encodedValue);
|
storeCertInObject("vaultCert", encodedValue);
|
||||||
|
setVaultCertVal(fileName);
|
||||||
|
clearValidationError("vault_cert");
|
||||||
}}
|
}}
|
||||||
accept=".cer,.crt,.cert,.pem"
|
accept=".cer,.crt,.cert,.pem"
|
||||||
id="vault_cert"
|
id="vault_cert"
|
||||||
name="vault_cert"
|
name="vault_cert"
|
||||||
label="Cert"
|
label="Cert"
|
||||||
|
error={validationErrors["vault_cert"] || ""}
|
||||||
|
value={vaultCertVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("vaultCA", encodedValue);
|
storeCertInObject("vaultCA", encodedValue);
|
||||||
|
setVaultCAVal(fileName);
|
||||||
|
clearValidationError("vault_ca");
|
||||||
}}
|
}}
|
||||||
accept=".cer,.crt,.cert,.pem"
|
accept=".cer,.crt,.cert,.pem"
|
||||||
id="vault_ca"
|
id="vault_ca"
|
||||||
name="vault_ca"
|
name="vault_ca"
|
||||||
label="CA"
|
label="CA"
|
||||||
|
error={validationErrors["vault_ca"] || ""}
|
||||||
|
value={vaultCAVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -1329,9 +1696,11 @@ const AddTenant = ({
|
|||||||
name="vault_ping"
|
name="vault_ping"
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setVaultPing(e.target.value);
|
setVaultPing(e.target.value);
|
||||||
|
clearValidationError("vault_ping");
|
||||||
}}
|
}}
|
||||||
label="Ping"
|
label="Ping"
|
||||||
value={vaultPing}
|
value={vaultPing}
|
||||||
|
error={validationErrors["vault_ping"] || ""}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -1443,11 +1812,11 @@ const AddTenant = ({
|
|||||||
name="gemalto_token"
|
name="gemalto_token"
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setGemaltoToken(e.target.value);
|
setGemaltoToken(e.target.value);
|
||||||
clearValidationError("gemalto_endpoint");
|
clearValidationError("gemalto_token");
|
||||||
}}
|
}}
|
||||||
label="Token"
|
label="Token"
|
||||||
value={gemaltoToken}
|
value={gemaltoToken}
|
||||||
error={validationErrors["gemalto_endpoint"] || ""}
|
error={validationErrors["gemalto_token"] || ""}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -1473,6 +1842,7 @@ const AddTenant = ({
|
|||||||
name="gemalto_retry"
|
name="gemalto_retry"
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setGemaltoRetry(e.target.value);
|
setGemaltoRetry(e.target.value);
|
||||||
|
clearValidationError("gemalto_retry");
|
||||||
}}
|
}}
|
||||||
label="Domain"
|
label="Domain"
|
||||||
value={gemaltoRetry}
|
value={gemaltoRetry}
|
||||||
@@ -1482,13 +1852,17 @@ const AddTenant = ({
|
|||||||
<h5>TLS</h5>
|
<h5>TLS</h5>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
onChange={(encodedValue) => {
|
onChange={(encodedValue, fileName) => {
|
||||||
storeCertInObject("gemaltoCA", encodedValue);
|
storeCertInObject("gemaltoCA", encodedValue);
|
||||||
|
setGemaltoCAVal(fileName);
|
||||||
|
clearValidationError("gemalto_ca");
|
||||||
}}
|
}}
|
||||||
accept=".cer,.crt,.cert,.pem"
|
accept=".cer,.crt,.cert,.pem"
|
||||||
id="gemalto_ca"
|
id="gemalto_ca"
|
||||||
name="gemalto_ca"
|
name="gemalto_ca"
|
||||||
label="CA"
|
label="CA"
|
||||||
|
error={validationErrors["gemalto_ca"] || ""}
|
||||||
|
value={gemaltoCAVal}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -1501,7 +1875,7 @@ const AddTenant = ({
|
|||||||
buttons: [
|
buttons: [
|
||||||
cancelButton,
|
cancelButton,
|
||||||
{ label: "Back", type: "back", enabled: true },
|
{ label: "Back", type: "back", enabled: true },
|
||||||
{ label: "Next", type: "next", enabled: true },
|
{ label: "Next", type: "next", enabled: encryptionValid },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
|
|||||||
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
|
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
|
||||||
import history from "../../../../history";
|
import history from "../../../../history";
|
||||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||||
import TenantCredentialsPrompt from "./TenantCredentialsPrompt/TenantCredentialsPrompt";
|
|
||||||
import { NewTenantCredential } from "./TenantCredentialsPrompt/types";
|
|
||||||
|
|
||||||
interface ITenantsList {
|
interface ITenantsList {
|
||||||
classes: any;
|
classes: any;
|
||||||
@@ -100,11 +98,11 @@ const ListTenants = ({ classes }: ITenantsList) => {
|
|||||||
const [
|
const [
|
||||||
createdAccount,
|
createdAccount,
|
||||||
setCreatedAccount,
|
setCreatedAccount,
|
||||||
] = useState<NewTenantCredential | null>(null);
|
] = useState<NewServiceAccount | null>(null);
|
||||||
|
|
||||||
const closeAddModalAndRefresh = (
|
const closeAddModalAndRefresh = (
|
||||||
reloadData: boolean,
|
reloadData: boolean,
|
||||||
res: NewTenantCredential | null
|
res: NewServiceAccount | null
|
||||||
) => {
|
) => {
|
||||||
setCreateTenantOpen(false);
|
setCreateTenantOpen(false);
|
||||||
|
|
||||||
@@ -238,7 +236,7 @@ const ListTenants = ({ classes }: ITenantsList) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showNewCredentials && (
|
{showNewCredentials && (
|
||||||
<TenantCredentialsPrompt
|
<CredentialsPrompt
|
||||||
newServiceAccount={createdAccount}
|
newServiceAccount={createdAccount}
|
||||||
open={showNewCredentials}
|
open={showNewCredentials}
|
||||||
closeModal={() => {
|
closeModal={() => {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export interface IValidation {
|
|||||||
required: boolean;
|
required: boolean;
|
||||||
pattern?: RegExp;
|
pattern?: RegExp;
|
||||||
customPatternMessage?: string;
|
customPatternMessage?: string;
|
||||||
customValidation?: boolean;
|
customValidation?: boolean; // The validation to trigger the error
|
||||||
customValidationMessage?: string;
|
customValidationMessage?: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user