diff --git a/portals/admin/package-lock.json b/portals/admin/package-lock.json
new file mode 100644
index 00000000000..10281dff4b9
--- /dev/null
+++ b/portals/admin/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "admin",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}
diff --git a/portals/admin/src/main/webapp/package-lock.json b/portals/admin/src/main/webapp/package-lock.json
index a2e6d97c918..cc0916d574b 100644
--- a/portals/admin/src/main/webapp/package-lock.json
+++ b/portals/admin/src/main/webapp/package-lock.json
@@ -13,6 +13,7 @@
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@hapi/joi": "^15.1.1",
+ "@material-ui/icons": "^4.11.3",
"@monaco-editor/react": "^4.6.0",
"@mui/icons-material": "^5.15.4",
"@mui/lab": "^5.0.0-alpha.160",
@@ -30,6 +31,8 @@
"lodash.clonedeep": "^4.5.0",
"lodash.isempty": "^4.4.0",
"lodash.sortby": "^4.7.0",
+ "material-ui-chip-input": "^1.1.0",
+ "material-ui-color": "^1.2.0",
"moment": "^2.29.4",
"mui-chips-input": "^2.1.3",
"mui-datatables": "^4.3.0",
@@ -3470,6 +3473,206 @@
"node": ">=10"
}
},
+ "node_modules/@material-ui/core": {
+ "version": "4.12.4",
+ "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.4.tgz",
+ "integrity": "sha512-tr7xekNlM9LjA6pagJmL8QCgZXaubWUwkJnoYcMKd4gw/t4XiyvnTkjdGrUVicyB2BsdaAv1tvow45bPM4sSwQ==",
+ "deprecated": "Material UI v4 doesn't receive active development since September 2021. See the guide https://mui.com/material-ui/migration/migration-v4/ to upgrade to v5.",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.4.4",
+ "@material-ui/styles": "^4.11.5",
+ "@material-ui/system": "^4.12.2",
+ "@material-ui/types": "5.1.0",
+ "@material-ui/utils": "^4.11.3",
+ "@types/react-transition-group": "^4.2.0",
+ "clsx": "^1.0.4",
+ "hoist-non-react-statics": "^3.3.2",
+ "popper.js": "1.16.1-lts",
+ "prop-types": "^15.7.2",
+ "react-is": "^16.8.0 || ^17.0.0",
+ "react-transition-group": "^4.4.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/material-ui"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.6 || ^17.0.0",
+ "react": "^16.8.0 || ^17.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@material-ui/core/node_modules/@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@material-ui/core/node_modules/@material-ui/styles": {
+ "version": "4.11.5",
+ "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz",
+ "integrity": "sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA==",
+ "deprecated": "Material UI v4 doesn't receive active development since September 2021. See the guide https://mui.com/material-ui/migration/migration-v4/ to upgrade to v5.",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.4.4",
+ "@emotion/hash": "^0.8.0",
+ "@material-ui/types": "5.1.0",
+ "@material-ui/utils": "^4.11.3",
+ "clsx": "^1.0.4",
+ "csstype": "^2.5.2",
+ "hoist-non-react-statics": "^3.3.2",
+ "jss": "^10.5.1",
+ "jss-plugin-camel-case": "^10.5.1",
+ "jss-plugin-default-unit": "^10.5.1",
+ "jss-plugin-global": "^10.5.1",
+ "jss-plugin-nested": "^10.5.1",
+ "jss-plugin-props-sort": "^10.5.1",
+ "jss-plugin-rule-value-function": "^10.5.1",
+ "jss-plugin-vendor-prefixer": "^10.5.1",
+ "prop-types": "^15.7.2"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/material-ui"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.6 || ^17.0.0",
+ "react": "^16.8.0 || ^17.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@material-ui/core/node_modules/@material-ui/system": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.2.tgz",
+ "integrity": "sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.4.4",
+ "@material-ui/utils": "^4.11.3",
+ "csstype": "^2.5.2",
+ "prop-types": "^15.7.2"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/material-ui"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.6 || ^17.0.0",
+ "react": "^16.8.0 || ^17.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@material-ui/core/node_modules/@material-ui/utils": {
+ "version": "4.11.3",
+ "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.3.tgz",
+ "integrity": "sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.4.4",
+ "prop-types": "^15.7.2",
+ "react-is": "^16.8.0 || ^17.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0"
+ }
+ },
+ "node_modules/@material-ui/core/node_modules/clsx": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+ "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@material-ui/core/node_modules/csstype": {
+ "version": "2.6.21",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
+ "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@material-ui/core/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@material-ui/icons": {
+ "version": "4.11.3",
+ "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz",
+ "integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.4.4"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "peerDependencies": {
+ "@material-ui/core": "^4.0.0",
+ "@types/react": "^16.8.6 || ^17.0.0",
+ "react": "^16.8.0 || ^17.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@material-ui/types": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz",
+ "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==",
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "@types/react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@monaco-editor/loader": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.4.0.tgz",
@@ -7849,6 +8052,17 @@
"url": "https://github.com/sponsors/fb55"
}
},
+ "node_modules/css-vendor": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
+ "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.8.3",
+ "is-in-browser": "^1.0.2"
+ }
+ },
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
@@ -10745,6 +10959,13 @@
"integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==",
"license": "ISC"
},
+ "node_modules/hyphenate-style-name": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
+ "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
+ "license": "BSD-3-Clause",
+ "peer": true
+ },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -11168,6 +11389,13 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-in-browser": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
+ "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/is-inside-container": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
@@ -13405,6 +13633,104 @@
"node": ">=12.0.0"
}
},
+ "node_modules/jss": {
+ "version": "10.10.0",
+ "resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz",
+ "integrity": "sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.3.1",
+ "csstype": "^3.0.2",
+ "is-in-browser": "^1.1.3",
+ "tiny-warning": "^1.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/jss"
+ }
+ },
+ "node_modules/jss-plugin-camel-case": {
+ "version": "10.10.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz",
+ "integrity": "sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.3.1",
+ "hyphenate-style-name": "^1.0.3",
+ "jss": "10.10.0"
+ }
+ },
+ "node_modules/jss-plugin-default-unit": {
+ "version": "10.10.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz",
+ "integrity": "sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "10.10.0"
+ }
+ },
+ "node_modules/jss-plugin-global": {
+ "version": "10.10.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz",
+ "integrity": "sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "10.10.0"
+ }
+ },
+ "node_modules/jss-plugin-nested": {
+ "version": "10.10.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz",
+ "integrity": "sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "10.10.0",
+ "tiny-warning": "^1.0.2"
+ }
+ },
+ "node_modules/jss-plugin-props-sort": {
+ "version": "10.10.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz",
+ "integrity": "sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "10.10.0"
+ }
+ },
+ "node_modules/jss-plugin-rule-value-function": {
+ "version": "10.10.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz",
+ "integrity": "sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "10.10.0",
+ "tiny-warning": "^1.0.2"
+ }
+ },
+ "node_modules/jss-plugin-vendor-prefixer": {
+ "version": "10.10.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz",
+ "integrity": "sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.3.1",
+ "css-vendor": "^2.0.8",
+ "jss": "10.10.0"
+ }
+ },
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -13758,6 +14084,66 @@
"tmpl": "1.0.5"
}
},
+ "node_modules/material-ui-chip-input": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/material-ui-chip-input/-/material-ui-chip-input-1.1.0.tgz",
+ "integrity": "sha512-95JsxYtzBFUyvzLC0Ae8qo1h8heu9wcSgnGjF/Sy9QQ9pL/ufLVUyjS8uFULW4kEyeNZbZurux3KZKC3FLnoqg==",
+ "license": "MIT",
+ "dependencies": {
+ "classnames": "^2.2.5",
+ "prop-types": "^15.6.1"
+ },
+ "peerDependencies": {
+ "@material-ui/core": "^1.0.0 || ^3.1.0",
+ "react": "^16.3.0",
+ "react-dom": "^16.3.0"
+ }
+ },
+ "node_modules/material-ui-color": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/material-ui-color/-/material-ui-color-1.2.0.tgz",
+ "integrity": "sha512-bD2Rww+hakJxD2/19uxc280Vh292DnRStLke2LDFavVtGd5fzOz09zIrHO3ZHlMkJFsvwx6IwiB4/932ftv0sQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@material-ui/core": "^4.9.5",
+ "material-ui-popup-state": "^1.5.3",
+ "prop-types": "^15.7.2",
+ "react": "^16.0.0 || ^17.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0"
+ }
+ },
+ "node_modules/material-ui-popup-state": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/material-ui-popup-state/-/material-ui-popup-state-1.9.3.tgz",
+ "integrity": "sha512-+Ete5Tzw5rXlYfmqptOS8kBUH8vnK5OJsd6IQ7SHtLjU0PsvsmM73M/k8ot0xkX4RmPGuNRsFbK3mlCe/ClQuw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@material-ui/types": "^6.0.1",
+ "classnames": "^2.2.6",
+ "prop-types": "^15.7.2"
+ },
+ "peerDependencies": {
+ "@material-ui/core": "^4.0.0 || ^5.0.0-beta",
+ "react": "^15.0.0 || ^16.0.0 || ^17.0.0"
+ }
+ },
+ "node_modules/material-ui-popup-state/node_modules/@material-ui/types": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-6.0.2.tgz",
+ "integrity": "sha512-/XUca4wUb9pWimLLdM1PE8KS8rTbDEGohSGkGtk3WST7lm23m+8RYv9uOmrvOg/VSsl4bMiOv4t2/LCb+RLbTg==",
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "@types/react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -15185,6 +15571,13 @@
"node": ">=4"
}
},
+ "node_modules/popper.js": {
+ "version": "1.16.1-lts",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
+ "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/possible-typed-array-names": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
diff --git a/portals/admin/src/main/webapp/package.json b/portals/admin/src/main/webapp/package.json
index 66aa97d7c84..7b225c35fce 100644
--- a/portals/admin/src/main/webapp/package.json
+++ b/portals/admin/src/main/webapp/package.json
@@ -32,6 +32,7 @@
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@hapi/joi": "^15.1.1",
+ "@material-ui/icons": "^4.11.3",
"@monaco-editor/react": "^4.6.0",
"@mui/icons-material": "^5.15.4",
"@mui/lab": "^5.0.0-alpha.160",
@@ -49,6 +50,8 @@
"lodash.clonedeep": "^4.5.0",
"lodash.isempty": "^4.4.0",
"lodash.sortby": "^4.7.0",
+ "material-ui-chip-input": "^1.1.0",
+ "material-ui-color": "^1.2.0",
"moment": "^2.29.4",
"mui-chips-input": "^2.1.3",
"mui-datatables": "^4.3.0",
diff --git a/portals/admin/src/main/webapp/site/public/locales/en.json b/portals/admin/src/main/webapp/site/public/locales/en.json
index 02c3505abec..8bd7b9c39e1 100644
--- a/portals/admin/src/main/webapp/site/public/locales/en.json
+++ b/portals/admin/src/main/webapp/site/public/locales/en.json
@@ -206,6 +206,7 @@
"Api.Provider": "Provider",
"Api.Version": "Version",
"Apis.Details.Scopes.CreateScope.roles.help": "Enter a valid role and press `Enter`.",
+ "Apis.Details.Scopes.Roles.Invalid": "Invalid Role(s) Found",
"Apis.Details.Scopes.permission.status.allow": "Allow",
"Apis.Details.Scopes.permission.status.deny": "Deny",
"Apis.Details.Scopes.permission.status.none": "None",
@@ -334,6 +335,8 @@
"Dashboard.tasksWorkflow.noTasks.card.description": "Manage workflow tasks, increase productivity and enhance competitiveness by enabling developers to easily deploy business processes and models.",
"Dashboard.tasksWorkflow.noTasks.card.title": "All the pending tasks completed",
"Form.Dialog.Base.cancel.btn": "Cancel",
+ "Gateway.AddEditGWEnvironment.permission.allowed": "Use of this Gateway Environment is \"Allowed\" for above roles.",
+ "Gateway.AddEditGWEnvironment.permission.denied": "Use of this Gateway Environment is \"Denied\" for above roles.",
"GatewayEnvironments.AddEditGWEnvironment.form.description.help": "Description of the Gateway Environment",
"GatewayEnvironments.AddEditGWEnvironment.form.description.label": "Description",
"GatewayEnvironments.AddEditGWEnvironment.form.displayName": "Display Name",
@@ -350,6 +353,9 @@
"GatewayEnvironments.AddEditGWEnvironment.form.info.edit.successful": "Gateway Environment edited successfully",
"GatewayEnvironments.AddEditGWEnvironment.form.name": "Name",
"GatewayEnvironments.AddEditGWEnvironment.form.name.help": "Name of the Gateway Environment",
+ "GatewayEnvironments.AddEditGWEnvironment.form.permission.type": "Gateway Environment Permission",
+ "GatewayEnvironments.AddEditGWEnvironment.form.permissions": "Permissions",
+ "GatewayEnvironments.AddEditGWEnvironment.form.permissions.add.description": "Permissions for the Gateway Environment",
"GatewayEnvironments.AddEditGWEnvironment.form.save.button.label": "Save",
"GatewayEnvironments.AddEditGWEnvironment.form.type.helper.text": "Supported Key Type of the Gateway Environment",
"GatewayEnvironments.AddEditGWEnvironment.form.type.hybrid.option": "Hybrid",
diff --git a/portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/AddEditGWEnvironment.jsx b/portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/AddEditGWEnvironment.jsx
index 7b7213ec568..bfa8346bdf9 100644
--- a/portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/AddEditGWEnvironment.jsx
+++ b/portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/AddEditGWEnvironment.jsx
@@ -19,8 +19,10 @@
import React, { useEffect, useReducer, useState } from 'react';
import { styled } from '@mui/material/styles';
import API from 'AppData/api';
+import base64url from 'base64url';
import PropTypes from 'prop-types';
import { useAppContext } from 'AppComponents/Shared/AppContext';
+import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';
import { FormattedMessage, useIntl } from 'react-intl';
import Select from '@mui/material//Select';
@@ -35,6 +37,10 @@ import Typography from '@mui/material/Typography';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
import FormLabel from '@mui/material/FormLabel';
+import { MuiChipsInput } from 'mui-chips-input';
+import Error from '@material-ui/icons/Error';
+import InputAdornment from '@material-ui/core/InputAdornment';
+import { red } from '@material-ui/core/colors/';
import AddEditVhost from 'AppComponents/GatewayEnvironments/AddEditVhost';
const styles = {
@@ -77,6 +83,15 @@ const StyledLabel = styled('span')({ ...styles.label, ...styles.newLabel });
const StyledSpan = styled('span')(({ theme }) => ({ color: theme.palette.error.dark }));
+const useStyles = styled(() => ({
+ chipInputBox: {
+ marginRight: '30px',
+ marginLeft: '10px',
+ marginTop: '10px',
+ marginBottom: '10px',
+ },
+}));
+
/**
* Reducer
* @param {JSON} state State
@@ -91,10 +106,16 @@ function reducer(state, { field, value }) {
case 'gatewayType':
case 'description':
case 'type':
+ case 'roles':
case 'vhosts':
return { ...state, [field]: value };
case 'editDetails':
return value;
+ case 'permissionType':
+ return {
+ ...state,
+ permissions: { ...state.permissions, [field]: value },
+ };
default:
return state;
}
@@ -106,6 +127,7 @@ function reducer(state, { field, value }) {
* @returns {JSX}.
*/
function AddEditGWEnvironment(props) {
+ const classes = useStyles();
const intl = useIntl();
const {
updateList, dataRow, icon, triggerButtonText, title,
@@ -115,6 +137,9 @@ function AddEditGWEnvironment(props) {
host: '', httpContext: '', httpsPort: 8243, httpPort: 8280, wssPort: 8099, wsPort: 9099, isNew: true,
};
const { settings } = useAppContext();
+ const [validRoles, setValidRoles] = useState([]);
+ const [invalidRoles, setInvalidRoles] = useState([]);
+ const [roleValidity, setRoleValidity] = useState(true);
const { gatewayTypes } = settings;
const [initialState, setInitialState] = useState({
displayName: '',
@@ -122,15 +147,61 @@ function AddEditGWEnvironment(props) {
gatewayType: gatewayTypes && gatewayTypes.length > 1 ? 'Regular' : gatewayTypes[0],
type: 'hybrid',
vhosts: [defaultVhost],
+ permissions: {
+ roles: [],
+ permissionType: 'PUBLIC',
+ },
});
const [editMode, setIsEditMode] = useState(false);
const [state, dispatch] = useReducer(reducer, initialState);
const {
- name, displayName, description, vhosts, type, gatewayType,
+ name, displayName, description, vhosts, type, gatewayType, permissions,
} = state;
+ let permissionType = '';
+ if (permissions) {
+ permissionType = state.permissions.permissionType;
+ }
+ const handleRoleDeletion = (role) => {
+ if (invalidRoles.includes(role)) {
+ const invalidRolesArray = invalidRoles.filter((existingRole) => existingRole !== role);
+ setInvalidRoles(invalidRolesArray);
+ if (invalidRolesArray.length === 0) {
+ setRoleValidity(true);
+ }
+ } else {
+ setValidRoles(validRoles.filter((existingRole) => existingRole !== role));
+ }
+ };
+
+ const restApi = new API();
+ const handleRoleAddition = (role) => {
+ const promise = restApi.validateSystemRole(base64url.encode(role));
+ promise
+ .then(() => {
+ setValidRoles(validRoles.concat(role));
+ if (invalidRoles.length === 0) {
+ setRoleValidity(true);
+ } else {
+ setRoleValidity(false);
+ }
+ })
+ .catch((error) => {
+ if (error.status === 404) {
+ setInvalidRoles(invalidRoles.concat(role));
+ setRoleValidity(false);
+ } else {
+ Alert.error('Error when validating role: ' + role);
+ console.error('Error when validating role ' + error);
+ }
+ });
+ };
const onChange = (e) => {
+ if (e.target.name === 'GatewayPermissionRestrict') {
+ permissionType = e.target.value;
+ dispatch({ field: 'permissionType', value: permissionType });
+ }
dispatch({ field: e.target.name, value: e.target.value });
};
@@ -147,6 +218,10 @@ function AddEditGWEnvironment(props) {
gatewayType: '',
type: 'hybrid',
vhosts: [defaultVhost],
+ permissions: {
+ roles: [],
+ permissionType: 'PUBLIC',
+ },
});
}, []);
@@ -322,18 +397,18 @@ function AddEditGWEnvironment(props) {
});
});
}
-
- const restApi = new API();
+ permissions.permissionType = state.permissions.permissionType;
+ permissions.roles = validRoles;
let promiseAPICall;
if (dataRow) {
// assign the update promise to the promiseAPICall
promiseAPICall = restApi.updateGatewayEnvironment(
- dataRow.id, name.trim(), displayName, type, description, gatewayType, vhostDto,
+ dataRow.id, name.trim(), displayName, type, description, gatewayType, vhostDto, permissions,
);
} else {
// assign the create promise to the promiseAPICall
promiseAPICall = restApi.addGatewayEnvironment(name.trim(), displayName, type, description,
- gatewayType, vhostDto);
+ gatewayType, vhostDto, permissions);
}
return promiseAPICall.then(() => {
@@ -372,6 +447,7 @@ function AddEditGWEnvironment(props) {
type: originalType,
vhosts: originalVhosts,
gatewayType: originalGatewayType,
+ permissions: originalPermissions,
} = dataRow;
setIsEditMode(true);
dispatch({
@@ -383,6 +459,7 @@ function AddEditGWEnvironment(props) {
gatewayType: originalGatewayType,
description: originalDescription,
vhosts: originalVhosts,
+ permissions: originalPermissions,
},
});
}
@@ -614,6 +691,148 @@ function AddEditGWEnvironment(props) {
/>
+ {/* Permissions */}
+
+
+
+
+
+ )}
+ onChange={onChange}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ (permissionType === 'ALLOW' || permissionType === 'DENY')
+ && (
+
+
+
+
+ ),
+ }}
+ onAddChip={handleRoleAddition}
+ renderChip={(ChipComponent, key, ChipProps) => (
+ handleRoleDeletion(ChipProps.label)}
+ data-testid={ChipProps.label}
+ style={{
+ backgroundColor:
+ invalidRoles.includes(ChipProps.label)
+ ? red[300] : null,
+ margin: '8px 8px 8px 0',
+ float: 'left',
+ }}
+ />
+ )}
+ error={!roleValidity}
+ helperText={
+ !roleValidity ? (
+
+ ) : [
+ (permissionType === 'ALLOW'
+ ? (
+
+ )
+ : (
+
+ )
+ ),
+ ' ',
+ ,
+ ]
+ }
+ />
+
+ )
+ }
+
+
{
+ return (
+
+ );
+ },
+ },
+ },
];
} else {
columProps = [
@@ -174,6 +195,26 @@ export default function ListGWEnviornments() {
},
},
},
+ {
+ name: 'permissions',
+ label: intl.formatMessage({
+ id: 'AdminPages.Gateways.table.header.permission',
+ defaultMessage: 'Permission',
+ }),
+ options: {
+ sort: false,
+ customBodyRender: (permissions) => {
+ return (
+
+ );
+ },
+ },
+ },
];
}
const addButtonProps = {
diff --git a/portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/Permission.jsx b/portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/Permission.jsx
new file mode 100644
index 00000000000..ad29f151f3b
--- /dev/null
+++ b/portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/Permission.jsx
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2025, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
+ *
+ * WSO2 Inc. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import Popper from '@mui/material/Popper';
+
+/**
+ * Renders a Permission list
+ * @class Environments
+ * @extends {React.Component}
+ */
+export default function SimplePopper(props) {
+ const {
+ type,
+ roles,
+ } = props;
+ const [anchorEl, setAnchorEl] = React.useState(null);
+
+ let msg = roles.toString();
+ if (type === 'PUBLIC') {
+ msg = 'No Visibility Restrictions!';
+ }
+
+ const handleClick = (event) => {
+ setAnchorEl(anchorEl ? null : event.currentTarget);
+ };
+
+ const open = Boolean(anchorEl);
+ const id = open ? 'simple-popper' : undefined;
+
+ return (
+
+
+ {type}
+
+
+
+ {msg}
+
+
+
+ );
+}
diff --git a/portals/admin/src/main/webapp/source/src/app/data/api.js b/portals/admin/src/main/webapp/source/src/app/data/api.js
index e7c2d04c0bf..2720021eafc 100644
--- a/portals/admin/src/main/webapp/source/src/app/data/api.js
+++ b/portals/admin/src/main/webapp/source/src/app/data/api.js
@@ -494,9 +494,9 @@ class API extends Resource {
/**
* Add a Gateway Environment
*/
- addGatewayEnvironment(name, displayName, type, description, gatewayType, vhosts, provider="wso2", callback = null) {
+ addGatewayEnvironment(name, displayName, type, description, gatewayType, vhosts, permissions, provider="wso2", callback = null) {
return this.client.then((client) => {
- const data = { name, displayName, type, description, gatewayType, vhosts, provider };
+ const data = { name, displayName, type, description, gatewayType, vhosts, permissions, provider };
const payload = {
'Content-Type': 'application/json',
};
@@ -511,9 +511,9 @@ class API extends Resource {
/**
* Update a Gateway Environment
*/
- updateGatewayEnvironment(id, name, displayName, type, description, gatewayType, vhosts, callback = null) {
+ updateGatewayEnvironment(id, name, displayName, type, description, gatewayType, vhosts, permissions, callback = null) {
return this.client.then((client) => {
- const data = { name, displayName, type, description, gatewayType, vhosts };
+ const data = { name, displayName, type, description, gatewayType, vhosts, permissions };
return client.apis['Environments'].put_environments__environmentId_(
{ environmentId: id },
{ requestBody: data },
diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/Permission.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/Permission.jsx
index 6d9f68a8637..ad29f151f3b 100644
--- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/Permission.jsx
+++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/Permission.jsx
@@ -35,7 +35,7 @@ export default function SimplePopper(props) {
let msg = roles.toString();
if (type === 'PUBLIC') {
- msg = 'No Visibility Restrictions!'
+ msg = 'No Visibility Restrictions!';
}
const handleClick = (event) => {
@@ -51,10 +51,19 @@ export default function SimplePopper(props) {
{type}
-
+
{msg}
);
-}
\ No newline at end of file
+}