Added storage class selector to add pool modal (#699)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.a19f3d53.chunk.css",
|
||||
"main.js": "/static/js/main.a8ae7284.chunk.js",
|
||||
"main.js.map": "/static/js/main.a8ae7284.chunk.js.map",
|
||||
"main.js": "/static/js/main.fb87f627.chunk.js",
|
||||
"main.js.map": "/static/js/main.fb87f627.chunk.js.map",
|
||||
"runtime-main.js": "/static/js/runtime-main.f48e99e5.js",
|
||||
"runtime-main.js.map": "/static/js/runtime-main.f48e99e5.js.map",
|
||||
"static/css/2.f324abd6.chunk.css": "/static/css/2.f324abd6.chunk.css",
|
||||
@@ -20,6 +20,6 @@
|
||||
"static/css/2.f324abd6.chunk.css",
|
||||
"static/js/2.26e254ca.chunk.js",
|
||||
"static/css/main.a19f3d53.chunk.css",
|
||||
"static/js/main.a8ae7284.chunk.js"
|
||||
"static/js/main.fb87f627.chunk.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.f324abd6.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.26e254ca.chunk.js"></script><script src="/static/js/main.a8ae7284.chunk.js"></script></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.f324abd6.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.26e254ca.chunk.js"></script><script src="/static/js/main.fb87f627.chunk.js"></script></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/main.fb87f627.chunk.js.map
Normal file
1
portal-ui/build/static/js/main.fb87f627.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,244 +0,0 @@
|
||||
// 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, { useState, useEffect, createRef, ChangeEvent } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import get from "lodash/get";
|
||||
import { InputLabel, Tooltip } from "@material-ui/core";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import {
|
||||
fieldBasic,
|
||||
tooltipHelper,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import DeleteIcon from "../../../../icons/DeleteIcon";
|
||||
import HelpIcon from "../../../../icons/HelpIcon";
|
||||
import { IPool } from "./types";
|
||||
|
||||
interface IPoolsMultiSelector {
|
||||
elements: IPool[];
|
||||
name: string;
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
classes: any;
|
||||
|
||||
onChange: (elements: IPool[]) => void;
|
||||
}
|
||||
|
||||
const gridBasic = {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "calc(50% - 20px) calc(50% - 20px) 30px",
|
||||
gridGap: 8,
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...fieldBasic,
|
||||
...tooltipHelper,
|
||||
inputLabel: {
|
||||
...fieldBasic.inputLabel,
|
||||
width: 116,
|
||||
},
|
||||
inputContainer: {
|
||||
height: 150,
|
||||
overflowY: "auto",
|
||||
padding: 15,
|
||||
position: "relative",
|
||||
border: "1px solid #c4c4c4",
|
||||
},
|
||||
labelContainer: {
|
||||
display: "flex",
|
||||
},
|
||||
inputGrid: {
|
||||
...gridBasic,
|
||||
},
|
||||
inputTitles: {
|
||||
...gridBasic,
|
||||
marginBottom: 5,
|
||||
},
|
||||
deleteIconContainer: {
|
||||
alignSelf: "center",
|
||||
"& button": {
|
||||
padding: 0,
|
||||
marginBottom: 10,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const PoolsMultiSelector = ({
|
||||
elements,
|
||||
name,
|
||||
label,
|
||||
tooltip = "",
|
||||
onChange,
|
||||
classes,
|
||||
}: IPoolsMultiSelector) => {
|
||||
const defaultPool: IPool = {
|
||||
name: "",
|
||||
servers: 0,
|
||||
capacity: "",
|
||||
volumes: 0,
|
||||
volumes_per_server: 0,
|
||||
volume_configuration: { size: 0, storage_class: "", labels: null },
|
||||
};
|
||||
|
||||
const [currentElements, setCurrentElements] = useState<IPool[]>([]);
|
||||
const [internalCounter, setInternalCounter] = useState<number>(1);
|
||||
const bottomList = createRef<HTMLDivElement>();
|
||||
|
||||
// Use effect to send new values to onChange
|
||||
useEffect(() => {
|
||||
onChange(currentElements);
|
||||
}, [currentElements]);
|
||||
|
||||
// Use effect to set initial values
|
||||
useEffect(() => {
|
||||
if (currentElements.length === 0 && elements.length === 0) {
|
||||
// Initial Value
|
||||
setCurrentElements([{ ...defaultPool, name: "pool-1" }]);
|
||||
} else if (currentElements.length === 0 && elements.length > 0) {
|
||||
setCurrentElements(elements);
|
||||
setInternalCounter(elements.length);
|
||||
}
|
||||
}, [currentElements, elements]);
|
||||
|
||||
// If the last input is not empty, we add a new one
|
||||
const addEmptyRow = (elementsUp: IPool[]) => {
|
||||
const lastElement = elementsUp[elementsUp.length - 1];
|
||||
const internalElement = internalCounter + 1;
|
||||
if (
|
||||
lastElement.servers !== 0 &&
|
||||
lastElement.name !== "" &&
|
||||
!isNaN(lastElement.servers)
|
||||
) {
|
||||
elementsUp.push({
|
||||
...defaultPool,
|
||||
name: `pool-${internalElement}`,
|
||||
});
|
||||
const refScroll = bottomList.current;
|
||||
|
||||
setInternalCounter(internalElement);
|
||||
|
||||
if (refScroll) {
|
||||
refScroll.scrollIntoView(false);
|
||||
}
|
||||
}
|
||||
|
||||
return elementsUp;
|
||||
};
|
||||
|
||||
// Onchange function for input box, we get the dataset-index & only update that value in the array
|
||||
const onChangeElement = (e: ChangeEvent<HTMLInputElement>, field: string) => {
|
||||
e.persist();
|
||||
|
||||
let updatedElement = [...currentElements];
|
||||
const index = get(e.target, "dataset.index", 0);
|
||||
|
||||
const rowPosition: IPool = updatedElement[index];
|
||||
|
||||
rowPosition.servers =
|
||||
field === "servers" ? parseInt(e.target.value) : rowPosition.servers;
|
||||
rowPosition.name = field === "name" ? e.target.value : rowPosition.name;
|
||||
|
||||
updatedElement[index] = rowPosition;
|
||||
|
||||
updatedElement = addEmptyRow(updatedElement);
|
||||
setCurrentElements(updatedElement);
|
||||
};
|
||||
|
||||
const deleteElement = (poolToRemove: number) => {
|
||||
const poolsClone = [...currentElements];
|
||||
|
||||
const newArray = poolsClone
|
||||
.slice(0, poolToRemove)
|
||||
.concat(poolsClone.slice(poolToRemove + 1, poolsClone.length));
|
||||
|
||||
setCurrentElements(newArray);
|
||||
};
|
||||
|
||||
const inputs = currentElements.map((element, index) => {
|
||||
return (
|
||||
<React.Fragment key={`pool-${name}-${index.toString()}`}>
|
||||
<div>
|
||||
<InputBoxWrapper
|
||||
id={`${name}-${index.toString()}-name`}
|
||||
label={""}
|
||||
name={`${name}-${index.toString()}-name`}
|
||||
value={currentElements[index].name}
|
||||
onChange={(e) => onChangeElement(e, "name")}
|
||||
index={index}
|
||||
key={`Pools-${name}-${index.toString()}-name`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
min="0"
|
||||
id={`${name}-${index.toString()}-servers`}
|
||||
label={""}
|
||||
name={`${name}-${index.toString()}-servers`}
|
||||
value={currentElements[index].servers.toString(10)}
|
||||
onChange={(e) => onChangeElement(e, "servers")}
|
||||
index={index}
|
||||
key={`Pools-${name}-${index.toString()}-servers`}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.deleteIconContainer}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
deleteElement(index);
|
||||
}}
|
||||
disabled={index === currentElements.length - 1}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
<InputLabel className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
<div>
|
||||
<HelpIcon className={classes.tooltip} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</InputLabel>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.inputTitles}>
|
||||
<div>Name</div>
|
||||
<div>Servers</div>
|
||||
</div>
|
||||
<div className={classes.inputContainer}>
|
||||
<div className={classes.inputGrid}>{inputs}</div>
|
||||
</div>
|
||||
<div ref={bottomList} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(PoolsMultiSelector);
|
||||
@@ -38,7 +38,7 @@ export interface IAddPoolRequest {
|
||||
|
||||
export interface IVolumeConfiguration {
|
||||
size: number;
|
||||
storage_class: string;
|
||||
storage_class_name: string;
|
||||
labels: { [key: string]: any } | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import get from "lodash/get";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
|
||||
@@ -10,6 +11,8 @@ import api from "../../../../common/api";
|
||||
import { IAddPoolRequest, ITenant } from "../ListTenants/types";
|
||||
import { IAffinityModel } from "../../../../common/types";
|
||||
import { getHardcodedAffinity } from "./utils";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import { IQuotaElement, IQuotas, Opts } from "../ListTenants/utils";
|
||||
|
||||
interface IAddPoolProps {
|
||||
tenant: ITenant;
|
||||
@@ -67,10 +70,42 @@ const AddPoolModal = ({
|
||||
const [numberOfNodes, setNumberOfNodes] = useState<number>(0);
|
||||
const [volumeSize, setVolumeSize] = useState<number>(0);
|
||||
const [volumesPerServer, setVolumesPerSever] = useState<number>(0);
|
||||
const [selectedStorageClass, setSelectedStorageClass] = useState<string>("");
|
||||
const [storageClasses, setStorageClasses] = useState<Opts[]>([]);
|
||||
|
||||
const instanceCapacity: number = volumeSize * 1073741824 * volumesPerServer;
|
||||
const totalCapacity: number = instanceCapacity * numberOfNodes;
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedStorageClass("");
|
||||
|
||||
setStorageClasses([]);
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/namespaces/${tenant.namespace}/resourcequotas/${tenant.namespace}-storagequota`
|
||||
)
|
||||
.then((res: IQuotas) => {
|
||||
const elements: IQuotaElement[] = get(res, "elements", []);
|
||||
|
||||
const newStorage = elements.map((storageClass: any) => {
|
||||
const name = get(storageClass, "name", "").split(
|
||||
".storageclass.storage.k8s.io/requests.storage"
|
||||
)[0];
|
||||
|
||||
return { label: name, value: name };
|
||||
});
|
||||
|
||||
setStorageClasses(newStorage);
|
||||
if (newStorage.length > 0) {
|
||||
setSelectedStorageClass(newStorage[0].value);
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [tenant]);
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
onClose={() => onClosePoolAndReload(false)}
|
||||
@@ -97,7 +132,7 @@ const AddPoolModal = ({
|
||||
volumes_per_server: volumesPerServer,
|
||||
volume_configuration: {
|
||||
size: volumeSize * 1073741824,
|
||||
storage_class: "",
|
||||
storage_class_name: selectedStorageClass,
|
||||
labels: null,
|
||||
},
|
||||
affinity: hardCodedAffinity,
|
||||
@@ -155,6 +190,19 @@ const AddPoolModal = ({
|
||||
value={volumesPerServer.toString(10)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SelectWrapper
|
||||
id="storage_class"
|
||||
name="storage_class"
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
setSelectedStorageClass(e.target.value as string);
|
||||
}}
|
||||
label="Storage Class"
|
||||
value={selectedStorageClass}
|
||||
options={storageClasses}
|
||||
disabled={storageClasses.length < 1}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} className={classes.bottomContainer}>
|
||||
<div className={classes.factorElements}>
|
||||
|
||||
Reference in New Issue
Block a user