Add Tenant in non-linear way (#1027)
* Add Tenant in non-linear way Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
@@ -42,6 +42,8 @@ var (
|
||||
serviceAccounts = "/account"
|
||||
changePassword = "/account/change-password"
|
||||
tenants = "/tenants"
|
||||
tenantsAdd = "/tenants/add"
|
||||
tenantsAddSub = "/tenants/add/*"
|
||||
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
|
||||
tenantHop = "/namespaces/:tenantNamespace/tenants/:tenantName/hop"
|
||||
podsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName"
|
||||
@@ -317,6 +319,8 @@ var endpointRules = map[string]ConfigurationActionSet{
|
||||
// operatorRules contains the mapping between endpoints and ActionSets for operator only mode
|
||||
var operatorRules = map[string]ConfigurationActionSet{
|
||||
tenants: tenantsActionSet,
|
||||
tenantsAdd: tenantsActionSet,
|
||||
tenantsAddSub: tenantsActionSet,
|
||||
tenantsDetail: tenantsActionSet,
|
||||
tenantHop: tenantsActionSet,
|
||||
tenantsDetailSummary: tenantsActionSet,
|
||||
|
||||
@@ -19,8 +19,6 @@ package acl
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
type args struct {
|
||||
@@ -111,80 +109,10 @@ func TestOperatorOnlyEndpoints(t *testing.T) {
|
||||
tests := []endpoint{
|
||||
{
|
||||
name: "Operator Only - all admin endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"admin:*",
|
||||
},
|
||||
},
|
||||
want: 15,
|
||||
},
|
||||
{
|
||||
name: "Operator Only - all s3 endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 15,
|
||||
},
|
||||
{
|
||||
name: "Operator Only - all admin and s3 endpoints",
|
||||
args: args{
|
||||
[]string{
|
||||
"admin:*",
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 15,
|
||||
},
|
||||
{
|
||||
name: "Operator Only - default endpoints",
|
||||
args: args{
|
||||
[]string{},
|
||||
},
|
||||
want: 15,
|
||||
args: args{},
|
||||
want: 17,
|
||||
},
|
||||
}
|
||||
|
||||
validateEndpoints(t, tests)
|
||||
}
|
||||
|
||||
func TestGetActionsStringFromPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
policy *iampolicy.Policy
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "parse ReadOnly policy",
|
||||
args: args{
|
||||
policy: &iampolicy.ReadOnly,
|
||||
},
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "parse WriteOnly policy",
|
||||
args: args{
|
||||
policy: &iampolicy.WriteOnly,
|
||||
},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "parse AdminDiagnostics policy",
|
||||
args: args{
|
||||
policy: &iampolicy.AdminDiagnostics,
|
||||
},
|
||||
want: 8,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := GetActionsStringFromPolicy(tt.args.policy); !reflect.DeepEqual(len(got), tt.want) {
|
||||
t.Errorf("GetActionsStringFromPolicy() = %v, want %v", len(got), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,6 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
|
||||
interface IConsoleLogo {
|
||||
width?: number;
|
||||
}
|
||||
|
||||
const ConsoleLogo = () => {
|
||||
return (
|
||||
|
||||
@@ -27,9 +27,6 @@ const OperatorLogo = ({ width = 120 }: IOperatorLogo) => {
|
||||
viewBox="0 0 606.583 134.691"
|
||||
width={width}
|
||||
>
|
||||
<defs>
|
||||
<style>{".prefix__cls-1{fill:#fff}"}</style>
|
||||
</defs>
|
||||
<g id="prefix__Layer_2" data-name="Layer 2">
|
||||
<g id="prefix__Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
|
||||
@@ -587,6 +587,9 @@ export const wizardCommon = {
|
||||
},
|
||||
},
|
||||
},
|
||||
paperWrapper: {
|
||||
padding: 12,
|
||||
},
|
||||
};
|
||||
|
||||
export const buttonsStyles = {
|
||||
|
||||
@@ -18,7 +18,9 @@ import React, { useState, Fragment } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { IWizardMain } from "./types";
|
||||
import WizardPage from "./WizardPage";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import { Grid, List } from "@material-ui/core";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -28,32 +30,10 @@ const styles = (theme: Theme) =>
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
},
|
||||
wizFromContainer: {
|
||||
height: "calc(100vh - 270px)",
|
||||
minHeight: 450,
|
||||
padding: "0 30px",
|
||||
},
|
||||
wizFromContainer: {},
|
||||
wizFromModal: {
|
||||
position: "relative",
|
||||
},
|
||||
wizardSteps: {
|
||||
minWidth: 180,
|
||||
marginRight: 10,
|
||||
borderRight: "#eaeaea 1px solid",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
"& ul": {
|
||||
padding: "0 15px 0 40px",
|
||||
marginTop: 0,
|
||||
|
||||
"& li": {
|
||||
listStyle: "lower-roman",
|
||||
marginBottom: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
modalWizardSteps: {
|
||||
padding: 5,
|
||||
borderBottom: "#eaeaea 1px solid",
|
||||
@@ -85,6 +65,7 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
paddedContentGrid: {
|
||||
marginTop: 8,
|
||||
padding: "0 10px",
|
||||
},
|
||||
stepsLabel: {
|
||||
@@ -158,6 +139,26 @@ const GenericWizard = ({
|
||||
}
|
||||
|
||||
const stepsList = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<List component="nav" dense={true}>
|
||||
{wizardSteps.map((step, index) => {
|
||||
return (
|
||||
<ListItem
|
||||
button
|
||||
onClick={() => pageChange(index)}
|
||||
key={`wizard-${index.toString()}`}
|
||||
selected={currentStep === index}
|
||||
>
|
||||
<ListItemText primary={step.label} />
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
const stepsListModal = () => {
|
||||
return (
|
||||
<ul>
|
||||
{wizardSteps.map((step, index) => {
|
||||
@@ -186,16 +187,13 @@ const GenericWizard = ({
|
||||
<Fragment>
|
||||
<div className={classes.stepsMasterContainer}>
|
||||
<div className={`${classes.stepsLabel} stepsModalTitle`}>Steps</div>
|
||||
<div className={classes.modalWizardSteps}>{stepsList()}</div>
|
||||
<div className={classes.modalWizardSteps}>{stepsListModal()}</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Grid item xs={12} sm={3} md={3} lg={3} xl={2}>
|
||||
<div className={classes.wizardSteps}>
|
||||
<span className={classes.stepsLabel}>Steps</span>
|
||||
{stepsList()}
|
||||
</div>
|
||||
<Grid item xs={12} sm={2} md={2} lg={2} xl={2}>
|
||||
{stepsList()}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
@@ -203,9 +201,9 @@ const GenericWizard = ({
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={forModal ? 12 : 9}
|
||||
md={forModal ? 12 : 9}
|
||||
lg={forModal ? 12 : 9}
|
||||
sm={forModal ? 12 : 10}
|
||||
md={forModal ? 12 : 10}
|
||||
lg={forModal ? 12 : 10}
|
||||
xl={forModal ? 12 : 10}
|
||||
className={forModal ? "" : classes.paddedContentGrid}
|
||||
>
|
||||
|
||||
@@ -28,7 +28,7 @@ const styles = (theme: Theme) =>
|
||||
wizardComponent: {
|
||||
overflowY: "auto",
|
||||
marginBottom: 10,
|
||||
height: "calc(100vh - 342px)",
|
||||
height: "calc(100vh - 100px - 80px)",
|
||||
maxWidth: 840,
|
||||
width: "100%",
|
||||
},
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React, { Fragment } from "react";
|
||||
import React from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { AppState } from "../../../../store";
|
||||
import { connect } from "react-redux";
|
||||
import { setMenuOpen, userLoggedIn } from "../../../../actions";
|
||||
import OperatorLogo from "../../../../icons/OperatorLogo";
|
||||
import ConsoleLogo from "../../../../icons/ConsoleLogo";
|
||||
|
||||
|
||||
@@ -14,12 +14,10 @@
|
||||
// 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 } from "react";
|
||||
import clsx from "clsx";
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||
import Drawer from "@material-ui/core/Drawer";
|
||||
import Container from "@material-ui/core/Container";
|
||||
import Snackbar from "@material-ui/core/Snackbar";
|
||||
import history from "../../history";
|
||||
@@ -58,6 +56,7 @@ import Storage from "./Storage/Storage";
|
||||
import Metrics from "./Dashboard/Metrics";
|
||||
import Hop from "./Tenants/TenantDetails/hop/Hop";
|
||||
import MainError from "./Common/MainError/MainError";
|
||||
import AddTenant from "./Tenants/AddTenant/AddTenant";
|
||||
|
||||
const drawerWidth = 245;
|
||||
|
||||
@@ -299,6 +298,10 @@ const Console = ({
|
||||
component: TenantsMain,
|
||||
path: "/tenants",
|
||||
},
|
||||
{
|
||||
component: AddTenant,
|
||||
path: "/tenants/add",
|
||||
},
|
||||
{
|
||||
component: Storage,
|
||||
path: "/storage",
|
||||
|
||||
@@ -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, { Fragment, useState } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import {
|
||||
@@ -265,18 +265,6 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
});
|
||||
|
||||
// Menu State builder for groups
|
||||
const menuStateBuilder = () => {
|
||||
let elements: any = [];
|
||||
menuGroups.forEach((menuItem) => {
|
||||
if (menuItem.collapsible) {
|
||||
elements[menuItem.group] = true;
|
||||
}
|
||||
});
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
interface IMenuProps {
|
||||
classes: any;
|
||||
userLoggedIn: typeof userLoggedIn;
|
||||
@@ -511,14 +499,6 @@ const Menu = ({
|
||||
item.fsHidden !== false
|
||||
);
|
||||
|
||||
const handleDrawerOpen = () => {
|
||||
setMenuOpen(true);
|
||||
};
|
||||
|
||||
const handleDrawerClose = () => {
|
||||
setMenuOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Drawer
|
||||
|
||||
@@ -40,24 +40,26 @@ 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 { resetAddTenantForm, 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";
|
||||
import Affinity from "./Steps/Affinity";
|
||||
import PageHeader from "../../Common/PageHeader/PageHeader";
|
||||
import history from "../../../../history";
|
||||
import Images from "./Steps/Images";
|
||||
|
||||
interface IAddTenantProps {
|
||||
closeAndRefresh: (reloadData: boolean) => any;
|
||||
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
|
||||
resetAddTenantForm: typeof resetAddTenantForm;
|
||||
updateAddField: typeof updateAddField;
|
||||
fields: IFieldStore;
|
||||
certificates: ICertificatesItems;
|
||||
selectedStorageClass: string;
|
||||
namespace: string;
|
||||
validPages: string[];
|
||||
advancedMode: boolean;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
@@ -73,13 +75,13 @@ const styles = (theme: Theme) =>
|
||||
|
||||
const AddTenant = ({
|
||||
classes,
|
||||
advancedMode,
|
||||
fields,
|
||||
certificates,
|
||||
selectedStorageClass,
|
||||
namespace,
|
||||
validPages,
|
||||
setModalErrorSnackMessage,
|
||||
closeAndRefresh,
|
||||
resetAddTenantForm,
|
||||
}: IAddTenantProps) => {
|
||||
// Modals
|
||||
const [showNewCredentials, setShowNewCredentials] = useState<boolean>(false);
|
||||
@@ -605,144 +607,80 @@ const AddTenant = ({
|
||||
type: "other",
|
||||
enabled: true,
|
||||
action: () => {
|
||||
closeAndRefresh(false);
|
||||
history.push("/tenants");
|
||||
},
|
||||
};
|
||||
|
||||
const createButton = {
|
||||
label: "Create",
|
||||
type: "submit",
|
||||
enabled:
|
||||
!addSending &&
|
||||
selectedStorageClass !== "" &&
|
||||
validPages.includes("tenantSize"),
|
||||
action: () => {
|
||||
setAddSending(true);
|
||||
},
|
||||
};
|
||||
|
||||
const wizardSteps: IWizardElement[] = [
|
||||
{
|
||||
label: "Name Tenant",
|
||||
label: "Setup",
|
||||
componentRender: <NameTenant />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("nameTenant"),
|
||||
},
|
||||
],
|
||||
buttons: [cancelButton, createButton],
|
||||
},
|
||||
{
|
||||
label: "Configure",
|
||||
advancedOnly: true,
|
||||
componentRender: <Configure />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("configure"),
|
||||
},
|
||||
],
|
||||
buttons: [cancelButton, createButton],
|
||||
},
|
||||
{
|
||||
label: "Pod Affinity",
|
||||
label: "Images",
|
||||
advancedOnly: true,
|
||||
componentRender: <Images />,
|
||||
buttons: [cancelButton, createButton],
|
||||
},
|
||||
{
|
||||
label: "Pod Placement",
|
||||
advancedOnly: true,
|
||||
componentRender: <Affinity />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("affinity"),
|
||||
},
|
||||
],
|
||||
buttons: [cancelButton, createButton],
|
||||
},
|
||||
{
|
||||
label: "Identity Provider",
|
||||
advancedOnly: true,
|
||||
componentRender: <IdentityProvider />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("identityProvider"),
|
||||
},
|
||||
],
|
||||
buttons: [cancelButton, createButton],
|
||||
},
|
||||
{
|
||||
label: "Security",
|
||||
advancedOnly: true,
|
||||
componentRender: <Security />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("security"),
|
||||
},
|
||||
],
|
||||
buttons: [cancelButton, createButton],
|
||||
},
|
||||
{
|
||||
label: "Encryption",
|
||||
advancedOnly: true,
|
||||
componentRender: <Encryption />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("encryption"),
|
||||
},
|
||||
],
|
||||
buttons: [cancelButton, createButton],
|
||||
},
|
||||
{
|
||||
label: "Tenant Size",
|
||||
componentRender: <TenantSize />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Next",
|
||||
type: "next",
|
||||
enabled: validPages.includes("tenantSize"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Preview Configuration",
|
||||
label: "Review",
|
||||
componentRender: <Preview />,
|
||||
buttons: [
|
||||
cancelButton,
|
||||
{ label: "Back", type: "back", enabled: true },
|
||||
{
|
||||
label: "Create",
|
||||
type: "submit",
|
||||
enabled: !addSending,
|
||||
action: () => {
|
||||
setAddSending(true);
|
||||
},
|
||||
},
|
||||
],
|
||||
buttons: [cancelButton, createButton],
|
||||
},
|
||||
];
|
||||
|
||||
let filteredWizardSteps = wizardSteps;
|
||||
|
||||
if (!advancedMode) {
|
||||
filteredWizardSteps = wizardSteps.filter((step) => !step.advancedOnly);
|
||||
}
|
||||
|
||||
const closeCredentialsModal = () => {
|
||||
closeAndRefresh(true);
|
||||
resetAddTenantForm();
|
||||
history.push("/tenants");
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.customTitle}>
|
||||
Create New Tenant
|
||||
</Grid>
|
||||
{addSending && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
{showNewCredentials && (
|
||||
<CredentialsPrompt
|
||||
newServiceAccount={createdAccount}
|
||||
@@ -753,7 +691,13 @@ const AddTenant = ({
|
||||
entity="Tenant"
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<PageHeader label={"Create New Tenant"} />
|
||||
<Grid container className={classes.container}>
|
||||
{addSending && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<GenericWizard wizardSteps={filteredWizardSteps} />
|
||||
</Grid>
|
||||
@@ -763,16 +707,18 @@ const AddTenant = ({
|
||||
};
|
||||
|
||||
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,
|
||||
selectedStorageClass:
|
||||
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
setModalErrorSnackMessage,
|
||||
updateAddField,
|
||||
resetAddTenantForm,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(AddTenant));
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React, { Fragment, useCallback, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Grid, IconButton } from "@material-ui/core";
|
||||
import { Grid, IconButton, Paper } from "@material-ui/core";
|
||||
import { AppState } from "../../../../../store";
|
||||
import { isPageValid, updateAddField } from "../../actions";
|
||||
import { setModalErrorSnackMessage } from "../../../../../actions";
|
||||
@@ -190,9 +190,9 @@ const Affinity = ({
|
||||
}, [isPageValid, podAffinity, nodeSelectorLabels]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Paper className={classes.paperWrapper}>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Pod Affinity</h3>
|
||||
<h3 className={classes.h3Section}>Pod Placement</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Configure how pods will be assigned to nodes
|
||||
</span>
|
||||
@@ -208,11 +208,11 @@ const Affinity = ({
|
||||
}}
|
||||
selectorOptions={[
|
||||
{ label: "None", value: "none" },
|
||||
{ label: "Default (Pod Anti-afinnity)", value: "default" },
|
||||
{ label: "Default (Pod Anti-Affinnity)", value: "default" },
|
||||
{ label: "Node Selector", value: "nodeSelector" },
|
||||
]}
|
||||
/>
|
||||
MinIO supports multiple configurations for Pod Afinnity
|
||||
MinIO supports multiple configurations for Pod Affinity
|
||||
</Grid>
|
||||
{podAffinity === "nodeSelector" && (
|
||||
<Fragment>
|
||||
@@ -371,7 +371,7 @@ const Affinity = ({
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React, { Fragment, useCallback, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import { Grid, Paper } from "@material-ui/core";
|
||||
import {
|
||||
modalBasic,
|
||||
wizardCommon,
|
||||
@@ -304,208 +304,13 @@ const Configure = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Paper className={classes.paperWrapper}>
|
||||
<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 docker image 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.2021-08-20T18-32-01Z"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="logSearchImage"
|
||||
name="logSearchImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("logSearchImage", e.target.value);
|
||||
cleanValidation("logSearchImage");
|
||||
}}
|
||||
label="Log Search API's Image"
|
||||
value={logSearchImage}
|
||||
error={validationErrors["logSearchImage"] || ""}
|
||||
placeholder="E.g. minio/logsearchapi:v4.1.1"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="kesImage"
|
||||
name="kesImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("kesImage", e.target.value);
|
||||
cleanValidation("kesImage");
|
||||
}}
|
||||
label="KES Image"
|
||||
value={kesImage}
|
||||
error={validationErrors["kesImage"] || ""}
|
||||
placeholder="E.g. minio/kes:v0.14.0"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="logSearchPostgresImage"
|
||||
name="logSearchPostgresImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("logSearchPostgresImage", e.target.value);
|
||||
cleanValidation("logSearchPostgresImage");
|
||||
}}
|
||||
label="Log Search Postgres's Image"
|
||||
value={logSearchPostgresImage}
|
||||
error={validationErrors["logSearchPostgresImage"] || ""}
|
||||
placeholder="E.g. library/postgres:13"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="logSearchPostgresInitImage"
|
||||
name="logSearchPostgresInitImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("logSearchPostgresInitImage", e.target.value);
|
||||
cleanValidation("logSearchPostgresInitImage");
|
||||
}}
|
||||
label="Log Search Postgres's Init Image"
|
||||
value={logSearchPostgresInitImage}
|
||||
error={validationErrors["logSearchPostgresInitImage"] || ""}
|
||||
placeholder="E.g. library/busybox:1.33.1"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="prometheusImage"
|
||||
name="prometheusImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("prometheusImage", e.target.value);
|
||||
cleanValidation("prometheusImage");
|
||||
}}
|
||||
label="Prometheus Image"
|
||||
value={prometheusImage}
|
||||
error={validationErrors["prometheusImage"] || ""}
|
||||
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="prometheusSidecarImage"
|
||||
name="prometheusSidecarImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("prometheusSidecarImage", e.target.value);
|
||||
cleanValidation("prometheusSidecarImage");
|
||||
}}
|
||||
label="Prometheus Sidecar Image"
|
||||
value={prometheusSidecarImage}
|
||||
error={validationErrors["prometheusSidecarImage"] || ""}
|
||||
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="prometheusInitImage"
|
||||
name="prometheusInitImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("prometheusInitImage", e.target.value);
|
||||
cleanValidation("prometheusInitImage");
|
||||
}}
|
||||
label="Prometheus Init Image"
|
||||
value={prometheusInitImage}
|
||||
error={validationErrors["prometheusInitImage"] || ""}
|
||||
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
||||
/>
|
||||
</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}>
|
||||
@@ -662,7 +467,7 @@ const Configure = ({
|
||||
<br />
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
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 { Paper, Typography } from "@material-ui/core";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import {
|
||||
updateAddField,
|
||||
@@ -328,7 +328,7 @@ const Encryption = ({
|
||||
]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Paper className={classes.paperWrapper}>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Encryption</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
@@ -862,7 +862,7 @@ const Encryption = ({
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,13 @@
|
||||
import React, { Fragment, useCallback, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Grid, IconButton, Tooltip, Typography } from "@material-ui/core";
|
||||
import {
|
||||
Grid,
|
||||
IconButton,
|
||||
Paper,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import CasinoIcon from "@material-ui/icons/Casino";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import {
|
||||
@@ -378,7 +384,7 @@ const IdentityProvider = ({
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
<Paper className={classes.paperWrapper}>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Identity Provider</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
@@ -696,7 +702,7 @@ const IdentityProvider = ({
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
543
portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx
Normal file
543
portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx
Normal file
@@ -0,0 +1,543 @@
|
||||
// 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, useCallback, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Grid, Paper } from "@material-ui/core";
|
||||
import {
|
||||
modalBasic,
|
||||
wizardCommon,
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import { isPageValid, updateAddField } 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 IImagesProps {
|
||||
updateAddField: typeof updateAddField;
|
||||
isPageValid: typeof isPageValid;
|
||||
storageClasses: any;
|
||||
classes: any;
|
||||
customImage: boolean;
|
||||
imageName: string;
|
||||
customDockerhub: boolean;
|
||||
imageRegistry: string;
|
||||
imageRegistryUsername: string;
|
||||
imageRegistryPassword: string;
|
||||
exposeMinIO: boolean;
|
||||
exposeConsole: boolean;
|
||||
prometheusCustom: boolean;
|
||||
logSearchCustom: boolean;
|
||||
logSearchVolumeSize: string;
|
||||
logSearchSizeFactor: string;
|
||||
prometheusVolumeSize: string;
|
||||
prometheusSizeFactor: string;
|
||||
logSearchSelectedStorageClass: string;
|
||||
logSearchImage: string;
|
||||
kesImage: string;
|
||||
logSearchPostgresImage: string;
|
||||
logSearchPostgresInitImage: string;
|
||||
prometheusSelectedStorageClass: string;
|
||||
prometheusImage: string;
|
||||
prometheusSidecarImage: string;
|
||||
prometheusInitImage: string;
|
||||
selectedStorageClass: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
...wizardCommon,
|
||||
});
|
||||
|
||||
const Images = ({
|
||||
classes,
|
||||
storageClasses,
|
||||
customImage,
|
||||
imageName,
|
||||
customDockerhub,
|
||||
imageRegistry,
|
||||
imageRegistryUsername,
|
||||
imageRegistryPassword,
|
||||
exposeMinIO,
|
||||
exposeConsole,
|
||||
prometheusCustom,
|
||||
logSearchCustom,
|
||||
logSearchVolumeSize,
|
||||
logSearchSizeFactor,
|
||||
logSearchImage,
|
||||
kesImage,
|
||||
logSearchPostgresImage,
|
||||
logSearchPostgresInitImage,
|
||||
prometheusVolumeSize,
|
||||
prometheusSizeFactor,
|
||||
logSearchSelectedStorageClass,
|
||||
prometheusSelectedStorageClass,
|
||||
prometheusImage,
|
||||
prometheusSidecarImage,
|
||||
prometheusInitImage,
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
selectedStorageClass,
|
||||
}: IImagesProps) => {
|
||||
const [validationErrors, setValidationErrors] = useState<any>({});
|
||||
|
||||
// Common
|
||||
const updateField = useCallback(
|
||||
(field: string, value: any) => {
|
||||
updateAddField("configure", field, value);
|
||||
},
|
||||
[updateAddField]
|
||||
);
|
||||
|
||||
// Validation
|
||||
useEffect(() => {
|
||||
let customAccountValidation: IValidation[] = [];
|
||||
|
||||
if (prometheusCustom) {
|
||||
customAccountValidation = [
|
||||
...customAccountValidation,
|
||||
{
|
||||
fieldKey: "prometheus_storage_class",
|
||||
required: true,
|
||||
value: prometheusSelectedStorageClass,
|
||||
customValidation: prometheusSelectedStorageClass === "",
|
||||
customValidationMessage: "Field cannot be empty",
|
||||
},
|
||||
{
|
||||
fieldKey: "prometheus_volume_size",
|
||||
required: true,
|
||||
value: prometheusVolumeSize,
|
||||
customValidation:
|
||||
prometheusVolumeSize === "" || parseInt(prometheusVolumeSize) <= 0,
|
||||
customValidationMessage: `Volume size must be present and be greatter than 0`,
|
||||
},
|
||||
];
|
||||
}
|
||||
if (logSearchCustom) {
|
||||
customAccountValidation = [
|
||||
...customAccountValidation,
|
||||
{
|
||||
fieldKey: "log_search_storage_class",
|
||||
required: true,
|
||||
value: logSearchSelectedStorageClass,
|
||||
customValidation: logSearchSelectedStorageClass === "",
|
||||
customValidationMessage: "Field cannot be empty",
|
||||
},
|
||||
{
|
||||
fieldKey: "log_search_volume_size",
|
||||
required: true,
|
||||
value: logSearchVolumeSize,
|
||||
customValidation:
|
||||
logSearchVolumeSize === "" || parseInt(logSearchVolumeSize) <= 0,
|
||||
customValidationMessage: `Volume size must be present and be greatter than 0`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (customImage) {
|
||||
customAccountValidation = [
|
||||
...customAccountValidation,
|
||||
{
|
||||
fieldKey: "image",
|
||||
required: false,
|
||||
value: imageName,
|
||||
pattern: /^((.*?)\/(.*?):(.+))$/,
|
||||
customPatternMessage: "Format must be of form: 'minio/minio:VERSION'",
|
||||
},
|
||||
{
|
||||
fieldKey: "logSearchImage",
|
||||
required: false,
|
||||
value: logSearchImage,
|
||||
pattern: /^((.*?)\/(.*?):(.+))$/,
|
||||
customPatternMessage:
|
||||
"Format must be of form: 'minio/logsearchapi:VERSION'",
|
||||
},
|
||||
{
|
||||
fieldKey: "kesImage",
|
||||
required: false,
|
||||
value: kesImage,
|
||||
pattern: /^((.*?)\/(.*?):(.+))$/,
|
||||
customPatternMessage: "Format must be of form: 'minio/kes:VERSION'",
|
||||
},
|
||||
{
|
||||
fieldKey: "logSearchPostgresImage",
|
||||
required: false,
|
||||
value: logSearchPostgresImage,
|
||||
pattern: /^((.*?)\/(.*?):(.+))$/,
|
||||
customPatternMessage:
|
||||
"Format must be of form: 'library/postgres:VERSION'",
|
||||
},
|
||||
{
|
||||
fieldKey: "logSearchPostgresInitImage",
|
||||
required: false,
|
||||
value: logSearchPostgresInitImage,
|
||||
pattern: /^((.*?)\/(.*?):(.+))$/,
|
||||
customPatternMessage:
|
||||
"Format must be of form: 'library/busybox:VERSION'",
|
||||
},
|
||||
{
|
||||
fieldKey: "prometheusImage",
|
||||
required: false,
|
||||
value: prometheusImage,
|
||||
pattern: /^((.*?)\/(.*?):(.+))$/,
|
||||
customPatternMessage:
|
||||
"Format must be of form: 'minio/prometheus:VERSION'",
|
||||
},
|
||||
{
|
||||
fieldKey: "prometheusSidecarImage",
|
||||
required: false,
|
||||
value: prometheusSidecarImage,
|
||||
pattern: /^((.*?)\/(.*?):(.+))$/,
|
||||
customPatternMessage:
|
||||
"Format must be of form: 'project/container:VERSION'",
|
||||
},
|
||||
{
|
||||
fieldKey: "prometheusInitImage",
|
||||
required: false,
|
||||
value: prometheusInitImage,
|
||||
pattern: /^((.*?)\/(.*?):(.+))$/,
|
||||
customPatternMessage:
|
||||
"Format must be of form: 'library/busybox: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,
|
||||
logSearchImage,
|
||||
kesImage,
|
||||
logSearchPostgresImage,
|
||||
logSearchPostgresInitImage,
|
||||
prometheusImage,
|
||||
prometheusSidecarImage,
|
||||
prometheusInitImage,
|
||||
customDockerhub,
|
||||
imageRegistry,
|
||||
imageRegistryUsername,
|
||||
imageRegistryPassword,
|
||||
isPageValid,
|
||||
prometheusCustom,
|
||||
logSearchCustom,
|
||||
prometheusSelectedStorageClass,
|
||||
prometheusVolumeSize,
|
||||
logSearchSelectedStorageClass,
|
||||
logSearchVolumeSize,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// New default values is current selection is invalid
|
||||
if (storageClasses.length > 0) {
|
||||
const filterPrometheus = storageClasses.filter(
|
||||
(item: any) => item.value === prometheusSelectedStorageClass
|
||||
);
|
||||
if (filterPrometheus.length === 0) {
|
||||
updateField("prometheusSelectedStorageClass", selectedStorageClass);
|
||||
}
|
||||
|
||||
const filterLogSearch = storageClasses.filter(
|
||||
(item: any) => item.value === logSearchSelectedStorageClass
|
||||
);
|
||||
if (filterLogSearch.length === 0) {
|
||||
updateField("logSearchSelectedStorageClass", selectedStorageClass);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
logSearchSelectedStorageClass,
|
||||
prometheusSelectedStorageClass,
|
||||
selectedStorageClass,
|
||||
storageClasses,
|
||||
updateField,
|
||||
]);
|
||||
|
||||
const cleanValidation = (fieldName: string) => {
|
||||
setValidationErrors(clearValidationError(validationErrors, fieldName));
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper className={classes.paperWrapper}>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Container Images</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Images used by the Tenant Deployment
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Fragment>
|
||||
<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.2021-08-20T18-32-01Z"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="logSearchImage"
|
||||
name="logSearchImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("logSearchImage", e.target.value);
|
||||
cleanValidation("logSearchImage");
|
||||
}}
|
||||
label="Log Search API's Image"
|
||||
value={logSearchImage}
|
||||
error={validationErrors["logSearchImage"] || ""}
|
||||
placeholder="E.g. minio/logsearchapi:v4.1.1"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="kesImage"
|
||||
name="kesImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("kesImage", e.target.value);
|
||||
cleanValidation("kesImage");
|
||||
}}
|
||||
label="KES Image"
|
||||
value={kesImage}
|
||||
error={validationErrors["kesImage"] || ""}
|
||||
placeholder="E.g. minio/kes:v0.14.0"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="logSearchPostgresImage"
|
||||
name="logSearchPostgresImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("logSearchPostgresImage", e.target.value);
|
||||
cleanValidation("logSearchPostgresImage");
|
||||
}}
|
||||
label="Log Search Postgres's Image"
|
||||
value={logSearchPostgresImage}
|
||||
error={validationErrors["logSearchPostgresImage"] || ""}
|
||||
placeholder="E.g. library/postgres:13"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="logSearchPostgresInitImage"
|
||||
name="logSearchPostgresInitImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("logSearchPostgresInitImage", e.target.value);
|
||||
cleanValidation("logSearchPostgresInitImage");
|
||||
}}
|
||||
label="Log Search Postgres's Init Image"
|
||||
value={logSearchPostgresInitImage}
|
||||
error={validationErrors["logSearchPostgresInitImage"] || ""}
|
||||
placeholder="E.g. library/busybox:1.33.1"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="prometheusImage"
|
||||
name="prometheusImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("prometheusImage", e.target.value);
|
||||
cleanValidation("prometheusImage");
|
||||
}}
|
||||
label="Prometheus Image"
|
||||
value={prometheusImage}
|
||||
error={validationErrors["prometheusImage"] || ""}
|
||||
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="prometheusSidecarImage"
|
||||
name="prometheusSidecarImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("prometheusSidecarImage", e.target.value);
|
||||
cleanValidation("prometheusSidecarImage");
|
||||
}}
|
||||
label="Prometheus Sidecar Image"
|
||||
value={prometheusSidecarImage}
|
||||
error={validationErrors["prometheusSidecarImage"] || ""}
|
||||
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="prometheusInitImage"
|
||||
name="prometheusInitImage"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateField("prometheusInitImage", e.target.value);
|
||||
cleanValidation("prometheusInitImage");
|
||||
}}
|
||||
label="Prometheus Init Image"
|
||||
value={prometheusInitImage}
|
||||
error={validationErrors["prometheusInitImage"] || ""}
|
||||
placeholder="E.g. quay.io/prometheus/prometheus:latest"
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
storageClasses: state.tenants.createTenant.storageClasses,
|
||||
customImage: state.tenants.createTenant.fields.configure.customImage,
|
||||
imageName: state.tenants.createTenant.fields.configure.imageName,
|
||||
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,
|
||||
prometheusCustom:
|
||||
state.tenants.createTenant.fields.configure.prometheusCustom,
|
||||
logSearchCustom: state.tenants.createTenant.fields.configure.logSearchCustom,
|
||||
logSearchVolumeSize:
|
||||
state.tenants.createTenant.fields.configure.logSearchVolumeSize,
|
||||
logSearchSizeFactor:
|
||||
state.tenants.createTenant.fields.configure.logSearchSizeFactor,
|
||||
prometheusVolumeSize:
|
||||
state.tenants.createTenant.fields.configure.prometheusVolumeSize,
|
||||
prometheusSizeFactor:
|
||||
state.tenants.createTenant.fields.configure.prometheusSizeFactor,
|
||||
logSearchSelectedStorageClass:
|
||||
state.tenants.createTenant.fields.configure.logSearchSelectedStorageClass,
|
||||
logSearchImage: state.tenants.createTenant.fields.configure.logSearchImage,
|
||||
kesImage: state.tenants.createTenant.fields.configure.kesImage,
|
||||
logSearchPostgresImage:
|
||||
state.tenants.createTenant.fields.configure.logSearchPostgresImage,
|
||||
logSearchPostgresInitImage:
|
||||
state.tenants.createTenant.fields.configure.logSearchPostgresInitImage,
|
||||
prometheusSelectedStorageClass:
|
||||
state.tenants.createTenant.fields.configure.prometheusSelectedStorageClass,
|
||||
prometheusImage: state.tenants.createTenant.fields.configure.prometheusImage,
|
||||
prometheusSidecarImage:
|
||||
state.tenants.createTenant.fields.configure.prometheusSidecarImage,
|
||||
prometheusInitImage:
|
||||
state.tenants.createTenant.fields.configure.prometheusInitImage,
|
||||
selectedStorageClass:
|
||||
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(Images));
|
||||
@@ -14,7 +14,13 @@
|
||||
// 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 React, {
|
||||
Fragment,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import get from "lodash/get";
|
||||
@@ -26,17 +32,16 @@ import {
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import { setModalErrorSnackMessage } from "../../../../../actions";
|
||||
import {
|
||||
setAdvancedMode,
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
setStorageClassesList,
|
||||
setLimitSize,
|
||||
setStorageClassesList,
|
||||
updateAddField,
|
||||
} from "../../actions";
|
||||
import {
|
||||
getLimitSizes,
|
||||
IQuotaElement,
|
||||
IQuotas,
|
||||
Opts,
|
||||
getLimitSizes,
|
||||
} from "../../ListTenants/utils";
|
||||
import { AppState } from "../../../../../store";
|
||||
import { commonFormValidation } from "../../../../../utils/validationFunctions";
|
||||
@@ -45,15 +50,20 @@ import { ErrorResponseHandler } from "../../../../../common/types";
|
||||
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";
|
||||
import AddIcon from "../../../../../icons/AddIcon";
|
||||
import AddNamespaceModal from "./helpers/AddNamespaceModal";
|
||||
import SizePreview from "./SizePreview";
|
||||
import TenantSize from "./TenantSize";
|
||||
import { Paper } from "@material-ui/core";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
sizePreview: {
|
||||
position: "fixed",
|
||||
},
|
||||
...modalBasic,
|
||||
...wizardCommon,
|
||||
});
|
||||
@@ -62,7 +72,6 @@ interface INameTenantScreen {
|
||||
classes: any;
|
||||
storageClasses: Opts[];
|
||||
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
|
||||
setAdvancedMode: typeof setAdvancedMode;
|
||||
updateAddField: typeof updateAddField;
|
||||
isPageValid: typeof isPageValid;
|
||||
setStorageClassesList: typeof setStorageClassesList;
|
||||
@@ -70,17 +79,14 @@ interface INameTenantScreen {
|
||||
tenantName: string;
|
||||
namespace: string;
|
||||
selectedStorageClass: string;
|
||||
advancedMode: boolean;
|
||||
}
|
||||
|
||||
const NameTenant = ({
|
||||
classes,
|
||||
storageClasses,
|
||||
advancedMode,
|
||||
tenantName,
|
||||
namespace,
|
||||
selectedStorageClass,
|
||||
setAdvancedMode,
|
||||
updateAddField,
|
||||
setStorageClassesList,
|
||||
setLimitSize,
|
||||
@@ -250,7 +256,7 @@ const NameTenant = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Fragment>
|
||||
{openAddNSConfirm && (
|
||||
<AddNamespaceModal
|
||||
addNamespaceOpen={openAddNSConfirm}
|
||||
@@ -258,87 +264,77 @@ const NameTenant = ({
|
||||
namespace={namespace}
|
||||
/>
|
||||
)}
|
||||
<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 container>
|
||||
<Grid item xs={8}>
|
||||
<Paper className={classes.paperWrapper}>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<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>
|
||||
<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"] || ""}
|
||||
overlayIcon={showCreateButton ? <AddIcon /> : null}
|
||||
overlayAction={addNamespace}
|
||||
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>
|
||||
<TenantSize />
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<div className={classes.sizePreview}>
|
||||
<SizePreview />
|
||||
</div>
|
||||
</Grid>
|
||||
</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"] || ""}
|
||||
overlayIcon={showCreateButton ? <AddIcon /> : null}
|
||||
overlayAction={addNamespace}
|
||||
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>
|
||||
</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:
|
||||
@@ -348,7 +344,6 @@ const mapState = (state: AppState) => ({
|
||||
|
||||
const connector = connect(mapState, {
|
||||
setModalErrorSnackMessage,
|
||||
setAdvancedMode,
|
||||
updateAddField,
|
||||
setStorageClassesList,
|
||||
setLimitSize,
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
modalBasic,
|
||||
wizardCommon,
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import { Paper } from "@material-ui/core";
|
||||
|
||||
interface IPreviewProps {
|
||||
classes: any;
|
||||
@@ -62,7 +63,7 @@ const Preview = ({
|
||||
enableTLS,
|
||||
}: IPreviewProps) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<Paper className={classes.paperWrapper}>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Review</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
@@ -125,7 +126,7 @@ const Preview = ({
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Fragment>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
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 { Button, Divider, Grid, Paper, Typography } from "@material-ui/core";
|
||||
import {
|
||||
modalBasic,
|
||||
wizardCommon,
|
||||
@@ -106,7 +106,7 @@ const Security = ({
|
||||
}, [enableTLS, enableAutoCert, enableCustomCerts, isPageValid]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Paper className={classes.paperWrapper}>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Security</h3>
|
||||
</div>
|
||||
@@ -286,7 +286,7 @@ const Security = ({
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
// 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, useCallback, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { AppState } from "../../../../../store";
|
||||
import { isPageValid, updateAddField } from "../../actions";
|
||||
import {
|
||||
modalBasic,
|
||||
wizardCommon,
|
||||
} from "../../../Common/FormComponents/common/styleLibrary";
|
||||
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 {
|
||||
calculateDistribution,
|
||||
erasureCodeCalc,
|
||||
getBytes,
|
||||
niceBytes,
|
||||
setMemoryResource,
|
||||
} from "../../../../../common/utils";
|
||||
import { ecListTransform, Opts } from "../../ListTenants/utils";
|
||||
import { IMemorySize } from "../../ListTenants/types";
|
||||
import {
|
||||
ErrorResponseHandler,
|
||||
ICapacity,
|
||||
IErasureCodeCalc,
|
||||
} from "../../../../../common/types";
|
||||
import { commonFormValidation } from "../../../../../utils/validationFunctions";
|
||||
import api from "../../../../../common/api";
|
||||
import { Divider } from "@material-ui/core";
|
||||
|
||||
interface ISizePreviewProps {
|
||||
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",
|
||||
},
|
||||
root: {
|
||||
margin: 4,
|
||||
},
|
||||
table: {
|
||||
"& .MuiTableCell-root": {
|
||||
fontSize: 13,
|
||||
},
|
||||
},
|
||||
...modalBasic,
|
||||
...wizardCommon,
|
||||
});
|
||||
|
||||
const SizePreview = ({
|
||||
classes,
|
||||
updateAddField,
|
||||
isPageValid,
|
||||
advancedMode,
|
||||
volumeSize,
|
||||
sizeFactor,
|
||||
drivesPerServer,
|
||||
nodes,
|
||||
memoryNode,
|
||||
ecParity,
|
||||
ecParityChoices,
|
||||
cleanECChoices,
|
||||
maxAllocableMemo,
|
||||
memorySize,
|
||||
distribution,
|
||||
ecParityCalc,
|
||||
limitSize,
|
||||
selectedStorageClass,
|
||||
}: ISizePreviewProps) => {
|
||||
const [errorFlag, setErrorFlag] = useState<boolean>(false);
|
||||
const [nodeError, setNodeError] = useState<string>("");
|
||||
const usableInformation = ecParityCalc.storageFactors.find(
|
||||
(element) => element.erasureCode === ecParity
|
||||
);
|
||||
|
||||
// Common
|
||||
const updateField = useCallback(
|
||||
(field: string, value: any) => {
|
||||
updateAddField("tenantSize", field, value);
|
||||
},
|
||||
[updateAddField]
|
||||
);
|
||||
|
||||
/*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))) {
|
||||
setNodeError("");
|
||||
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: ErrorResponseHandler) => {
|
||||
setErrorFlag(true);
|
||||
setNodeError(err.errorMessage);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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: ErrorResponseHandler) => {
|
||||
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: errorFlag,
|
||||
customValidationMessage: nodeError,
|
||||
},
|
||||
{
|
||||
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 === ""
|
||||
);
|
||||
}, [
|
||||
nodes,
|
||||
volumeSize,
|
||||
sizeFactor,
|
||||
memoryNode,
|
||||
distribution,
|
||||
drivesPerServer,
|
||||
ecParityCalc,
|
||||
memorySize,
|
||||
limitSize,
|
||||
selectedStorageClass,
|
||||
isPageValid,
|
||||
errorFlag,
|
||||
nodeError,
|
||||
]);
|
||||
|
||||
/* End Validation of pages */
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<h4>Resource Allocation</h4>
|
||||
<Divider />
|
||||
<Table className={classes.table} aria-label="simple table" size={"small"}>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell scope="row">Number of Servers</TableCell>
|
||||
<TableCell align="right">
|
||||
{parseInt(nodes) > 0 ? nodes : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell scope="row">Drives per Server</TableCell>
|
||||
<TableCell align="right">
|
||||
{distribution ? distribution.disks : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell scope="row">Drive Capacity</TableCell>
|
||||
<TableCell align="right">
|
||||
{distribution ? niceBytes(distribution.pvSize) : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell scope="row">Total Volumes</TableCell>
|
||||
<TableCell align="right">
|
||||
{distribution ? distribution.persistentVolumes : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{!advancedMode && (
|
||||
<TableRow>
|
||||
<TableCell 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>
|
||||
<Divider />
|
||||
<Table
|
||||
className={classes.table}
|
||||
aria-label="simple table"
|
||||
size={"small"}
|
||||
>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell scope="row">EC Parity</TableCell>
|
||||
<TableCell align="right">
|
||||
{ecParity !== "" ? ecParity : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell scope="row">Raw Capacity</TableCell>
|
||||
<TableCell align="right">
|
||||
{niceBytes(ecParityCalc.rawCapacity)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell scope="row">Usable Capacity</TableCell>
|
||||
<TableCell align="right">
|
||||
{niceBytes(usableInformation.maxCapacity)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell scope="row">Server Failures Tolerated</TableCell>
|
||||
<TableCell align="right">
|
||||
{distribution
|
||||
? Math.floor(
|
||||
usableInformation.maxFailureTolerations /
|
||||
distribution.disks
|
||||
)
|
||||
: "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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(SizePreview));
|
||||
@@ -24,10 +24,6 @@ import {
|
||||
wizardCommon,
|
||||
} 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 {
|
||||
calculateDistribution,
|
||||
erasureCodeCalc,
|
||||
@@ -102,9 +98,6 @@ const TenantSize = ({
|
||||
const [validationErrors, setValidationErrors] = useState<any>({});
|
||||
const [errorFlag, setErrorFlag] = useState<boolean>(false);
|
||||
const [nodeError, setNodeError] = useState<string>("");
|
||||
const usableInformation = ecParityCalc.storageFactors.find(
|
||||
(element) => element.erasureCode === ecParity
|
||||
);
|
||||
|
||||
// Common
|
||||
const updateField = useCallback(
|
||||
@@ -297,17 +290,23 @@ const TenantSize = ({
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Tenant Size</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Please select the desired capacity
|
||||
</span>
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.headerElement}>
|
||||
<h3 className={classes.h3Section}>Tenant Size</h3>
|
||||
<span className={classes.descriptionText}>
|
||||
Please select the desired capacity
|
||||
</span>
|
||||
</div>
|
||||
</Grid>
|
||||
{distribution.error !== "" && (
|
||||
<div className={classes.error}>{distribution.error}</div>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.error}>{distribution.error}</div>
|
||||
</Grid>
|
||||
)}
|
||||
{memorySize.error !== "" && (
|
||||
<div className={classes.error}>{memorySize.error}</div>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.error}>{memorySize.error}</div>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
@@ -319,6 +318,7 @@ const TenantSize = ({
|
||||
cleanValidation("nodes");
|
||||
}}
|
||||
label="Number of Servers"
|
||||
disabled={selectedStorageClass === ""}
|
||||
value={nodes}
|
||||
min="4"
|
||||
required
|
||||
@@ -336,6 +336,7 @@ const TenantSize = ({
|
||||
}}
|
||||
label="Number of Drives per Server"
|
||||
value={drivesPerServer}
|
||||
disabled={selectedStorageClass === ""}
|
||||
min="1"
|
||||
required
|
||||
error={validationErrors["drivesps"] || ""}
|
||||
@@ -354,6 +355,7 @@ const TenantSize = ({
|
||||
}}
|
||||
label="Total Size"
|
||||
value={volumeSize}
|
||||
disabled={selectedStorageClass === ""}
|
||||
required
|
||||
error={validationErrors["volume_size"] || ""}
|
||||
min="0"
|
||||
@@ -365,6 +367,7 @@ const TenantSize = ({
|
||||
id="size_factor"
|
||||
name="size_factor"
|
||||
value={sizeFactor}
|
||||
disabled={selectedStorageClass === ""}
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
updateField("sizeFactor", e.target.value as string);
|
||||
}}
|
||||
@@ -373,133 +376,43 @@ const TenantSize = ({
|
||||
</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>
|
||||
<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}
|
||||
disabled={selectedStorageClass === ""}
|
||||
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"
|
||||
disabled={selectedStorageClass === ""}
|
||||
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>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ import { connect } from "react-redux";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import { Button, IconButton } from "@material-ui/core";
|
||||
import { IconButton } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { ITenant, ITenantsResponse } from "./types";
|
||||
import { niceBytes } from "../../../../common/utils";
|
||||
@@ -27,77 +27,28 @@ import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
|
||||
import {
|
||||
actionsTray,
|
||||
searchField,
|
||||
settingsCommon,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { AddIcon, CircleIcon } from "../../../../icons";
|
||||
import { resetAddTenantForm } from "../actions";
|
||||
import { CircleIcon, CreateIcon } from "../../../../icons";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import api from "../../../../common/api";
|
||||
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
|
||||
import DeleteTenant from "./DeleteTenant";
|
||||
import AddTenant from "../AddTenant/AddTenant";
|
||||
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
|
||||
import history from "../../../../history";
|
||||
import SlideOptions from "../../Common/SlideOptions/SlideOptions";
|
||||
import BackSettingsIcon from "../../../../icons/BackSettingsIcon";
|
||||
import RefreshIcon from "../../../../icons/RefreshIcon";
|
||||
import SearchIcon from "../../../../icons/SearchIcon";
|
||||
|
||||
interface ITenantsList {
|
||||
classes: any;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
resetAddTenantForm: typeof resetAddTenantForm;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...settingsCommon,
|
||||
settingsOptionsContainer: {
|
||||
...settingsCommon.settingsOptionsContainer,
|
||||
height: "calc(100vh - 150px)",
|
||||
},
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
paper: {
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column",
|
||||
},
|
||||
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px",
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0),
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
...actionsTray.actionsTray,
|
||||
padding: "0 38px",
|
||||
},
|
||||
tenantsContainer: {
|
||||
padding: "15px 0",
|
||||
},
|
||||
customConfigurationPage: {
|
||||
height: "calc(100vh - 260px)",
|
||||
scrollbarWidth: "none" as const,
|
||||
"&::-webkit-scrollbar": {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
redState: {
|
||||
color: theme.palette.error.main,
|
||||
"& .MuiSvgIcon-root": {
|
||||
@@ -136,12 +87,7 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
});
|
||||
|
||||
const ListTenants = ({
|
||||
classes,
|
||||
setErrorSnackMessage,
|
||||
resetAddTenantForm,
|
||||
}: ITenantsList) => {
|
||||
const [currentPanel, setCurrentPanel] = useState<number>(0);
|
||||
const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [selectedTenant, setSelectedTenant] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
@@ -151,15 +97,6 @@ const ListTenants = ({
|
||||
const [createdAccount, setCreatedAccount] =
|
||||
useState<NewServiceAccount | null>(null);
|
||||
|
||||
const closeAddModalAndRefresh = (reloadData: boolean) => {
|
||||
setCurrentPanel(0);
|
||||
resetAddTenantForm();
|
||||
|
||||
if (reloadData) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
};
|
||||
|
||||
const closeDeleteModalAndRefresh = (reloadData: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
@@ -183,11 +120,6 @@ const ListTenants = ({
|
||||
setCreatedAccount(null);
|
||||
};
|
||||
|
||||
const backClick = () => {
|
||||
setCurrentPanel(currentPanel - 1);
|
||||
resetAddTenantForm();
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: redirectToTenantDetails },
|
||||
{ type: "delete", onClick: confirmDeleteTenant },
|
||||
@@ -240,10 +172,6 @@ const ListTenants = ({
|
||||
setIsLoading(true);
|
||||
}, []);
|
||||
|
||||
const createTenant = () => {
|
||||
setCurrentPanel(1);
|
||||
};
|
||||
|
||||
const healthStatusToClass = (health_status: string) => {
|
||||
switch (health_status) {
|
||||
case "red":
|
||||
@@ -277,104 +205,74 @@ const ListTenants = ({
|
||||
/>
|
||||
)}
|
||||
<Grid 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>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Create Tenant"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
history.push("/tenants/add");
|
||||
}}
|
||||
>
|
||||
<CreateIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.settingsOptionsContainer}>
|
||||
<SlideOptions
|
||||
slideOptions={[
|
||||
<Fragment>
|
||||
<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={<AddIcon />}
|
||||
onClick={createTenant}
|
||||
>
|
||||
Create Tenant
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.tenantsContainer}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFullObject: true,
|
||||
renderFunction: (t) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className={healthStatusToClass(
|
||||
t.health_status
|
||||
)}
|
||||
>
|
||||
<CircleIcon />
|
||||
</div>
|
||||
<div>{t.name}</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
{ 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>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFullObject: true,
|
||||
renderFunction: (t) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={healthStatusToClass(t.health_status)}>
|
||||
<CircleIcon />
|
||||
</div>
|
||||
<div>{t.name}</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
{ 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"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
@@ -383,7 +281,6 @@ const ListTenants = ({
|
||||
|
||||
const connector = connect(null, {
|
||||
setErrorSnackMessage,
|
||||
resetAddTenantForm,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(ListTenants));
|
||||
|
||||
@@ -61,7 +61,7 @@ const initialState: ITenantState = {
|
||||
selectedStorageClass: "",
|
||||
},
|
||||
configure: {
|
||||
customImage: false,
|
||||
customImage: true,
|
||||
imageName: "",
|
||||
customDockerhub: false,
|
||||
imageRegistry: "",
|
||||
|
||||
Reference in New Issue
Block a user