Add Pool Slice and Tenants Slice simplification (#2074)

Add Pool Slice and Tenants Slice simplification
Flatten Slice
AddPool Thunk
Return HMR support for Redux
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
Daniel Valdivia
2022-06-02 12:11:31 -07:00
committed by GitHub
parent 6c5f6934e7
commit 94e419e09c
32 changed files with 484 additions and 495 deletions

View File

@@ -63,33 +63,17 @@ const KeyPairView = ({ classes, records, recordName }: IKeyPairView) => {
};
const mapState = (state: AppState) => ({
loadingTenant: state.tenants.tenantDetails.loadingTenant,
selectedTenant: state.tenants.tenantDetails.currentTenant,
tenant: state.tenants.tenantDetails.tenantInfo,
logEnabled: get(state.tenants.tenantDetails.tenantInfo, "logEnabled", false),
monitoringEnabled: get(
state.tenants.tenantDetails.tenantInfo,
"monitoringEnabled",
false
),
encryptionEnabled: get(
state.tenants.tenantDetails.tenantInfo,
"encryptionEnabled",
false
),
minioTLS: get(state.tenants.tenantDetails.tenantInfo, "minioTLS", false),
consoleTLS: get(state.tenants.tenantDetails.tenantInfo, "consoleTLS", false),
consoleEnabled: get(
state.tenants.tenantDetails.tenantInfo,
"consoleEnabled",
false
),
adEnabled: get(state.tenants.tenantDetails.tenantInfo, "idpAdEnabled", false),
oidcEnabled: get(
state.tenants.tenantDetails.tenantInfo,
"idpOidcEnabled",
false
),
loadingTenant: state.tenants.loadingTenant,
selectedTenant: state.tenants.currentTenant,
tenant: state.tenants.tenantInfo,
logEnabled: get(state.tenants.tenantInfo, "logEnabled", false),
monitoringEnabled: get(state.tenants.tenantInfo, "monitoringEnabled", false),
encryptionEnabled: get(state.tenants.tenantInfo, "encryptionEnabled", false),
minioTLS: get(state.tenants.tenantInfo, "minioTLS", false),
consoleTLS: get(state.tenants.tenantInfo, "consoleTLS", false),
consoleEnabled: get(state.tenants.tenantInfo, "consoleEnabled", false),
adEnabled: get(state.tenants.tenantInfo, "idpAdEnabled", false),
oidcEnabled: get(state.tenants.tenantInfo, "idpOidcEnabled", false),
});
const connector = connect(mapState, null);

View File

@@ -53,7 +53,7 @@ const PodsSummary = ({ classes, match, history }: IPodsSummary) => {
const dispatch = useDispatch();
const loadingTenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.loadingTenant
(state: AppState) => state.tenants.loadingTenant
);
const [pods, setPods] = useState<IPodListElement[]>([]);

View File

@@ -14,18 +14,16 @@
// 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, useEffect, useState } from "react";
import React, { Fragment } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import {
formFieldStyles,
modalStyleUtils,
} from "../../../../Common/FormComponents/common/styleLibrary";
import Grid from "@mui/material/Grid";
import { generatePoolName, niceBytes } from "../../../../../../common/utils";
import { niceBytes } from "../../../../../../common/utils";
import { LinearProgress } from "@mui/material";
import { IAddPoolRequest } from "../../../ListTenants/types";
import PageHeader from "../../../../Common/PageHeader/PageHeader";
import PageLayout from "../../../../Common/Layout/PageLayout";
import GenericWizard from "../../../../Common/GenericWizard/GenericWizard";
@@ -39,20 +37,12 @@ import { AppState } from "../../../../../../store";
import { useDispatch, useSelector } from "react-redux";
import PoolConfiguration from "./PoolConfiguration";
import PoolPodPlacement from "./PoolPodPlacement";
import { ErrorResponseHandler } from "../../../../../../common/types";
import { getDefaultAffinity, getNodeSelector } from "../../utils";
import api from "../../../../../../common/api";
import BackLink from "../../../../../../common/BackLink";
import { setErrorSnackMessage } from "../../../../../../systemSlice";
import { resetPoolForm, setTenantDetailsLoad } from "../../../tenantsSlice";
import { resetPoolForm } from "./addPoolSlice";
import AddPoolCreateButton from "./AddPoolCreateButton";
import makeStyles from "@mui/styles/makeStyles";
interface IAddPoolProps {
classes: any;
open: boolean;
match: any;
}
const styles = (theme: Theme) =>
const useStyles = makeStyles((theme: Theme) =>
createStyles({
bottomContainer: {
display: "flex",
@@ -77,136 +67,20 @@ const styles = (theme: Theme) =>
},
...formFieldStyles,
...modalStyleUtils,
});
})
);
const requiredPages = ["setup", "affinity", "configure"];
const AddPool = ({ classes, open, match }: IAddPoolProps) => {
const AddPool = () => {
const dispatch = useDispatch();
const classes = useStyles();
const tenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.tenantInfo
);
const selectedStorageClass = useSelector(
(state: AppState) => state.tenants.addPool.fields.setup.storageClass
);
const validPages = useSelector(
(state: AppState) => state.tenants.addPool.validPages
);
const numberOfNodes = useSelector(
(state: AppState) => state.tenants.addPool.fields.setup.numberOfNodes
);
const volumeSize = useSelector(
(state: AppState) => state.tenants.addPool.fields.setup.volumeSize
);
const volumesPerServer = useSelector(
(state: AppState) => state.tenants.addPool.fields.setup.volumesPerServer
);
const affinityType = useSelector(
(state: AppState) => state.tenants.addPool.fields.affinity.podAffinity
);
const nodeSelectorLabels = useSelector(
(state: AppState) =>
state.tenants.addPool.fields.affinity.nodeSelectorLabels
);
const withPodAntiAffinity = useSelector(
(state: AppState) =>
state.tenants.addPool.fields.affinity.withPodAntiAffinity
);
const tolerations = useSelector(
(state: AppState) => state.tenants.addPool.fields.tolerations
);
const securityContextEnabled = useSelector(
(state: AppState) =>
state.tenants.addPool.fields.configuration.securityContextEnabled
);
const securityContext = useSelector(
(state: AppState) =>
state.tenants.addPool.fields.configuration.securityContext
);
const [addSending, setAddSending] = useState<boolean>(false);
const tenant = useSelector((state: AppState) => state.tenants.tenantInfo);
const sending = useSelector((state: AppState) => state.addPool.sending);
const poolsURL = `/namespaces/${tenant?.namespace || ""}/tenants/${
tenant?.name || ""
}/pools`;
useEffect(() => {
if (addSending && tenant) {
const poolName = generatePoolName(tenant.pools);
let affinityObject = {};
switch (affinityType) {
case "default":
affinityObject = {
affinity: getDefaultAffinity(tenant.name, poolName),
};
break;
case "nodeSelector":
affinityObject = {
affinity: getNodeSelector(
nodeSelectorLabels,
withPodAntiAffinity,
tenant.name,
poolName
),
};
break;
}
const tolerationValues = tolerations.filter(
(toleration) => toleration.key.trim() !== ""
);
const data: IAddPoolRequest = {
name: poolName,
servers: numberOfNodes,
volumes_per_server: volumesPerServer,
volume_configuration: {
size: volumeSize * 1073741824,
storage_class_name: selectedStorageClass,
labels: null,
},
tolerations: tolerationValues,
securityContext: securityContextEnabled ? securityContext : null,
...affinityObject,
};
api
.invoke(
"POST",
`/api/v1/namespaces/${tenant.namespace}/tenants/${tenant.name}/pools`,
data
)
.then(() => {
setAddSending(false);
dispatch(resetPoolForm());
dispatch(setTenantDetailsLoad(true));
history.push(poolsURL);
})
.catch((err: ErrorResponseHandler) => {
setAddSending(false);
dispatch(setErrorSnackMessage(err));
});
}
}, [
addSending,
poolsURL,
affinityType,
nodeSelectorLabels,
numberOfNodes,
securityContext,
securityContextEnabled,
selectedStorageClass,
tenant,
tolerations,
volumeSize,
volumesPerServer,
withPodAntiAffinity,
dispatch,
]);
const cancelButton = {
label: "Cancel",
type: "other",
@@ -218,15 +92,7 @@ const AddPool = ({ classes, open, match }: IAddPoolProps) => {
};
const createButton = {
label: "Create",
type: "submit",
enabled:
!addSending &&
selectedStorageClass !== "" &&
requiredPages.every((v) => validPages.includes(v)),
action: () => {
setAddSending(true);
},
componentRender: <AddPoolCreateButton key={"add-pool-crate"} />,
};
const wizardSteps: IWizardElement[] = [
@@ -272,8 +138,7 @@ const AddPool = ({ classes, open, match }: IAddPoolProps) => {
}
/>
</Grid>
{addSending && (
{sending && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
@@ -287,4 +152,4 @@ const AddPool = ({ classes, open, match }: IAddPoolProps) => {
);
};
export default withStyles(styles)(AddPool);
export default AddPool;

View File

@@ -0,0 +1,54 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 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 { Button } from "@mui/material";
import React from "react";
import { addPoolAsync } from "./addPoolThunks";
import { useDispatch, useSelector } from "react-redux";
import { AppState } from "../../../../../../store";
const AddPoolCreateButton = () => {
const dispatch = useDispatch();
const selectedStorageClass = useSelector(
(state: AppState) => state.addPool.setup.storageClass
);
const validPages = useSelector((state: AppState) => state.addPool.validPages);
const sending = useSelector((state: AppState) => state.addPool.sending);
const requiredPages = ["setup", "affinity", "configure"];
const enabled =
!sending &&
selectedStorageClass !== "" &&
requiredPages.every((v) => validPages.includes(v));
return (
<Button
id={"wizard-button-Create"}
variant="contained"
color="primary"
size="small"
onClick={() => {
dispatch(addPoolAsync());
}}
disabled={!enabled}
key={`button-AddTenant-Create`}
>
Create
</Button>
);
};
export default AddPoolCreateButton;

View File

@@ -33,7 +33,7 @@ import {
} from "../../../../../../utils/validationFunctions";
import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import { isPoolPageValid, setPoolField } from "../../../tenantsSlice";
import { isPoolPageValid, setPoolField } from "./addPoolSlice";
interface IConfigureProps {
classes: any;
@@ -81,12 +81,10 @@ const PoolConfiguration = ({ classes }: IConfigureProps) => {
const dispatch = useDispatch();
const securityContextEnabled = useSelector(
(state: AppState) =>
state.tenants.addPool.fields.configuration.securityContextEnabled
(state: AppState) => state.addPool.configuration.securityContextEnabled
);
const securityContext = useSelector(
(state: AppState) =>
state.tenants.addPool.fields.configuration.securityContext
(state: AppState) => state.addPool.configuration.securityContext
);
const [validationErrors, setValidationErrors] = useState<any>({});

View File

@@ -48,7 +48,7 @@ import {
setPoolField,
setPoolKeyValuePairs,
setPoolTolerationInfo,
} from "../../../tenantsSlice";
} from "./addPoolSlice";
interface IAffinityProps {
classes: any;
@@ -119,21 +119,19 @@ const Affinity = ({ classes }: IAffinityProps) => {
const dispatch = useDispatch();
const podAffinity = useSelector(
(state: AppState) => state.tenants.addPool.fields.affinity.podAffinity
(state: AppState) => state.addPool.affinity.podAffinity
);
const nodeSelectorLabels = useSelector(
(state: AppState) =>
state.tenants.addPool.fields.affinity.nodeSelectorLabels
(state: AppState) => state.addPool.affinity.nodeSelectorLabels
);
const withPodAntiAffinity = useSelector(
(state: AppState) =>
state.tenants.addPool.fields.affinity.withPodAntiAffinity
(state: AppState) => state.addPool.affinity.withPodAntiAffinity
);
const keyValuePairs = useSelector(
(state: AppState) => state.tenants.addPool.fields.nodeSelectorPairs
(state: AppState) => state.addPool.nodeSelectorPairs
);
const tolerations = useSelector(
(state: AppState) => state.tenants.addPool.fields.tolerations
(state: AppState) => state.addPool.tolerations
);
const [validationErrors, setValidationErrors] = useState<any>({});

View File

@@ -43,7 +43,7 @@ import {
isPoolPageValid,
setPoolField,
setPoolStorageClasses,
} from "../../../tenantsSlice";
} from "./addPoolSlice";
interface IPoolResourcesProps {
classes: any;
@@ -86,23 +86,21 @@ const styles = (theme: Theme) =>
const PoolResources = ({ classes }: IPoolResourcesProps) => {
const dispatch = useDispatch();
const tenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.tenantInfo
);
const tenant = useSelector((state: AppState) => state.tenants.tenantInfo);
const storageClasses = useSelector(
(state: AppState) => state.tenants.addPool.storageClasses
(state: AppState) => state.addPool.storageClasses
);
const numberOfNodes = useSelector((state: AppState) =>
state.tenants.addPool.fields.setup.numberOfNodes.toString()
state.addPool.setup.numberOfNodes.toString()
);
const storageClass = useSelector(
(state: AppState) => state.tenants.addPool.fields.setup.storageClass
(state: AppState) => state.addPool.setup.storageClass
);
const volumeSize = useSelector((state: AppState) =>
state.tenants.addPool.fields.setup.volumeSize.toString()
state.addPool.setup.volumeSize.toString()
);
const volumesPerServer = useSelector((state: AppState) =>
state.tenants.addPool.fields.setup.volumesPerServer.toString()
state.addPool.setup.volumesPerServer.toString()
);
const [validationErrors, setValidationErrors] = useState<any>({});

View File

@@ -0,0 +1,190 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 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 { createSlice, PayloadAction } from "@reduxjs/toolkit";
import {
ITolerationEffect,
ITolerationModel,
ITolerationOperator,
} from "../../../../../../common/types";
import {
IAddPoolSetup,
IPoolConfiguration,
ITenantAffinity,
LabelKeyPair,
} from "../../../types";
import { has } from "lodash";
import get from "lodash/get";
import { Opts } from "../../../ListTenants/utils";
import { addPoolAsync } from "./addPoolThunks";
export interface IAddPool {
addPoolLoading: boolean;
sending: boolean;
validPages: string[];
storageClasses: Opts[];
limitSize: any;
setup: IAddPoolSetup;
affinity: ITenantAffinity;
configuration: IPoolConfiguration;
tolerations: ITolerationModel[];
nodeSelectorPairs: LabelKeyPair[];
}
const initialState: IAddPool = {
addPoolLoading: false,
sending: false,
validPages: ["affinity", "configure"],
storageClasses: [],
limitSize: {},
setup: {
numberOfNodes: 0,
storageClass: "",
volumeSize: 0,
volumesPerServer: 0,
},
affinity: {
nodeSelectorLabels: "",
podAffinity: "default",
withPodAntiAffinity: true,
},
configuration: {
securityContextEnabled: false,
securityContext: {
runAsUser: "1000",
runAsGroup: "1000",
fsGroup: "1000",
runAsNonRoot: true,
},
},
nodeSelectorPairs: [{ key: "", value: "" }],
tolerations: [
{
key: "",
tolerationSeconds: { seconds: 0 },
value: "",
effect: ITolerationEffect.NoSchedule,
operator: ITolerationOperator.Equal,
},
],
};
export const addPoolSlice = createSlice({
name: "addPool",
initialState,
reducers: {
setPoolLoading: (state, action: PayloadAction<boolean>) => {
state.addPoolLoading = action.payload;
},
setPoolField: (
state,
action: PayloadAction<{
page:
| "setup"
| "affinity"
| "configuration"
| "tolerations"
| "nodeSelectorPairs";
field: string;
value: any;
}>
) => {
if (has(state, `${action.payload.page}.${action.payload.field}`)) {
const originPageNameItems = get(state, `${action.payload.page}`, {});
let newValue: any = {};
newValue[action.payload.field] = action.payload.value;
state[action.payload.page] = {
...originPageNameItems,
...newValue,
};
}
},
isPoolPageValid: (
state,
action: PayloadAction<{
page: string;
status: boolean;
}>
) => {
if (action.payload.status) {
if (!state.validPages.includes(action.payload.page)) {
state.validPages.push(action.payload.page);
}
} else {
state.validPages = state.validPages.filter(
(elm) => elm !== action.payload.page
);
}
},
setPoolStorageClasses: (state, action: PayloadAction<Opts[]>) => {
state.storageClasses = action.payload;
},
setPoolTolerationInfo: (
state,
action: PayloadAction<{
index: number;
tolerationValue: ITolerationModel;
}>
) => {
state.tolerations[action.payload.index] = action.payload.tolerationValue;
},
addNewPoolToleration: (state) => {
state.tolerations.push({
key: "",
tolerationSeconds: { seconds: 0 },
value: "",
effect: ITolerationEffect.NoSchedule,
operator: ITolerationOperator.Equal,
});
},
removePoolToleration: (state, action: PayloadAction<number>) => {
state.tolerations = state.tolerations.filter(
(_, index) => index !== action.payload
);
},
setPoolKeyValuePairs: (state, action: PayloadAction<LabelKeyPair[]>) => {
state.nodeSelectorPairs = action.payload;
},
resetPoolForm: () => initialState,
},
extraReducers: (builder) => {
builder
.addCase(addPoolAsync.pending, (state) => {
state.sending = true;
})
.addCase(addPoolAsync.rejected, (state) => {
state.sending = false;
})
.addCase(addPoolAsync.fulfilled, (state, action) => {
state.sending = false;
});
},
});
export const {
setPoolLoading,
resetPoolForm,
setPoolField,
isPoolPageValid,
setPoolStorageClasses,
setPoolTolerationInfo,
addNewPoolToleration,
removePoolToleration,
setPoolKeyValuePairs,
} = addPoolSlice.actions;
export default addPoolSlice.reducer;

View File

@@ -0,0 +1,107 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 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 { createAsyncThunk } from "@reduxjs/toolkit";
import { AppState } from "../../../../../../store";
import api from "../../../../../../common/api";
import { IAddPoolRequest } from "../../../ListTenants/types";
import { generatePoolName } from "../../../../../../common/utils";
import { ErrorResponseHandler } from "../../../../../../common/types";
import { setErrorSnackMessage } from "../../../../../../systemSlice";
import { getDefaultAffinity, getNodeSelector } from "../../utils";
import { resetPoolForm } from "./addPoolSlice";
import { getTenantAsync } from "../../../thunks/tenantDetailsAsync";
import history from "../../../../../../history";
export const addPoolAsync = createAsyncThunk(
"addPool/addPoolAsync",
async (_, { getState, rejectWithValue, dispatch }) => {
const state = getState() as AppState;
const tenant = state.tenants.tenantInfo;
const selectedStorageClass = state.addPool.setup.storageClass;
const numberOfNodes = state.addPool.setup.numberOfNodes;
const volumeSize = state.addPool.setup.volumeSize;
const volumesPerServer = state.addPool.setup.volumesPerServer;
const affinityType = state.addPool.affinity.podAffinity;
const nodeSelectorLabels = state.addPool.affinity.nodeSelectorLabels;
const withPodAntiAffinity = state.addPool.affinity.withPodAntiAffinity;
const tolerations = state.addPool.tolerations;
const securityContextEnabled =
state.addPool.configuration.securityContextEnabled;
const securityContext = state.addPool.configuration.securityContext;
if (tenant === null) {
return;
}
const poolName = generatePoolName(tenant.pools);
let affinityObject = {};
switch (affinityType) {
case "default":
affinityObject = {
affinity: getDefaultAffinity(tenant.name, poolName),
};
break;
case "nodeSelector":
affinityObject = {
affinity: getNodeSelector(
nodeSelectorLabels,
withPodAntiAffinity,
tenant.name,
poolName
),
};
break;
}
const tolerationValues = tolerations.filter(
(toleration) => toleration.key.trim() !== ""
);
const data: IAddPoolRequest = {
name: poolName,
servers: numberOfNodes,
volumes_per_server: volumesPerServer,
volume_configuration: {
size: volumeSize * 1073741824,
storage_class_name: selectedStorageClass,
labels: null,
},
tolerations: tolerationValues,
securityContext: securityContextEnabled ? securityContext : null,
...affinityObject,
};
const poolsURL = `/namespaces/${tenant?.namespace || ""}/tenants/${
tenant?.name || ""
}/pools`;
return api
.invoke(
"POST",
`/api/v1/namespaces/${tenant.namespace}/tenants/${tenant.name}/pools`,
data
)
.then(() => {
dispatch(resetPoolForm());
dispatch(getTenantAsync());
history.push(poolsURL);
})
.catch((err: ErrorResponseHandler) => {
dispatch(setErrorSnackMessage(err));
});
}
);

View File

@@ -66,11 +66,9 @@ const twoColCssGridLayoutConfig = {
};
const PoolDetails = ({ history }: IPoolDetails) => {
const tenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.tenantInfo
);
const tenant = useSelector((state: AppState) => state.tenants.tenantInfo);
const selectedPool = useSelector(
(state: AppState) => state.tenants.tenantDetails.selectedPool
(state: AppState) => state.tenants.selectedPool
);
const poolInformation =

View File

@@ -59,11 +59,9 @@ const PoolsListing = ({
const dispatch = useDispatch();
const loadingTenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.loadingTenant
);
const tenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.tenantInfo
(state: AppState) => state.tenants.loadingTenant
);
const tenant = useSelector((state: AppState) => state.tenants.tenantInfo);
const [pools, setPools] = useState<IPool[]>([]);
const [filter, setFilter] = useState<string>("");

View File

@@ -74,11 +74,9 @@ const EditPool = () => {
const dispatch = useDispatch();
const classes = useStyles();
const tenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.tenantInfo
);
const tenant = useSelector((state: AppState) => state.tenants.tenantInfo);
const selectedPool = useSelector(
(state: AppState) => state.tenants.tenantDetails.selectedPool
(state: AppState) => state.tenants.selectedPool
);
const editSending = useSelector(

View File

@@ -86,9 +86,7 @@ const styles = (theme: Theme) =>
const PoolResources = ({ classes }: IPoolResourcesProps) => {
const dispatch = useDispatch();
const tenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.tenantInfo
);
const tenant = useSelector((state: AppState) => state.tenants.tenantInfo);
const storageClasses = useSelector(
(state: AppState) => state.editPool.storageClasses
);

View File

@@ -31,8 +31,8 @@ export const editPoolAsync = createAsyncThunk(
async (_, { getState, rejectWithValue, dispatch }) => {
const state = getState() as AppState;
const tenant = state.tenants.tenantDetails.tenantInfo;
const selectedPool = state.tenants.tenantDetails.selectedPool;
const tenant = state.tenants.tenantInfo;
const selectedPool = state.tenants.selectedPool;
const selectedStorageClass = state.editPool.fields.setup.storageClass;
const numberOfNodes = state.editPool.fields.setup.numberOfNodes;
const volumeSize = state.editPool.fields.setup.volumeSize;

View File

@@ -52,10 +52,10 @@ const PoolsSummary = ({ classes, history, match }: IPoolsSummary) => {
const dispatch = useDispatch();
const selectedPool = useSelector(
(state: AppState) => state.tenants.tenantDetails.selectedPool
(state: AppState) => state.tenants.selectedPool
);
const poolDetailsOpen = useSelector(
(state: AppState) => state.tenants.tenantDetails.poolDetailsOpen
(state: AppState) => state.tenants.poolDetailsOpen
);
return (

View File

@@ -161,17 +161,15 @@ const TenantDetails = ({ classes, match, history }: ITenantDetailsProps) => {
const dispatch = useDispatch();
const loadingTenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.loadingTenant
(state: AppState) => state.tenants.loadingTenant
);
const selectedTenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.currentTenant
(state: AppState) => state.tenants.currentTenant
);
const selectedNamespace = useSelector(
(state: AppState) => state.tenants.tenantDetails.currentNamespace
);
const tenantInfo = useSelector(
(state: AppState) => state.tenants.tenantDetails.tenantInfo
(state: AppState) => state.tenants.currentNamespace
);
const tenantInfo = useSelector((state: AppState) => state.tenants.tenantInfo);
const [yamlScreenOpen, setYamlScreenOpen] = useState<boolean>(false);

View File

@@ -87,9 +87,7 @@ const styles = (theme: Theme) =>
const TenantEncryption = ({ classes }: ITenantEncryption) => {
const dispatch = useDispatch();
const tenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.tenantInfo
);
const tenant = useSelector((state: AppState) => state.tenants.tenantInfo);
const [encryptionEnabled, setEncryptionEnabled] = useState<boolean>(false);
const [encryptionType, setEncryptionType] = useState<string>("vault");

View File

@@ -98,7 +98,7 @@ const TenantEvents = ({
);
};
const mapState = (state: AppState) => ({
loadingTenant: state.tenants.tenantDetails.loadingTenant,
loadingTenant: state.tenants.loadingTenant,
});
const connector = connect(mapState, null);

View File

@@ -594,9 +594,9 @@ const TenantIdentityProvider = ({
};
const mapState = (state: AppState) => ({
loadingTenant: state.tenants.tenantDetails.loadingTenant,
selectedTenant: state.tenants.tenantDetails.currentTenant,
tenant: state.tenants.tenantDetails.tenantInfo,
loadingTenant: state.tenants.loadingTenant,
selectedTenant: state.tenants.currentTenant,
tenant: state.tenants.tenantInfo,
});
const connector = connect(mapState, null);

View File

@@ -50,11 +50,9 @@ const TenantLicense = ({ classes }: ITenantLicense) => {
const dispatch = useDispatch();
const loadingTenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.loadingTenant
);
const tenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.tenantInfo
(state: AppState) => state.tenants.loadingTenant
);
const tenant = useSelector((state: AppState) => state.tenants.tenantInfo);
const [licenseInfo, setLicenseInfo] = useState<SubnetInfo>();
const [loadingLicenseInfo, setLoadingLicenseInfo] = useState<boolean>(true);

View File

@@ -489,9 +489,9 @@ const TenantLogging = ({
};
const mapState = (state: AppState) => ({
loadingTenant: state.tenants.tenantDetails.loadingTenant,
selectedTenant: state.tenants.tenantDetails.currentTenant,
tenant: state.tenants.tenantDetails.tenantInfo,
loadingTenant: state.tenants.loadingTenant,
selectedTenant: state.tenants.currentTenant,
tenant: state.tenants.tenantInfo,
});
const connector = connect(mapState, null);

View File

@@ -531,9 +531,9 @@ const TenantSecurity = ({
};
const mapState = (state: AppState) => ({
loadingTenant: state.tenants.tenantDetails.loadingTenant,
selectedTenant: state.tenants.tenantDetails.currentTenant,
tenant: state.tenants.tenantDetails.tenantInfo,
loadingTenant: state.tenants.loadingTenant,
selectedTenant: state.tenants.currentTenant,
tenant: state.tenants.tenantInfo,
});
const connector = connect(mapState, null);

View File

@@ -184,26 +184,24 @@ const featureItemStyleProps = {
const TenantSummary = ({ classes, match }: ITenantsSummary) => {
const dispatch = useDispatch();
const tenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.tenantInfo
);
const tenant = useSelector((state: AppState) => state.tenants.tenantInfo);
const logEnabled = useSelector((state: AppState) =>
get(state.tenants.tenantDetails.tenantInfo, "logEnabled", false)
get(state.tenants.tenantInfo, "logEnabled", false)
);
const monitoringEnabled = useSelector((state: AppState) =>
get(state.tenants.tenantDetails.tenantInfo, "monitoringEnabled", false)
get(state.tenants.tenantInfo, "monitoringEnabled", false)
);
const encryptionEnabled = useSelector((state: AppState) =>
get(state.tenants.tenantDetails.tenantInfo, "encryptionEnabled", false)
get(state.tenants.tenantInfo, "encryptionEnabled", false)
);
const minioTLS = useSelector((state: AppState) =>
get(state.tenants.tenantDetails.tenantInfo, "minioTLS", false)
get(state.tenants.tenantInfo, "minioTLS", false)
);
const adEnabled = useSelector((state: AppState) =>
get(state.tenants.tenantDetails.tenantInfo, "idpAdEnabled", false)
get(state.tenants.tenantInfo, "idpAdEnabled", false)
);
const oidcEnabled = useSelector((state: AppState) =>
get(state.tenants.tenantDetails.tenantInfo, "idpOidcEnabled", false)
get(state.tenants.tenantInfo, "idpOidcEnabled", false)
);
const [poolCount, setPoolCount] = useState<number>(0);

View File

@@ -55,7 +55,7 @@ const TenantVolumes = ({ classes, history, match }: ITenantVolumesProps) => {
const dispatch = useDispatch();
const loadingTenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.loadingTenant
(state: AppState) => state.tenants.loadingTenant
);
const [records, setRecords] = useState<IStoragePVCs[]>([]);

View File

@@ -406,7 +406,7 @@ const PodDescribe = ({
}: IPodEventsProps) => {
const dispatch = useDispatch();
const loadingTenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.loadingTenant
(state: AppState) => state.tenants.loadingTenant
);
const [describeInfo, setDescribeInfo] = useState<DescribeResponse>();

View File

@@ -63,7 +63,7 @@ const PodEvents = ({
}: IPodEventsProps) => {
const dispatch = useDispatch();
const loadingTenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.loadingTenant
(state: AppState) => state.tenants.loadingTenant
);
const [events, setEvents] = useState<IEvent[]>([]);
const [loading, setLoading] = useState<boolean>(true);

View File

@@ -92,7 +92,7 @@ const PodLogs = ({
}: IPodLogsProps) => {
const dispatch = useDispatch();
const loadingTenant = useSelector(
(state: AppState) => state.tenants.tenantDetails.loadingTenant
(state: AppState) => state.tenants.loadingTenant
);
const [highlight, setHighlight] = useState<string>("");
const [logLines, setLogLines] = useState<string[]>([]);

View File

@@ -215,7 +215,7 @@ const PVCDescribe = ({
);
};
const mapState = (state: AppState) => ({
loadingTenant: state.tenants.tenantDetails.loadingTenant,
loadingTenant: state.tenants.loadingTenant,
});
const connector = connect(mapState, {
setErrorSnackMessage,

View File

@@ -15,15 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { IAddPoolFields, ITenantState, LabelKeyPair } from "./types";
import {
ITolerationEffect,
ITolerationModel,
ITolerationOperator,
} from "../../../common/types";
import get from "lodash/get";
import { has } from "lodash";
import { Opts } from "./ListTenants/utils";
import { ITenantState } from "./types";
import { ITenant } from "./ListTenants/types";
import { getTenantAsync } from "./thunks/tenantDetailsAsync";
@@ -46,53 +38,13 @@ export interface CertificateFile {
}
const initialState: ITenantState = {
tenantDetails: {
currentTenant: "",
currentNamespace: "",
loadingTenant: false,
tenantInfo: null,
currentTab: "summary",
selectedPool: null,
poolDetailsOpen: false,
},
addPool: {
addPoolLoading: false,
validPages: ["affinity", "configure"],
storageClasses: [],
limitSize: {},
fields: {
setup: {
numberOfNodes: 0,
storageClass: "",
volumeSize: 0,
volumesPerServer: 0,
},
affinity: {
nodeSelectorLabels: "",
podAffinity: "default",
withPodAntiAffinity: true,
},
configuration: {
securityContextEnabled: false,
securityContext: {
runAsUser: "1000",
runAsGroup: "1000",
fsGroup: "1000",
runAsNonRoot: true,
},
},
nodeSelectorPairs: [{ key: "", value: "" }],
tolerations: [
{
key: "",
tolerationSeconds: { seconds: 0 },
value: "",
effect: ITolerationEffect.NoSchedule,
operator: ITolerationOperator.Equal,
},
],
},
},
currentTenant: "",
currentNamespace: "",
loadingTenant: false,
tenantInfo: null,
currentTab: "summary",
selectedPool: null,
poolDetailsOpen: false,
};
export const tenantSlice = createSlice({
@@ -100,7 +52,7 @@ export const tenantSlice = createSlice({
initialState,
reducers: {
setTenantDetailsLoad: (state, action: PayloadAction<boolean>) => {
state.tenantDetails.loadingTenant = action.payload;
state.loadingTenant = action.payload;
},
setTenantName: (
state,
@@ -109,156 +61,36 @@ export const tenantSlice = createSlice({
namespace: string;
}>
) => {
state.tenantDetails.currentTenant = action.payload.name;
state.tenantDetails.currentNamespace = action.payload.namespace;
state.currentTenant = action.payload.name;
state.currentNamespace = action.payload.namespace;
},
setTenantInfo: (state, action: PayloadAction<ITenant | null>) => {
if (action.payload) {
state.tenantDetails.tenantInfo = action.payload;
state.tenantInfo = action.payload;
}
},
setTenantTab: (state, action: PayloadAction<string>) => {
state.tenantDetails.currentTab = action.payload;
state.currentTab = action.payload;
},
setPoolLoading: (state, action: PayloadAction<boolean>) => {
state.addPool.addPoolLoading = action.payload;
},
setPoolField: (
state,
action: PayloadAction<{
page: keyof IAddPoolFields;
field: string;
value: any;
}>
) => {
if (
has(
state.addPool.fields,
`${action.payload.page}.${action.payload.field}`
)
) {
const originPageNameItems = get(
state.addPool.fields,
`${action.payload.page}`,
{}
);
let newValue: any = {};
newValue[action.payload.field] = action.payload.value;
state.addPool.fields[action.payload.page] = {
...originPageNameItems,
...newValue,
};
}
},
isPoolPageValid: (
state,
action: PayloadAction<{
page: string;
status: boolean;
}>
) => {
if (action.payload.status) {
if (!state.addPool.validPages.includes(action.payload.page)) {
state.addPool.validPages.push(action.payload.page);
}
} else {
state.addPool.validPages = state.addPool.validPages.filter(
(elm) => elm !== action.payload.page
);
}
},
setPoolStorageClasses: (state, action: PayloadAction<Opts[]>) => {
state.addPool.storageClasses = action.payload;
},
setPoolTolerationInfo: (
state,
action: PayloadAction<{
index: number;
tolerationValue: ITolerationModel;
}>
) => {
state.addPool.fields.tolerations[action.payload.index] =
action.payload.tolerationValue;
},
addNewPoolToleration: (state) => {
state.addPool.fields.tolerations.push({
key: "",
tolerationSeconds: { seconds: 0 },
value: "",
effect: ITolerationEffect.NoSchedule,
operator: ITolerationOperator.Equal,
});
},
removePoolToleration: (state, action: PayloadAction<number>) => {
state.addPool.fields.tolerations =
state.addPool.fields.tolerations.filter(
(_, index) => index !== action.payload
);
},
setPoolKeyValuePairs: (state, action: PayloadAction<LabelKeyPair[]>) => {
state.addPool.fields.nodeSelectorPairs = action.payload;
},
resetPoolForm: (state) => {
state.addPool = {
addPoolLoading: false,
validPages: ["affinity", "configure"],
storageClasses: [],
limitSize: {},
fields: {
setup: {
numberOfNodes: 0,
storageClass: "",
volumeSize: 0,
volumesPerServer: 0,
},
affinity: {
nodeSelectorLabels: "",
podAffinity: "default",
withPodAntiAffinity: true,
},
configuration: {
securityContextEnabled: false,
securityContext: {
runAsUser: "1000",
runAsGroup: "1000",
fsGroup: "1000",
runAsNonRoot: true,
},
},
nodeSelectorPairs: [{ key: "", value: "" }],
tolerations: [
{
key: "",
tolerationSeconds: { seconds: 0 },
value: "",
effect: ITolerationEffect.NoSchedule,
operator: ITolerationOperator.Equal,
},
],
},
};
},
setSelectedPool: (state, action: PayloadAction<string | null>) => {
state.tenantDetails.selectedPool = action.payload;
state.selectedPool = action.payload;
},
setOpenPoolDetails: (state, action: PayloadAction<boolean>) => {
state.tenantDetails.poolDetailsOpen = action.payload;
state.poolDetailsOpen = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(getTenantAsync.pending, (state) => {
state.tenantDetails.loadingTenant = true;
state.loadingTenant = true;
})
.addCase(getTenantAsync.rejected, (state) => {
state.tenantDetails.loadingTenant = false;
state.loadingTenant = false;
})
.addCase(getTenantAsync.fulfilled, (state, action) => {
state.tenantDetails.loadingTenant = false;
state.tenantDetails.tenantInfo = action.payload;
state.loadingTenant = false;
state.tenantInfo = action.payload;
});
},
});
@@ -269,15 +101,6 @@ export const {
setTenantName,
setTenantInfo,
setTenantTab,
setPoolLoading,
resetPoolForm,
setPoolField,
isPoolPageValid,
setPoolStorageClasses,
setPoolTolerationInfo,
addNewPoolToleration,
removePoolToleration,
setPoolKeyValuePairs,
setSelectedPool,
setOpenPoolDetails,
} = tenantSlice.actions;

View File

@@ -27,8 +27,8 @@ export const getTenantAsync = createAsyncThunk(
async (_, { getState, rejectWithValue, dispatch }) => {
const state = getState() as AppState;
const currentNamespace = state.tenants.tenantDetails.currentNamespace;
const currentTenant = state.tenants.tenantDetails.currentTenant;
const currentNamespace = state.tenants.currentNamespace;
const currentTenant = state.tenants.currentTenant;
return api
.invoke(

View File

@@ -20,7 +20,6 @@ import {
IErasureCodeCalc,
IGCPConfig,
IGemaltoCredentials,
ITolerationModel,
} from "../../../common/types";
import { IResourcesSize, ITenant } from "./ListTenants/types";
import { KeyPair, Opts } from "./ListTenants/utils";
@@ -278,7 +277,7 @@ export interface ITenantAffinity {
withPodAntiAffinity: boolean;
}
export interface ITenantDetails {
export interface ITenantState {
currentTenant: string;
currentNamespace: string;
loadingTenant: boolean;
@@ -288,11 +287,6 @@ export interface ITenantDetails {
selectedPool: string | null;
}
export interface ITenantState {
tenantDetails: ITenantDetails;
addPool: IAddPool;
}
export interface ILabelKeyPair {
labelKey: string;
labelValue: string;
@@ -322,22 +316,6 @@ export interface IPoolConfiguration {
securityContext: ISecurityContext;
}
export interface IAddPoolFields {
setup: IAddPoolSetup;
affinity: ITenantAffinity;
configuration: IPoolConfiguration;
tolerations: ITolerationModel[];
nodeSelectorPairs: LabelKeyPair[];
}
export interface IAddPool {
addPoolLoading: boolean;
validPages: string[];
storageClasses: Opts[];
limitSize: any;
fields: IAddPoolFields;
}
export interface ITenantIdentityProviderResponse {
oidc?: {
callback_url: string;

View File

@@ -25,29 +25,39 @@ import bucketDetailsReducer from "./screens/Console/Buckets/BucketDetails/bucket
import objectBrowserReducer from "./screens/Console/ObjectBrowser/objectBrowserSlice";
import tenantsReducer from "./screens/Console/Tenants/tenantsSlice";
import dashboardReducer from "./screens/Console/Dashboard/dashboardSlice";
import { configureStore } from "@reduxjs/toolkit";
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import createTenantReducer from "./screens/Console/Tenants/AddTenant/createTenantSlice";
import addPoolReducer from "./screens/Console/Tenants/TenantDetails/Pools/AddPool/addPoolSlice";
import editPoolReducer from "./screens/Console/Tenants/TenantDetails/Pools/EditPool/editPoolSlice";
export const store = configureStore({
reducer: {
system: systemReducer,
trace: traceReducer,
logs: logReducer,
watch: watchReducer,
console: consoleReducer,
buckets: bucketsReducer,
bucketDetails: bucketDetailsReducer,
objectBrowser: objectBrowserReducer,
healthInfo: healthInfoReducer,
dashboard: dashboardReducer,
// Operator Reducers
tenants: tenantsReducer,
createTenant: createTenantReducer,
editPool: editPoolReducer,
},
const rootReducer = combineReducers({
system: systemReducer,
trace: traceReducer,
logs: logReducer,
watch: watchReducer,
console: consoleReducer,
buckets: bucketsReducer,
bucketDetails: bucketDetailsReducer,
objectBrowser: objectBrowserReducer,
healthInfo: healthInfoReducer,
dashboard: dashboardReducer,
// Operator Reducers
tenants: tenantsReducer,
createTenant: createTenantReducer,
addPool: addPoolReducer,
editPool: editPoolReducer,
});
export const store = configureStore({
reducer: rootReducer,
});
if (process.env.NODE_ENV !== "production" && module.hot) {
module.hot.accept(() => {
store.replaceReducer(rootReducer);
});
}
export type AppState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;