Added validation for single tenant in namespace (#701)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2021-04-23 15:38:02 -05:00
committed by GitHub
parent 3b55d63211
commit 9e35db0642
14 changed files with 100 additions and 41 deletions

View File

@@ -1,25 +1,25 @@
{
"files": {
"main.css": "/static/css/main.a19f3d53.chunk.css",
"main.js": "/static/js/main.50aea14a.chunk.js",
"main.js.map": "/static/js/main.50aea14a.chunk.js.map",
"main.js": "/static/js/main.c67bfa71.chunk.js",
"main.js.map": "/static/js/main.c67bfa71.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",
"static/js/2.88db5eb4.chunk.js": "/static/js/2.88db5eb4.chunk.js",
"static/js/2.88db5eb4.chunk.js.map": "/static/js/2.88db5eb4.chunk.js.map",
"static/js/2.dbfc7a4d.chunk.js": "/static/js/2.dbfc7a4d.chunk.js",
"static/js/2.dbfc7a4d.chunk.js.map": "/static/js/2.dbfc7a4d.chunk.js.map",
"index.html": "/index.html",
"static/css/2.f324abd6.chunk.css.map": "/static/css/2.f324abd6.chunk.css.map",
"static/css/main.a19f3d53.chunk.css.map": "/static/css/main.a19f3d53.chunk.css.map",
"static/js/2.88db5eb4.chunk.js.LICENSE.txt": "/static/js/2.88db5eb4.chunk.js.LICENSE.txt",
"static/js/2.dbfc7a4d.chunk.js.LICENSE.txt": "/static/js/2.dbfc7a4d.chunk.js.LICENSE.txt",
"static/media/minio_console_logo.0837460e.svg": "/static/media/minio_console_logo.0837460e.svg",
"static/media/minio_operator_logo.1312b7c9.svg": "/static/media/minio_operator_logo.1312b7c9.svg"
},
"entrypoints": [
"static/js/runtime-main.f48e99e5.js",
"static/css/2.f324abd6.chunk.css",
"static/js/2.88db5eb4.chunk.js",
"static/js/2.dbfc7a4d.chunk.js",
"static/css/main.a19f3d53.chunk.css",
"static/js/main.50aea14a.chunk.js"
"static/js/main.c67bfa71.chunk.js"
]
}

View File

@@ -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.88db5eb4.chunk.js"></script><script src="/static/js/main.50aea14a.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.dbfc7a4d.chunk.js"></script><script src="/static/js/main.c67bfa71.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

View File

@@ -477,6 +477,7 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
<img
className={classes.verifiedIcon}
src={"/verified.svg"}
alt="verified"
/>
</Grid>
</React.Fragment>

View File

@@ -135,7 +135,7 @@ const BrowseBuckets = ({
})
.then((res: HasPermissionResponse) => {
const canCreate = res.permissions
.filter((s) => s.id == "createBucket")
.filter((s) => s.id === "createBucket")
.pop();
if (canCreate && canCreate.can) {
setCanCreateBucket(true);

View File

@@ -32,7 +32,6 @@ import {
import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper";
import { k8sfactorForDropdown } from "../../../../../common/utils";
interface IConfigureProps {
updateAddField: typeof updateAddField;

View File

@@ -58,6 +58,7 @@ const styles = (theme: Theme) =>
interface INameTenantScreen {
classes: any;
storageClasses: Opts[];
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
setAdvancedMode: typeof setAdvancedMode;
updateAddField: typeof updateAddField;
isPageValid: typeof isPageValid;
@@ -81,8 +82,13 @@ const NameTenant = ({
setStorageClassesList,
setLimitSize,
isPageValid,
setModalErrorSnackMessage,
}: INameTenantScreen) => {
const [validationErrors, setValidationErrors] = useState<any>({});
const [emptyNamespace, setEmptyNamespace] = useState<boolean>(true);
const [loadingNamespaceInfo, setLoadingNamespaceInfo] = useState<boolean>(
false
);
// Common
const updateField = useCallback(
@@ -97,32 +103,61 @@ const NameTenant = ({
updateField("selectedStorageClass", "");
setStorageClassesList([]);
// Empty tenantValidation
api
.invoke(
"GET",
`/api/v1/namespaces/${namespace}/resourcequotas/${namespace}-storagequota`
)
.then((res: IQuotas) => {
const elements: IQuotaElement[] = get(res, "elements", []);
setLimitSize(getLimitSizes(res));
.invoke("GET", `/api/v1/namespaces/${namespace}/tenants`)
.then((res: any[]) => {
const tenantsList = get(res, "tenants", []);
const newStorage = elements.map((storageClass: any) => {
const name = get(storageClass, "name", "").split(
".storageclass.storage.k8s.io/requests.storage"
)[0];
return { label: name, value: name };
});
setStorageClassesList(newStorage);
if (newStorage.length > 0) {
updateField("selectedStorageClass", newStorage[0].value);
if (tenantsList && tenantsList.length > 0) {
setEmptyNamespace(false);
setLoadingNamespaceInfo(false);
return;
}
setEmptyNamespace(true);
// Storagequotas retrieval
api
.invoke(
"GET",
`/api/v1/namespaces/${namespace}/resourcequotas/${namespace}-storagequota`
)
.then((res: IQuotas) => {
const elements: IQuotaElement[] = get(res, "elements", []);
setLimitSize(getLimitSizes(res));
const newStorage = elements.map((storageClass: any) => {
const name = get(storageClass, "name", "").split(
".storageclass.storage.k8s.io/requests.storage"
)[0];
return { label: name, value: name };
});
setStorageClassesList(newStorage);
if (newStorage.length > 0) {
updateField("selectedStorageClass", newStorage[0].value);
}
setLoadingNamespaceInfo(false);
})
.catch((err: any) => {
console.error(err);
});
})
.catch((err: any) => {
console.error(err);
setModalErrorSnackMessage(
"Error validating if namespace already has tenants"
);
});
}, [namespace, setLimitSize, setStorageClassesList, updateField]);
}, [
namespace,
setLimitSize,
setModalErrorSnackMessage,
setStorageClassesList,
updateField,
]);
const debounceNamespace = useMemo(
() => debounce(getNamespaceInformation, 500),
@@ -132,6 +167,7 @@ const NameTenant = ({
useEffect(() => {
if (namespace !== "") {
debounceNamespace();
setLoadingNamespaceInfo(true);
// Cancel previous debounce calls during useEffect cleanup.
return debounceNamespace.cancel;
@@ -140,6 +176,21 @@ const NameTenant = ({
// Validation
useEffect(() => {
let customNamespaceError = false;
let errorMessage = "";
if (!emptyNamespace && !loadingNamespaceInfo) {
customNamespaceError = true;
errorMessage = "You can only create one tenant per namespace";
} else if (
storageClasses.length < 1 &&
emptyNamespace &&
!loadingNamespaceInfo
) {
customNamespaceError = true;
errorMessage = "Please enter a valid namespace";
}
const commonValidation = commonFormValidation([
{
fieldKey: "tenant-name",
@@ -153,8 +204,8 @@ const NameTenant = ({
fieldKey: "namespace",
required: true,
value: namespace,
customValidation: storageClasses.length < 1,
customValidationMessage: "Please enter a valid namespace",
customValidation: customNamespaceError,
customValidationMessage: errorMessage,
},
]);
@@ -166,7 +217,14 @@ const NameTenant = ({
isPageValid("nameTenant", isValid);
setValidationErrors(commonValidation);
}, [storageClasses, namespace, tenantName, isPageValid]);
}, [
storageClasses,
namespace,
tenantName,
isPageValid,
emptyNamespace,
loadingNamespaceInfo,
]);
const frmValidationCleanup = (fieldName: string) => {
setValidationErrors(clearValidationError(validationErrors, fieldName));

View File

@@ -606,6 +606,7 @@ const TenantDetails = ({
<img
className={classes.verifiedIcon}
src={"/verified.svg"}
alt="verified"
/>
</Grid>
</React.Fragment>

View File

@@ -29,7 +29,7 @@ const LoginCallback: FC<RouteComponentProps> = ({ location }) => {
const errorDescription = (location.search.match(
/error_description=([^&]+)/
) || [])[1];
if (error != undefined || errorDescription != undefined) {
if (error !== undefined || errorDescription !== undefined) {
setError(error);
setErrorDescription(errorDescription);
} else {
@@ -49,7 +49,7 @@ const LoginCallback: FC<RouteComponentProps> = ({ location }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}
}, []);
return error != "" || errorDescription != "" ? (
return error !== "" || errorDescription !== "" ? (
<div>
<h2>IDP Error:</h2>
<p>{error}</p>