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:
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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
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.c67bfa71.chunk.js.map
Normal file
1
portal-ui/build/static/js/main.c67bfa71.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -477,6 +477,7 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
|
||||
<img
|
||||
className={classes.verifiedIcon}
|
||||
src={"/verified.svg"}
|
||||
alt="verified"
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -606,6 +606,7 @@ const TenantDetails = ({
|
||||
<img
|
||||
className={classes.verifiedIcon}
|
||||
src={"/verified.svg"}
|
||||
alt="verified"
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user