Add Tenant page refactor (#617)
Refactored add tenant page to be working with reducers & settings styles
This commit is contained in:
@@ -32,7 +32,6 @@ var (
|
||||
bucketsDetail = "/buckets/:bucketName"
|
||||
serviceAccounts = "/account"
|
||||
tenants = "/tenants"
|
||||
addTenant = "/add-tenant"
|
||||
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
|
||||
remoteBuckets = "/remote-buckets"
|
||||
replication = "/replication"
|
||||
@@ -282,7 +281,6 @@ var endpointRules = map[string]ConfigurationActionSet{
|
||||
var operatorRules = map[string]ConfigurationActionSet{
|
||||
tenants: tenantsActionSet,
|
||||
tenantsDetail: tenantsActionSet,
|
||||
addTenant: tenantsActionSet,
|
||||
license: licenseActionSet,
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ func TestOperatorOnlyEndpoints(t *testing.T) {
|
||||
"admin:*",
|
||||
},
|
||||
},
|
||||
want: 4,
|
||||
want: 3,
|
||||
},
|
||||
{
|
||||
name: "Operator Only - all s3 endpoints",
|
||||
@@ -125,7 +125,7 @@ func TestOperatorOnlyEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 4,
|
||||
want: 3,
|
||||
},
|
||||
{
|
||||
name: "Operator Only - all admin and s3 endpoints",
|
||||
@@ -135,14 +135,14 @@ func TestOperatorOnlyEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 4,
|
||||
want: 3,
|
||||
},
|
||||
{
|
||||
name: "Operator Only - default endpoints",
|
||||
args: args{
|
||||
[]string{},
|
||||
},
|
||||
want: 4,
|
||||
want: 3,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -452,3 +452,55 @@ export const snackBarCommon = {
|
||||
maxWidth: "calc(100% - 140px)",
|
||||
},
|
||||
};
|
||||
|
||||
export const wizardCommon = {
|
||||
multiContainer: {
|
||||
display: "flex" as const,
|
||||
alignItems: "center" as const,
|
||||
justifyContent: "flex-start" as const,
|
||||
},
|
||||
sizeFactorContainer: {
|
||||
marginLeft: 8,
|
||||
alignSelf: "flex-start" as const,
|
||||
},
|
||||
headerElement: {
|
||||
position: "sticky" as const,
|
||||
top: 0,
|
||||
paddingTop: 5,
|
||||
marginBottom: 10,
|
||||
zIndex: 500,
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
tableTitle: {
|
||||
fontWeight: 700,
|
||||
width: "30%",
|
||||
},
|
||||
poolError: {
|
||||
color: "#dc1f2e",
|
||||
fontSize: "0.75rem",
|
||||
paddingLeft: 120,
|
||||
},
|
||||
error: {
|
||||
color: "#dc1f2e",
|
||||
fontSize: "0.75rem",
|
||||
},
|
||||
h3Section: {
|
||||
marginTop: 0,
|
||||
},
|
||||
descriptionText: {
|
||||
fontSize: 13,
|
||||
color: "#777777",
|
||||
},
|
||||
container: {
|
||||
padding: "77px 0 0 0",
|
||||
"& h6": {
|
||||
color: "#777777",
|
||||
fontSize: 14,
|
||||
},
|
||||
"& p": {
|
||||
"& span:not(*[class*='smallUnit'])": {
|
||||
fontSize: 16,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -18,23 +18,31 @@ import React, { useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { IWizardMain } from "./types";
|
||||
import WizardPage from "./WizardPage";
|
||||
import { Grid, Paper } from "@material-ui/core";
|
||||
import { Grid } from "@material-ui/core";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
wizardMain: {
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
},
|
||||
wizFromContainer: {
|
||||
marginTop: "32px",
|
||||
height: "calc(100vh - 365px)",
|
||||
minHeight: 450,
|
||||
padding: "0 30px",
|
||||
},
|
||||
wizardSteps: {
|
||||
minWidth: 180,
|
||||
marginRight: 10,
|
||||
borderRight: "#eaeaea 1px solid",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
"& ul": {
|
||||
padding: "0px 15px 0 30px",
|
||||
padding: "0 15px 0 40px",
|
||||
marginTop: "0px",
|
||||
|
||||
"& li": {
|
||||
@@ -56,15 +64,14 @@ const styles = (theme: Theme) =>
|
||||
boxShadow: "none",
|
||||
},
|
||||
},
|
||||
paddedGridItem: {
|
||||
padding: "0px 10px 0px 10px",
|
||||
paddedContentGrid: {
|
||||
padding: "0 10px",
|
||||
},
|
||||
menuPaper: {
|
||||
padding: "20px",
|
||||
},
|
||||
paperContainer: {
|
||||
padding: "10px",
|
||||
maxWidth: "900px",
|
||||
stepsLabel: {
|
||||
fontSize: 20,
|
||||
color: "#393939",
|
||||
fontWeight: 600,
|
||||
margin: "15px 12px",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -114,34 +121,25 @@ const GenericWizard = ({ classes, wizardSteps }: IWizardMain) => {
|
||||
|
||||
return (
|
||||
<Grid container className={classes.wizFromContainer}>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={3}
|
||||
md={3}
|
||||
lg={3}
|
||||
xl={2}
|
||||
className={classes.paddedGridItem}
|
||||
>
|
||||
<Paper className={classes.menuPaper}>
|
||||
<div className={classes.wizardSteps}>
|
||||
<ul>
|
||||
{wizardSteps.map((step, index) => {
|
||||
return (
|
||||
<li key={`wizard-${index.toString()}`}>
|
||||
<button
|
||||
onClick={() => pageChange(index)}
|
||||
disabled={index > currentStep}
|
||||
className={classes.buttonList}
|
||||
>
|
||||
{step.label}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</Paper>
|
||||
<Grid item xs={12} sm={3} md={3} lg={3} xl={2}>
|
||||
<div className={classes.wizardSteps}>
|
||||
<span className={classes.stepsLabel}>Steps</span>
|
||||
<ul>
|
||||
{wizardSteps.map((step, index) => {
|
||||
return (
|
||||
<li key={`wizard-${index.toString()}`}>
|
||||
<button
|
||||
onClick={() => pageChange(index)}
|
||||
disabled={index > currentStep}
|
||||
className={classes.buttonList}
|
||||
>
|
||||
{step.label}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
@@ -150,11 +148,9 @@ const GenericWizard = ({ classes, wizardSteps }: IWizardMain) => {
|
||||
md={9}
|
||||
lg={9}
|
||||
xl={10}
|
||||
className={classes.paddedGridItem}
|
||||
className={classes.paddedContentGrid}
|
||||
>
|
||||
<Paper className={classes.paperContainer}>
|
||||
<WizardPage page={wizardSteps[currentStep]} pageChange={pageChange} />
|
||||
</Paper>
|
||||
<WizardPage page={wizardSteps[currentStep]} pageChange={pageChange} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -28,11 +28,14 @@ const styles = (theme: Theme) =>
|
||||
wizardComponent: {
|
||||
overflowY: "auto",
|
||||
marginBottom: 10,
|
||||
height: "calc(100vh - 435px)",
|
||||
},
|
||||
buttonsContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end" as const,
|
||||
padding: "10px 0",
|
||||
borderTop: "#EAEAEA 1px solid",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ import Users from "./Users/Users";
|
||||
import Groups from "./Groups/Groups";
|
||||
import ConfigurationMain from "./Configurations/ConfigurationMain";
|
||||
import WebhookPanel from "./Configurations/ConfigurationPanels/WebhookPanel";
|
||||
import ListTenants from "./Tenants/ListTenants/ListTenants";
|
||||
import TenantsMain from "./Tenants/TenantsMain";
|
||||
import TenantDetails from "./Tenants/TenantDetails/TenantDetails";
|
||||
import ObjectBrowser from "./ObjectBrowser/ObjectBrowser";
|
||||
import ObjectRouting from "./Buckets/ListBuckets/Objects/ListObjects/ObjectRouting";
|
||||
@@ -55,7 +55,6 @@ import LogsMain from "./Logs/LogsMain";
|
||||
import Heal from "./Heal/Heal";
|
||||
import Watch from "./Watch/Watch";
|
||||
import HealthInfo from "./HealthInfo/HealthInfo";
|
||||
import AddTenant from "./Tenants/ListTenants/AddTenant";
|
||||
|
||||
const drawerWidth = 245;
|
||||
|
||||
@@ -291,13 +290,9 @@ const Console = ({
|
||||
path: "/webhook/audit",
|
||||
},
|
||||
{
|
||||
component: ListTenants,
|
||||
component: TenantsMain,
|
||||
path: "/tenants",
|
||||
},
|
||||
{
|
||||
component: AddTenant,
|
||||
path: "/add-tenant",
|
||||
},
|
||||
{
|
||||
component: TenantDetails,
|
||||
path: "/namespaces/:tenantNamespace/tenants/:tenantName",
|
||||
|
||||
621
portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx
Normal file
621
portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx
Normal file
@@ -0,0 +1,621 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useState, Fragment } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { LinearProgress } from "@material-ui/core";
|
||||
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
modalBasic,
|
||||
settingsCommon,
|
||||
wizardCommon,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import api from "../../../../common/api";
|
||||
import { generatePoolName } from "../../../../common/utils";
|
||||
import GenericWizard from "../../Common/GenericWizard/GenericWizard";
|
||||
import { IWizardElement } from "../../Common/GenericWizard/types";
|
||||
import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
|
||||
import { IAffinityModel, ITenantCreator } from "../../../../common/types";
|
||||
import { KeyPair } from "../ListTenants/utils";
|
||||
|
||||
import { setModalErrorSnackMessage } from "../../../../actions";
|
||||
import { getHardcodedAffinity } from "../TenantDetails/utils";
|
||||
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
|
||||
import NameTenant from "./Steps/NameTenant";
|
||||
import { AppState } from "../../../../store";
|
||||
import { ICertificatesItems, IFieldStore } from "../types";
|
||||
import { updateAddField } from "../actions";
|
||||
import Configure from "./Steps/Configure";
|
||||
import IdentityProvider from "./Steps/IdentityProvider";
|
||||
import Security from "./Steps/Security";
|
||||
import Encryption from "./Steps/Encryption";
|
||||
import TenantSize from "./Steps/TenantSize";
|
||||
import Preview from "./Steps/Preview";
|
||||
|
||||
interface IAddTenantProps {
|
||||
closeAndRefresh: (reloadData: boolean) => any;
|
||||
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
|
||||
updateAddField: typeof updateAddField;
|
||||
fields: IFieldStore;
|
||||
certificates: ICertificatesItems;
|
||||
namespace: string;
|
||||
validPages: string[];
|
||||
advancedMode: boolean;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
...wizardCommon,
|
||||
...settingsCommon,
|
||||
});
|
||||
|
||||
const AddTenant = ({
|
||||
classes,
|
||||
advancedMode,
|
||||
fields,
|
||||
certificates,
|
||||
namespace,
|
||||
validPages,
|
||||
setModalErrorSnackMessage,
|
||||
closeAndRefresh,
|
||||
}: IAddTenantProps) => {
|
||||
// Modals
|
||||
const [showNewCredentials, setShowNewCredentials] = useState<boolean>(false);
|
||||
const [
|
||||
createdAccount,
|
||||
setCreatedAccount,
|
||||
] = useState<NewServiceAccount | null>(null);
|
||||
|
||||
// Fields
|
||||
const [addSending, setAddSending] = useState<boolean>(false);
|
||||
|
||||
/* Send Information to backend */
|
||||
useEffect(() => {
|
||||
const tenantName = fields.nameTenant.tenantName;
|
||||
const selectedStorageClass = fields.nameTenant.selectedStorageClass;
|
||||
const imageName = fields.configure.imageName;
|
||||
const consoleImage = fields.configure.consoleImage;
|
||||
const customDockerhub = fields.configure.customDockerhub;
|
||||
const imageRegistry = fields.configure.imageRegistry;
|
||||
const imageRegistryUsername = fields.configure.imageRegistryUsername;
|
||||
const imageRegistryPassword = fields.configure.imageRegistryPassword;
|
||||
const exposeMinIO = fields.configure.exposeMinIO;
|
||||
const exposeConsole = fields.configure.exposeConsole;
|
||||
const idpSelection = fields.identityProvider.idpSelection;
|
||||
const openIDURL = fields.identityProvider.openIDURL;
|
||||
const openIDClientID = fields.identityProvider.openIDClientID;
|
||||
const openIDSecretID = fields.identityProvider.openIDSecretID;
|
||||
const ADURL = fields.identityProvider.ADURL;
|
||||
const ADSkipTLS = fields.identityProvider.ADSkipTLS;
|
||||
const ADServerInsecure = fields.identityProvider.ADServerInsecure;
|
||||
const ADUserNameFilter = fields.identityProvider.ADUserNameFilter;
|
||||
const ADGroupBaseDN = fields.identityProvider.ADGroupBaseDN;
|
||||
const ADGroupSearchFilter = fields.identityProvider.ADGroupSearchFilter;
|
||||
const ADNameAttribute = fields.identityProvider.ADNameAttribute;
|
||||
const minioCertificates = certificates.minioCertificates;
|
||||
const caCertificates = certificates.caCertificates;
|
||||
const consoleCertificate = certificates.consoleCertificate;
|
||||
const serverCertificate = certificates.serverCertificate;
|
||||
const clientCertificate = certificates.clientCertificate;
|
||||
const vaultCertificate = certificates.vaultCertificate;
|
||||
const vaultCA = certificates.vaultCA;
|
||||
const gemaltoCA = certificates.gemaltoCA;
|
||||
const enableEncryption = fields.encryption.enableEncryption;
|
||||
const encryptionType = fields.encryption.encryptionType;
|
||||
const gemaltoEndpoint = fields.encryption.gemaltoEndpoint;
|
||||
const gemaltoToken = fields.encryption.gemaltoToken;
|
||||
const gemaltoDomain = fields.encryption.gemaltoDomain;
|
||||
const gemaltoRetry = fields.encryption.gemaltoRetry;
|
||||
const awsEndpoint = fields.encryption.awsEndpoint;
|
||||
const awsRegion = fields.encryption.awsRegion;
|
||||
const awsKMSKey = fields.encryption.awsKMSKey;
|
||||
const awsAccessKey = fields.encryption.awsAccessKey;
|
||||
const awsSecretKey = fields.encryption.awsSecretKey;
|
||||
const awsToken = fields.encryption.awsToken;
|
||||
const vaultEndpoint = fields.encryption.vaultEndpoint;
|
||||
const vaultEngine = fields.encryption.vaultEngine;
|
||||
const vaultNamespace = fields.encryption.vaultNamespace;
|
||||
const vaultPrefix = fields.encryption.vaultPrefix;
|
||||
const vaultAppRoleEngine = fields.encryption.vaultAppRoleEngine;
|
||||
const vaultId = fields.encryption.vaultId;
|
||||
const vaultSecret = fields.encryption.vaultSecret;
|
||||
const vaultRetry = fields.encryption.vaultRetry;
|
||||
const vaultPing = fields.encryption.vaultPing;
|
||||
const gcpProjectID = fields.encryption.gcpProjectID;
|
||||
const gcpEndpoint = fields.encryption.gcpEndpoint;
|
||||
const gcpClientEmail = fields.encryption.gcpClientEmail;
|
||||
const gcpClientID = fields.encryption.gcpClientID;
|
||||
const gcpPrivateKeyID = fields.encryption.gcpPrivateKeyID;
|
||||
const gcpPrivateKey = fields.encryption.gcpPrivateKey;
|
||||
const enableAutoCert = fields.security.enableAutoCert;
|
||||
const ecParity = fields.tenantSize.ecParity;
|
||||
const distribution = fields.tenantSize.distribution;
|
||||
const memorySize = fields.tenantSize.memorySize;
|
||||
|
||||
if (addSending) {
|
||||
const poolName = generatePoolName([]);
|
||||
|
||||
const hardCodedAffinity: IAffinityModel = getHardcodedAffinity(
|
||||
tenantName,
|
||||
poolName
|
||||
);
|
||||
|
||||
const erasureCode = ecParity.split(":")[1];
|
||||
|
||||
let dataSend: ITenantCreator = {
|
||||
name: tenantName,
|
||||
namespace: namespace,
|
||||
access_key: "",
|
||||
secret_key: "",
|
||||
enable_tls: enableAutoCert,
|
||||
enable_console: true,
|
||||
enable_prometheus: true,
|
||||
service_name: "",
|
||||
image: imageName,
|
||||
console_image: consoleImage,
|
||||
expose_minio: exposeMinIO,
|
||||
expose_console: exposeConsole,
|
||||
pools: [
|
||||
{
|
||||
name: poolName,
|
||||
servers: distribution.nodes,
|
||||
volumes_per_server: distribution.disks,
|
||||
volume_configuration: {
|
||||
size: distribution.pvSize,
|
||||
storage_class_name: selectedStorageClass,
|
||||
},
|
||||
resources: {
|
||||
requests: {
|
||||
memory: memorySize.request,
|
||||
},
|
||||
limits: {
|
||||
memory: memorySize.limit,
|
||||
},
|
||||
},
|
||||
affinity: hardCodedAffinity,
|
||||
},
|
||||
],
|
||||
erasureCodingParity: parseInt(erasureCode, 10),
|
||||
};
|
||||
|
||||
if (customDockerhub) {
|
||||
dataSend = {
|
||||
...dataSend,
|
||||
image_registry: {
|
||||
registry: imageRegistry,
|
||||
username: imageRegistryUsername,
|
||||
password: imageRegistryPassword,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let tenantCerts: any = null;
|
||||
let consoleCerts: any = null;
|
||||
let caCerts: any = null;
|
||||
|
||||
if (caCertificates.length > 0) {
|
||||
caCerts = {
|
||||
ca_certificates: caCertificates
|
||||
.map((keyPair: KeyPair) => keyPair.encoded_cert)
|
||||
.filter((keyPair) => keyPair),
|
||||
};
|
||||
}
|
||||
|
||||
if (minioCertificates.length > 0) {
|
||||
tenantCerts = {
|
||||
minio: minioCertificates
|
||||
.map((keyPair: KeyPair) => ({
|
||||
crt: keyPair.encoded_cert,
|
||||
key: keyPair.encoded_key,
|
||||
}))
|
||||
.filter((keyPair) => keyPair.crt && keyPair.key),
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
consoleCertificate.encoded_cert !== "" &&
|
||||
consoleCertificate.encoded_key !== ""
|
||||
) {
|
||||
consoleCerts = {
|
||||
console: {
|
||||
crt: consoleCertificate.encoded_cert,
|
||||
key: consoleCertificate.encoded_key,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (tenantCerts || consoleCerts || caCerts) {
|
||||
dataSend = {
|
||||
...dataSend,
|
||||
tls: {
|
||||
...tenantCerts,
|
||||
...consoleCerts,
|
||||
...caCerts,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (enableEncryption) {
|
||||
let insertEncrypt = {};
|
||||
|
||||
switch (encryptionType) {
|
||||
case "gemalto":
|
||||
let gemaltoCAIntroduce = {};
|
||||
|
||||
if (gemaltoCA.encoded_cert !== "") {
|
||||
gemaltoCAIntroduce = {
|
||||
ca: gemaltoCA.encoded_cert,
|
||||
};
|
||||
}
|
||||
insertEncrypt = {
|
||||
gemalto: {
|
||||
keysecure: {
|
||||
endpoint: gemaltoEndpoint,
|
||||
credentials: {
|
||||
token: gemaltoToken,
|
||||
domain: gemaltoDomain,
|
||||
retry: parseInt(gemaltoRetry),
|
||||
},
|
||||
tls: {
|
||||
...gemaltoCAIntroduce,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
break;
|
||||
case "aws":
|
||||
insertEncrypt = {
|
||||
aws: {
|
||||
secretsmanager: {
|
||||
endpoint: awsEndpoint,
|
||||
region: awsRegion,
|
||||
kmskey: awsKMSKey,
|
||||
credentials: {
|
||||
accesskey: awsAccessKey,
|
||||
secretkey: awsSecretKey,
|
||||
token: awsToken,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
break;
|
||||
case "gcp":
|
||||
insertEncrypt = {
|
||||
gcp: {
|
||||
secretmanager: {
|
||||
project_id: gcpProjectID,
|
||||
endpoint: gcpEndpoint,
|
||||
credentials: {
|
||||
client_email: gcpClientEmail,
|
||||
client_id: gcpClientID,
|
||||
private_key_id: gcpPrivateKeyID,
|
||||
private_key: gcpPrivateKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
break;
|
||||
case "vault":
|
||||
let vaultKeyPair = null;
|
||||
let vaultCAInsert = null;
|
||||
if (
|
||||
vaultCertificate.encoded_key !== "" &&
|
||||
vaultCertificate.encoded_cert !== ""
|
||||
) {
|
||||
vaultKeyPair = {
|
||||
key: vaultCertificate.encoded_key,
|
||||
crt: vaultCertificate.encoded_cert,
|
||||
};
|
||||
}
|
||||
if (vaultCA.encoded_cert !== "") {
|
||||
vaultCAInsert = {
|
||||
ca: vaultCA.encoded_cert,
|
||||
};
|
||||
}
|
||||
let vaultTLS = null;
|
||||
if (vaultKeyPair || vaultCA) {
|
||||
vaultTLS = {
|
||||
tls: {
|
||||
...vaultKeyPair,
|
||||
...vaultCAInsert,
|
||||
},
|
||||
};
|
||||
}
|
||||
insertEncrypt = {
|
||||
vault: {
|
||||
endpoint: vaultEndpoint,
|
||||
engine: vaultEngine,
|
||||
namespace: vaultNamespace,
|
||||
prefix: vaultPrefix,
|
||||
approle: {
|
||||
engine: vaultAppRoleEngine,
|
||||
id: vaultId,
|
||||
secret: vaultSecret,
|
||||
retry: parseInt(vaultRetry),
|
||||
},
|
||||
...vaultTLS,
|
||||
status: {
|
||||
ping: parseInt(vaultPing),
|
||||
},
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
let encryptionServerKeyPair: any = {};
|
||||
let encryptionClientKeyPair: any = {};
|
||||
|
||||
if (
|
||||
clientCertificate.encoded_key !== "" &&
|
||||
clientCertificate.encoded_cert !== ""
|
||||
) {
|
||||
encryptionClientKeyPair = {
|
||||
client: {
|
||||
key: clientCertificate.encoded_key,
|
||||
crt: clientCertificate.encoded_cert,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
serverCertificate.encoded_key !== "" &&
|
||||
serverCertificate.encoded_cert !== ""
|
||||
) {
|
||||
encryptionServerKeyPair = {
|
||||
server: {
|
||||
key: serverCertificate.encoded_key,
|
||||
crt: serverCertificate.encoded_cert,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
dataSend = {
|
||||
...dataSend,
|
||||
encryption: {
|
||||
...encryptionClientKeyPair,
|
||||
...encryptionServerKeyPair,
|
||||
...insertEncrypt,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (idpSelection !== "Built-in") {
|
||||
let dataIDP: any = {};
|
||||
|
||||
switch (idpSelection) {
|
||||
case "OpenID":
|
||||
dataIDP = {
|
||||
oidc: {
|
||||
url: openIDURL,
|
||||
client_id: openIDClientID,
|
||||
secret_id: openIDSecretID,
|
||||
},
|
||||
};
|
||||
break;
|
||||
case "AD":
|
||||
dataIDP = {
|
||||
active_directory: {
|
||||
url: ADURL,
|
||||
skip_tls_verification: ADSkipTLS,
|
||||
server_insecure: ADServerInsecure,
|
||||
username_format: "",
|
||||
user_search_filter: ADUserNameFilter,
|
||||
group_search_base_dn: ADGroupBaseDN,
|
||||
group_search_filter: ADGroupSearchFilter,
|
||||
group_name_attribute: ADNameAttribute,
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
dataSend = {
|
||||
...dataSend,
|
||||
idp: { ...dataIDP },
|
||||
};
|
||||
}
|
||||
|
||||
api
|
||||
.invoke("POST", `/api/v1/tenants`, dataSend)
|
||||
.then((res) => {
|
||||
const newSrvAcc: NewServiceAccount = {
|
||||
console: {
|
||||
accessKey: res.console.access_key,
|
||||
secretKey: res.console.secret_key,
|
||||
},
|
||||
};
|
||||
|
||||
setAddSending(false);
|
||||
|
||||
setShowNewCredentials(true);
|
||||
setCreatedAccount(newSrvAcc);
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddSending(false);
|
||||
setModalErrorSnackMessage(err);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [addSending]);
|
||||
|
||||
const cancelButton = {
|
||||
label: "Cancel",
|
||||
type: "other",
|
||||
enabled: true,
|
||||
action: () => {
|
||||
closeAndRefresh(false);
|
||||
},
|
||||
};
|
||||
|
||||
const wizardSteps: IWizardElement[] = [
|
||||
{
|
||||
label: "Name Tenant",
|
||||
componentRender: <NameTenant />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("nameTenant"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Configure",
|
||||
advancedOnly: true,
|
||||
componentRender: <Configure />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("configure"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Identity Provider",
|
||||
advancedOnly: true,
|
||||
componentRender: <IdentityProvider />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("identityProvider"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Security",
|
||||
advancedOnly: true,
|
||||
componentRender: <Security />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("security"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Encryption",
|
||||
advancedOnly: true,
|
||||
componentRender: <Encryption />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("encryption"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Tenant Size",
|
||||
componentRender: <TenantSize />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("tenantSize"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Preview Configuration",
|
||||
componentRender: <Preview />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Create",
|
||||
type: "submit",
|
||||
enabled: !addSending,
|
||||
action: () => {
|
||||
setAddSending(true);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
let filteredWizardSteps = wizardSteps;
|
||||
|
||||
if (!advancedMode) {
|
||||
filteredWizardSteps = wizardSteps.filter((step) => !step.advancedOnly);
|
||||
}
|
||||
|
||||
const closeCredentialsModal = () => {
|
||||
closeAndRefresh(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.customTitle}>
|
||||
Create New Tenant
|
||||
</Grid>
|
||||
{addSending && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
{showNewCredentials && (
|
||||
<CredentialsPrompt
|
||||
newServiceAccount={createdAccount}
|
||||
open={showNewCredentials}
|
||||
closeModal={() => {
|
||||
closeCredentialsModal();
|
||||
}}
|
||||
entity="Tenant"
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<GenericWizard wizardSteps={filteredWizardSteps} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
advancedMode: state.tenants.createTenant.advancedModeOn,
|
||||
namespace: state.tenants.createTenant.fields.nameTenant.namespace,
|
||||
validPages: state.tenants.createTenant.validPages,
|
||||
fields: state.tenants.createTenant.fields,
|
||||
certificates: state.tenants.createTenant.certificates,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
setModalErrorSnackMessage,
|
||||
updateAddField,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(AddTenant));
|
||||
@@ -0,0 +1,328 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 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, useCallback, Fragment } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import {
|
||||
modalBasic,
|
||||
wizardCommon,
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import { updateAddField, isPageValid } from "../../actions";
|
||||
import { AppState } from "../../../../../store";
|
||||
import { clearValidationError } from "../../utils";
|
||||
import {
|
||||
commonFormValidation,
|
||||
IValidation,
|
||||
} from "../../../../../utils/validationFunctions";
|
||||
import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
|
||||
interface IConfigureProps {
|
||||
updateAddField: typeof updateAddField;
|
||||
isPageValid: typeof isPageValid;
|
||||
classes: any;
|
||||
customImage: boolean;
|
||||
imageName: string;
|
||||
consoleImage: string;
|
||||
customDockerhub: boolean;
|
||||
imageRegistry: string;
|
||||
imageRegistryUsername: string;
|
||||
imageRegistryPassword: string;
|
||||
exposeMinIO: boolean;
|
||||
exposeConsole: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
...wizardCommon,
|
||||
});
|
||||
|
||||
const Configure = ({
|
||||
classes,
|
||||
customImage,
|
||||
imageName,
|
||||
consoleImage,
|
||||
customDockerhub,
|
||||
imageRegistry,
|
||||
imageRegistryUsername,
|
||||
imageRegistryPassword,
|
||||
exposeMinIO,
|
||||
exposeConsole,
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
}: IConfigureProps) => {
|
||||
const [validationErrors, setValidationErrors] = useState<any>({});
|
||||
|
||||
// Common
|
||||
const updateField = useCallback(
|
||||
(field: string, value: any) => {
|
||||
updateAddField("configure", field, value);
|
||||
},
|
||||
[updateAddField]
|
||||
);
|
||||
|
||||
// Validation
|
||||
useEffect(() => {
|
||||
let customAccountValidation: IValidation[] = [];
|
||||
|
||||
if (customImage) {
|
||||
customAccountValidation = [
|
||||
...customAccountValidation,
|
||||
{
|
||||
fieldKey: "image",
|
||||
required: true,
|
||||
value: imageName,
|
||||
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'",
|
||||
},
|
||||
];
|
||||
if (customDockerhub) {
|
||||
customAccountValidation = [
|
||||
...customAccountValidation,
|
||||
{
|
||||
fieldKey: "registry",
|
||||
required: true,
|
||||
value: imageRegistry,
|
||||
},
|
||||
{
|
||||
fieldKey: "registryUsername",
|
||||
required: true,
|
||||
value: imageRegistryUsername,
|
||||
},
|
||||
{
|
||||
fieldKey: "registryPassword",
|
||||
required: true,
|
||||
value: imageRegistryPassword,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const commonVal = commonFormValidation(customAccountValidation);
|
||||
|
||||
isPageValid("configure", Object.keys(commonVal).length === 0);
|
||||
|
||||
setValidationErrors(commonVal);
|
||||
}, [
|
||||
customImage,
|
||||
imageName,
|
||||
consoleImage,
|
||||
customDockerhub,
|
||||
imageRegistry,
|
||||
imageRegistryUsername,
|
||||
imageRegistryPassword,
|
||||
isPageValid,
|
||||
]);
|
||||
|
||||
const cleanValidation = (fieldName: string) => {
|
||||
setValidationErrors(clearValidationError(validationErrors, fieldName));
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Configure</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Basic configurations for tenant management
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="custom_image"
|
||||
id="custom_image"
|
||||
name="custom_image"
|
||||
checked={customImage}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
updateField("customImage", checked);
|
||||
}}
|
||||
label={"Use custom image"}
|
||||
/>
|
||||
</Grid>
|
||||
{customImage && (
|
||||
<Fragment>
|
||||
Please enter the MinIO image from dockerhub to use
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="image"
|
||||
name="image"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("imageName", e.target.value);
|
||||
cleanValidation("image");
|
||||
}}
|
||||
label="MinIO's Image"
|
||||
value={imageName}
|
||||
error={validationErrors["image"] || ""}
|
||||
placeholder="E.g. minio/minio:RELEASE.2020-05-08T02-40-49Z"
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="consoleImage"
|
||||
name="consoleImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("consoleImage", e.target.value);
|
||||
cleanValidation("consoleImage");
|
||||
}}
|
||||
label="Console's Image"
|
||||
value={consoleImage}
|
||||
error={validationErrors["consoleImage"] || ""}
|
||||
placeholder="E.g. minio/console:v0.3.13"
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
{customImage && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="custom_docker_hub"
|
||||
id="custom_docker_hub"
|
||||
name="custom_docker_hub"
|
||||
checked={customDockerhub}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
updateField("customDockerhub", checked);
|
||||
}}
|
||||
label={"Set/Update Image Registry"}
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
{customDockerhub && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="registry"
|
||||
name="registry"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("imageRegistry", e.target.value);
|
||||
}}
|
||||
label="Endpoint"
|
||||
value={imageRegistry}
|
||||
error={validationErrors["registry"] || ""}
|
||||
placeholder="E.g. https://index.docker.io/v1/"
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="registryUsername"
|
||||
name="registryUsername"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("imageRegistryUsername", e.target.value);
|
||||
}}
|
||||
label="Username"
|
||||
value={imageRegistryUsername}
|
||||
error={validationErrors["registryUsername"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="registryPassword"
|
||||
name="registryPassword"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("imageRegistryPassword", e.target.value);
|
||||
}}
|
||||
label="Password"
|
||||
value={imageRegistryPassword}
|
||||
error={validationErrors["registryPassword"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Expose Services</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Whether the tenant's services should request an external IP.
|
||||
</span>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="expose_minio"
|
||||
id="expose_minio"
|
||||
name="expose_minio"
|
||||
checked={exposeMinIO}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
updateField("exposeMinIO", checked);
|
||||
}}
|
||||
label={"Expose MiniO Service"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="expose_console"
|
||||
id="expose_console"
|
||||
name="expose_console"
|
||||
checked={exposeConsole}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
updateField("exposeConsole", checked);
|
||||
}}
|
||||
label={"Expose Console Service"}
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
customImage: state.tenants.createTenant.fields.configure.customImage,
|
||||
imageName: state.tenants.createTenant.fields.configure.imageName,
|
||||
consoleImage: state.tenants.createTenant.fields.configure.consoleImage,
|
||||
customDockerhub: state.tenants.createTenant.fields.configure.customDockerhub,
|
||||
imageRegistry: state.tenants.createTenant.fields.configure.imageRegistry,
|
||||
imageRegistryUsername:
|
||||
state.tenants.createTenant.fields.configure.imageRegistryUsername,
|
||||
imageRegistryPassword:
|
||||
state.tenants.createTenant.fields.configure.imageRegistryPassword,
|
||||
exposeMinIO: state.tenants.createTenant.fields.configure.exposeMinIO,
|
||||
exposeConsole: state.tenants.createTenant.fields.configure.exposeConsole,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(Configure));
|
||||
@@ -0,0 +1,923 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment, useState, useEffect, useCallback } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Typography } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import {
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
addFileServerCert,
|
||||
addFileClientCert,
|
||||
addFileVaultCert,
|
||||
addFileVaultCa,
|
||||
addFileGemaltoCa,
|
||||
} from "../../actions";
|
||||
import {
|
||||
modalBasic,
|
||||
wizardCommon,
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import { AppState } from "../../../../../store";
|
||||
import { clearValidationError } from "../../utils";
|
||||
import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
import FileSelector from "../../../Common/FormComponents/FileSelector/FileSelector";
|
||||
import RadioGroupSelector from "../../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
import {
|
||||
commonFormValidation,
|
||||
IValidation,
|
||||
} from "../../../../../utils/validationFunctions";
|
||||
import { KeyPair } from "../../ListTenants/utils";
|
||||
|
||||
interface IEncryptionProps {
|
||||
classes: any;
|
||||
updateAddField: typeof updateAddField;
|
||||
isPageValid: typeof isPageValid;
|
||||
addFileServerCert: typeof addFileServerCert;
|
||||
addFileClientCert: typeof addFileClientCert;
|
||||
addFileVaultCert: typeof addFileVaultCert;
|
||||
addFileVaultCa: typeof addFileVaultCa;
|
||||
addFileGemaltoCa: typeof addFileGemaltoCa;
|
||||
enableEncryption: boolean;
|
||||
encryptionType: string;
|
||||
gemaltoEndpoint: string;
|
||||
gemaltoToken: string;
|
||||
gemaltoDomain: string;
|
||||
gemaltoRetry: string;
|
||||
awsEndpoint: string;
|
||||
awsRegion: string;
|
||||
awsKMSKey: string;
|
||||
awsAccessKey: string;
|
||||
awsSecretKey: string;
|
||||
awsToken: string;
|
||||
vaultEndpoint: string;
|
||||
vaultEngine: string;
|
||||
vaultNamespace: string;
|
||||
vaultPrefix: string;
|
||||
vaultAppRoleEngine: string;
|
||||
vaultId: string;
|
||||
vaultSecret: string;
|
||||
vaultRetry: string;
|
||||
vaultPing: string;
|
||||
gcpProjectID: string;
|
||||
gcpEndpoint: string;
|
||||
gcpClientEmail: string;
|
||||
gcpClientID: string;
|
||||
gcpPrivateKeyID: string;
|
||||
gcpPrivateKey: string;
|
||||
enableCustomCertsForKES: boolean;
|
||||
enableAutoCert: boolean;
|
||||
enableTLS: boolean;
|
||||
enableCustomCerts: boolean;
|
||||
minioCertificates: KeyPair[];
|
||||
serverCertificate: KeyPair;
|
||||
clientCertificate: KeyPair;
|
||||
vaultCertificate: KeyPair;
|
||||
vaultCA: KeyPair;
|
||||
gemaltoCA: KeyPair;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
...wizardCommon,
|
||||
});
|
||||
|
||||
const Encryption = ({
|
||||
classes,
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
addFileServerCert,
|
||||
addFileClientCert,
|
||||
addFileVaultCert,
|
||||
addFileVaultCa,
|
||||
addFileGemaltoCa,
|
||||
enableEncryption,
|
||||
enableCustomCerts,
|
||||
encryptionType,
|
||||
gemaltoEndpoint,
|
||||
gemaltoToken,
|
||||
gemaltoDomain,
|
||||
gemaltoRetry,
|
||||
awsEndpoint,
|
||||
awsRegion,
|
||||
awsKMSKey,
|
||||
awsAccessKey,
|
||||
awsSecretKey,
|
||||
awsToken,
|
||||
vaultEndpoint,
|
||||
vaultEngine,
|
||||
vaultNamespace,
|
||||
vaultPrefix,
|
||||
vaultAppRoleEngine,
|
||||
vaultId,
|
||||
vaultSecret,
|
||||
vaultRetry,
|
||||
vaultPing,
|
||||
gcpProjectID,
|
||||
gcpEndpoint,
|
||||
gcpClientEmail,
|
||||
gcpClientID,
|
||||
gcpPrivateKeyID,
|
||||
gcpPrivateKey,
|
||||
enableCustomCertsForKES,
|
||||
enableAutoCert,
|
||||
enableTLS,
|
||||
minioCertificates,
|
||||
serverCertificate,
|
||||
clientCertificate,
|
||||
vaultCertificate,
|
||||
vaultCA,
|
||||
gemaltoCA,
|
||||
}: IEncryptionProps) => {
|
||||
const [validationErrors, setValidationErrors] = useState<any>({});
|
||||
|
||||
let encryptionAvailable = false;
|
||||
if (
|
||||
enableTLS &&
|
||||
(enableAutoCert ||
|
||||
(minioCertificates &&
|
||||
minioCertificates.filter(
|
||||
(item) => item.encoded_key && item.encoded_cert
|
||||
).length > 0))
|
||||
) {
|
||||
encryptionAvailable = true;
|
||||
}
|
||||
|
||||
// Common
|
||||
const updateField = useCallback(
|
||||
(field: string, value: any) => {
|
||||
updateAddField("encryption", field, value);
|
||||
},
|
||||
[updateAddField]
|
||||
);
|
||||
|
||||
const cleanValidation = (fieldName: string) => {
|
||||
setValidationErrors(clearValidationError(validationErrors, fieldName));
|
||||
};
|
||||
|
||||
// Validation
|
||||
useEffect(() => {
|
||||
let encryptionValidation: IValidation[] = [];
|
||||
|
||||
if (enableEncryption) {
|
||||
if (enableCustomCerts) {
|
||||
encryptionValidation = [
|
||||
...encryptionValidation,
|
||||
{
|
||||
fieldKey: "serverKey",
|
||||
required: !enableAutoCert,
|
||||
value: serverCertificate.encoded_key,
|
||||
},
|
||||
{
|
||||
fieldKey: "serverCert",
|
||||
required: !enableAutoCert,
|
||||
value: serverCertificate.encoded_cert,
|
||||
},
|
||||
{
|
||||
fieldKey: "clientKey",
|
||||
required: !enableAutoCert,
|
||||
value: clientCertificate.encoded_key,
|
||||
},
|
||||
{
|
||||
fieldKey: "clientCert",
|
||||
required: !enableAutoCert,
|
||||
value: clientCertificate.encoded_cert,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (encryptionType === "vault") {
|
||||
encryptionValidation = [
|
||||
...encryptionValidation,
|
||||
{
|
||||
fieldKey: "vault_endpoint",
|
||||
required: true,
|
||||
value: vaultEndpoint,
|
||||
},
|
||||
{
|
||||
fieldKey: "vault_id",
|
||||
required: true,
|
||||
value: vaultId,
|
||||
},
|
||||
{
|
||||
fieldKey: "vault_secret",
|
||||
required: true,
|
||||
value: vaultSecret,
|
||||
},
|
||||
{
|
||||
fieldKey: "vault_ping",
|
||||
required: false,
|
||||
value: vaultPing,
|
||||
customValidation: parseInt(vaultPing) < 0,
|
||||
customValidationMessage: "Value needs to be 0 or greater",
|
||||
},
|
||||
{
|
||||
fieldKey: "vault_retry",
|
||||
required: false,
|
||||
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: false,
|
||||
value: gemaltoRetry,
|
||||
customValidation: parseInt(gemaltoRetry) < 0,
|
||||
customValidationMessage: "Value needs to be 0 or greater",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const commonVal = commonFormValidation(encryptionValidation);
|
||||
|
||||
isPageValid("encryption", Object.keys(commonVal).length === 0);
|
||||
|
||||
setValidationErrors(commonVal);
|
||||
}, [
|
||||
enableEncryption,
|
||||
encryptionType,
|
||||
vaultEndpoint,
|
||||
vaultEngine,
|
||||
vaultId,
|
||||
vaultSecret,
|
||||
vaultPing,
|
||||
vaultRetry,
|
||||
awsEndpoint,
|
||||
awsRegion,
|
||||
awsSecretKey,
|
||||
awsAccessKey,
|
||||
gemaltoEndpoint,
|
||||
gemaltoToken,
|
||||
gemaltoDomain,
|
||||
gemaltoRetry,
|
||||
gcpProjectID,
|
||||
isPageValid,
|
||||
enableAutoCert,
|
||||
enableCustomCerts,
|
||||
serverCertificate.encoded_key,
|
||||
serverCertificate.encoded_cert,
|
||||
clientCertificate.encoded_key,
|
||||
clientCertificate.encoded_cert,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Encryption</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
How would you like to encrypt the information at rest.
|
||||
</span>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="enableEncryption"
|
||||
id="enableEncryption"
|
||||
name="enableEncryption"
|
||||
checked={enableEncryption}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
updateField("enableEncryption", checked);
|
||||
}}
|
||||
label={"Enable Server Side Encryption"}
|
||||
disabled={!encryptionAvailable}
|
||||
/>
|
||||
</Grid>
|
||||
{enableEncryption && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroupSelector
|
||||
currentSelection={encryptionType}
|
||||
id="encryptionType"
|
||||
name="encryptionType"
|
||||
label="Encryption Options"
|
||||
onChange={(e) => {
|
||||
updateField("encryptionType", e.target.value);
|
||||
}}
|
||||
selectorOptions={[
|
||||
{ label: "Vault", value: "vault" },
|
||||
{ label: "AWS", value: "aws" },
|
||||
{ label: "Gemalto", value: "gemalto" },
|
||||
{ label: "GCP", value: "gcp" },
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
{encryptionType === "vault" && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="enableCustomCertsForKES"
|
||||
id="enableCustomCertsForKES"
|
||||
name="enableCustomCertsForKES"
|
||||
checked={enableCustomCertsForKES || !enableAutoCert}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
updateField("enableCustomCertsForKES", checked);
|
||||
}}
|
||||
label={"Custom Certificates"}
|
||||
disabled={!enableAutoCert}
|
||||
/>
|
||||
</Grid>
|
||||
{(enableCustomCertsForKES || !enableAutoCert) && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="overline" display="block" gutterBottom>
|
||||
Encryption Service Certificates
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid container>
|
||||
<Grid item xs={6}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addFileServerCert("key", fileName, encodedValue);
|
||||
cleanValidation("serverKey");
|
||||
}}
|
||||
accept=".key,.pem"
|
||||
id="serverKey"
|
||||
name="serverKey"
|
||||
label="Key"
|
||||
error={validationErrors["serverKey"] || ""}
|
||||
value={serverCertificate.key}
|
||||
required={!enableAutoCert}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addFileServerCert("cert", fileName, encodedValue);
|
||||
cleanValidation("serverCert");
|
||||
}}
|
||||
accept=".cer,.crt,.cert,.pem"
|
||||
id="serverCert"
|
||||
name="serverCert"
|
||||
label="Cert"
|
||||
error={validationErrors["serverCert"] || ""}
|
||||
value={serverCertificate.cert}
|
||||
required={!enableAutoCert}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="overline" display="block" gutterBottom>
|
||||
Mutual TLS authentication
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid container>
|
||||
<Grid item xs={6}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addFileClientCert("key", fileName, encodedValue);
|
||||
cleanValidation("clientKey");
|
||||
}}
|
||||
accept=".key,.pem"
|
||||
id="clientKey"
|
||||
name="clientKey"
|
||||
label="Key"
|
||||
error={validationErrors["clientKey"] || ""}
|
||||
value={clientCertificate.key}
|
||||
required={!enableAutoCert}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addFileClientCert("cert", fileName, encodedValue);
|
||||
cleanValidation("clientCert");
|
||||
}}
|
||||
accept=".cer,.crt,.cert,.pem"
|
||||
id="clientCert"
|
||||
name="clientCert"
|
||||
label="Cert"
|
||||
error={validationErrors["clientCert"] || ""}
|
||||
value={clientCertificate.cert}
|
||||
required={!enableAutoCert}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="vault_endpoint"
|
||||
name="vault_endpoint"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("vaultEndpoint", e.target.value);
|
||||
cleanValidation("vault_endpoint");
|
||||
}}
|
||||
label="Endpoint"
|
||||
value={vaultEndpoint}
|
||||
error={validationErrors["vault_endpoint"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="vault_engine"
|
||||
name="vault_engine"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("vaultEngine", e.target.value);
|
||||
cleanValidation("vault_engine");
|
||||
}}
|
||||
label="Engine"
|
||||
value={vaultEngine}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="vault_namespace"
|
||||
name="vault_namespace"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("vaultNamespace", e.target.value);
|
||||
}}
|
||||
label="Namespace"
|
||||
value={vaultNamespace}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="vault_prefix"
|
||||
name="vault_prefix"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("vaultPrefix", e.target.value);
|
||||
}}
|
||||
label="Prefix"
|
||||
value={vaultPrefix}
|
||||
/>
|
||||
</Grid>
|
||||
<h5>App Role</h5>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="vault_approle_engine"
|
||||
name="vault_approle_engine"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("vaultAppRoleEngine", e.target.value);
|
||||
}}
|
||||
label="Engine"
|
||||
value={vaultAppRoleEngine}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="vault_id"
|
||||
name="vault_id"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("vaultId", e.target.value);
|
||||
cleanValidation("vault_id");
|
||||
}}
|
||||
label="AppRole ID"
|
||||
value={vaultId}
|
||||
error={validationErrors["vault_id"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="vault_secret"
|
||||
name="vault_secret"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("vaultSecret", e.target.value);
|
||||
cleanValidation("vault_secret");
|
||||
}}
|
||||
label="AppRole Secret"
|
||||
value={vaultSecret}
|
||||
error={validationErrors["vault_secret"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
min="0"
|
||||
id="vault_retry"
|
||||
name="vault_retry"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("vaultRetry", e.target.value);
|
||||
cleanValidation("vault_retry");
|
||||
}}
|
||||
label="Retry (Seconds)"
|
||||
value={vaultRetry}
|
||||
error={validationErrors["vault_retry"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<h5>Mutual TLS authentication (optional)</h5>
|
||||
<Grid container>
|
||||
<Grid item xs={6}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addFileVaultCert("key", fileName, encodedValue);
|
||||
cleanValidation("vault_key");
|
||||
}}
|
||||
accept=".key,.pem"
|
||||
id="vault_key"
|
||||
name="vault_key"
|
||||
label="Key"
|
||||
value={vaultCertificate.key}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addFileVaultCert("cert", fileName, encodedValue);
|
||||
cleanValidation("vault_cert");
|
||||
}}
|
||||
accept=".cer,.crt,.cert,.pem"
|
||||
id="vault_cert"
|
||||
name="vault_cert"
|
||||
label="Cert"
|
||||
value={vaultCertificate.cert}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addFileVaultCa(fileName, encodedValue);
|
||||
cleanValidation("vault_ca");
|
||||
}}
|
||||
accept=".cer,.crt,.cert,.pem"
|
||||
id="vault_ca"
|
||||
name="vault_ca"
|
||||
label="CA"
|
||||
value={vaultCA.cert}
|
||||
/>
|
||||
</Grid>
|
||||
<h5>Status</h5>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
min="0"
|
||||
id="vault_ping"
|
||||
name="vault_ping"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("vaultPing", e.target.value);
|
||||
cleanValidation("vault_ping");
|
||||
}}
|
||||
label="Ping (Seconds)"
|
||||
value={vaultPing}
|
||||
error={validationErrors["vault_ping"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
{encryptionType === "gcp" && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="gcp_project_id"
|
||||
name="gcp_project_id"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("gcpProjectID", e.target.value);
|
||||
}}
|
||||
label="Project ID"
|
||||
value={gcpProjectID}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="gcp_endpoint"
|
||||
name="gcp_endpoint"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("gcpEndpoint", e.target.value);
|
||||
}}
|
||||
label="Endpoint"
|
||||
value={gcpEndpoint}
|
||||
/>
|
||||
</Grid>
|
||||
<h5>Credentials</h5>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="gcp_client_email"
|
||||
name="gcp_client_email"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("gcpClientEmail", e.target.value);
|
||||
}}
|
||||
label="Client Email"
|
||||
value={gcpClientEmail}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="gcp_client_id"
|
||||
name="gcp_client_id"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("gcpClientID", e.target.value);
|
||||
}}
|
||||
label="Client ID"
|
||||
value={gcpClientID}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="gcp_private_key_id"
|
||||
name="gcp_private_key_id"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("gcpPrivateKeyID", e.target.value);
|
||||
}}
|
||||
label="Private Key ID"
|
||||
value={gcpPrivateKeyID}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="gcp_private_key"
|
||||
name="gcp_private_key"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("gcpPrivateKey", e.target.value);
|
||||
}}
|
||||
label="Private Key"
|
||||
value={gcpPrivateKey}
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
{encryptionType === "aws" && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="aws_endpoint"
|
||||
name="aws_endpoint"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("awsEndpoint", e.target.value);
|
||||
cleanValidation("aws_endpoint");
|
||||
}}
|
||||
label="Endpoint"
|
||||
value={awsEndpoint}
|
||||
error={validationErrors["aws_endpoint"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="aws_region"
|
||||
name="aws_region"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("awsRegion", e.target.value);
|
||||
cleanValidation("aws_region");
|
||||
}}
|
||||
label="Region"
|
||||
value={awsRegion}
|
||||
error={validationErrors["aws_region"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="aws_kmsKey"
|
||||
name="aws_kmsKey"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("awsKMSKey", e.target.value);
|
||||
}}
|
||||
label="KMS Key"
|
||||
value={awsKMSKey}
|
||||
/>
|
||||
</Grid>
|
||||
<h5>Credentials</h5>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="aws_accessKey"
|
||||
name="aws_accessKey"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("awsAccessKey", e.target.value);
|
||||
cleanValidation("aws_accessKey");
|
||||
}}
|
||||
label="Access Key"
|
||||
value={awsAccessKey}
|
||||
error={validationErrors["aws_accessKey"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="aws_secretKey"
|
||||
name="aws_secretKey"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("awsSecretKey", e.target.value);
|
||||
cleanValidation("aws_secretKey");
|
||||
}}
|
||||
label="Secret Key"
|
||||
value={awsSecretKey}
|
||||
error={validationErrors["aws_secretKey"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="aws_token"
|
||||
name="aws_token"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("awsToken", e.target.value);
|
||||
}}
|
||||
label="Token"
|
||||
value={awsToken}
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
{encryptionType === "gemalto" && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="gemalto_endpoint"
|
||||
name="gemalto_endpoint"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("gemaltoEndpoint", e.target.value);
|
||||
cleanValidation("gemalto_endpoint");
|
||||
}}
|
||||
label="Endpoint"
|
||||
value={gemaltoEndpoint}
|
||||
error={validationErrors["gemalto_endpoint"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<h5>Credentials</h5>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="gemalto_token"
|
||||
name="gemalto_token"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("gemaltoToken", e.target.value);
|
||||
cleanValidation("gemalto_token");
|
||||
}}
|
||||
label="Token"
|
||||
value={gemaltoToken}
|
||||
error={validationErrors["gemalto_token"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="gemalto_domain"
|
||||
name="gemalto_domain"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("gemaltoDomain", e.target.value);
|
||||
cleanValidation("gemalto_domain");
|
||||
}}
|
||||
label="Domain"
|
||||
value={gemaltoDomain}
|
||||
error={validationErrors["gemalto_domain"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
min="0"
|
||||
id="gemalto_retry"
|
||||
name="gemalto_retry"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("gemaltoRetry", e.target.value);
|
||||
cleanValidation("gemalto_retry");
|
||||
}}
|
||||
label="Retry (seconds)"
|
||||
value={gemaltoRetry}
|
||||
error={validationErrors["gemalto_retry"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<h5>Custom CA Root certificate verification</h5>
|
||||
<Grid item xs={12}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addFileGemaltoCa(fileName, encodedValue);
|
||||
cleanValidation("gemalto_ca");
|
||||
}}
|
||||
accept=".cer,.crt,.cert,.pem"
|
||||
id="gemalto_ca"
|
||||
name="gemalto_ca"
|
||||
label="CA"
|
||||
value={gemaltoCA.cert}
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
enableEncryption:
|
||||
state.tenants.createTenant.fields.encryption.enableEncryption,
|
||||
encryptionType: state.tenants.createTenant.fields.encryption.encryptionType,
|
||||
gemaltoEndpoint: state.tenants.createTenant.fields.encryption.gemaltoEndpoint,
|
||||
gemaltoToken: state.tenants.createTenant.fields.encryption.gemaltoToken,
|
||||
gemaltoDomain: state.tenants.createTenant.fields.encryption.gemaltoDomain,
|
||||
gemaltoRetry: state.tenants.createTenant.fields.encryption.gemaltoRetry,
|
||||
awsEndpoint: state.tenants.createTenant.fields.encryption.awsEndpoint,
|
||||
awsRegion: state.tenants.createTenant.fields.encryption.awsRegion,
|
||||
awsKMSKey: state.tenants.createTenant.fields.encryption.awsKMSKey,
|
||||
awsAccessKey: state.tenants.createTenant.fields.encryption.awsAccessKey,
|
||||
awsSecretKey: state.tenants.createTenant.fields.encryption.awsSecretKey,
|
||||
awsToken: state.tenants.createTenant.fields.encryption.awsToken,
|
||||
vaultEndpoint: state.tenants.createTenant.fields.encryption.vaultEndpoint,
|
||||
vaultEngine: state.tenants.createTenant.fields.encryption.vaultEngine,
|
||||
vaultNamespace: state.tenants.createTenant.fields.encryption.vaultNamespace,
|
||||
vaultPrefix: state.tenants.createTenant.fields.encryption.vaultPrefix,
|
||||
vaultAppRoleEngine:
|
||||
state.tenants.createTenant.fields.encryption.vaultAppRoleEngine,
|
||||
vaultId: state.tenants.createTenant.fields.encryption.vaultId,
|
||||
vaultSecret: state.tenants.createTenant.fields.encryption.vaultSecret,
|
||||
vaultRetry: state.tenants.createTenant.fields.encryption.vaultRetry,
|
||||
vaultPing: state.tenants.createTenant.fields.encryption.vaultPing,
|
||||
gcpProjectID: state.tenants.createTenant.fields.encryption.gcpProjectID,
|
||||
gcpEndpoint: state.tenants.createTenant.fields.encryption.gcpEndpoint,
|
||||
gcpClientEmail: state.tenants.createTenant.fields.encryption.gcpClientEmail,
|
||||
gcpClientID: state.tenants.createTenant.fields.encryption.gcpClientID,
|
||||
gcpPrivateKeyID: state.tenants.createTenant.fields.encryption.gcpPrivateKeyID,
|
||||
gcpPrivateKey: state.tenants.createTenant.fields.encryption.gcpPrivateKey,
|
||||
enableCustomCertsForKES:
|
||||
state.tenants.createTenant.fields.encryption.enableCustomCertsForKES,
|
||||
enableAutoCert: state.tenants.createTenant.fields.security.enableAutoCert,
|
||||
enableTLS: state.tenants.createTenant.fields.security.enableTLS,
|
||||
minioCertificates: state.tenants.createTenant.certificates.minioCertificates,
|
||||
serverCertificate: state.tenants.createTenant.certificates.serverCertificate,
|
||||
clientCertificate: state.tenants.createTenant.certificates.clientCertificate,
|
||||
vaultCertificate: state.tenants.createTenant.certificates.vaultCertificate,
|
||||
vaultCA: state.tenants.createTenant.certificates.vaultCA,
|
||||
gemaltoCA: state.tenants.createTenant.certificates.gemaltoCA,
|
||||
enableCustomCerts:
|
||||
state.tenants.createTenant.fields.security.enableCustomCerts,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
addFileServerCert,
|
||||
addFileClientCert,
|
||||
addFileVaultCert,
|
||||
addFileVaultCa,
|
||||
addFileGemaltoCa,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(Encryption));
|
||||
@@ -0,0 +1,394 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 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, useCallback, Fragment } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Grid, Typography } from "@material-ui/core";
|
||||
import {
|
||||
modalBasic,
|
||||
wizardCommon,
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import { updateAddField, isPageValid } from "../../actions";
|
||||
import {
|
||||
commonFormValidation,
|
||||
IValidation,
|
||||
} from "../../../../../utils/validationFunctions";
|
||||
import { AppState } from "../../../../../store";
|
||||
import { clearValidationError } from "../../utils";
|
||||
import RadioGroupSelector from "../../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
|
||||
import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
interface IIdentityProviderProps {
|
||||
classes: any;
|
||||
idpSelection: string;
|
||||
openIDURL: string;
|
||||
openIDClientID: string;
|
||||
openIDSecretID: string;
|
||||
ADURL: string;
|
||||
ADSkipTLS: boolean;
|
||||
ADServerInsecure: boolean;
|
||||
ADUserNameFilter: string;
|
||||
ADGroupBaseDN: string;
|
||||
ADGroupSearchFilter: string;
|
||||
ADNameAttribute: string;
|
||||
updateAddField: typeof updateAddField;
|
||||
isPageValid: typeof isPageValid;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
...wizardCommon,
|
||||
});
|
||||
|
||||
const IdentityProvider = ({
|
||||
classes,
|
||||
idpSelection,
|
||||
openIDURL,
|
||||
openIDClientID,
|
||||
openIDSecretID,
|
||||
ADURL,
|
||||
ADSkipTLS,
|
||||
ADServerInsecure,
|
||||
ADUserNameFilter,
|
||||
ADGroupBaseDN,
|
||||
ADGroupSearchFilter,
|
||||
ADNameAttribute,
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
}: IIdentityProviderProps) => {
|
||||
const [validationErrors, setValidationErrors] = useState<any>({});
|
||||
|
||||
// Common
|
||||
const updateField = useCallback(
|
||||
(field: string, value: any) => {
|
||||
updateAddField("identityProvider", field, value);
|
||||
},
|
||||
[updateAddField]
|
||||
);
|
||||
|
||||
const cleanValidation = (fieldName: string) => {
|
||||
setValidationErrors(clearValidationError(validationErrors, fieldName));
|
||||
};
|
||||
|
||||
// Validation
|
||||
|
||||
useEffect(() => {
|
||||
let customIDPValidation: IValidation[] = [];
|
||||
|
||||
if (idpSelection === "Built-in") {
|
||||
isPageValid("identityProvider", 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);
|
||||
|
||||
isPageValid("identityProvider", Object.keys(commonVal).length === 0);
|
||||
|
||||
setValidationErrors(commonVal);
|
||||
}, [
|
||||
idpSelection,
|
||||
openIDURL,
|
||||
openIDClientID,
|
||||
openIDSecretID,
|
||||
ADURL,
|
||||
ADUserNameFilter,
|
||||
ADGroupBaseDN,
|
||||
ADGroupSearchFilter,
|
||||
ADNameAttribute,
|
||||
isPageValid,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Identity Provider</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Access to the tenant can be controlled via an external Identity
|
||||
Manager.
|
||||
</span>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroupSelector
|
||||
currentSelection={idpSelection}
|
||||
id="idp-options"
|
||||
name="idp-options"
|
||||
label="Protocol"
|
||||
onChange={(e) => {
|
||||
updateField("idpSelection", e.target.value);
|
||||
}}
|
||||
selectorOptions={[
|
||||
{ label: "Built-in", value: "Built-in" },
|
||||
{ label: "OpenID", value: "OpenID" },
|
||||
{ label: "Active Directory", value: "AD" },
|
||||
]}
|
||||
/>
|
||||
MinIO supports both OpenID and Active Directory
|
||||
</Grid>
|
||||
|
||||
{idpSelection === "OpenID" && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="openID_URL"
|
||||
name="openID_URL"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("openIDURL", e.target.value);
|
||||
cleanValidation("openID_URL");
|
||||
}}
|
||||
label="URL"
|
||||
value={openIDURL}
|
||||
error={validationErrors["openID_URL"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="openID_clientID"
|
||||
name="openID_clientID"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("openIDClientID", e.target.value);
|
||||
cleanValidation("openID_clientID");
|
||||
}}
|
||||
label="Client ID"
|
||||
value={openIDClientID}
|
||||
error={validationErrors["openID_clientID"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="openID_secretID"
|
||||
name="openID_secretID"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("openIDSecretID", e.target.value);
|
||||
cleanValidation("openID_secretID");
|
||||
}}
|
||||
label="Secret ID"
|
||||
value={openIDSecretID}
|
||||
error={validationErrors["openID_secretID"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
{idpSelection === "AD" && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="AD_URL"
|
||||
name="AD_URL"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("ADURL", e.target.value);
|
||||
cleanValidation("AD_URL");
|
||||
}}
|
||||
label="URL"
|
||||
value={ADURL}
|
||||
error={validationErrors["AD_URL"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="ad_skipTLS"
|
||||
id="ad_skipTLS"
|
||||
name="ad_skipTLS"
|
||||
checked={ADSkipTLS}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
updateField("ADSkipTLS", checked);
|
||||
}}
|
||||
label={"Skip TLS Verification"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="ad_serverInsecure"
|
||||
id="ad_serverInsecure"
|
||||
name="ad_serverInsecure"
|
||||
checked={ADServerInsecure}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
updateField("ADServerInsecure", checked);
|
||||
}}
|
||||
label={"Server Insecure"}
|
||||
/>
|
||||
</Grid>
|
||||
{ADServerInsecure ? (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
className={classes.error}
|
||||
variant="caption"
|
||||
display="block"
|
||||
gutterBottom
|
||||
>
|
||||
Warning: All traffic with Active Directory will be unencrypted
|
||||
</Typography>
|
||||
<br />
|
||||
</Grid>
|
||||
) : null}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="ad_userNameFilter"
|
||||
name="ad_userNameFilter"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("ADUserNameFilter", e.target.value);
|
||||
cleanValidation("ad_userNameFilter");
|
||||
}}
|
||||
label="User Search Filter"
|
||||
value={ADUserNameFilter}
|
||||
error={validationErrors["ad_userNameFilter"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="ad_groupBaseDN"
|
||||
name="ad_groupBaseDN"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("ADGroupBaseDN", e.target.value);
|
||||
cleanValidation("ad_groupBaseDN");
|
||||
}}
|
||||
label="Group Search Base DN"
|
||||
value={ADGroupBaseDN}
|
||||
error={validationErrors["ad_groupBaseDN"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="ad_groupSearchFilter"
|
||||
name="ad_groupSearchFilter"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("ADGroupSearchFilter", e.target.value);
|
||||
cleanValidation("ad_groupSearchFilter");
|
||||
}}
|
||||
label="Group Search Filter"
|
||||
value={ADGroupSearchFilter}
|
||||
error={validationErrors["ad_groupSearchFilter"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="ad_nameAttribute"
|
||||
name="ad_nameAttribute"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("ADNameAttribute", e.target.value);
|
||||
cleanValidation("ad_nameAttribute");
|
||||
}}
|
||||
label="Group Name Attribute"
|
||||
value={ADNameAttribute}
|
||||
error={validationErrors["ad_nameAttribute"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
idpSelection: state.tenants.createTenant.fields.identityProvider.idpSelection,
|
||||
openIDURL: state.tenants.createTenant.fields.identityProvider.openIDURL,
|
||||
openIDClientID:
|
||||
state.tenants.createTenant.fields.identityProvider.openIDClientID,
|
||||
openIDSecretID:
|
||||
state.tenants.createTenant.fields.identityProvider.openIDSecretID,
|
||||
ADURL: state.tenants.createTenant.fields.identityProvider.ADURL,
|
||||
ADSkipTLS: state.tenants.createTenant.fields.identityProvider.ADSkipTLS,
|
||||
ADServerInsecure:
|
||||
state.tenants.createTenant.fields.identityProvider.ADServerInsecure,
|
||||
ADUserNameFilter:
|
||||
state.tenants.createTenant.fields.identityProvider.ADUserNameFilter,
|
||||
ADGroupBaseDN:
|
||||
state.tenants.createTenant.fields.identityProvider.ADGroupBaseDN,
|
||||
ADGroupSearchFilter:
|
||||
state.tenants.createTenant.fields.identityProvider.ADGroupSearchFilter,
|
||||
ADNameAttribute:
|
||||
state.tenants.createTenant.fields.identityProvider.ADNameAttribute,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(IdentityProvider));
|
||||
@@ -0,0 +1,272 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 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, useMemo, useCallback } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import get from "lodash/get";
|
||||
import debounce from "lodash/debounce";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import {
|
||||
modalBasic,
|
||||
wizardCommon,
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import { setModalErrorSnackMessage } from "../../../../../actions";
|
||||
import {
|
||||
setAdvancedMode,
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
setStorageClassesList,
|
||||
setLimitSize,
|
||||
} from "../../actions";
|
||||
import {
|
||||
IQuotaElement,
|
||||
IQuotas,
|
||||
Opts,
|
||||
getLimitSizes,
|
||||
} from "../../ListTenants/utils";
|
||||
import { AppState } from "../../../../../store";
|
||||
import { commonFormValidation } from "../../../../../utils/validationFunctions";
|
||||
import { clearValidationError } from "../../utils";
|
||||
import api from "../../../../../common/api";
|
||||
import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
...wizardCommon,
|
||||
});
|
||||
|
||||
interface INameTenantScreen {
|
||||
classes: any;
|
||||
storageClasses: Opts[];
|
||||
setAdvancedMode: typeof setAdvancedMode;
|
||||
updateAddField: typeof updateAddField;
|
||||
isPageValid: typeof isPageValid;
|
||||
setStorageClassesList: typeof setStorageClassesList;
|
||||
setLimitSize: typeof setLimitSize;
|
||||
tenantName: string;
|
||||
namespace: string;
|
||||
selectedStorageClass: string;
|
||||
advancedMode: boolean;
|
||||
}
|
||||
|
||||
const NameTenant = ({
|
||||
classes,
|
||||
storageClasses,
|
||||
advancedMode,
|
||||
tenantName,
|
||||
namespace,
|
||||
selectedStorageClass,
|
||||
setAdvancedMode,
|
||||
updateAddField,
|
||||
setStorageClassesList,
|
||||
setLimitSize,
|
||||
isPageValid,
|
||||
}: INameTenantScreen) => {
|
||||
const [validationErrors, setValidationErrors] = useState<any>({});
|
||||
|
||||
// Common
|
||||
const updateField = useCallback(
|
||||
(field: string, value: string) => {
|
||||
updateAddField("nameTenant", field, value);
|
||||
},
|
||||
[updateAddField]
|
||||
);
|
||||
|
||||
// Storage classes retrieval
|
||||
const getNamespaceInformation = useCallback(() => {
|
||||
updateField("selectedStorageClass", "");
|
||||
|
||||
setStorageClassesList([]);
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/namespaces/${namespace}/resourcequotas/${namespace}-storagequota`
|
||||
)
|
||||
.then((res: IQuotas) => {
|
||||
const elements: IQuotaElement[] = get(res, "elements", []);
|
||||
setLimitSize(getLimitSizes(res));
|
||||
|
||||
const newStorage = elements.map((storageClass: any) => {
|
||||
const name = get(storageClass, "name", "").split(
|
||||
".storageclass.storage.k8s.io/requests.storage"
|
||||
)[0];
|
||||
|
||||
return { label: name, value: name };
|
||||
});
|
||||
|
||||
setStorageClassesList(newStorage);
|
||||
if (newStorage.length > 0) {
|
||||
updateField("selectedStorageClass", newStorage[0].value);
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [namespace, setLimitSize, setStorageClassesList, updateField]);
|
||||
|
||||
const debounceNamespace = useMemo(
|
||||
() => debounce(getNamespaceInformation, 500),
|
||||
[getNamespaceInformation]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (namespace !== "") {
|
||||
debounceNamespace();
|
||||
|
||||
// Cancel previous debounce calls during useEffect cleanup.
|
||||
return debounceNamespace.cancel;
|
||||
}
|
||||
}, [debounceNamespace, namespace]);
|
||||
|
||||
// Validation
|
||||
useEffect(() => {
|
||||
const commonValidation = commonFormValidation([
|
||||
{
|
||||
fieldKey: "tenant-name",
|
||||
required: true,
|
||||
pattern: /^[a-z0-9-]{3,63}$/,
|
||||
customPatternMessage:
|
||||
"Name only can contain lowercase letters, numbers and '-'. Min. Length: 3",
|
||||
value: tenantName,
|
||||
},
|
||||
{
|
||||
fieldKey: "namespace",
|
||||
required: true,
|
||||
value: namespace,
|
||||
customValidation: storageClasses.length < 1,
|
||||
customValidationMessage: "Please enter a valid namespace",
|
||||
},
|
||||
]);
|
||||
|
||||
const isValid =
|
||||
!("tenant-name" in commonValidation) &&
|
||||
!("namespace" in commonValidation) &&
|
||||
storageClasses.length > 0;
|
||||
|
||||
isPageValid("nameTenant", isValid);
|
||||
|
||||
setValidationErrors(commonValidation);
|
||||
}, [storageClasses, namespace, tenantName, isPageValid]);
|
||||
|
||||
const frmValidationCleanup = (fieldName: string) => {
|
||||
setValidationErrors(clearValidationError(validationErrors, fieldName));
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Name Tenant</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
How would you like to name this new tenant?
|
||||
</span>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="tenant-name"
|
||||
name="tenant-name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("tenantName", e.target.value);
|
||||
frmValidationCleanup("tenant-name");
|
||||
}}
|
||||
label="Name"
|
||||
value={tenantName}
|
||||
required
|
||||
error={validationErrors["tenant-name"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="namespace"
|
||||
name="namespace"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("namespace", e.target.value);
|
||||
frmValidationCleanup("namespace");
|
||||
}}
|
||||
label="Namespace"
|
||||
value={namespace}
|
||||
error={validationErrors["namespace"] || ""}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
id="storage_class"
|
||||
name="storage_class"
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
updateField("selectedStorageClass", e.target.value as string);
|
||||
}}
|
||||
label="Storage Class"
|
||||
value={selectedStorageClass}
|
||||
options={storageClasses}
|
||||
disabled={storageClasses.length < 1}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
<span className={classes.descriptionText}>
|
||||
Check 'Advanced Mode' for additional configuration options, such as
|
||||
configuring an Identity Provider, Encryption at rest, and customized
|
||||
TLS/SSL Certificates.
|
||||
<br />
|
||||
Leave 'Advanced Mode' unchecked to use the secure default settings for
|
||||
the tenant.
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
<FormSwitchWrapper
|
||||
value="adv_mode"
|
||||
id="adv_mode"
|
||||
name="adv_mode"
|
||||
checked={advancedMode}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
setAdvancedMode(checked);
|
||||
}}
|
||||
label={"Advanced Mode"}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
advancedMode: state.tenants.createTenant.advancedModeOn,
|
||||
tenantName: state.tenants.createTenant.fields.nameTenant.tenantName,
|
||||
namespace: state.tenants.createTenant.fields.nameTenant.namespace,
|
||||
selectedStorageClass:
|
||||
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
|
||||
storageClasses: state.tenants.createTenant.storageClasses,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
setModalErrorSnackMessage,
|
||||
setAdvancedMode,
|
||||
updateAddField,
|
||||
setStorageClassesList,
|
||||
setLimitSize,
|
||||
isPageValid,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(NameTenant));
|
||||
@@ -0,0 +1,156 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import { AppState } from "../../../../../store";
|
||||
import {
|
||||
modalBasic,
|
||||
wizardCommon,
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
interface IPreviewProps {
|
||||
classes: any;
|
||||
tenantName: string;
|
||||
customImage: boolean;
|
||||
imageName: string;
|
||||
consoleImage: string;
|
||||
namespace: string;
|
||||
selectedStorageClass: string;
|
||||
volumeSize: string;
|
||||
sizeFactor: string;
|
||||
advancedMode: boolean;
|
||||
enableTLS: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
...wizardCommon,
|
||||
});
|
||||
|
||||
const Preview = ({
|
||||
classes,
|
||||
tenantName,
|
||||
customImage,
|
||||
imageName,
|
||||
consoleImage,
|
||||
namespace,
|
||||
selectedStorageClass,
|
||||
volumeSize,
|
||||
sizeFactor,
|
||||
advancedMode,
|
||||
enableTLS,
|
||||
}: IPreviewProps) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Review</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Review the details of the new tenant
|
||||
</span>
|
||||
</div>
|
||||
<Table size="small">
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Tenant Name
|
||||
</TableCell>
|
||||
<TableCell>{tenantName}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
{customImage && (
|
||||
<Fragment>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
MinIO Image
|
||||
</TableCell>
|
||||
<TableCell>{imageName}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Console Image
|
||||
</TableCell>
|
||||
<TableCell>{consoleImage}</TableCell>
|
||||
</TableRow>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{namespace !== "" && (
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Namespace
|
||||
</TableCell>
|
||||
<TableCell>{namespace}</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Storage Class
|
||||
</TableCell>
|
||||
<TableCell>{selectedStorageClass}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Total Size
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{volumeSize} {sizeFactor}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{advancedMode && (
|
||||
<Fragment>
|
||||
<TableRow>
|
||||
<TableCell align="right" className={classes.tableTitle}>
|
||||
Enable TLS
|
||||
</TableCell>
|
||||
<TableCell>{enableTLS ? "Enabled" : "Disabled"}</TableCell>
|
||||
</TableRow>
|
||||
</Fragment>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
advancedMode: state.tenants.createTenant.advancedModeOn,
|
||||
enableTLS: state.tenants.createTenant.fields.security.enableTLS,
|
||||
tenantName: state.tenants.createTenant.fields.nameTenant.tenantName,
|
||||
selectedStorageClass:
|
||||
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
|
||||
customImage: state.tenants.createTenant.fields.configure.customImage,
|
||||
imageName: state.tenants.createTenant.fields.configure.imageName,
|
||||
consoleImage: state.tenants.createTenant.fields.configure.consoleImage,
|
||||
namespace: state.tenants.createTenant.fields.nameTenant.namespace,
|
||||
volumeSize: state.tenants.createTenant.fields.tenantSize.volumeSize,
|
||||
sizeFactor: state.tenants.createTenant.fields.tenantSize.sizeFactor,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {});
|
||||
|
||||
export default withStyles(styles)(connector(Preview));
|
||||
@@ -0,0 +1,358 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 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, useCallback, Fragment } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Button, Divider, Grid, Typography } from "@material-ui/core";
|
||||
import {
|
||||
modalBasic,
|
||||
wizardCommon,
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
addFileToCaCertificates,
|
||||
deleteCaCertificate,
|
||||
addCaCertificate,
|
||||
addKeyPair,
|
||||
addFileToKeyPair,
|
||||
deleteKeyPair,
|
||||
addConsoleCertificate,
|
||||
} from "../../actions";
|
||||
import { AppState } from "../../../../../store";
|
||||
import { KeyPair } from "../../ListTenants/utils";
|
||||
import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
import FileSelector from "../../../Common/FormComponents/FileSelector/FileSelector";
|
||||
|
||||
interface ISecurityProps {
|
||||
classes: any;
|
||||
enableTLS: boolean;
|
||||
enableAutoCert: boolean;
|
||||
enableCustomCerts: boolean;
|
||||
minioCertificates: KeyPair[];
|
||||
caCertificates: KeyPair[];
|
||||
consoleCertificate: KeyPair;
|
||||
updateAddField: typeof updateAddField;
|
||||
isPageValid: typeof isPageValid;
|
||||
addFileToCaCertificates: typeof addFileToCaCertificates;
|
||||
deleteCaCertificate: typeof deleteCaCertificate;
|
||||
addCaCertificate: typeof addCaCertificate;
|
||||
addKeyPair: typeof addKeyPair;
|
||||
addFileToKeyPair: typeof addFileToKeyPair;
|
||||
deleteKeyPair: typeof deleteKeyPair;
|
||||
addConsoleCertificate: typeof addConsoleCertificate;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
...wizardCommon,
|
||||
});
|
||||
|
||||
const Security = ({
|
||||
classes,
|
||||
enableTLS,
|
||||
enableAutoCert,
|
||||
enableCustomCerts,
|
||||
minioCertificates,
|
||||
caCertificates,
|
||||
consoleCertificate,
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
addFileToCaCertificates,
|
||||
deleteCaCertificate,
|
||||
addCaCertificate,
|
||||
addKeyPair,
|
||||
addFileToKeyPair,
|
||||
deleteKeyPair,
|
||||
}: ISecurityProps) => {
|
||||
// Common
|
||||
const updateField = useCallback(
|
||||
(field: string, value: any) => {
|
||||
updateAddField("security", field, value);
|
||||
},
|
||||
[updateAddField]
|
||||
);
|
||||
|
||||
// Validation
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableTLS) {
|
||||
isPageValid("security", true);
|
||||
return;
|
||||
}
|
||||
if (enableAutoCert) {
|
||||
isPageValid("security", true);
|
||||
return;
|
||||
}
|
||||
if (enableCustomCerts) {
|
||||
isPageValid("security", true);
|
||||
return;
|
||||
}
|
||||
isPageValid("security", false);
|
||||
}, [enableTLS, enableAutoCert, enableCustomCerts, isPageValid]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Security</h3>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="enableTLS"
|
||||
id="enableTLS"
|
||||
name="enableTLS"
|
||||
checked={enableTLS}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
updateField("enableTLS", checked);
|
||||
}}
|
||||
label={"Enable TLS"}
|
||||
/>
|
||||
Enable TLS for the tenant, this is required for Encryption Configuration
|
||||
{enableTLS && (
|
||||
<Fragment>
|
||||
<br />
|
||||
<br />
|
||||
<Typography variant="caption" display="block" gutterBottom>
|
||||
AutoCert: MinIO Operator will generate all TLS certificates
|
||||
automatically
|
||||
</Typography>
|
||||
<Typography variant="caption" display="block" gutterBottom>
|
||||
Custom certificates: Allow user to provide your own certificates
|
||||
</Typography>
|
||||
<br />
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
{enableTLS && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="enableAutoCert"
|
||||
id="enableAutoCert"
|
||||
name="enableAutoCert"
|
||||
checked={enableAutoCert}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
updateField("enableAutoCert", checked);
|
||||
}}
|
||||
label={"Enable AutoCert"}
|
||||
/>
|
||||
<FormSwitchWrapper
|
||||
value="enableCustomCerts"
|
||||
id="enableCustomCerts"
|
||||
name="enableCustomCerts"
|
||||
checked={enableCustomCerts}
|
||||
onChange={(e) => {
|
||||
const targetD = e.target;
|
||||
const checked = targetD.checked;
|
||||
|
||||
updateField("enableCustomCerts", checked);
|
||||
}}
|
||||
label={"Custom Certificates"}
|
||||
/>
|
||||
</Grid>
|
||||
{enableCustomCerts && (
|
||||
<Fragment>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="overline" display="block" gutterBottom>
|
||||
MinIO Certificates
|
||||
</Typography>
|
||||
</Grid>
|
||||
{minioCertificates.map((keyPair: KeyPair) => (
|
||||
<Fragment key={keyPair.id}>
|
||||
<Grid item xs={5}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addFileToKeyPair(
|
||||
keyPair.id,
|
||||
"key",
|
||||
fileName,
|
||||
encodedValue
|
||||
);
|
||||
}}
|
||||
accept=".key,.pem"
|
||||
id="tlsKey"
|
||||
name="tlsKey"
|
||||
label="Key"
|
||||
value={keyPair.key}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={5}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addFileToKeyPair(
|
||||
keyPair.id,
|
||||
"cert",
|
||||
fileName,
|
||||
encodedValue
|
||||
);
|
||||
}}
|
||||
accept=".cer,.crt,.cert,.pem"
|
||||
id="tlsCert"
|
||||
name="tlsCert"
|
||||
label="Cert"
|
||||
value={keyPair.cert}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
deleteKeyPair(keyPair.id);
|
||||
}}
|
||||
color="secondary"
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
))}
|
||||
<Grid item xs={12}>
|
||||
<Button onClick={addKeyPair} color="primary">
|
||||
Add More
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
<Divider />
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="overline" display="block" gutterBottom>
|
||||
CA Certificates
|
||||
</Typography>
|
||||
</Grid>
|
||||
{caCertificates.map((keyPair: KeyPair) => (
|
||||
<Fragment key={keyPair.id}>
|
||||
<Grid item xs={10}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addFileToCaCertificates(
|
||||
keyPair.id,
|
||||
"cert",
|
||||
fileName,
|
||||
encodedValue
|
||||
);
|
||||
}}
|
||||
accept=".cer,.crt,.cert,.pem"
|
||||
id="tlsCert"
|
||||
name="tlsCert"
|
||||
label="Cert"
|
||||
value={keyPair.cert}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
deleteCaCertificate(keyPair.id);
|
||||
}}
|
||||
color="secondary"
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
))}
|
||||
<Grid item xs={12}>
|
||||
<Button onClick={addCaCertificate} color="primary">
|
||||
Add More
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
<Divider />
|
||||
<br />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="overline" display="block" gutterBottom>
|
||||
Console Certificates
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addConsoleCertificate("key", fileName, encodedValue);
|
||||
}}
|
||||
accept=".key,.pem"
|
||||
id="consoleKey"
|
||||
name="consoleKey"
|
||||
label="Key"
|
||||
value={consoleCertificate.key}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<FileSelector
|
||||
onChange={(encodedValue, fileName) => {
|
||||
addConsoleCertificate("cert", fileName, encodedValue);
|
||||
}}
|
||||
accept=".cer,.crt,.cert,.pem"
|
||||
id="consoleCert"
|
||||
name="consoleCert"
|
||||
label="Cert"
|
||||
value={consoleCertificate.cert}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
enableTLS: state.tenants.createTenant.fields.security.enableTLS,
|
||||
enableAutoCert: state.tenants.createTenant.fields.security.enableAutoCert,
|
||||
enableCustomCerts:
|
||||
state.tenants.createTenant.fields.security.enableCustomCerts,
|
||||
minioCertificates: state.tenants.createTenant.certificates.minioCertificates,
|
||||
caCertificates: state.tenants.createTenant.certificates.caCertificates,
|
||||
consoleCertificate:
|
||||
state.tenants.createTenant.certificates.consoleCertificate,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
addFileToCaCertificates,
|
||||
deleteCaCertificate,
|
||||
addCaCertificate,
|
||||
addKeyPair,
|
||||
addFileToKeyPair,
|
||||
deleteKeyPair,
|
||||
addConsoleCertificate,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(Security));
|
||||
@@ -0,0 +1,520 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment, useState, useEffect, useCallback } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { AppState } from "../../../../../store";
|
||||
import { updateAddField, isPageValid } from "../../actions";
|
||||
import {
|
||||
wizardCommon,
|
||||
modalBasic,
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import {
|
||||
getBytes,
|
||||
k8sfactorForDropdown,
|
||||
niceBytes,
|
||||
calculateDistribution,
|
||||
erasureCodeCalc,
|
||||
setMemoryResource,
|
||||
} from "../../../../../common/utils";
|
||||
import { clearValidationError } from "../../utils";
|
||||
import { ecListTransform, Opts } from "../../ListTenants/utils";
|
||||
import { IMemorySize } from "../../ListTenants/types";
|
||||
import { ICapacity, IErasureCodeCalc } from "../../../../../common/types";
|
||||
import api from "../../../../../common/api";
|
||||
import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import { commonFormValidation } from "../../../../../utils/validationFunctions";
|
||||
|
||||
interface ITenantSizeProps {
|
||||
classes: any;
|
||||
updateAddField: typeof updateAddField;
|
||||
isPageValid: typeof isPageValid;
|
||||
advancedMode: boolean;
|
||||
volumeSize: string;
|
||||
sizeFactor: string;
|
||||
drivesPerServer: string;
|
||||
nodes: string;
|
||||
memoryNode: string;
|
||||
ecParity: string;
|
||||
ecParityChoices: Opts[];
|
||||
cleanECChoices: string[];
|
||||
maxAllocableMemo: number;
|
||||
memorySize: IMemorySize;
|
||||
distribution: any;
|
||||
ecParityCalc: IErasureCodeCalc;
|
||||
limitSize: any;
|
||||
selectedStorageClass: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
...wizardCommon,
|
||||
});
|
||||
|
||||
const TenantSize = ({
|
||||
classes,
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
advancedMode,
|
||||
volumeSize,
|
||||
sizeFactor,
|
||||
drivesPerServer,
|
||||
nodes,
|
||||
memoryNode,
|
||||
ecParity,
|
||||
ecParityChoices,
|
||||
cleanECChoices,
|
||||
maxAllocableMemo,
|
||||
memorySize,
|
||||
distribution,
|
||||
ecParityCalc,
|
||||
limitSize,
|
||||
selectedStorageClass,
|
||||
}: ITenantSizeProps) => {
|
||||
const [validationErrors, setValidationErrors] = useState<any>({});
|
||||
const usableInformation = ecParityCalc.storageFactors.find(
|
||||
(element) => element.erasureCode === ecParity
|
||||
);
|
||||
|
||||
// Common
|
||||
const updateField = useCallback(
|
||||
(field: string, value: any) => {
|
||||
updateAddField("tenantSize", field, value);
|
||||
},
|
||||
[updateAddField]
|
||||
);
|
||||
|
||||
const cleanValidation = (fieldName: string) => {
|
||||
setValidationErrors(clearValidationError(validationErrors, fieldName));
|
||||
};
|
||||
|
||||
/*Debounce functions*/
|
||||
|
||||
// Storage Quotas
|
||||
|
||||
const validateMemorySize = useCallback(() => {
|
||||
const memSize = parseInt(memoryNode) || 0;
|
||||
const clusterSize = volumeSize || 0;
|
||||
const maxMemSize = maxAllocableMemo || 0;
|
||||
const clusterSizeFactor = sizeFactor;
|
||||
|
||||
const clusterSizeBytes = getBytes(
|
||||
clusterSize.toString(10),
|
||||
clusterSizeFactor
|
||||
);
|
||||
const memoSize = setMemoryResource(memSize, clusterSizeBytes, maxMemSize);
|
||||
|
||||
updateField("memorySize", memoSize);
|
||||
}, [maxAllocableMemo, memoryNode, sizeFactor, updateField, volumeSize]);
|
||||
|
||||
const getMaxAllocableMemory = (nodes: string) => {
|
||||
if (nodes !== "" && !isNaN(parseInt(nodes))) {
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/cluster/max-allocatable-memory?num_nodes=${nodes}`
|
||||
)
|
||||
.then((res: { max_memory: number }) => {
|
||||
const maxMemory = res.max_memory ? res.max_memory : 0;
|
||||
updateField("maxAllocableMemo", maxMemory);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
updateField("maxAllocableMemo", 0);
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
validateMemorySize();
|
||||
}, [memoryNode, validateMemorySize]);
|
||||
|
||||
useEffect(() => {
|
||||
validateMemorySize();
|
||||
}, [maxAllocableMemo, validateMemorySize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ecParityChoices.length > 0 && distribution.error === "") {
|
||||
const ecCodeValidated = erasureCodeCalc(
|
||||
cleanECChoices,
|
||||
distribution.persistentVolumes,
|
||||
distribution.pvSize,
|
||||
distribution.nodes
|
||||
);
|
||||
|
||||
updateField("ecParityCalc", ecCodeValidated);
|
||||
updateField("ecParity", ecCodeValidated.defaultEC);
|
||||
}
|
||||
}, [ecParityChoices.length, distribution, cleanECChoices, updateField]);
|
||||
/*End debounce functions*/
|
||||
|
||||
/*Calculate Allocation*/
|
||||
useEffect(() => {
|
||||
validateClusterSize();
|
||||
getECValue();
|
||||
getMaxAllocableMemory(nodes);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [nodes, volumeSize, sizeFactor, drivesPerServer]);
|
||||
|
||||
const validateClusterSize = () => {
|
||||
const size = volumeSize;
|
||||
const factor = sizeFactor;
|
||||
const limitSize = getBytes("12", "Ti", true);
|
||||
|
||||
const clusterCapacity: ICapacity = {
|
||||
unit: factor,
|
||||
value: size.toString(),
|
||||
};
|
||||
|
||||
const distrCalculate = calculateDistribution(
|
||||
clusterCapacity,
|
||||
parseInt(nodes),
|
||||
parseInt(limitSize),
|
||||
parseInt(drivesPerServer)
|
||||
);
|
||||
|
||||
updateField("distribution", distrCalculate);
|
||||
};
|
||||
|
||||
const getECValue = () => {
|
||||
updateField("ecParity", "");
|
||||
|
||||
if (nodes.trim() !== "" && drivesPerServer.trim() !== "") {
|
||||
api
|
||||
.invoke("GET", `/api/v1/get-parity/${nodes}/${drivesPerServer}`)
|
||||
.then((ecList: string[]) => {
|
||||
updateField("ecParityChoices", ecListTransform(ecList));
|
||||
updateField("cleanECChoices", ecList);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
updateField("ecparityChoices", []);
|
||||
isPageValid("tenantSize", false);
|
||||
updateField("ecParity", "");
|
||||
});
|
||||
}
|
||||
};
|
||||
/*Calculate Allocation End*/
|
||||
|
||||
/* Validations of pages */
|
||||
|
||||
useEffect(() => {
|
||||
const parsedSize = getBytes(volumeSize, sizeFactor, true);
|
||||
|
||||
const commonValidation = commonFormValidation([
|
||||
{
|
||||
fieldKey: "nodes",
|
||||
required: true,
|
||||
value: nodes,
|
||||
customValidation: parseInt(nodes) < 4,
|
||||
customValidationMessage: "Number of nodes cannot be less than 4",
|
||||
},
|
||||
{
|
||||
fieldKey: "volume_size",
|
||||
required: true,
|
||||
value: volumeSize,
|
||||
customValidation:
|
||||
parseInt(parsedSize) < 1073741824 ||
|
||||
parseInt(parsedSize) > limitSize[selectedStorageClass],
|
||||
customValidationMessage: `Volume size must be greater than 1Gi and less than ${niceBytes(
|
||||
limitSize[selectedStorageClass],
|
||||
true
|
||||
)}`,
|
||||
},
|
||||
{
|
||||
fieldKey: "memory_per_node",
|
||||
required: true,
|
||||
value: memoryNode,
|
||||
customValidation: parseInt(memoryNode) < 2,
|
||||
customValidationMessage: "Memory size must be greater than 2Gi",
|
||||
},
|
||||
{
|
||||
fieldKey: "drivesps",
|
||||
required: true,
|
||||
value: drivesPerServer,
|
||||
customValidation: parseInt(drivesPerServer) < 1,
|
||||
customValidationMessage: "There must be at least one drive",
|
||||
},
|
||||
]);
|
||||
|
||||
isPageValid(
|
||||
"tenantSize",
|
||||
!("nodes" in commonValidation) &&
|
||||
!("volume_size" in commonValidation) &&
|
||||
!("memory_per_node" in commonValidation) &&
|
||||
!("drivesps" in commonValidation) &&
|
||||
distribution.error === "" &&
|
||||
ecParityCalc.error === 0 &&
|
||||
memorySize.error === ""
|
||||
);
|
||||
|
||||
setValidationErrors(commonValidation);
|
||||
}, [
|
||||
nodes,
|
||||
volumeSize,
|
||||
sizeFactor,
|
||||
memoryNode,
|
||||
distribution,
|
||||
drivesPerServer,
|
||||
ecParityCalc,
|
||||
memorySize,
|
||||
limitSize,
|
||||
selectedStorageClass,
|
||||
]);
|
||||
|
||||
/* End Validation of pages */
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Tenant Size</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Please select the desired capacity
|
||||
</span>
|
||||
</div>
|
||||
<span className={classes.error}>{distribution.error}</span>
|
||||
<span className={classes.error}>{memorySize.error}</span>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="nodes"
|
||||
name="nodes"
|
||||
type="number"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("nodes", e.target.value);
|
||||
cleanValidation("nodes");
|
||||
}}
|
||||
label="Number of Servers"
|
||||
value={nodes}
|
||||
min="4"
|
||||
required
|
||||
error={validationErrors["nodes"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="drivesps"
|
||||
name="drivesps"
|
||||
type="number"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("drivesPerServer", e.target.value);
|
||||
cleanValidation("drivesps");
|
||||
}}
|
||||
label="Number of Drives per Server"
|
||||
value={drivesPerServer}
|
||||
min="1"
|
||||
required
|
||||
error={validationErrors["drivesps"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.multiContainer}>
|
||||
<div>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
id="volume_size"
|
||||
name="volume_size"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("volumeSize", e.target.value);
|
||||
cleanValidation("volume_size");
|
||||
}}
|
||||
label="Total Size"
|
||||
value={volumeSize}
|
||||
required
|
||||
error={validationErrors["volume_size"] || ""}
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.sizeFactorContainer}>
|
||||
<SelectWrapper
|
||||
label={"Unit"}
|
||||
id="size_factor"
|
||||
name="size_factor"
|
||||
value={sizeFactor}
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
updateField("sizeFactor", e.target.value as string);
|
||||
}}
|
||||
options={k8sfactorForDropdown()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
{advancedMode && (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
id="memory_per_node"
|
||||
name="memory_per_node"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("memoryNode", e.target.value);
|
||||
cleanValidation("memory_per_node");
|
||||
}}
|
||||
label="Memory per Node [Gi]"
|
||||
value={memoryNode}
|
||||
required
|
||||
error={validationErrors["memory_per_node"] || ""}
|
||||
min="2"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
id="ec_parity"
|
||||
name="ec_parity"
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
updateField("ecParity", e.target.value as string);
|
||||
}}
|
||||
label="Erasure Code Parity"
|
||||
value={ecParity}
|
||||
options={ecParityChoices}
|
||||
/>
|
||||
<span className={classes.descriptionText}>
|
||||
Please select the desired parity. This setting will change the max
|
||||
usable capacity in the cluster
|
||||
</span>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
<h4>Resource Allocation</h4>
|
||||
<Table className={classes.table} aria-label="simple table">
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Number of Servers
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{parseInt(nodes) > 0 ? nodes : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Drives per Server
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{distribution ? distribution.disks : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Drive Capacity
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{distribution ? niceBytes(distribution.pvSize) : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Total Number of Volumes
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{distribution ? distribution.persistentVolumes : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{!advancedMode && (
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Memory per Node
|
||||
</TableCell>
|
||||
<TableCell align="right">{memoryNode} Gi</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{ecParityCalc.error === 0 && usableInformation && (
|
||||
<Fragment>
|
||||
<h4>Erasure Code Configuration</h4>
|
||||
<Table className={classes.table} aria-label="simple table">
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
EC Parity
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{ecParity !== "" ? ecParity : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Raw Capacity
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{niceBytes(ecParityCalc.rawCapacity)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Usable Capacity
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{niceBytes(usableInformation.maxCapacity)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Number of server failures to tolerate
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{distribution
|
||||
? Math.floor(
|
||||
usableInformation.maxFailureTolerations /
|
||||
distribution.disks
|
||||
)
|
||||
: "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
advancedMode: state.tenants.createTenant.advancedModeOn,
|
||||
volumeSize: state.tenants.createTenant.fields.tenantSize.volumeSize,
|
||||
sizeFactor: state.tenants.createTenant.fields.tenantSize.sizeFactor,
|
||||
drivesPerServer: state.tenants.createTenant.fields.tenantSize.drivesPerServer,
|
||||
nodes: state.tenants.createTenant.fields.tenantSize.nodes,
|
||||
memoryNode: state.tenants.createTenant.fields.tenantSize.memoryNode,
|
||||
ecParity: state.tenants.createTenant.fields.tenantSize.ecParity,
|
||||
ecParityChoices: state.tenants.createTenant.fields.tenantSize.ecParityChoices,
|
||||
cleanECChoices: state.tenants.createTenant.fields.tenantSize.cleanECChoices,
|
||||
maxAllocableMemo:
|
||||
state.tenants.createTenant.fields.tenantSize.maxAllocableMemo,
|
||||
memorySize: state.tenants.createTenant.fields.tenantSize.memorySize,
|
||||
distribution: state.tenants.createTenant.fields.tenantSize.distribution,
|
||||
ecParityCalc: state.tenants.createTenant.fields.tenantSize.ecParityCalc,
|
||||
limitSize: state.tenants.createTenant.fields.tenantSize.limitSize,
|
||||
selectedStorageClass:
|
||||
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(TenantSize));
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@
|
||||
// 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 React, { useEffect, useState, Fragment } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
@@ -27,28 +27,33 @@ import { niceBytes } from "../../../../common/utils";
|
||||
import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
settingsCommon,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { CreateIcon } from "../../../../icons";
|
||||
import api from "../../../../common/api";
|
||||
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
|
||||
import DeleteTenant from "./DeleteTenant";
|
||||
import AddTenant from "./AddTenant";
|
||||
import AddTenant from "../AddTenant/AddTenant";
|
||||
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
|
||||
import history from "../../../../history";
|
||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||
import PageHeader from "../../Common/PageHeader/PageHeader";
|
||||
import { Link } from "react-router-dom";
|
||||
import SlideOptions from "../../Common/SlideOptions/SlideOptions";
|
||||
import BackSettingsIcon from "../../../../icons/BackSettingsIcon";
|
||||
import { resetAddTenantForm } from "../actions";
|
||||
|
||||
interface ITenantsList {
|
||||
classes: any;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
resetAddTenantForm: typeof resetAddTenantForm;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...settingsCommon,
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
@@ -74,13 +79,28 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
actionsTray: {
|
||||
...actionsTray.actionsTray,
|
||||
padding: "0 38px",
|
||||
},
|
||||
tenantsContainer: {
|
||||
padding: "15px 0",
|
||||
},
|
||||
customConfigurationPage: {
|
||||
height: "calc(100vh - 440px)",
|
||||
scrollbarWidth: "none" as const,
|
||||
"&::-webkit-scrollbar": {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
|
||||
const [createTenantOpen, setCreateTenantOpen] = useState<boolean>(false);
|
||||
const ListTenants = ({
|
||||
classes,
|
||||
setErrorSnackMessage,
|
||||
resetAddTenantForm,
|
||||
}: ITenantsList) => {
|
||||
const [currentPanel, setCurrentPanel] = useState<number>(0);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [selectedTenant, setSelectedTenant] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
@@ -92,16 +112,9 @@ const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
|
||||
setCreatedAccount,
|
||||
] = useState<NewServiceAccount | null>(null);
|
||||
|
||||
const closeAddModalAndRefresh = (
|
||||
reloadData: boolean,
|
||||
res: NewServiceAccount | null
|
||||
) => {
|
||||
setCreateTenantOpen(false);
|
||||
|
||||
if (res !== null) {
|
||||
setShowNewCredentials(true);
|
||||
setCreatedAccount(res);
|
||||
}
|
||||
const closeAddModalAndRefresh = (reloadData: boolean) => {
|
||||
setCurrentPanel(0);
|
||||
resetAddTenantForm();
|
||||
|
||||
if (reloadData) {
|
||||
setIsLoading(true);
|
||||
@@ -131,6 +144,11 @@ const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
|
||||
setCreatedAccount(null);
|
||||
};
|
||||
|
||||
const backClick = () => {
|
||||
setCurrentPanel(currentPanel - 1);
|
||||
resetAddTenantForm();
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: redirectToTenantDetails },
|
||||
{ type: "delete", onClick: confirmDeleteTenant },
|
||||
@@ -183,14 +201,12 @@ const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
|
||||
setIsLoading(true);
|
||||
}, []);
|
||||
|
||||
const createTenant = () => {
|
||||
setCurrentPanel(1);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{createTenantOpen && (
|
||||
<AddTenant
|
||||
open={createTenantOpen}
|
||||
closeModalAndRefresh={closeAddModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<Fragment>
|
||||
{deleteOpen && (
|
||||
<DeleteTenant
|
||||
deleteOpen={deleteOpen}
|
||||
@@ -208,74 +224,105 @@ const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
|
||||
entity="Tenant"
|
||||
/>
|
||||
)}
|
||||
<PageHeader label={"Tenants"} />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Tenants"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterTenants(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Refresh Tenant List"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
setIsLoading(true);
|
||||
}}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
component={Link}
|
||||
to="/add-tenant"
|
||||
>
|
||||
Create Tenant
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Namespace", elementKey: "namespace" },
|
||||
{ label: "Capacity", elementKey: "capacity" },
|
||||
{ label: "# of Pools", elementKey: "pool_count" },
|
||||
{ label: "State", elementKey: "currentState" },
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
records={filteredRecords}
|
||||
entityName="Tenants"
|
||||
idField="name"
|
||||
/>
|
||||
<div className={classes.settingsOptionsContainer}>
|
||||
<SlideOptions
|
||||
slideOptions={[
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.customTitle}>
|
||||
Tenants List
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} className={classes.tenantsContainer}>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Tenants"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterTenants(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Refresh Tenant List"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
setIsLoading(true);
|
||||
}}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={createTenant}
|
||||
>
|
||||
Create Tenant
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.tenantsContainer}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Namespace", elementKey: "namespace" },
|
||||
{ label: "Capacity", elementKey: "capacity" },
|
||||
{ label: "# of Pools", elementKey: "pool_count" },
|
||||
{ label: "State", elementKey: "currentState" },
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
records={filteredRecords}
|
||||
entityName="Tenants"
|
||||
idField="name"
|
||||
customPaperHeight={classes.customConfigurationPage}
|
||||
noBackground
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>,
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.backContainer}>
|
||||
<button
|
||||
onClick={backClick}
|
||||
className={classes.backButton}
|
||||
>
|
||||
<BackSettingsIcon />
|
||||
Back To Tenants List
|
||||
</button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
{currentPanel === 1 && (
|
||||
<AddTenant closeAndRefresh={closeAddModalAndRefresh} />
|
||||
)}
|
||||
</Grid>
|
||||
</Fragment>,
|
||||
]}
|
||||
currentSlide={currentPanel}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const connector = connect(null, {
|
||||
setErrorSnackMessage,
|
||||
resetAddTenantForm,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(ListTenants));
|
||||
|
||||
59
portal-ui/src/screens/Console/Tenants/TenantsMain.tsx
Normal file
59
portal-ui/src/screens/Console/Tenants/TenantsMain.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, { Fragment, useState } from "react";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
|
||||
import Tab from "@material-ui/core/Tab";
|
||||
import Tabs from "@material-ui/core/Tabs";
|
||||
import ListTenants from "./ListTenants/ListTenants";
|
||||
|
||||
interface IConfigurationMain {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
headerLabel: {
|
||||
fontSize: 22,
|
||||
fontWeight: 600,
|
||||
color: "#000",
|
||||
marginTop: 4,
|
||||
},
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const TenantsMain = ({ classes }: IConfigurationMain) => {
|
||||
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||
return (
|
||||
<Fragment>
|
||||
<PageHeader label="Tenants" />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid item xs={12} className={classes.headerLabel}>
|
||||
Tenants Management
|
||||
</Grid>
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
onChange={(_, newValue: number) => {
|
||||
setSelectedTab(newValue);
|
||||
}}
|
||||
aria-label="tenant-tabs"
|
||||
>
|
||||
<Tab label="Tenants" />
|
||||
</Tabs>
|
||||
<Grid item xs={12}>
|
||||
{selectedTab === 0 && (
|
||||
<Grid item xs={12}>
|
||||
<ListTenants />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(TenantsMain);
|
||||
222
portal-ui/src/screens/Console/Tenants/actions.ts
Normal file
222
portal-ui/src/screens/Console/Tenants/actions.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 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 { Opts } from "./ListTenants/utils";
|
||||
import {
|
||||
ADD_TENANT_SET_ADVANCED_MODE,
|
||||
ADD_TENANT_SET_CURRENT_PAGE,
|
||||
ADD_TENANT_UPDATE_FIELD,
|
||||
ADD_TENANT_SET_PAGE_VALID,
|
||||
ADD_TENANT_SET_STORAGE_CLASSES_LIST,
|
||||
ADD_TENANT_SET_LIMIT_SIZE,
|
||||
ADD_TENANT_ADD_CA_KEYPAIR,
|
||||
ADD_TENANT_DELETE_CA_KEYPAIR,
|
||||
ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR,
|
||||
ADD_TENANT_ADD_MINIO_KEYPAIR,
|
||||
ADD_TENANT_DELETE_MINIO_KEYPAIR,
|
||||
ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR,
|
||||
ADD_TENANT_ADD_CONSOLE_CERT,
|
||||
ADD_TENANT_ENCRYPTION_SERVER_CERT,
|
||||
ADD_TENANT_ENCRYPTION_CLIENT_CERT,
|
||||
ADD_TENANT_ENCRYPTION_VAULT_CERT,
|
||||
ADD_TENANT_ENCRYPTION_VAULT_CA,
|
||||
ADD_TENANT_ENCRYPTION_GEMALTO_CA,
|
||||
ADD_TENANT_RESET_FORM,
|
||||
} from "./types";
|
||||
|
||||
// Basic actions
|
||||
export const setWizardPage = (page: number) => {
|
||||
return {
|
||||
type: ADD_TENANT_SET_CURRENT_PAGE,
|
||||
page,
|
||||
};
|
||||
};
|
||||
|
||||
export const setAdvancedMode = (state: boolean) => {
|
||||
return {
|
||||
type: ADD_TENANT_SET_ADVANCED_MODE,
|
||||
state,
|
||||
};
|
||||
};
|
||||
|
||||
export const updateAddField = (
|
||||
pageName: string,
|
||||
fieldName: string,
|
||||
value: any
|
||||
) => {
|
||||
return {
|
||||
type: ADD_TENANT_UPDATE_FIELD,
|
||||
pageName,
|
||||
field: fieldName,
|
||||
value,
|
||||
};
|
||||
};
|
||||
|
||||
export const isPageValid = (pageName: string, valid: boolean) => {
|
||||
return {
|
||||
type: ADD_TENANT_SET_PAGE_VALID,
|
||||
pageName,
|
||||
valid,
|
||||
};
|
||||
};
|
||||
|
||||
// Name Tenant actions
|
||||
|
||||
export const setStorageClassesList = (storageClasses: Opts[]) => {
|
||||
return {
|
||||
type: ADD_TENANT_SET_STORAGE_CLASSES_LIST,
|
||||
storageClasses,
|
||||
};
|
||||
};
|
||||
|
||||
export const setLimitSize = (limitSize: any) => {
|
||||
return {
|
||||
type: ADD_TENANT_SET_LIMIT_SIZE,
|
||||
limitSize,
|
||||
};
|
||||
};
|
||||
|
||||
// Security actions
|
||||
|
||||
export const addCaCertificate = () => {
|
||||
return {
|
||||
type: ADD_TENANT_ADD_CA_KEYPAIR,
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteCaCertificate = (id: string) => {
|
||||
return {
|
||||
type: ADD_TENANT_DELETE_CA_KEYPAIR,
|
||||
id,
|
||||
};
|
||||
};
|
||||
|
||||
export const addFileToCaCertificates = (
|
||||
id: string,
|
||||
key: string,
|
||||
fileName: string,
|
||||
value: string
|
||||
) => {
|
||||
return {
|
||||
type: ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR,
|
||||
id,
|
||||
key,
|
||||
fileName,
|
||||
value,
|
||||
};
|
||||
};
|
||||
|
||||
export const addKeyPair = () => {
|
||||
return {
|
||||
type: ADD_TENANT_ADD_MINIO_KEYPAIR,
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteKeyPair = (id: string) => {
|
||||
return {
|
||||
type: ADD_TENANT_DELETE_MINIO_KEYPAIR,
|
||||
id,
|
||||
};
|
||||
};
|
||||
|
||||
export const addFileToKeyPair = (
|
||||
id: string,
|
||||
key: string,
|
||||
fileName: string,
|
||||
value: string
|
||||
) => {
|
||||
return {
|
||||
type: ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR,
|
||||
id,
|
||||
key,
|
||||
fileName,
|
||||
value,
|
||||
};
|
||||
};
|
||||
|
||||
export const addConsoleCertificate = (
|
||||
key: string,
|
||||
fileName: string,
|
||||
value: string
|
||||
) => {
|
||||
return {
|
||||
type: ADD_TENANT_ADD_CONSOLE_CERT,
|
||||
key,
|
||||
fileName,
|
||||
value,
|
||||
};
|
||||
};
|
||||
|
||||
export const addFileServerCert = (
|
||||
key: string,
|
||||
fileName: string,
|
||||
value: string
|
||||
) => {
|
||||
return {
|
||||
type: ADD_TENANT_ENCRYPTION_SERVER_CERT,
|
||||
key,
|
||||
fileName,
|
||||
value,
|
||||
};
|
||||
};
|
||||
|
||||
export const addFileClientCert = (
|
||||
key: string,
|
||||
fileName: string,
|
||||
value: string
|
||||
) => {
|
||||
return {
|
||||
type: ADD_TENANT_ENCRYPTION_CLIENT_CERT,
|
||||
key,
|
||||
fileName,
|
||||
value,
|
||||
};
|
||||
};
|
||||
|
||||
export const addFileVaultCert = (
|
||||
key: string,
|
||||
fileName: string,
|
||||
value: string
|
||||
) => {
|
||||
return {
|
||||
type: ADD_TENANT_ENCRYPTION_VAULT_CERT,
|
||||
key,
|
||||
fileName,
|
||||
value,
|
||||
};
|
||||
};
|
||||
|
||||
export const addFileVaultCa = (fileName: string, value: string) => {
|
||||
return {
|
||||
type: ADD_TENANT_ENCRYPTION_VAULT_CA,
|
||||
fileName,
|
||||
value,
|
||||
};
|
||||
};
|
||||
|
||||
export const addFileGemaltoCa = (fileName: string, value: string) => {
|
||||
return {
|
||||
type: ADD_TENANT_ENCRYPTION_GEMALTO_CA,
|
||||
fileName,
|
||||
value,
|
||||
};
|
||||
};
|
||||
|
||||
export const resetAddTenantForm = () => {
|
||||
return {
|
||||
type: ADD_TENANT_RESET_FORM,
|
||||
};
|
||||
};
|
||||
592
portal-ui/src/screens/Console/Tenants/reducer.ts
Normal file
592
portal-ui/src/screens/Console/Tenants/reducer.ts
Normal file
@@ -0,0 +1,592 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 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 has from "lodash/has";
|
||||
import get from "lodash/get";
|
||||
import {
|
||||
TenantsManagementTypes,
|
||||
ITenantState,
|
||||
ADD_TENANT_SET_CURRENT_PAGE,
|
||||
ADD_TENANT_SET_ADVANCED_MODE,
|
||||
ADD_TENANT_UPDATE_FIELD,
|
||||
ADD_TENANT_SET_PAGE_VALID,
|
||||
ADD_TENANT_SET_STORAGE_CLASSES_LIST,
|
||||
ADD_TENANT_ADD_MINIO_KEYPAIR,
|
||||
ADD_TENANT_DELETE_MINIO_KEYPAIR,
|
||||
ADD_TENANT_ADD_CA_KEYPAIR,
|
||||
ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR,
|
||||
ADD_TENANT_DELETE_CA_KEYPAIR,
|
||||
ADD_TENANT_ADD_CONSOLE_CERT,
|
||||
ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR,
|
||||
ADD_TENANT_ENCRYPTION_SERVER_CERT,
|
||||
ADD_TENANT_ENCRYPTION_CLIENT_CERT,
|
||||
ADD_TENANT_ENCRYPTION_VAULT_CERT,
|
||||
ADD_TENANT_ENCRYPTION_VAULT_CA,
|
||||
ADD_TENANT_ENCRYPTION_GEMALTO_CA,
|
||||
ADD_TENANT_RESET_FORM,
|
||||
} from "./types";
|
||||
import { KeyPair } from "./ListTenants/utils";
|
||||
|
||||
const initialState: ITenantState = {
|
||||
createTenant: {
|
||||
page: 0,
|
||||
validPages: [],
|
||||
advancedModeOn: false,
|
||||
storageClasses: [],
|
||||
limitSize: {},
|
||||
fields: {
|
||||
nameTenant: {
|
||||
tenantName: "",
|
||||
namespace: "",
|
||||
selectedStorageClass: "",
|
||||
},
|
||||
configure: {
|
||||
customImage: false,
|
||||
imageName: "",
|
||||
consoleImage: "",
|
||||
customDockerhub: false,
|
||||
imageRegistry: "",
|
||||
imageRegistryUsername: "",
|
||||
imageRegistryPassword: "",
|
||||
exposeMinIO: true,
|
||||
exposeConsole: true,
|
||||
},
|
||||
identityProvider: {
|
||||
idpSelection: "Built-in",
|
||||
openIDURL: "",
|
||||
openIDClientID: "",
|
||||
openIDSecretID: "",
|
||||
ADURL: "",
|
||||
ADSkipTLS: false,
|
||||
ADServerInsecure: false,
|
||||
ADUserNameFilter: "",
|
||||
ADGroupBaseDN: "",
|
||||
ADGroupSearchFilter: "",
|
||||
ADNameAttribute: "",
|
||||
},
|
||||
security: {
|
||||
enableAutoCert: true,
|
||||
enableCustomCerts: false,
|
||||
enableTLS: true,
|
||||
},
|
||||
encryption: {
|
||||
enableEncryption: false,
|
||||
encryptionType: "vault",
|
||||
gemaltoEndpoint: "",
|
||||
gemaltoToken: "",
|
||||
gemaltoDomain: "",
|
||||
gemaltoRetry: "0",
|
||||
awsEndpoint: "",
|
||||
awsRegion: "",
|
||||
awsKMSKey: "",
|
||||
awsAccessKey: "",
|
||||
awsSecretKey: "",
|
||||
awsToken: "",
|
||||
vaultEndpoint: "",
|
||||
vaultEngine: "",
|
||||
vaultNamespace: "",
|
||||
vaultPrefix: "",
|
||||
vaultAppRoleEngine: "",
|
||||
vaultId: "",
|
||||
vaultSecret: "",
|
||||
vaultRetry: "0",
|
||||
vaultPing: "0",
|
||||
gcpProjectID: "",
|
||||
gcpEndpoint: "",
|
||||
gcpClientEmail: "",
|
||||
gcpClientID: "",
|
||||
gcpPrivateKeyID: "",
|
||||
gcpPrivateKey: "",
|
||||
enableCustomCertsForKES: false,
|
||||
},
|
||||
tenantSize: {
|
||||
volumeSize: "100",
|
||||
sizeFactor: "Gi",
|
||||
drivesPerServer: "1",
|
||||
nodes: "4",
|
||||
memoryNode: "2",
|
||||
ecParity: "",
|
||||
ecParityChoices: [],
|
||||
cleanECChoices: [],
|
||||
maxAllocableMemo: 0,
|
||||
memorySize: {
|
||||
error: "",
|
||||
limit: 0,
|
||||
request: 0,
|
||||
},
|
||||
distribution: {
|
||||
error: "",
|
||||
nodes: 0,
|
||||
persistentVolumes: 0,
|
||||
disks: 0,
|
||||
volumePerDisk: 0,
|
||||
},
|
||||
ecParityCalc: {
|
||||
error: 0,
|
||||
defaultEC: "",
|
||||
erasureCodeSet: 0,
|
||||
maxEC: "",
|
||||
rawCapacity: "0",
|
||||
storageFactors: [],
|
||||
},
|
||||
limitSize: {},
|
||||
},
|
||||
},
|
||||
certificates: {
|
||||
minioCertificates: [
|
||||
{
|
||||
id: Date.now().toString(),
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
],
|
||||
caCertificates: [
|
||||
{
|
||||
id: Date.now().toString(),
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
],
|
||||
consoleCertificate: {
|
||||
id: "console_cert_pair",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
serverCertificate: {
|
||||
id: "encryptionServerCertificate",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
clientCertificate: {
|
||||
id: "encryptionClientCertificate",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
vaultCertificate: {
|
||||
id: "encryptionVaultCertificate",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
vaultCA: {
|
||||
id: "encryptionVaultCA",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
gemaltoCA: {
|
||||
id: "encryptionGemaltoCA",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function tenantsReducer(
|
||||
state = initialState,
|
||||
action: TenantsManagementTypes
|
||||
): ITenantState {
|
||||
let newState: ITenantState = { ...state };
|
||||
|
||||
switch (action.type) {
|
||||
case ADD_TENANT_SET_CURRENT_PAGE:
|
||||
newState.createTenant.page = action.page;
|
||||
|
||||
return { ...newState };
|
||||
case ADD_TENANT_SET_ADVANCED_MODE:
|
||||
newState.createTenant.advancedModeOn = action.state;
|
||||
|
||||
return { ...newState };
|
||||
case ADD_TENANT_UPDATE_FIELD:
|
||||
if (
|
||||
has(newState.createTenant.fields, `${action.pageName}.${action.field}`)
|
||||
) {
|
||||
const originPageNameItems = get(
|
||||
newState.createTenant.fields,
|
||||
`${action.pageName}`,
|
||||
{}
|
||||
);
|
||||
|
||||
let newValue: typeof originPageNameItems = {};
|
||||
newValue[action.field] = action.value;
|
||||
|
||||
const joinValue = { ...originPageNameItems, ...newValue };
|
||||
|
||||
newState.createTenant.fields[action.pageName] = { ...joinValue };
|
||||
|
||||
return { ...newState };
|
||||
}
|
||||
return state;
|
||||
case ADD_TENANT_SET_PAGE_VALID:
|
||||
let originValidPages = state.createTenant.validPages;
|
||||
|
||||
if (action.valid) {
|
||||
if (!originValidPages.includes(action.pageName)) {
|
||||
originValidPages.push(action.pageName);
|
||||
|
||||
newState.createTenant.validPages = [...originValidPages];
|
||||
}
|
||||
} else {
|
||||
const newSetOfPages = originValidPages.filter(
|
||||
(elm) => elm !== action.pageName
|
||||
);
|
||||
|
||||
newState.createTenant.validPages = [...newSetOfPages];
|
||||
}
|
||||
|
||||
return { ...newState };
|
||||
case ADD_TENANT_SET_STORAGE_CLASSES_LIST:
|
||||
const changeCL = {
|
||||
...state,
|
||||
createTenant: {
|
||||
...state.createTenant,
|
||||
storageClasses: action.storageClasses,
|
||||
},
|
||||
};
|
||||
return { ...changeCL };
|
||||
case ADD_TENANT_ADD_MINIO_KEYPAIR:
|
||||
const minioCerts = [
|
||||
...state.createTenant.certificates.minioCertificates,
|
||||
{
|
||||
id: Date.now().toString(),
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
];
|
||||
newState.createTenant.certificates.minioCertificates = [...minioCerts];
|
||||
return { ...newState };
|
||||
case ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR:
|
||||
const minioCertificates =
|
||||
state.createTenant.certificates.minioCertificates;
|
||||
|
||||
const NCertList = minioCertificates.map((item: KeyPair) => {
|
||||
if (item.id === action.id) {
|
||||
return {
|
||||
...item,
|
||||
[action.key]: action.fileName,
|
||||
[`encoded_${action.key}`]: action.value,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
newState.createTenant.certificates.minioCertificates = [...NCertList];
|
||||
return { ...newState };
|
||||
case ADD_TENANT_DELETE_MINIO_KEYPAIR:
|
||||
const minioCertsList = state.createTenant.certificates.minioCertificates;
|
||||
|
||||
if (minioCertsList.length > 1) {
|
||||
const cleanMinioCertsList = minioCertsList.filter(
|
||||
(item: KeyPair) => item.id !== action.id
|
||||
);
|
||||
newState.createTenant.certificates.minioCertificates = [
|
||||
...cleanMinioCertsList,
|
||||
];
|
||||
return { ...newState };
|
||||
}
|
||||
return { ...state };
|
||||
case ADD_TENANT_ADD_CA_KEYPAIR:
|
||||
const CACerts = [
|
||||
...state.createTenant.certificates.caCertificates,
|
||||
{
|
||||
id: Date.now().toString(),
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
];
|
||||
newState.createTenant.certificates.caCertificates = [...CACerts];
|
||||
return { ...newState };
|
||||
case ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR:
|
||||
const caCertificates = state.createTenant.certificates.caCertificates;
|
||||
|
||||
const NACList = caCertificates.map((item: KeyPair) => {
|
||||
if (item.id === action.id) {
|
||||
return {
|
||||
...item,
|
||||
[action.key]: action.fileName,
|
||||
[`encoded_${action.key}`]: action.value,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
newState.createTenant.certificates.caCertificates = [...NACList];
|
||||
return { ...newState };
|
||||
case ADD_TENANT_DELETE_CA_KEYPAIR:
|
||||
const CACertsList = state.createTenant.certificates.minioCertificates;
|
||||
|
||||
if (CACertsList.length > 1) {
|
||||
const cleanMinioCertsList = CACertsList.filter(
|
||||
(item: KeyPair) => item.id !== action.id
|
||||
);
|
||||
newState.createTenant.certificates.caCertificates = [
|
||||
...cleanMinioCertsList,
|
||||
];
|
||||
return { ...newState };
|
||||
}
|
||||
return { ...state };
|
||||
case ADD_TENANT_ADD_CONSOLE_CERT:
|
||||
const consoleCert = state.createTenant.certificates.consoleCertificate;
|
||||
|
||||
newState.createTenant.certificates.consoleCertificate = {
|
||||
...consoleCert,
|
||||
[action.key]: action.fileName,
|
||||
[`encoded_${action.key}`]: action.value,
|
||||
};
|
||||
|
||||
return { ...newState };
|
||||
case ADD_TENANT_ENCRYPTION_SERVER_CERT:
|
||||
const encServerCert = state.createTenant.certificates.serverCertificate;
|
||||
|
||||
newState.createTenant.certificates.serverCertificate = {
|
||||
...encServerCert,
|
||||
[action.key]: action.fileName,
|
||||
[`encoded_${action.key}`]: action.value,
|
||||
};
|
||||
|
||||
return { ...newState };
|
||||
case ADD_TENANT_ENCRYPTION_CLIENT_CERT:
|
||||
const encClientCert = state.createTenant.certificates.clientCertificate;
|
||||
|
||||
newState.createTenant.certificates.clientCertificate = {
|
||||
...encClientCert,
|
||||
[action.key]: action.fileName,
|
||||
[`encoded_${action.key}`]: action.value,
|
||||
};
|
||||
|
||||
return { ...newState };
|
||||
case ADD_TENANT_ENCRYPTION_VAULT_CERT:
|
||||
const encVaultCert = state.createTenant.certificates.vaultCertificate;
|
||||
|
||||
newState.createTenant.certificates.vaultCertificate = {
|
||||
...encVaultCert,
|
||||
[action.key]: action.fileName,
|
||||
[`encoded_${action.key}`]: action.value,
|
||||
};
|
||||
|
||||
return { ...newState };
|
||||
case ADD_TENANT_ENCRYPTION_VAULT_CA:
|
||||
const encVaultCA = state.createTenant.certificates.vaultCA;
|
||||
|
||||
newState.createTenant.certificates.vaultCA = {
|
||||
...encVaultCA,
|
||||
cert: action.fileName,
|
||||
encoded_cert: action.value,
|
||||
};
|
||||
|
||||
return { ...newState };
|
||||
case ADD_TENANT_ENCRYPTION_GEMALTO_CA:
|
||||
const encGemaltoCA = state.createTenant.certificates.gemaltoCA;
|
||||
|
||||
newState.createTenant.certificates.gemaltoCA = {
|
||||
...encGemaltoCA,
|
||||
cert: action.fileName,
|
||||
encoded_cert: action.value,
|
||||
};
|
||||
|
||||
return { ...newState };
|
||||
case ADD_TENANT_RESET_FORM:
|
||||
return {
|
||||
...state,
|
||||
createTenant: {
|
||||
page: 0,
|
||||
validPages: [],
|
||||
advancedModeOn: false,
|
||||
storageClasses: [],
|
||||
limitSize: {},
|
||||
fields: {
|
||||
nameTenant: {
|
||||
tenantName: "",
|
||||
namespace: "",
|
||||
selectedStorageClass: "",
|
||||
},
|
||||
configure: {
|
||||
customImage: false,
|
||||
imageName: "",
|
||||
consoleImage: "",
|
||||
customDockerhub: false,
|
||||
imageRegistry: "",
|
||||
imageRegistryUsername: "",
|
||||
imageRegistryPassword: "",
|
||||
exposeMinIO: true,
|
||||
exposeConsole: true,
|
||||
},
|
||||
identityProvider: {
|
||||
idpSelection: "Built-in",
|
||||
openIDURL: "",
|
||||
openIDClientID: "",
|
||||
openIDSecretID: "",
|
||||
ADURL: "",
|
||||
ADSkipTLS: false,
|
||||
ADServerInsecure: false,
|
||||
ADUserNameFilter: "",
|
||||
ADGroupBaseDN: "",
|
||||
ADGroupSearchFilter: "",
|
||||
ADNameAttribute: "",
|
||||
},
|
||||
security: {
|
||||
enableAutoCert: true,
|
||||
enableCustomCerts: false,
|
||||
enableTLS: true,
|
||||
},
|
||||
encryption: {
|
||||
enableEncryption: false,
|
||||
encryptionType: "vault",
|
||||
gemaltoEndpoint: "",
|
||||
gemaltoToken: "",
|
||||
gemaltoDomain: "",
|
||||
gemaltoRetry: "0",
|
||||
awsEndpoint: "",
|
||||
awsRegion: "",
|
||||
awsKMSKey: "",
|
||||
awsAccessKey: "",
|
||||
awsSecretKey: "",
|
||||
awsToken: "",
|
||||
vaultEndpoint: "",
|
||||
vaultEngine: "",
|
||||
vaultNamespace: "",
|
||||
vaultPrefix: "",
|
||||
vaultAppRoleEngine: "",
|
||||
vaultId: "",
|
||||
vaultSecret: "",
|
||||
vaultRetry: "0",
|
||||
vaultPing: "0",
|
||||
gcpProjectID: "",
|
||||
gcpEndpoint: "",
|
||||
gcpClientEmail: "",
|
||||
gcpClientID: "",
|
||||
gcpPrivateKeyID: "",
|
||||
gcpPrivateKey: "",
|
||||
enableCustomCertsForKES: false,
|
||||
},
|
||||
tenantSize: {
|
||||
volumeSize: "100",
|
||||
sizeFactor: "Gi",
|
||||
drivesPerServer: "1",
|
||||
nodes: "4",
|
||||
memoryNode: "2",
|
||||
ecParity: "",
|
||||
ecParityChoices: [],
|
||||
cleanECChoices: [],
|
||||
maxAllocableMemo: 0,
|
||||
memorySize: {
|
||||
error: "",
|
||||
limit: 0,
|
||||
request: 0,
|
||||
},
|
||||
distribution: {
|
||||
error: "",
|
||||
nodes: 0,
|
||||
persistentVolumes: 0,
|
||||
disks: 0,
|
||||
volumePerDisk: 0,
|
||||
},
|
||||
ecParityCalc: {
|
||||
error: 0,
|
||||
defaultEC: "",
|
||||
erasureCodeSet: 0,
|
||||
maxEC: "",
|
||||
rawCapacity: "0",
|
||||
storageFactors: [],
|
||||
},
|
||||
limitSize: {},
|
||||
},
|
||||
},
|
||||
certificates: {
|
||||
minioCertificates: [
|
||||
{
|
||||
id: Date.now().toString(),
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
],
|
||||
caCertificates: [
|
||||
{
|
||||
id: Date.now().toString(),
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
],
|
||||
consoleCertificate: {
|
||||
id: "console_cert_pair",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
serverCertificate: {
|
||||
id: "encryptionServerCertificate",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
clientCertificate: {
|
||||
id: "encryptionClientCertificate",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
vaultCertificate: {
|
||||
id: "encryptionVaultCertificate",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
vaultCA: {
|
||||
id: "encryptionVaultCA",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
gemaltoCA: {
|
||||
id: "encryptionGemaltoCA",
|
||||
key: "",
|
||||
cert: "",
|
||||
encoded_key: "",
|
||||
encoded_cert: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
306
portal-ui/src/screens/Console/Tenants/types.ts
Normal file
306
portal-ui/src/screens/Console/Tenants/types.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 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 { IErasureCodeCalc } from "../../../common/types";
|
||||
import { IMemorySize } from "./ListTenants/types";
|
||||
import { KeyPair, Opts } from "./ListTenants/utils";
|
||||
|
||||
export const ADD_TENANT_SET_CURRENT_PAGE = "ADD_TENANT/SET_CURRENT_PAGE";
|
||||
export const ADD_TENANT_SET_ADVANCED_MODE = "ADD_TENANT/SET_ADVANCED_MODE";
|
||||
export const ADD_TENANT_UPDATE_FIELD = "ADD_TENANT/UPDATE_FIELD";
|
||||
export const ADD_TENANT_SET_PAGE_VALID = "ADD_TENANT/SET_PAGE_VALID";
|
||||
export const ADD_TENANT_RESET_FORM = "ADD_TENANT/RESET_FORM";
|
||||
|
||||
// Name Tenant
|
||||
export const ADD_TENANT_SET_STORAGE_CLASSES_LIST =
|
||||
"ADD_TENANT/SET_STORAGE_CLASSES_LIST";
|
||||
export const ADD_TENANT_SET_LIMIT_SIZE = "ADD_TENANT/SET_LIMIT_SIZE";
|
||||
|
||||
// Security
|
||||
export const ADD_TENANT_ADD_MINIO_KEYPAIR = "ADD_TENANT/ADD_MINIO_KEYPAIR";
|
||||
export const ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR =
|
||||
"ADD_TENANT/ADD_FILE_MINIO_KEYPAIR";
|
||||
export const ADD_TENANT_DELETE_MINIO_KEYPAIR =
|
||||
"ADD_TENANT/DELETE_MINIO_KEYPAIR";
|
||||
export const ADD_TENANT_ADD_CA_KEYPAIR = "ADD_TENANT/ADD_CA_KEYPAIR";
|
||||
export const ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR =
|
||||
"ADD_TENANT/ADD_FILE_TO_CA_KEYPAIR";
|
||||
export const ADD_TENANT_DELETE_CA_KEYPAIR = "ADD_TENANT/DELETE_CA_KEYPAIR";
|
||||
export const ADD_TENANT_ADD_CONSOLE_CERT = "ADD_TENANT/ADD_CONSOLE_CERT";
|
||||
|
||||
// Encryption
|
||||
export const ADD_TENANT_ENCRYPTION_SERVER_CERT =
|
||||
"ADD_TENANT/ENCRYPTION_SERVER_CERT";
|
||||
export const ADD_TENANT_ENCRYPTION_CLIENT_CERT =
|
||||
"ADD_TENANT/ENCRYPTION_CLIENT_CERT";
|
||||
export const ADD_TENANT_ENCRYPTION_VAULT_CERT =
|
||||
"ADD_TENANT/ENCRYPTION_VAULT_CERT";
|
||||
export const ADD_TENANT_ENCRYPTION_VAULT_CA = "ADD_TENANT/ENCRYPTION_VAULT_CA";
|
||||
export const ADD_TENANT_ENCRYPTION_GEMALTO_CA =
|
||||
"ADD_TENANT/ENCRYPTION_GEMALTO_CA";
|
||||
|
||||
export interface ICreateTenant {
|
||||
page: number;
|
||||
validPages: string[];
|
||||
advancedModeOn: boolean;
|
||||
storageClasses: Opts[];
|
||||
limitSize: any;
|
||||
fields: IFieldStore;
|
||||
certificates: ICertificatesItems;
|
||||
}
|
||||
|
||||
export interface ICertificatesItems {
|
||||
minioCertificates: KeyPair[];
|
||||
caCertificates: KeyPair[];
|
||||
consoleCertificate: KeyPair;
|
||||
serverCertificate: KeyPair;
|
||||
clientCertificate: KeyPair;
|
||||
vaultCertificate: KeyPair;
|
||||
vaultCA: KeyPair;
|
||||
gemaltoCA: KeyPair;
|
||||
}
|
||||
|
||||
export interface IFieldStore {
|
||||
nameTenant: INameTenantFields;
|
||||
configure: IConfigureFields;
|
||||
identityProvider: IIdentityProviderFields;
|
||||
security: ISecurityFields;
|
||||
encryption: IEncryptionFields;
|
||||
tenantSize: ITenantSizeFields;
|
||||
}
|
||||
|
||||
export interface INameTenantFields {
|
||||
tenantName: string;
|
||||
namespace: string;
|
||||
selectedStorageClass: string;
|
||||
}
|
||||
|
||||
export interface IConfigureFields {
|
||||
customImage: boolean;
|
||||
imageName: string;
|
||||
consoleImage: string;
|
||||
customDockerhub: boolean;
|
||||
imageRegistry: string;
|
||||
imageRegistryUsername: string;
|
||||
imageRegistryPassword: string;
|
||||
exposeMinIO: boolean;
|
||||
exposeConsole: boolean;
|
||||
}
|
||||
|
||||
export interface IIdentityProviderFields {
|
||||
idpSelection: string;
|
||||
openIDURL: string;
|
||||
openIDClientID: string;
|
||||
openIDSecretID: string;
|
||||
ADURL: string;
|
||||
ADSkipTLS: boolean;
|
||||
ADServerInsecure: boolean;
|
||||
ADUserNameFilter: string;
|
||||
ADGroupBaseDN: string;
|
||||
ADGroupSearchFilter: string;
|
||||
ADNameAttribute: string;
|
||||
}
|
||||
|
||||
export interface ISecurityFields {
|
||||
enableTLS: boolean;
|
||||
enableAutoCert: boolean;
|
||||
enableCustomCerts: boolean;
|
||||
}
|
||||
|
||||
export interface IEncryptionFields {
|
||||
enableEncryption: boolean;
|
||||
encryptionType: string;
|
||||
gemaltoEndpoint: string;
|
||||
gemaltoToken: string;
|
||||
gemaltoDomain: string;
|
||||
gemaltoRetry: string;
|
||||
awsEndpoint: string;
|
||||
awsRegion: string;
|
||||
awsKMSKey: string;
|
||||
awsAccessKey: string;
|
||||
awsSecretKey: string;
|
||||
awsToken: string;
|
||||
vaultEndpoint: string;
|
||||
vaultEngine: string;
|
||||
vaultNamespace: string;
|
||||
vaultPrefix: string;
|
||||
vaultAppRoleEngine: string;
|
||||
vaultId: string;
|
||||
vaultSecret: string;
|
||||
vaultRetry: string;
|
||||
vaultPing: string;
|
||||
gcpProjectID: string;
|
||||
gcpEndpoint: string;
|
||||
gcpClientEmail: string;
|
||||
gcpClientID: string;
|
||||
gcpPrivateKeyID: string;
|
||||
gcpPrivateKey: string;
|
||||
enableCustomCertsForKES: boolean;
|
||||
}
|
||||
|
||||
export interface ITenantSizeFields {
|
||||
volumeSize: string;
|
||||
sizeFactor: string;
|
||||
drivesPerServer: string;
|
||||
nodes: string;
|
||||
memoryNode: string;
|
||||
ecParity: string;
|
||||
ecParityChoices: Opts[];
|
||||
cleanECChoices: string[];
|
||||
maxAllocableMemo: number;
|
||||
memorySize: IMemorySize;
|
||||
distribution: any;
|
||||
ecParityCalc: IErasureCodeCalc;
|
||||
limitSize: any;
|
||||
}
|
||||
|
||||
export interface ITenantState {
|
||||
createTenant: ICreateTenant;
|
||||
}
|
||||
|
||||
interface SetTenantWizardPage {
|
||||
type: typeof ADD_TENANT_SET_CURRENT_PAGE;
|
||||
page: number;
|
||||
}
|
||||
|
||||
interface SetAdvancedMode {
|
||||
type: typeof ADD_TENANT_SET_ADVANCED_MODE;
|
||||
state: boolean;
|
||||
}
|
||||
|
||||
interface UpdateATField {
|
||||
type: typeof ADD_TENANT_UPDATE_FIELD;
|
||||
pageName: keyof IFieldStore;
|
||||
field: keyof FieldsToHandle;
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface SetPageValid {
|
||||
type: typeof ADD_TENANT_SET_PAGE_VALID;
|
||||
pageName: keyof IFieldStore;
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
interface SetStorageClassesList {
|
||||
type: typeof ADD_TENANT_SET_STORAGE_CLASSES_LIST;
|
||||
storageClasses: Opts[];
|
||||
}
|
||||
|
||||
interface SetLimitSize {
|
||||
type: typeof ADD_TENANT_SET_LIMIT_SIZE;
|
||||
limitSize: any;
|
||||
}
|
||||
|
||||
interface AddMinioKeyPair {
|
||||
type: typeof ADD_TENANT_ADD_MINIO_KEYPAIR;
|
||||
}
|
||||
|
||||
interface AddFileToMinioKeyPair {
|
||||
type: typeof ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR;
|
||||
id: string;
|
||||
key: string;
|
||||
fileName: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface DeleteMinioKeyPair {
|
||||
type: typeof ADD_TENANT_DELETE_MINIO_KEYPAIR;
|
||||
id: string;
|
||||
}
|
||||
interface AddCAKeyPair {
|
||||
type: typeof ADD_TENANT_ADD_CA_KEYPAIR;
|
||||
}
|
||||
|
||||
interface AddFileToCAKeyPair {
|
||||
type: typeof ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR;
|
||||
id: string;
|
||||
key: string;
|
||||
fileName: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface DeleteCAKeyPair {
|
||||
type: typeof ADD_TENANT_DELETE_CA_KEYPAIR;
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface AddFileConsoleCert {
|
||||
type: typeof ADD_TENANT_ADD_CONSOLE_CERT;
|
||||
key: string;
|
||||
fileName: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
// Encryption Certs
|
||||
interface AddFileServerCert {
|
||||
type: typeof ADD_TENANT_ENCRYPTION_SERVER_CERT;
|
||||
key: string;
|
||||
fileName: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface AddFileClientCert {
|
||||
type: typeof ADD_TENANT_ENCRYPTION_CLIENT_CERT;
|
||||
key: string;
|
||||
fileName: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface AddFileVaultCert {
|
||||
type: typeof ADD_TENANT_ENCRYPTION_VAULT_CERT;
|
||||
key: string;
|
||||
fileName: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface AddFileVaultCa {
|
||||
type: typeof ADD_TENANT_ENCRYPTION_VAULT_CA;
|
||||
fileName: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface AddFileGemaltoCa {
|
||||
type: typeof ADD_TENANT_ENCRYPTION_GEMALTO_CA;
|
||||
fileName: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface ResetForm {
|
||||
type: typeof ADD_TENANT_RESET_FORM;
|
||||
}
|
||||
|
||||
export type FieldsToHandle = INameTenantFields;
|
||||
|
||||
export type TenantsManagementTypes =
|
||||
| SetTenantWizardPage
|
||||
| SetAdvancedMode
|
||||
| UpdateATField
|
||||
| SetPageValid
|
||||
| SetStorageClassesList
|
||||
| SetLimitSize
|
||||
| AddMinioKeyPair
|
||||
| DeleteMinioKeyPair
|
||||
| AddCAKeyPair
|
||||
| DeleteCAKeyPair
|
||||
| AddFileConsoleCert
|
||||
| AddFileToMinioKeyPair
|
||||
| AddFileToCAKeyPair
|
||||
| AddFileServerCert
|
||||
| AddFileClientCert
|
||||
| AddFileVaultCert
|
||||
| AddFileVaultCa
|
||||
| AddFileGemaltoCa
|
||||
| ResetForm;
|
||||
25
portal-ui/src/screens/Console/Tenants/utils.ts
Normal file
25
portal-ui/src/screens/Console/Tenants/utils.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 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/>.
|
||||
|
||||
export const clearValidationError = (
|
||||
validationErrors: any,
|
||||
fieldKey: string
|
||||
) => {
|
||||
const newValidationElement = { ...validationErrors };
|
||||
delete newValidationElement[fieldKey];
|
||||
|
||||
return newValidationElement;
|
||||
};
|
||||
@@ -24,6 +24,7 @@ import { watchReducer } from "./screens/Console/Watch/reducers";
|
||||
import { consoleReducer } from "./screens/Console/reducer";
|
||||
import { bucketsReducer } from "./screens/Console/Buckets/reducers";
|
||||
import { objectBrowserReducer } from "./screens/Console/ObjectBrowser/reducers";
|
||||
import { tenantsReducer } from "./screens/Console/Tenants/reducer";
|
||||
|
||||
const globalReducer = combineReducers({
|
||||
system: systemReducer,
|
||||
@@ -34,6 +35,7 @@ const globalReducer = combineReducers({
|
||||
buckets: bucketsReducer,
|
||||
objectBrowser: objectBrowserReducer,
|
||||
healthInfo: healthInfoReducer,
|
||||
tenants: tenantsReducer,
|
||||
});
|
||||
|
||||
declare global {
|
||||
|
||||
Reference in New Issue
Block a user