diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..7dc10ba260fe116876c0ea0da0aaf85ab3b37cfd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,22 @@ +# Ignore node_modules directory +node_modules/ + +# Ignore npm debug log +npm-debug.log + +# Ignore build artifacts +dist/ +build/ +out/ + +# Ignore development files +*.log +*.pid +*.lock + +# Ignore version control files +.git/ +.gitignore + +# Ignore certificates +ssl/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index f5d62d3d9ebbe763252e7507468f110736b1f0e1..de4780544c28261524c90e347e9b6d78f9cfdee0 100755 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ yarn-error.log* # Misc .DS_Store + +ssl/* +public/env-config.js diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..0591f3ce9f5c191ed48c2c633c77ce13fbe86f47 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,15 @@ +stages: + - build + +build: + stage: build + image: docker:latest + services: + - docker:dind + script: + - apk add jq + - version=$(jq -r .version package.json) + - docker build -t registry.forgemia.inra.fr/in-sylva-development/in-sylva.portal:$version . + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker push registry.forgemia.inra.fr/in-sylva-development/in-sylva.portal:$version + when: manual diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4e5eee977f43af791d82209090737ce5dcfe3d43 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM node:18.20.5 as portal + +WORKDIR /app/ +COPY package.json . +RUN yarn install +COPY . . +RUN yarn build + +FROM nginx:1.24-bullseye +COPY --from=portal /app/build /usr/share/nginx/html + +RUN rm /etc/nginx/conf.d/default.conf +COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf +COPY nginx/gzip.conf /etc/nginx/conf.d/gzip.conf + +WORKDIR /usr/share/nginx/html +RUN chown -R :www-data /usr/share/nginx/html + +COPY ./env.sh . +RUN chmod +x env.sh + +COPY .env.production .env +RUN ./env.sh -e .env -o ./ + +CMD ["nginx", "-g", "daemon off;"] diff --git a/env.sh b/env.sh new file mode 100755 index 0000000000000000000000000000000000000000..5c94083cf6ba0fb86ef903310d3b9034474cdb83 --- /dev/null +++ b/env.sh @@ -0,0 +1,121 @@ +#!/bin/bash + +usage() { + BASENAME=$(basename "$0") + echo "Usage: $BASENAME -e <env-file> -o <output-file>" + echo " -e, --env-file Path to .env file" + echo " -o, --output-file Path to output '.env-config.js' file" + exit 1 +} + +while getopts "e:o:" opt; do + case $opt in + e) + ENV_FILE=$OPTARG + ;; + o) + OUT_DIRECTORY=$OPTARG + ;; + \?) + # Invalid option + echo "Invalid option: -$OPTARG" + usage + exit 1 + ;; + :) + echo "Option -$OPTARG requires an argument." + usage + exit 1 + ;; + esac +done + +# Check if required options are provided +if [ -z "$OUT_DIRECTORY" ]; then + echo "Error: -o (output directory) is required." + echo "" + usage + exit 1 +fi + +if [ -z "$ENV_FILE" ]; then + echo "Error: -e (environment file) is required." + echo "" + usage + exit 1 +fi + +ENV_FILE_PATH=$(realpath $ENV_FILE) + +# Check if the environment file exists +if [[ ! -f $ENV_FILE_PATH ]]; then + echo "Environment file does not exist" + echo "" + usage + exit 1 +fi + +# Check if the environment file is readable +if [[ ! -r $ENV_FILE_PATH ]]; then + echo "Environment file is not readable" + echo "" + usage + exit 1 +fi + +# Check if the environment file is empty +if [[ ! -s $ENV_FILE_PATH ]]; then + echo "Environment file is empty" + echo "" + usage + exit 1 +fi + +# Check if the environment file has the correct format +BAD_LINES=$(grep -v -P '^[^=\s]+=[^=\s]+$' "$ENV_FILE_PATH") +if [ -n "$BAD_LINES" ]; then + echo "Environment file has incorrect format or contains empty values:" + echo "$BAD_LINES" + echo "" + usage + exit 1 +fi + +FULL_PATH_OUTPUT_DIRECTORY=$(realpath $OUT_DIRECTORY) + +# Check if the output directory is a directory, exists, and can be written +if [[ ! -d $FULL_PATH_OUTPUT_DIRECTORY ]]; then + echo "Output directory does not exist or is not a directory" + echo "" + usage + exit 1 +fi + +if [[ ! -w $FULL_PATH_OUTPUT_DIRECTORY ]]; then + echo "Output directory is not writable" + echo "" + usage + exit 1 +fi + +OUTPUT_FILE="env-config.js" +FULL_OUTPUT_PATH="$FULL_PATH_OUTPUT_DIRECTORY/$OUTPUT_FILE" + +# Remove the output file if it exists +if [[ -f $FULL_OUTPUT_PATH ]]; then + rm $FULL_OUTPUT_PATH +fi + +# Add assignment +echo "window._env_ = {" >>$FULL_OUTPUT_PATH + +while read -r line || [[ -n "$line" ]]; do + # Split env variables by '=' + varname=$(echo $line | cut -d'=' -f1) + value=$(echo $line | cut -d'=' -f2) + + # Append configuration property to JS file + echo " $varname: \"$value\"," >>$FULL_OUTPUT_PATH +done <$ENV_FILE_PATH + +echo "}" >>$FULL_OUTPUT_PATH diff --git a/nginx/gzip.conf b/nginx/gzip.conf new file mode 100644 index 0000000000000000000000000000000000000000..2f54ae1e8e846ed57825b4597e24077efa3a6277 --- /dev/null +++ b/nginx/gzip.conf @@ -0,0 +1,44 @@ +# Enable Gzip compressed. +# gzip on; + + # Enable compression both for HTTP/1.0 and HTTP/1.1 (required for CloudFront). + gzip_http_version 1.0; + + # Compression level (1-9). + # 5 is a perfect compromise between size and cpu usage, offering about + # 75% reduction for most ascii files (almost identical to level 9). + gzip_comp_level 5; + + # Don't compress anything that's already small and unlikely to shrink much + # if at all (the default is 20 bytes, which is bad as that usually leads to + # larger files after gzipping). + gzip_min_length 256; + + # Compress data even for clients that are connecting to us via proxies, + # identified by the "Via" header (required for CloudFront). + gzip_proxied any; + + # Tell proxies to cache both the gzipped and regular version of a resource + # whenever the client's Accept-Encoding capabilities header varies; + # Avoids the issue where a non-gzip capable client (which is extremely rare + # today) would display gibberish if their proxy gave them the gzipped version. + gzip_vary on; + + # Compress all output labeled with one of the following MIME-types. + gzip_types + application/atom+xml + application/javascript + application/json + application/rss+xml + application/vnd.ms-fontobject + application/x-font-ttf + application/x-web-app-manifest+json + application/xhtml+xml + application/xml + font/opentype + image/svg+xml + image/x-icon + text/css + text/plain + text/x-component; + # text/html is always compressed by HttpGzipModule \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000000000000000000000000000000000000..e3c6319bf6d02236da9783965614cc9cac5aeb7c --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,60 @@ +include /etc/nginx/mime.types; + +server { + + listen 80; + server_name -; + + ssl_certificate /etc/ssl/fullchain.pem; + ssl_certificate_key /etc/ssl/fullchain.pem.key; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + + access_log /var/log/nginx/host.access.log; + error_log /var/log/nginx/host.error.log; + + root /usr/share/nginx/html; + index index.html index.htm; + + location /static/media/ { + try_files $uri /usr/share/nginx/html/static/media; + } + + location / { + + root /usr/share/nginx/html; + index index.html; + autoindex on; + set $fallback_file /index.html; + if ($http_accept !~ text/html) { + set $fallback_file /null; + } + if ($uri ~ /$) { + set $fallback_file /null; + } + try_files $uri $fallback_file; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + + add_header 'Access-Control-Allow-Origin' "$http_origin" always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always; + } + + error_page 500 502 503 504 /50x.html; + + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/package.json b/package.json index b3e53d7a40f36a746a325f8bb292c7116ef6564d..e3f8cc457211452a7eea10e9ee5a97056db6a78b 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,32 @@ { "name": "in-sylva.portal", - "version": "1.0.0", + "version": "1.1.1-alpha", "private": true, "homepage": ".", "dependencies": { "@elastic/datemath": "^5.0.3", "@elastic/eui": "^27.4.0", - "@in-sylva/json-view": "git+ssh://git@forgemia.inra.fr:in-sylva-development/json-view.git", - "@in-sylva/react-use-storage": "git+ssh://git@forgemia.inra.fr:in-sylva-development/react-use-storage.git", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.48", "@material-ui/styles": "^4.10.0", + "@microlink/react-json-view": "^1.23.1", "moment": "^2.27.0", "mui-datatables": "^3.4.0", + "oidc-client-ts": "^3.1.0", "papaparse": "5.3.0", "react": "^16.13.1", "react-apexcharts": "^1.3.3", "react-dom": "^16.13.1", + "react-oidc-context": "^3.2.0", "react-router-dom": "^5.2.0", "react-scripts": "^3.3.0", "recharts": "^2.0.0-beta.1", "tinycolor2": "^1.4.1" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", + "start": "./env.sh -e .env.development -o ./public && BROWSER=none NODE_OPTIONS=--openssl-legacy-provider react-scripts start", + "build": "NODE_OPTIONS=--openssl-legacy-provider react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "lint": "eslint ./src", @@ -47,10 +48,10 @@ "devDependencies": { "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", - "lint-staged": "14.0.1", "husky": "8.0.3", - "prettier": "^3.2.5", - "jscs": "^3.0.7" + "jscs": "^3.0.7", + "lint-staged": "14.0.1", + "prettier": "^3.2.5" }, "husky": { "hooks": { diff --git a/public/index.html b/public/index.html index e4c2254cb4f24c9b42eba53051c4dedf7a6021ce..8661142e9799ddf1bd16fc310f756ae2ce93cbbb 100755 --- a/public/index.html +++ b/public/index.html @@ -1,12 +1,13 @@ <!DOCTYPE html> <html lang="en"> -<head> - <meta charset="utf-8" /> - <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.png" /> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - <meta name="theme-color" content="#000000" /> - <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> - <!-- + <head> + <meta charset="utf-8" /> + <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.png" /> + <meta name="viewport" + content="width=device-width, initial-scale=1, shrink-to-fit=no" /> + <meta name="theme-color" content="#000000" /> + <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> + <!-- Notice the use of %PUBLIC_URL% in the tags above. It will be replaced with the URL of the `public` folder during the build. Only files inside the `public` folder can be referenced from the HTML. @@ -15,11 +16,11 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - <title>IN-SYLVA Portal</title> - <script src="%PUBLIC_URL%/env-config.js"></script> -</head> -<body style="font-family: 'Roboto', sans-serif;"> - <noscript>You need to enable JavaScript to run this app.</noscript> - <div id="root"></div> -</body> + <title>IN-SYLVA Portal</title> + <script src="%PUBLIC_URL%/env-config.js"></script> + </head> + <body style="font-family: 'Roboto', sans-serif;"> + <noscript>You need to enable JavaScript to run this app.</noscript> + <div id="root"></div> + </body> </html> diff --git a/src/actions/group.js b/src/actions/group.js deleted file mode 100644 index 9aaf645da550ad510eb6ebea487911b6c3c8890d..0000000000000000000000000000000000000000 --- a/src/actions/group.js +++ /dev/null @@ -1,149 +0,0 @@ -import { InSylvaGatekeeperClient } from '../context/InSylvaGatekeeperClient'; -import { getGatekeeperBaseUrl } from '../utils'; - -const igClient = new InSylvaGatekeeperClient(); -igClient.baseUrl = getGatekeeperBaseUrl(); - -export const createGroup = async (store, name, description, kcId, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const group = await request.createGroup({ name, description, kcId }); - if (group) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return group; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const getGroups = async (store, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const groups = await request.getGroups(); - if (groups) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return groups; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const createGroupUser = async (store, groupId, kcId, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const groupUser = await request.createGroupUser({ groupId, kcId }); - if (groupUser) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return groupUser; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const getGroupUsers = async (store, groupId, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const groupUser = await request.getGroupUsers({ groupId }); - if (groupUser) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return groupUser; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const createGroupPolicy = async (store, groupId, policyId, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const groupPolicy = await request.createGroupPolicy({ groupId, policyId }); - if (groupPolicy) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return groupPolicy; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const deleteGroup = async (store, groupId, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const groupUser = await request.deleteGroup({ id: groupId }); - if (groupUser) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return groupUser; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const deleteGroupPolicy = async (store, id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const groupPolicy = await request.deleteGroupPolicy({ id }); - if (groupPolicy) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return groupPolicy; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const deleteGroupUser = async (store, groupId, userId, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const groupPolicy = await request.deleteGroupUser({ groupId, userId }); - if (groupPolicy) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return groupPolicy; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; diff --git a/src/actions/index.js b/src/actions/index.js deleted file mode 100644 index 65fb44f30c5a5228c2d1c074cbc8a04d396cc6ff..0000000000000000000000000000000000000000 --- a/src/actions/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import * as user from './user'; -import * as source from './source'; -import * as policy from './policy'; -import * as group from './group'; -export { user }; -export { source }; -export { policy }; -export { group }; diff --git a/src/actions/policy.js b/src/actions/policy.js deleted file mode 100644 index 1cd9e248ec310adc8e7a736cb7d9a88426d5c176..0000000000000000000000000000000000000000 --- a/src/actions/policy.js +++ /dev/null @@ -1,262 +0,0 @@ -import { InSylvaGatekeeperClient } from '../context/InSylvaGatekeeperClient'; -import { getGatekeeperBaseUrl } from '../utils'; - -const igClient = new InSylvaGatekeeperClient(); -igClient.baseUrl = getGatekeeperBaseUrl(); - -export const createPolicy = async (store, name, kcId, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.createPolicy({ name, kcId }); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const createPolicyField = async ( - store, - policyId, - stdFieldId, - request = igClient -) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.createPolicyField({ policyId, stdFieldId }); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const updatePolicy = async ( - store, - id, - name, - sourceId, - isDefault, - request = igClient -) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.updatePolicy({ id, name, sourceId, isDefault }); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const updatePolicyField = async ( - store, - id, - policyId, - stdFieldId, - request = igClient -) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.updatePolicyField({ id, policyId, stdFieldId }); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const deletePolicyField = async (store, id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.deletePolicyField({ id }); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const deletePolicy = async (store, id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.deletePolicy({ id }); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const getPolicies = async (store, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.getPolicies({}); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const getPoliciesByUser = async (store, kc_id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.getPoliciesByUser(kc_id); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const getPoliciesWithSourcesByUser = async (store, kc_id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.getPoliciesWithSourcesByUser(kc_id); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const getAssignedPolicies = async (store, policyId, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.getAssignedPolicies({ policyId }); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const getPoliciesWithSources = async (store, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.getPoliciesWithSources(); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const getGroupDetailsByPolicy = async (store, policyId, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.getGroupDetailsByPolicy({ policyId }); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const createPolicySource = async ( - store, - policyId, - sourceId, - request = igClient -) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.createPolicySource({ policyId, sourceId }); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; diff --git a/src/actions/source.js b/src/actions/source.js deleted file mode 100644 index cf41d7da0cadb15243593eceff8a0e9ce43820b4..0000000000000000000000000000000000000000 --- a/src/actions/source.js +++ /dev/null @@ -1,336 +0,0 @@ -import { InSylvaSourceManager } from '../context/InSylvaSourceManager'; - -const smClient = new InSylvaSourceManager(); -smClient.baseUrl = process.env.REACT_APP_IN_SYLVA_SOURCE_MANAGER_PORT - ? `${process.env.REACT_APP_IN_SYLVA_SOURCE_MANAGER_HOST}:${process.env.REACT_APP_IN_SYLVA_SOURCE_MANAGER_PORT}` - : `${window._env_.REACT_APP_IN_SYLVA_SOURCE_MANAGER_HOST}`; - -export const createSource = async ( - store, - metaUrfms, - name, - description, - kc_id, - request = smClient -) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.createSource(metaUrfms, name, description, kc_id); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const sources = async (store, kc_id, request = smClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.sources(kc_id); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const indexedSources = async (store, kc_id, request = smClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.indexedSources(kc_id); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const createStdField = async ( - store, - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional, - request = smClient -) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.addStdField( - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional - ); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const createAddtlField = async ( - store, - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional, - request = smClient -) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.addAddtlField( - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional - ); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const updateStdField = async ( - store, - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional, - request = smClient -) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.updateStdField( - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional - ); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const updateSource = async (store, kc_id, source_id, request = smClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.updateSource(kc_id, source_id); - - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const publicFields = async (store, request = smClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const stdFields = await request.publicFields(); - - if (stdFields) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return stdFields; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const privateFields = async (store, request = smClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const stdFields = await request.privateFields(); - - if (stdFields) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return stdFields; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const allSources = async (store, request = smClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - const result = await request.allSource(); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const allIndexedSources = async (store, request = smClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - const result = await request.allIndexedSource(); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const truncateStdField = async (store, request = smClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - const result = await request.truncateStdField(); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const deleteSource = async (store, sourceId, kc_id, request = smClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - const result = await request.deleteSource(sourceId, kc_id); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const mergeAndSendSource = async ( - store, - kc_id, - name, - description, - sources, - request = smClient -) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - const result = await request.mergeAndSendSource({ - kc_id, - name, - description, - sources, - }); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return result; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; diff --git a/src/actions/user.js b/src/actions/user.js deleted file mode 100644 index 180a086fa62aa4ef1b185efdc8f2a5b59c8c220a..0000000000000000000000000000000000000000 --- a/src/actions/user.js +++ /dev/null @@ -1,322 +0,0 @@ -import { InSylvaGatekeeperClient } from '../context/InSylvaGatekeeperClient'; -import { getGatekeeperBaseUrl } from '../utils'; - -const igClient = new InSylvaGatekeeperClient(); -igClient.baseUrl = getGatekeeperBaseUrl(); - -export const createUser = async ( - store, - username, - email, - password, - roleId, - request = igClient -) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const nUser = await request.createUser({ username, email, password, roleId }); - if (nUser) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const findUser = async (store, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const users = await request.findUser(); - if (users) { - const status = 'SUCCESS'; - store.setState({ users, status, isLoading: false }); - return users; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const updateUser = async (store, user, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.updateUser({ - id: user.id, - firstName: user.firstName, - lastName: user.lastName, - }); - if (result) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const findOneUser = async (store, id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const user = await request.findOneUser(id); - - if (user) { - const status = 'SUCCESS'; - store.setState({ user: user, status, isLoading: false }); - return user; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const findOneUserWithGroupAndRole = async (store, id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const user = await request.findOneUserWithGroupAndRole(id); - - if (user) { - const status = 'SUCCESS'; - store.setState({ user: user, status, isLoading: false }); - return user; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const deleteUser = async (store, id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const user = await request.deleteUser({ id: id }); - if (user) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export async function fetchRequests(store, request = igClient) { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - try { - const requests = await request.getRequests(); - if (requests) { - return requests; - } - } catch (error) { - console.error(error); - } -} - -export async function fetchPendingRequests(store, request = igClient) { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - try { - const requests = await request.getPendingRequests(); - if (requests) { - return requests; - } - } catch (error) { - console.error(error); - } -} - -export const processUserRequest = async (store, requestId) => { - store.setState({ isLoading: true }); - igClient.token = sessionStorage.getItem('access_token'); - try { - return await igClient.processUserRequest(requestId); - } catch (error) { - console.error(error); - } -}; - -export const deleteUserRequest = async (store, requestId) => { - store.setState({ isLoading: true }); - igClient.token = sessionStorage.getItem('access_token'); - try { - return await igClient.deleteUserRequest(requestId); - } catch (error) { - console.error(error); - } -}; - -export const createRole = async (store, name, description, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const role = await request.createRole({ name: name, description: description }); - if (role) { - const status = 'SUCCESS'; - store.setState({ role, status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const findRole = async (store, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const roles = await request.findRole(); - if (roles) { - const status = 'SUCCESS'; - store.setState({ roles, status, isLoading: false }); - return roles; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const allocateRoles = async (store, roles, users, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.allocateRoles({ roles, users }); - if (result === true) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const allocateRolesToUser = async (store, userId, roleId, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const result = await request.allocateRolesToUser({ userId, roleId }); - if (result === true) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const allocatedRoles = async (store, kc_id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - - const allocatedRoles = await request.allocatedRoles(kc_id); - if (allocatedRoles) { - const status = 'SUCCESS'; - store.setState({ allocatedRoles, status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const setKcId = async (store, email, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - const { kc_id } = await request.kcId(email); - - store.setState({ kcId: kc_id }); - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const deleteRole = async (store, id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - const role = await request.deleteRole({ id }); - - if (role) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const getAssignedUserByRole = async (store, id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - const assignedUserByRole = await request.getAssignedUserByRole({ id }); - - if (assignedUserByRole) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return assignedUserByRole; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; - -export const usersWithGroupAndRole = async (store, id, request = igClient) => { - try { - store.setState({ isLoading: true }); - request.token = sessionStorage.getItem('access_token'); - const usersWithGroupAndRole = await request.usersWithGroupAndRole(); - - if (usersWithGroupAndRole) { - const status = 'SUCCESS'; - store.setState({ status, isLoading: false }); - return usersWithGroupAndRole; - } - } catch (error) { - const isError404 = error.response && error.response.status === 404; - const status = isError404 ? 'NOT_FOUND' : 'ERROR'; - store.setState({ status, isLoading: false }); - } -}; diff --git a/src/components/App.js b/src/components/App.js index 147678306b36251f4f2e381cb439bcfc3d47dab7..9812ec8437b9252543eeea570bf15fe7e328a13a 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -1,16 +1,18 @@ import React from 'react'; import { HashRouter, Route, Switch, Redirect } from 'react-router-dom'; import Layout from './Layout'; -import Error from '../pages/error'; +import Error404 from '../pages/error/404'; +import Error403 from '../pages/error/403'; export default function App() { return ( <HashRouter> <Switch> - <Route exact path="/" render={() => <Redirect to="/app/dashboard" />} /> - <Route exact path="/app" render={() => <Redirect to="/app/dashboard" />} /> - <Route path="/app" component={Layout} /> - <Route path="/Error" component={Error} /> + <Route exact path="/" render={() => <Redirect to="/home" />} /> + <Route path="/not-found" component={Error404} /> + <Route path="/forbidden" component={Error403} /> + <Route path="/" component={Layout} /> + <Route render={() => <Redirect to="/not-found" />} /> </Switch> </HashRouter> ); diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index a534badb7db6d16d5bf1f82a7a4888068a16dec4..6c09d239561874df24c87951ef57b26e1994b1e0 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState } from 'react'; import { AppBar, Toolbar, IconButton, Menu } from '@material-ui/core'; import { Menu as MenuIcon, @@ -13,30 +13,15 @@ import { useLayoutDispatch, toggleSidebar, } from '../../context/LayoutContext'; -import { useUserDispatch, signOut } from '../../context/UserContext'; -import store from '../../store/index'; +import { useAuth } from 'react-oidc-context'; -export default function Header(props) { +export default function Header({ email }) { const classes = useStyles(); - let [, globalActions] = store(); const layoutState = useLayoutState(); const layoutDispatch = useLayoutDispatch(); - const userDispatch = useUserDispatch(); - let [user, setUser] = useState({}); + const auth = useAuth(); let [profileMenu, setProfileMenu] = useState(null); - const loadUser = useCallback(async () => { - if (sessionStorage.getItem('userId')) { - const userId = sessionStorage.getItem('userId'); - const user = await globalActions.user.findOneUser(userId); - setUser(user); - } - }, [globalActions]); - - useEffect(() => { - loadUser(); - }, [loadUser]); - return ( <AppBar position="fixed" className={classes.appBar}> <Toolbar className={classes.toolbar}> @@ -87,14 +72,18 @@ export default function Header(props) { <div className={classes.profileMenuUser}> <div className={classes.profileMenuItem}> <Typography variant="h4" weight="medium"> - {user.username} + {email} </Typography> </div> <div className={classes.profileMenuItem}> <Typography className={classes.profileMenuLink} color="primary" - onClick={() => signOut(userDispatch, props.history)} + onClick={async () => { + await auth.signoutRedirect({ + post_logout_redirect_uri: `${process.env.REACT_APP_BASE_URL}`, + }); + }} > Sign out </Typography> diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js index f2240a914f2728234bcd5bf2dca6ee3419218ff1..239296a768ee8095a03b093ba4f0f763773657fb 100644 --- a/src/components/Layout/Layout.js +++ b/src/components/Layout/Layout.js @@ -1,9 +1,10 @@ -import React from 'react'; -import { Route, Switch, withRouter } from 'react-router-dom'; +import React, { useState, useEffect } from 'react'; +import { Route, Switch, withRouter, Redirect } from 'react-router-dom'; import classnames from 'classnames'; import useStyles from './styles'; import Header from '../Header'; import Sidebar from '../Sidebar'; +import Home from '../../pages/home'; import Dashboard from '../../pages/dashboard'; import Users from '../../pages/users'; import Roles from '../../pages/roles/Roles'; @@ -13,35 +14,112 @@ import Fields from '../../pages/fields'; import Policies from '../../pages/policies'; import Groups from '../../pages/groups/Groups'; import { useLayoutState } from '../../context/LayoutContext'; +import { useAuth } from 'react-oidc-context'; +import { createUser, findUserBySub } from '../../services/GatekeeperService'; + +const ProtectedRoute = ({ isAllowed, redirectPath = '/forbidden', path, component }) => { + if (!isAllowed) { + return <Redirect to={redirectPath} />; + } + return <Route path={path} component={component} />; +}; function Layout(props) { const classes = useStyles(); const layoutState = useLayoutState(); + const auth = useAuth(); + const [user, setUser] = useState(null); + const [roles, setRoles] = useState([]); + + useEffect(() => { + const fetchUser = async (sub) => { + let user = await findUserBySub(sub); + if (!user.id) { + // User registered on Keycloak but not in the database + if (auth.user?.profile?.email) { + const result = await createUser(sub, auth.user?.profile?.email); + + if (result) { + user = await findUserBySub(sub); + } else { + auth.signoutRedirect(); + } + } + } + setUser(user); + setRoles(user.roles.map((r) => r.role_id)); + }; + if (!auth || auth.isLoading) { + return; + } + if (auth.isAuthenticated) { + fetchUser(auth.user.profile.sub); + } else { + auth.signinRedirect(); + } + }, [auth]); return ( - <div className={classes.root}> - <> - <Header history={props.history} /> - <Sidebar /> - <div - className={classnames(classes.content, { - [classes.contentShift]: layoutState.isSidebarOpened, - })} - > - <div className={classes.fakeToolbar} /> - <Switch> - <Route path="/app/dashboard" component={Dashboard} /> - <Route path="/app/users" component={Users} /> - <Route path="/app/roles" component={Roles} /> - <Route path="/app/groups" component={Groups} /> - <Route path="/app/requests" component={Requests} /> - <Route path="/app/policies" component={Policies} /> - <Route path="/app/sources" component={Sources} /> - <Route path="/app/fields" component={Fields} /> - </Switch> - </div> - </> - </div> + user && + roles.length > 0 && ( + <div className={classes.root}> + <> + <Header email={user.email} usehistory={props.history} /> + <Sidebar roles={roles} /> + <div + className={classnames(classes.content, { + [classes.contentShift]: layoutState.isSidebarOpened, + })} + > + <div className={classes.fakeToolbar} /> + <Switch> + <Route path="/home" component={Home} /> + <ProtectedRoute + path="/dashboard" + isAllowed={roles.includes(1)} + component={Dashboard} + /> + <ProtectedRoute + path="/users" + isAllowed={roles.includes(1)} + component={Users} + /> + <ProtectedRoute + isAllowed={roles.includes(1)} + path="/roles" + component={Roles} + /> + <ProtectedRoute + isAllowed={roles.includes(1)} + path="/groups" + component={Groups} + /> + <ProtectedRoute + isAllowed={roles.includes(1)} + path="/requests" + component={Requests} + /> + <ProtectedRoute + isAllowed={roles.includes(1)} + path="/policies" + component={Policies} + /> + <ProtectedRoute + isAllowed={roles.includes(1) || roles.includes(2)} + path="/sources" + component={Sources} + /> + <ProtectedRoute + isAllowed={roles.includes(1) || roles.includes(2)} + path="/fields" + component={Fields} + /> + <Route render={() => <Redirect to="/not-found" />} /> + </Switch> + </div> + </> + </div> + ) ); } diff --git a/src/components/Sidebar/Sidebar.js b/src/components/Sidebar/Sidebar.js index dc3b4e65b2c5396c228f311d4f7db2c523b4e3ce..e6a2006017e6540a8a10efb75be1569f7ac8d9e1 100644 --- a/src/components/Sidebar/Sidebar.js +++ b/src/components/Sidebar/Sidebar.js @@ -11,20 +11,18 @@ import { useLayoutDispatch, toggleSidebar, } from '../../context/LayoutContext'; -import { getSideBarItems, getUserRoleId } from '../../utils'; +import { getSideBarItems } from '../../utils'; -const Sidebar = ({ location }) => { +const Sidebar = ({ location, roles }) => { const classes = useStyles(); const theme = useTheme(); let { isSidebarOpened } = useLayoutState(); const layoutDispatch = useLayoutDispatch(); let [isPermanent, setPermanent] = useState(true); const [sidebarItems, setSidebarItems] = useState([]); - const [userRoleId, setUserRoleId] = useState(0); useEffect(() => { setSidebarItems(getSideBarItems()); - setUserRoleId(getUserRoleId()); const handleWindowWidthChange = () => { const windowWidth = window.innerWidth; const breakpointWidth = theme.breakpoints.values.md; @@ -41,11 +39,11 @@ const Sidebar = ({ location }) => { return function cleanup() { window.removeEventListener('resize', handleWindowWidthChange); }; - }, [setSidebarItems, setUserRoleId, isPermanent, theme]); + }, [setSidebarItems, isPermanent, theme]); const buildSidebarItems = () => { return sidebarItems.map((item) => { - if (item.roles.some((authorizedRoleId) => authorizedRoleId === userRoleId)) { + if (item.roles.some((authorizedRoleId) => roles.includes(authorizedRoleId))) { return ( <SidebarLink key={item.id} diff --git a/src/context/InSylvaGatekeeperClient.js b/src/context/InSylvaGatekeeperClient.js deleted file mode 100644 index f976cd7d1417d7fcdabad83740d3a703e1ca9634..0000000000000000000000000000000000000000 --- a/src/context/InSylvaGatekeeperClient.js +++ /dev/null @@ -1,307 +0,0 @@ -class InSylvaGatekeeperClient { - async post(method, path, requestContent) { - const headers = { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - Authorization: `Bearer ${this.token}`, - }; - - const response = await fetch(`${this.baseUrl}${path}`, { - method: method, - headers, - body: JSON.stringify(requestContent), - mode: 'cors', - }); - return await response.json(); - } - - async createUser({ username, email, password, roleId }) { - const path = `/user`; - return await this.post('POST', `${path}`, { - username, - email, - password, - roleId, - }); - } - - async kcId({ email }) { - const path = `/user/kcid`; - - return await this.post('POST', `${path}`, { - email, - }); - } - - async updateUser({ id, firstName, lastName }) { - const path = `/user/update`; - return await this.post('PUT', `${path}`, { - id, - firstName, - lastName, - }); - } - - async findUser() { - const path = `/user/find`; - return await this.post('GET', `${path}`); - } - - async findOneUser(id) { - const path = `/user/findOne`; - return await this.post('POST', `${path}`, { - id, - }); - } - - async deleteUser({ id }) { - const path = `/user/delete`; - return await this.post('DELETE', `${path}`, { - userId: id, - }); - } - - async getRequests() { - const path = `/user/list-requests`; - return await this.post('GET', `${path}`); - } - - async getPendingRequests() { - const path = `/user/list-pending-requests`; - return await this.post('GET', `${path}`); - } - - async processUserRequest(id) { - const path = `/user/process-request`; - return await this.post('POST', `${path}`, { - id, - }); - } - - async deleteUserRequest(id) { - const path = `/user/delete-request`; - return await this.post('DELETE', `${path}`, { - id, - }); - } - - async createRole({ name, description }) { - const path = `/role`; - return await this.post('POST', `${path}`, { - name, - description, - }); - } - - async findRole() { - const path = `/role/find`; - return await this.post('GET', `${path}`); - } - - async allocateRoles({ roles, users }) { - try { - const path = `/allocate-role-to-user`; - for (let x = 0; x < users.length; x++) { - for (let y = 0; y < roles.length; y++) { - await this.post('POST', `${path}`, { - kc_id: users[x], - role_id: roles[y], - }); - } - } - return true; - } catch (error) { - return false; - } - } - - async allocateRolesToUser({ userId, roleId }) { - const path = `/allocate-role-to-user`; - return await this.post('POST', `${path}`, { - kc_id: userId, - role_id: roleId, - }); - } - async allocatedRoles(kc_id) { - const path = `/allocatedRoles`; - return await this.post('POST', `${path}`, { - kc_id, - }); - } - - async createPolicy({ name, kcId }) { - const path = `/policy/add`; - return await this.post('POST', `${path}`, { - name, - kcId, - }); - } - - async createPolicyField({ policyId, stdFieldId }) { - const path = `/policyField/add`; - return await this.post('POST', `${path}`, { - policyId, - stdFieldId, - }); - } - - async updatePolicy({ id, name, sourceId, isDefault }) { - const path = `/policy/update`; - return await this.post('PUT', `${path}`, { - id, - name, - sourceId, - isDefault, - }); - } - - async updatePolicyField({ id, policyId, stdFieldId }) { - const path = `/policyField/update`; - return this.post('PUT', `${path}`, { - id, - policyId, - stdFieldId, - }); - } - - async deletePolicyField({ id }) { - const path = `/policyField/delete`; - return await this.post('DELETE', `${path}`, { - id, - }); - } - - async deletePolicy({ id }) { - const path = `/policy/delete`; - return await this.post('DELETE', `${path}`, { - id, - }); - } - - async getPolicies() { - const path = `/policy/list`; - return await this.post('GET', `${path}`); - } - - async getPoliciesByUser(kcId) { - const path = `/policy/list-by-user`; - return await this.post('POST', `${path}`, { kcId }); - } - - async getPoliciesWithSourcesByUser(kcId) { - const path = `/policy/policies-with-sources-by-user`; - return await this.post('POST', `${path}`, { kcId }); - } - - async getPoliciesWithSources() { - const path = `/policy/policies-with-sources`; - return await this.post('GET', `${path}`); - } - - async getPolicyFields() { - const path = `/policyField/list`; - return await this.post('GET', `${path}`, {}); - } - - async getGroups() { - const path = `/user/groups`; - return await this.post('POST', `${path}`, {}); - } - - async getGroupPolicies() { - const path = `/user/group-policies`; - return await this.post('GET', `${path}`, {}); - } - - async getGroupUsers({ groupId }) { - const path = `/user/group-users`; - return await this.post('POST', `${path}`, { groupId }); - } - - async createGroup({ name, description, kcId }) { - const path = `/user/add-group`; - return await this.post('POST', `${path}`, { name, description, kcId }); - } - - async createGroupPolicy({ groupId, policyId }) { - const path = `/user/add-group-policy`; - return await this.post('POST', `${path}`, { groupId, policyId }); - } - - async createGroupUser({ groupId, kcId }) { - const path = `/user/add-group-user`; - return await this.post('POST', `${path}`, { groupId, kcId }); - } - - async updateGroup({ id, userId, name, description }) { - const path = `/user/update-group`; - return await this.post('PUT', `${path}`, { name, description, id, userId }); - } - - async updateGroupPolicy({ id, groupId, policyId }) { - const path = `/user/update-group-policy`; - return await this.post('PUT', `${path}`, { id, groupId, policyId }); - } - - async updateGroupUser({ id, kcId, groupId }) { - const path = `/user/update-group-user`; - return await this.post('PUT', `${path}`, { id, kcId, groupId }); - } - - async deleteGroup({ id }) { - const path = `/user/delete-group`; - return await this.post('DELETE', `${path}`, { id }); - } - - async deleteGroupPolicy({ id }) { - const path = `/user/delete-group-policy`; - return await this.post('DELETE', `${path}`, { id }); - } - - async deleteGroupUser({ groupId, userId }) { - const path = `/user/delete-group-user`; - return await this.post('DELETE', `${path}`, { groupId, userId }); - } - - async getAssignedPolicies({ policyId }) { - const path = `/policy/assigned-fields`; - return await this.post('POST', `${path}`, { policyId }); - } - - async deleteRole({ id }) { - const path = `/role/delete`; - return await this.post('DELETE', `${path}`, { id }); - } - - async getAssignedUserByRole({ id }) { - const path = `/user/assigned-by-role`; - return await this.post('POST', `${path}`, { id }); - } - - async getGroupDetailsByPolicy({ policyId }) { - const path = `/policy/policies-with-groups`; - return await this.post('POST', `${path}`, { policyId }); - } - - async usersWithGroupAndRole() { - const path = `/user/with-groups-and-roles`; - return await this.post('GET', `${path}`); - } - - async findOneUserWithGroupAndRole(id) { - const path = `/user/one-with-groups-and-roles`; - return await this.post('POST', `${path}`, { - id, - }); - } - - async createPolicySource({ policyId, sourceId }) { - const path = `/policySource/add`; - return await this.post('POST', `${path}`, { policyId, sourceId }); - } -} - -InSylvaGatekeeperClient.prototype.baseUrl = null; -InSylvaGatekeeperClient.prototype.token = null; - -export { InSylvaGatekeeperClient }; diff --git a/src/context/InSylvaSourceManager.js b/src/context/InSylvaSourceManager.js deleted file mode 100644 index a02e89c3e40836e37f0f8fa03801169c9063f641..0000000000000000000000000000000000000000 --- a/src/context/InSylvaSourceManager.js +++ /dev/null @@ -1,155 +0,0 @@ -class InSylvaSourceManager { - async post(method, path, requestContent) { - const headers = { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - Authorization: `Bearer ${this.token}`, - }; - - const response = await fetch(`${this.baseUrl}${path}`, { - method: method, - headers, - body: JSON.stringify(requestContent), - mode: 'cors', - }); - return await response.json(); - } - - async createSource(metaUrfms, name, description, kc_id) { - const path = `/source`; - return await this.post('POST', `${path}`, { - metaUrfms, - name, - description, - kc_id, - }); - } - - async sources(kc_id) { - const path = '/sources'; - return await this.post('POST', `${path}`, { kc_id }); - } - - async indexedSources(kc_id) { - const path = '/indexed-sources'; - return await this.post('POST', `${path}`, { kc_id }); - } - - async allSource() { - const path = '/allSource'; - return await this.post('GET', `${path}`); - } - - async allIndexedSource() { - const path = '/allIndexedSource'; - return await this.post('GET', `${path}`); - } - - async addStdField( - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional - ) { - const path = `/addStdField`; - return await this.post('POST', `${path}`, { - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional, - }); - } - - async addAddtlField( - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional - ) { - const path = `/addAddtlField`; - return await this.post('POST', `${path}`, { - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional, - }); - } - - async updateStdField( - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional - ) { - const path = `/updateStdField`; - return await this.post('POST', `${path}`, { - category, - field_name, - definition_and_comment, - obligation_or_condition, - field_type, - cardinality, - values, - isPublic, - isOptional, - }); - } - - async updateSource(kc_id, source_id) { - const path = '/update_source'; - return await this.post('POST', `${path}`, { kc_id, source_id }); - } - - async publicFields() { - const path = `/stdFields`; - return this.post('GET', `${path}`); - } - async privateFields() { - const path = `/privateFieldList`; - return this.post('GET', `${path}`); - } - - async truncateStdField() { - const path = `/stdFields/truncate`; - return this.post('DELETE', `${path}`); - } - - async deleteSource(sourceId, kc_id) { - const path = `/source/delete`; - return this.post('POST', `${path}`, { sourceId, kc_id }); - } - - async mergeAndSendSource({ kc_id, name, description, sources }) { - const path = `/source/merge-and-send`; - return this.post('POST', `${path}`, { kc_id, name, description, sources }); - } -} - -InSylvaSourceManager.prototype.baseUrl = null; -InSylvaSourceManager.prototype.token = null; -export { InSylvaSourceManager }; diff --git a/src/context/KeycloakClient.js b/src/context/KeycloakClient.js deleted file mode 100644 index 1dcd05596d13fa7577dd078008b75a03729c94aa..0000000000000000000000000000000000000000 --- a/src/context/KeycloakClient.js +++ /dev/null @@ -1,84 +0,0 @@ -class KeycloakClient { - async post(path, requestContent) { - const headers = { - 'Content-Type': 'application/x-www-form-urlencoded', - // "Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE", - // "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization" - }; - let formBody = []; - for (const property in requestContent) { - const encodedKey = encodeURIComponent(property); - const encodedValue = encodeURIComponent(requestContent[property]); - formBody.push(encodedKey + '=' + encodedValue); - } - formBody = formBody.join('&'); - - const response = await fetch(`${this.baseUrl}${path}`, { - method: 'POST', - headers, - body: formBody, - mode: 'cors', - }); - if (response.ok === true) { - // ok - } else { - // throw new Error(response.status); - return await response.json(); - } - if (response.statusText === 'No Content') { - // ok - } else { - return await response.json(); - } - } - - async login({ - realm = this.realm, - client_id = this.client_id, - username, - password, - grant_type = this.grant_type, - }) { - const path = `/auth/realms/${realm}/protocol/openid-connect/token`; - const token = await this.post(`${path}`, { - client_id, - username, - password, - grant_type, - }); - return { token }; - } - - async refreshToken({ - realm = this.realm, - client_id = this.client_id, - // client_secret : 'optional depending on the type of client', - grant_type = 'refresh_token', - refresh_token, - }) { - const path = `/auth/realms/${realm}/protocol/openid-connect/token`; - const token = await this.post(`${path}`, { - client_id, - grant_type, - refresh_token, - }); - return { token }; - } - - async logout({ realm = this.realm, client_id = this.client_id }) { - const refresh_token = sessionStorage.getItem('refresh_token'); - const path = `/auth/realms/${realm}/protocol/openid-connect/logout`; - if (refresh_token) { - await this.post(`${path}`, { - client_id, - refresh_token, - }); - } - } -} - -KeycloakClient.prototype.baseUrl = null; -KeycloakClient.prototype.client_id = null; -KeycloakClient.prototype.grant_type = null; -KeycloakClient.prototype.realm = null; -export { KeycloakClient }; diff --git a/src/context/UserContext.js b/src/context/UserContext.js deleted file mode 100644 index 5c209e8562a3b3fe992427cfe079efc1f376cd12..0000000000000000000000000000000000000000 --- a/src/context/UserContext.js +++ /dev/null @@ -1,101 +0,0 @@ -import React from 'react'; -import { KeycloakClient } from './KeycloakClient'; -import { InSylvaGatekeeperClient } from './InSylvaGatekeeperClient'; -import { getGatekeeperBaseUrl, getLoginUrl, redirect } from '../utils.js'; - -const UserStateContext = React.createContext(); -const UserDispatchContext = React.createContext(); - -const kcClient = new KeycloakClient(); -kcClient.baseUrl = process.env.REACT_APP_IN_SYLVA_KEYCLOAK_PORT - ? `${process.env.REACT_APP_IN_SYLVA_KEYCLOAK_HOST}:${process.env.REACT_APP_IN_SYLVA_KEYCLOAK_PORT}` - : `${window._env_.REACT_APP_IN_SYLVA_KEYCLOAK_HOST}`; -kcClient.realm = process.env.REACT_APP_IN_SYLVA_KEYCLOAK_PORT - ? `${process.env.REACT_APP_IN_SYLVA_REALM}` - : `${window._env_.REACT_APP_IN_SYLVA_REALM}`; -kcClient.client_id = process.env.REACT_APP_IN_SYLVA_KEYCLOAK_PORT - ? `${process.env.REACT_APP_IN_SYLVA_CLIENT_ID}` - : `${window._env_.REACT_APP_IN_SYLVA_CLIENT_ID}`; -kcClient.grant_type = process.env.REACT_APP_IN_SYLVA_KEYCLOAK_PORT - ? `${process.env.REACT_APP_IN_SYLVA_GRANT_TYPE}` - : `${window._env_.REACT_APP_IN_SYLVA_GRANT_TYPE}`; - -const igClient = new InSylvaGatekeeperClient(); -igClient.baseUrl = getGatekeeperBaseUrl(); - -function userReducer(state, action) { - switch (action.type) { - case 'LOGIN_SUCCESS': - return { ...state, isAuthenticated: true }; - case 'SIGN_OUT_SUCCESS': - return { ...state, isAuthenticated: false }; - case 'LOGIN_FAILURE': - return { ...state, isAuthenticated: false }; - case 'USER_CREATED': - return { ...state }; - default: { - throw new Error(`Unhandled action type: ${action.type} `); - } - } -} - -function UserProvider({ children }) { - const [state, dispatch] = React.useReducer(userReducer, { - isAuthenticated: !!sessionStorage.getItem('access_token'), - }); - - return ( - <UserStateContext.Provider value={state}> - <UserDispatchContext.Provider value={dispatch}> - {children} - </UserDispatchContext.Provider> - </UserStateContext.Provider> - ); -} - -function useUserState() { - const context = React.useContext(UserStateContext); - if (context === undefined) { - throw new Error('useUserState must be used within a UserProvider'); - } - return context; -} - -function useUserDispatch() { - const context = React.useContext(UserDispatchContext); - if (context === undefined) { - throw new Error('useUserDispatch must be used within a UserProvider'); - } - return context; -} - -async function checkUserLogin(userId, accessToken, refreshToken, roleId) { - if (!!userId && !!accessToken && !!refreshToken && !!roleId) { - sessionStorage.setItem('userId', userId); - sessionStorage.setItem('access_token', accessToken); - sessionStorage.setItem('refresh_token', refreshToken); - sessionStorage.setItem('roleId', roleId); - //To Do: - // Load the users histories from UserHistory(userId) endpoint - // Load the users result filters from Result_Filter(userId) endpoints - // Load the users policies from Policies(userId) endpoint - if (!sessionStorage.getItem('token_refresh_time')) { - sessionStorage.setItem('token_refresh_time', Date.now()); - } - } else { - console.error('users not logged in'); - } -} - -async function signOut(dispatch) { - await kcClient.logout({}); - dispatch({ type: 'SIGN_OUT_SUCCESS' }); - sessionStorage.removeItem('access_token'); - sessionStorage.removeItem('refresh_token'); - sessionStorage.removeItem('portal'); - sessionStorage.removeItem('userId'); - sessionStorage.removeItem('roleId'); - redirect(getLoginUrl() + '?requestType=portal'); -} - -export { UserProvider, useUserState, useUserDispatch, signOut, checkUserLogin }; diff --git a/src/images/logo.png b/src/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4fabf38732fa03638d007d456bf3b3dffab5f3ea Binary files /dev/null and b/src/images/logo.png differ diff --git a/src/images/mongo_logo.svg b/src/images/mongo_logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..65c4a12504dacb5fdd70242147a1e08758231fc6 --- /dev/null +++ b/src/images/mongo_logo.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> +<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"> + <circle cx="512" cy="512" r="512" style="fill:#13aa52"/> + <path d="M648.86 449.44c-32.34-142.73-108.77-189.66-117-207.59-9-12.65-18.12-35.15-18.12-35.15-.15-.38-.39-1.05-.67-1.7-.93 12.65-1.41 17.53-13.37 30.29-18.52 14.48-113.54 94.21-121.27 256.37-7.21 151.24 109.25 241.36 125 252.85l1.79 1.27v-.11c.1.76 5 36 8.44 73.34H526a726.68 726.68 0 0 1 13-78.53l1-.65a204.48 204.48 0 0 0 20.11-16.45l.72-.65c33.48-30.93 93.67-102.47 93.08-216.53a347.07 347.07 0 0 0-5.05-56.76zM512.35 659.12s0-212.12 7-212.08c5.46 0 12.53 273.61 12.53 273.61-9.72-1.17-19.53-45.03-19.53-61.53z" style="fill:#fff"/> +</svg> \ No newline at end of file diff --git a/src/index.js b/src/index.js index c7ada2c163e7a547f4be8eb51ab96c36a6444c5e..3d4a198db2809c794b2d296a3cbc183033d5df4d 100755 --- a/src/index.js +++ b/src/index.js @@ -6,36 +6,26 @@ import Themes from './themes'; import '@elastic/eui/dist/eui_theme_light.css'; import App from './components/App'; import { LayoutProvider } from './context/LayoutContext'; -import { UserProvider, checkUserLogin } from './context/UserContext'; -import { getLoginUrl, getUrlParam, redirect } from './utils.js'; +import { AuthProvider } from 'react-oidc-context'; +import { userManager } from './services/GatekeeperService'; -const userId = getUrlParam('kcId', ''); -const accessToken = getUrlParam('accessToken', ''); -const roleId = getUrlParam('roleId', ''); - -let refreshToken = getUrlParam('refreshToken', ''); -if (refreshToken.includes('#/app/portal')) { - refreshToken = refreshToken.substring(0, refreshToken.indexOf('#')); -} - -checkUserLogin(userId, accessToken, refreshToken, roleId); - -//To-Do -// * delete previous tokens from session storage -// * refresh tokens - -if (sessionStorage.getItem('access_token')) { - ReactDOM.render( +ReactDOM.render( + <AuthProvider + userManager={userManager} + onSigninCallback={() => { + window.history.replaceState( + {}, + document.title, + window.location.pathname + '#/home' + ); + }} + > <LayoutProvider> - <UserProvider> - <ThemeProvider theme={Themes.default}> - <CssBaseline /> - <App userId={userId} accessToken={accessToken} refreshToken={refreshToken} /> - </ThemeProvider> - </UserProvider> - </LayoutProvider>, - document.getElementById('root') - ); -} else { - redirect(getLoginUrl() + '?requestType=portal'); -} + <ThemeProvider theme={Themes.default}> + <CssBaseline /> + <App /> + </ThemeProvider> + </LayoutProvider> + </AuthProvider>, + document.getElementById('root') +); diff --git a/src/pages/dashboard/Dashboard.js b/src/pages/dashboard/Dashboard.js index 1932fe669f554712b30432382de9eae09f088442..5807df60009bee18d6f0fd8d7f98ed3ec2f5ce88 100644 --- a/src/pages/dashboard/Dashboard.js +++ b/src/pages/dashboard/Dashboard.js @@ -2,6 +2,7 @@ import React from 'react'; import { Grid, LinearProgress } from '@material-ui/core'; import { useTheme } from '@material-ui/styles'; import keycloakLogo from '../../images/Keycloak_Logo.svg'; +import mongoLogo from '../../images/mongo_logo.svg'; import { ResponsiveContainer, AreaChart, @@ -70,6 +71,13 @@ urlMaps.set( export default function Dashboard() { const classes = useStyles(); const theme = useTheme(); + const { + REACT_APP_KIBANA_URL, + REACT_APP_PGADMIN_URL, + REACT_APP_KEYCLOAK_URL, + REACT_APP_PORTAINER_URL, + REACT_APP_MONGO_EXPRESS_URL, + } = process.env; return ( <> @@ -83,9 +91,8 @@ export default function Dashboard() { <div> <EuiButton aria-label="Go to Kibana" - onClick={() => { - window.open(urlMaps.get('kibana'), '_blank'); - }} + href={`${REACT_APP_KIBANA_URL}`} + target="_blank" > Go for it </EuiButton> @@ -96,16 +103,15 @@ export default function Dashboard() { </EuiFlexItem> <EuiFlexItem> <EuiCard - icon={<EuiIcon size="xxl" type="logoPostgres" />} - title="PgAdmin" - description="Access to Postgresql" + icon={<EuiIcon size="xxl" type={mongoLogo} />} + title="MongoDB" + description="Access to MongoDB" footer={ <div> <EuiButton - aria-label="Go to Postgresql" - onClick={() => { - window.open(urlMaps.get('postgresql'), '_blank'); - }} + aria-label="Go to MongoDB" + href={`${REACT_APP_MONGO_EXPRESS_URL}`} + target="_blank" > Go for it </EuiButton> @@ -116,36 +122,15 @@ export default function Dashboard() { </EuiFlexItem> <EuiFlexItem> <EuiCard - icon={<EuiIcon size="xxl" type="logoMongodb" />} - title="MongoDb" - description="Access to MongoDb" - footer={ - <div> - <EuiButton - aria-label="Go to MongoDb" - onClick={() => { - window.open(urlMaps.get('mongoDb'), '_blank'); - }} - > - Go for it - </EuiButton> - <EuiSpacer size="xs" /> - </div> - } - /> - </EuiFlexItem> - <EuiFlexItem> - <EuiCard - icon={<EuiIcon size="xxl" type="logoElastic" />} - title="Elasticsearch" - description="Access to Elasticsearch" + icon={<EuiIcon size="xxl" type="logoPostgres" />} + title="PgAdmin" + description="Access to PgAdmin" footer={ <div> <EuiButton - aria-label="Go to Elasticsearch" - onClick={() => { - window.open(urlMaps.get('elasticsearch'), '_blank'); - }} + aria-label="Go to PgAdmin" + href={`${REACT_APP_PGADMIN_URL}`} + target="_blank" > Go for it </EuiButton> @@ -163,9 +148,8 @@ export default function Dashboard() { <div> <EuiButton aria-label="Go to Keycloak" - onClick={() => { - window.open(urlMaps.get('keycloak'), '_blank'); - }} + href={`${REACT_APP_KEYCLOAK_URL}`} + target="_blank" > Go for it </EuiButton> @@ -183,9 +167,8 @@ export default function Dashboard() { <div> <EuiButton aria-label="Go to Portainer" - onClick={() => { - window.open(urlMaps.get('portainer'), '_blank'); - }} + href={`${REACT_APP_PORTAINER_URL}`} + target="_blank" > Go for it </EuiButton> diff --git a/src/pages/error/403.js b/src/pages/error/403.js new file mode 100644 index 0000000000000000000000000000000000000000..1cace7388d5f7a16a77754c4684f2fc14d552b53 --- /dev/null +++ b/src/pages/error/403.js @@ -0,0 +1,44 @@ +import React from 'react'; +import { Grid, Paper, Typography, Button } from '@material-ui/core'; +import classnames from 'classnames'; +import useStyles from './styles'; +import logo from './logo.png'; + +export default function Error403() { + const classes = useStyles(); + + return ( + <Grid container className={classes.container}> + <div className={classes.logotype}> + <img className={classes.logotypeIcon} src={logo} alt="logo" /> + <Typography variant="h3" color="white" className={classes.logotypeText}> + In-Sylva Project + </Typography> + </div> + <Paper classes={{ root: classes.paperRoot }}> + <Typography + variant="h1" + color="primary" + className={classnames(classes.textRow, classes.errorCode)} + > + 403 + </Typography> + <Typography variant="h5" color="primary" className={classes.textRow}> + Forbidden + </Typography> + <Typography variant="h5" color="primary" className={classes.textRow}> + Oops. Looks like you don't have the right permissions to access this page + </Typography> + <Button + href="#/home" + variant="contained" + color="primary" + size="large" + className={classes.backButton} + > + Back to Home + </Button> + </Paper> + </Grid> + ); +} diff --git a/src/pages/error/Error.js b/src/pages/error/404.js similarity index 77% rename from src/pages/error/Error.js rename to src/pages/error/404.js index e466863f529a49cd148bcb9819919f03add19f77..ccf5ebe4a6ff6d6054283b6e79bdd5219dce2b18 100644 --- a/src/pages/error/Error.js +++ b/src/pages/error/404.js @@ -1,13 +1,11 @@ import React from 'react'; import { Grid, Paper, Typography, Button } from '@material-ui/core'; -import { Link } from 'react-router-dom'; import classnames from 'classnames'; import useStyles from './styles'; import logo from './logo.png'; -export default function Error() { +export default function Error404() { const classes = useStyles(); - return ( <Grid container className={classes.container}> <div className={classes.logotype}> @@ -25,21 +23,15 @@ export default function Error() { 404 </Typography> <Typography variant="h5" color="primary" className={classes.textRow}> - Oops. Looks like the page you're looking for no longer exists + Not Found </Typography> - <Typography - variant="h6" - color="text" - colorBrightness="secondary" - className={classnames(classes.textRow, classes.safetyText)} - > - But we're here to bring you back to safety + <Typography variant="h5" color="primary" className={classes.textRow}> + Oops. Looks like the page you're looking for no longer exists </Typography> <Button + href="#/home" variant="contained" color="primary" - component={Link} - to="/" size="large" className={classes.backButton} > diff --git a/src/pages/fields/Fields.js b/src/pages/fields/Fields.js index 1ec424cf21b94468c3631f7e3ea9b5920b07fff8..46f766abc590a8ba71e3465474a0efebe564445a 100644 --- a/src/pages/fields/Fields.js +++ b/src/pages/fields/Fields.js @@ -1,5 +1,4 @@ -import React, { useState, useCallback, useEffect, memo, useRef } from 'react'; -import store from '../../store/index'; +import React, { useState, useCallback, useEffect, memo } from 'react'; import { EuiForm, EuiPageContent, @@ -16,6 +15,11 @@ import { } from '@elastic/eui'; import { ShowAlert } from '../../components/Common'; import Papa from 'papaparse'; +import { + getPublicFields, + deleteAllStdFields, + createStdField, +} from '../../services/GatekeeperService'; const renderFiles = (files) => { if (files && files.length > 0) { @@ -33,7 +37,7 @@ const renderFiles = (files) => { } }; -const NewFieldsForm = memo(({ files, globalState, onFilePickerChange, onSaveField }) => { +const NewFieldsForm = memo(({ files, onFilePickerChange, onSaveField }) => { return ( <> <EuiForm component="form"> @@ -56,7 +60,7 @@ const NewFieldsForm = memo(({ files, globalState, onFilePickerChange, onSaveFiel </EuiFormRow> <EuiFormRow label=""> { - <EuiButton fill onClick={onSaveField} isLoading={globalState.isLoading}> + <EuiButton fill onClick={onSaveField}> Load </EuiButton> } @@ -66,69 +70,47 @@ const NewFieldsForm = memo(({ files, globalState, onFilePickerChange, onSaveFiel ); }); -const StdFields = memo( - ({ - stdFields, - stdFieldColumns, - tableref, - pagination, - sorting, - onTableChange, - onSelection, - search, - isLoading, - }) => { - return ( - <> - <EuiForm component="form"> - <EuiFormRow label="Uploaded standard fields" fullWidth> - <EuiInMemoryTable - // ref={tableref} - // itemId="id" - // isSelectable={true} - items={stdFields} - loading={isLoading} - columns={stdFieldColumns} - search={search} - pagination={true} - sorting={true} - // onChange={onTableChange} - // rowheader={"field_name"} - // selection={onSelection} - /> - </EuiFormRow> - </EuiForm> - </> - ); - } -); +const StdFields = memo(({ stdFields, stdFieldColumns }) => { + return ( + <> + <EuiForm component="form"> + <EuiFormRow label="Uploaded standard fields" fullWidth> + <EuiInMemoryTable + items={stdFields} + columns={stdFieldColumns} + search={{ + box: { + incremental: true, + }, + }} + pagination={true} + sorting={true} + /> + </EuiFormRow> + </EuiForm> + </> + ); +}); let debounceTimeoutId; let requestTimeoutId; const Fields = (props) => { - const [globalState, globalActions] = store(); const [selectedTabNumber, setSelectedTabNumber] = useState(0); const [open, setOpen] = useState(false); const [alertMessage, setAlertMessage] = useState(''); const [severity, setSeverity] = useState('info'); const [files, setFiles] = useState(); - const [fields, setFields] = useState([]); + const [loadedFields, setLoadedFields] = useState([]); const [stdFields, setStdFields] = useState([]); - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(5); - const [sortDirection, setSortDirection] = useState('asc'); - const [sortField, setSortField] = useState('field_name'); - const [totalItemCount, setTotalItemCount] = useState(0); - const tableref = useRef(); - const [isLoading, setIsLoading] = useState(false); + const [filteredFields, setFilteredFields] = useState([]); const loadStdFields = useCallback(async () => { - const fields = await globalActions.source.publicFields(); + const fields = await getPublicFields(); if (fields) { - setStdFields((prevFields) => [...prevFields, ...fields]); - setTotalItemCount(typeof fields === typeof undefined ? 0 : fields.length); + setStdFields(fields); + setFilteredFields(fields); } - }, [globalActions.source]); + }, []); useEffect(() => { // clean up controller @@ -138,69 +120,25 @@ const Fields = (props) => { } // cancel subscription to useEffect return () => (isSubscribed = false); - }, [totalItemCount, loadStdFields]); + }, [loadStdFields]); const onQueryChange = ({ query }) => { clearTimeout(debounceTimeoutId); - clearTimeout(requestTimeoutId); - debounceTimeoutId = setTimeout(() => { - setIsLoading(true); - requestTimeoutId = setTimeout(() => { - const items = stdFields.filter((field) => { - const normalizedFieldName = - `${field.field_name} ${field.Definition_and_comment}`.toLowerCase(); - const normalizedQuery = query.text.toLowerCase(); - return normalizedFieldName.indexOf(normalizedQuery) !== -1; - }); - if (query.text !== '') { - setIsLoading(false); - setStdFields(items); - } else { - loadStdFields(); - } - }, 1000); + const items = stdFields.filter((field) => { + const normalizedFieldName = + `${field.field_name} ${field.Definition_and_comment}`.toLowerCase(); + const normalizedQuery = query.text.toLowerCase(); + return normalizedFieldName.indexOf(normalizedQuery) !== -1; + }); + if (query.text !== '') { + setFilteredFields(items); + } else { + setFilteredFields([]); + } }, 300); }; - const search = { - onChange: onQueryChange, - box: { - incremental: true, - }, - }; - - /* - const onSelection = { - selectable: source => !source.is_send, - onSelectionChange: onSelectionChange, - initialSelected: unsentSources, - }; */ - - const onTableChange = ({ page = {}, sort = {} }) => { - const { index: pageIndex, size: pageSize } = page; - const { field: sortField, direction: sortDirection } = sort; - - setPageIndex(pageIndex); - setPageSize(pageSize); - setSortField(sortField); - setSortDirection(sortDirection); - }; - - const pagination = { - pageIndex: pageIndex, - pageSize: pageSize, - totalItemCount: totalItemCount, - pageSizeOptions: [3, 5, 8], - }; - - const sorting = { - sort: { - field: sortField, - direction: sortDirection, - }, - }; - const onFilePickerChange = async (files) => { setFiles(files); await handleSelectedFile(files); @@ -209,8 +147,8 @@ const Fields = (props) => { const handleSelectedFile = async (files) => { for (const file of files) { const reader = new FileReader(); - await reader.readAsText(file); reader.onload = () => handleData(reader.result); + reader.readAsText(file); } }; @@ -230,38 +168,70 @@ const Fields = (props) => { }); result.data.forEach((item) => { + // lowercase the key + Object.keys(item).forEach((key) => { + const lowercasedKey = key.toLowerCase(); + if (key !== lowercasedKey) { + item[lowercasedKey] = item[key]; + delete item[key]; + } + }); rows.push(item); }); if (columns && rows) { - // (preColumns => ([...preColumns, ...columns])) - setFields((preRows) => [...preRows, ...rows]); + setLoadedFields((preRows) => [...preRows, ...rows]); } } }; const onSaveField = async () => { - if (fields) { - await globalActions.source.truncateStdField(); + if (loadedFields) { + await deleteAllStdFields(); + try { + for (const field of loadedFields) { + const { + cardinality, + category, + definition_and_comment, + field_name, + field_type, + isoptional, + is_optional, + ispublic, + is_public, + obligation_or_condition, + values, + list_url, + default_display_fields + } = field; - await fields.forEach((item) => { - globalActions.source.createStdField( - item.Category, - item.Field_name, - item.Definition_and_comment, - item.Obligation_or_condition, - item.Field_type, - item.Cardinality, - item.Values, - item.is_public, - false - ); - }); + console.log("fields: ", field); + const isOptional = isoptional || is_optional; + const isPublic = ispublic || is_public; + const response = await createStdField( + cardinality, + category, + definition_and_comment, + field_name, + field_type, + isOptional?.toString().toLowerCase() === 'true', + isPublic?.toString().toLowerCase() === 'true', + obligation_or_condition, + values, + list_url, + default_display_fields + ); + } - setOpen(true); - setAlertMessage('Standard fields added.'); - setSeverity('success'); - loadStdFields(); + // Reload fields after processing + loadStdFields(); + } catch (error) { + console.error('Unexpected error during field saving:', error); + setOpen(true); + setAlertMessage('An unexpected error occurred.'); + setSeverity('error'); + } } }; @@ -285,6 +255,18 @@ const Fields = (props) => { field: 'Obligation_or_condition', name: 'Obligation or condition', }, + { + field: 'cardinality', + name: 'Cardinality', + }, + { + field: 'default_display_fields', + name: 'Default display fields', + }, + { + field: 'list_url', + name: 'List URL', + }, { field: 'values', name: 'Values', @@ -303,7 +285,6 @@ const Fields = (props) => { <br /> <NewFieldsForm files={files} - globalState={globalState} onFilePickerChange={onFilePickerChange} onSaveField={onSaveField} /> @@ -316,16 +297,7 @@ const Fields = (props) => { content: ( <> <br /> - <StdFields - stdFields={stdFields} - stdFieldColumns={stdFieldColumns} - pagination={pagination} - sorting={sorting} - tableref={tableref} - onTableChange={onTableChange} - search={search} - loading={isLoading} - /> + <StdFields stdFields={stdFields} stdFieldColumns={stdFieldColumns} /> </> ), }, diff --git a/src/pages/groups/Groups.js b/src/pages/groups/Groups.js index eeb16555cb07b97c352c2b6c9fadc2ca5ae818ef..8e4441da83659465e2db27d4e8751001bea54439 100644 --- a/src/pages/groups/Groups.js +++ b/src/pages/groups/Groups.js @@ -1,5 +1,4 @@ import React, { useState, Fragment, useEffect, memo, useCallback } from 'react'; -import store from '../../store/index'; import { ShowAlert } from '../../components/Common'; import { EuiForm, @@ -19,10 +18,17 @@ import { EuiSelectable, EuiComboBox, } from '@elastic/eui'; +import { + createGroup, + getGroups, + getUsers, + deleteGroup, + addUserToGroup, + removeUserFromGroup, +} from '../../services/GatekeeperService'; const NewGroupForm = memo( ({ - globalState, groupNameValue, setGroupNameValue, groupDescriptionValue, @@ -50,7 +56,7 @@ const NewGroupForm = memo( </EuiFormRow> <EuiSpacer /> { - <EuiButton fill onClick={onSaveGroup} isLoading={globalState.isLoading}> + <EuiButton fill onClick={onSaveGroup}> Save </EuiButton> } @@ -65,7 +71,7 @@ const NewGroupForm = memo( ); const GroupAssignment = memo( - ({ groups, setGroups, users, setUsers, onGroupAssignment, globalState }) => { + ({ groups, setGroups, users, setUsers, onGroupAssignment }) => { return ( <> <EuiForm component="form"> @@ -106,12 +112,7 @@ const GroupAssignment = memo( </EuiFlexGroup> <EuiSpacer /> { - <EuiButton - type="submit" - onClick={onGroupAssignment} - isLoading={globalState.isLoading} - fill - > + <EuiButton type="submit" onClick={onGroupAssignment} fill> Save </EuiButton> } @@ -135,7 +136,7 @@ const AssignedGroups = memo( <EuiFormRow label="Select specific group"> <EuiComboBox placeholder="Select a group" - singleSelection={{ asPlainText: true }} + singleSelection={true} options={groups} selectedOptions={selectedGroup} onChange={(e) => { @@ -154,20 +155,22 @@ const AssignedGroups = memo( const Groups = () => { const [selectedTabNumber, setSelectedTabNumber] = useState(0); - const [globalState, globalActions] = store(); const [groupNameValue, setGroupNameValue] = useState(''); const [groupDescriptionValue, setGroupDescriptionValue] = useState(''); const [groups, setGroups] = useState([]); + const [open, setOpen] = useState(false); const [alertMessage, setAlertMessage] = useState(''); const [severity, setSeverity] = useState('info'); + const [users, setUsers] = useState([]); const [tblGroups, setTblGroups] = useState([]); const [selectedGroup, setSelectedGroup] = useState([]); const [groupedUsers, setGroupedUsers] = useState([]); + const [refresh, setRefresh] = useState(true); const loadGroups = useCallback(async () => { - const _groups = await globalActions.group.getGroups(); + const _groups = await getGroups(); if (_groups) { const grouplist = []; for (const group of _groups) { @@ -178,55 +181,62 @@ const Groups = () => { } setGroups(_groups); } - }, [globalActions.group]); + }, []); const loadUsers = useCallback(async () => { - const _users = await globalActions.user.findUser(); + const _users = await getUsers(); const usersList = []; if (_users) { for (const user of _users) { - usersList.push({ value: user.id, label: user.username }); + usersList.push({ value: user.id, label: user.email, sub: user.kc_id }); } if (usersList) { setUsers(usersList); } } - }, [globalActions.user]); + }, []); useEffect(() => { - let isSubscribed = true; - if (isSubscribed) { + if (refresh) { + setRefresh(false); loadGroups(); loadUsers(); + onSelectedGroup([]); } - return () => (isSubscribed = false); - }, [loadGroups, loadUsers]); + }, [loadGroups, loadUsers, refresh, setRefresh]); const onDeleteGroup = async (group) => { if (group) { - await globalActions.group.deleteGroup(group.id); + const result = await deleteGroup(group.id); + if (result) { + setAlertMessage('Group has been deleted.'); + setSeverity('success'); + setRefresh(true); + } else { + setAlertMessage(`Error: ${result.error}`); + setSeverity('error'); + } + setOpen(true); } - loadGroups(); }; const onSaveGroup = async () => { - if (groupNameValue && sessionStorage.getItem('userId')) { - await globalActions.group.createGroup( - groupNameValue, - groupDescriptionValue, - sessionStorage.getItem('userId') - ); - - if (globalState.status === 'SUCCESS') { - setOpen(true); + if (groupNameValue && groupDescriptionValue) { + const result = await createGroup(groupNameValue, groupDescriptionValue); + if (result?.id) { setAlertMessage('Group has been created.'); setSeverity('success'); + setRefresh(true); + } else { + setAlertMessage(`Error: ${result.error}`); + setSeverity('error'); } - await loadGroups(); + setOpen(true); } }; - const onGroupAssignment = async () => { + const onGroupAssignment = async (e) => { + e.preventDefault(); const checkedGroups = tblGroups.filter((e) => { return e.checked === 'on'; }); @@ -237,30 +247,37 @@ const Groups = () => { for (const user of checkedUsers) { for (const group of checkedGroups) { - await globalActions.group.createGroupUser(group.value, user.value); + const response = await addUserToGroup(user.sub, group.value); } - } - - if (globalState.status === 'SUCCESS') { - setOpen(true); - setAlertMessage('Users-group assignment has been completed.'); - setSeverity('success'); + setRefresh(true); } }; const onRemoveUserFromGroup = async (e) => { - await globalActions.group.deleteGroupUser(e.groupid, e.userid); - const groupedUsers = await globalActions.group.getGroupUsers(e.groupid); - setGroupedUsers(groupedUsers); + await removeUserFromGroup(e.sub, e.groupId); + onSelectedGroup([]); + setRefresh(true); }; const onSelectedGroup = async (e) => { if (e.length > 0) { - const groupedUsers = await globalActions.group.getGroupUsers(e[0].value); - setGroupedUsers(groupedUsers); + const selectedGroup = groups.filter((group) => { + return group.id === e[0].value; + }); + const groupUsers = selectedGroup.map((group) => { + return group.users.map((user) => ({ + email: user.user.email, + name: group.name, + groupId: group.id, + description: group.description, + sub: user.user.kc_id, + })); + }); + setGroupedUsers(groupUsers.flatMap((x) => x)); setSelectedGroup(e); } else { - setSelectedGroup(e); + setGroupedUsers([]); + setSelectedGroup([]); } }; @@ -271,7 +288,9 @@ const Groups = () => { icon: 'trash', type: 'icon', color: 'danger', - onClick: onDeleteGroup, + onClick: async (group) => { + await onDeleteGroup(group); + }, }, ]; @@ -289,11 +308,11 @@ const Groups = () => { const groupColumns = [ { field: 'name', name: 'Group name' }, { field: 'description', name: 'Group description' }, - { name: 'Actions', actions: groupActions }, + { name: 'Actions', actions: groupActions, truncateText: true }, ]; const assignedGroupedUserColumns = [ - { field: 'username', name: 'Username' }, + { field: 'email', name: 'Email' }, { field: 'name', name: 'Group name' }, { field: 'description', name: 'Group description' }, { name: 'Actions', actions: assignedGroupedUserActions }, @@ -306,7 +325,6 @@ const Groups = () => { content: ( <> <NewGroupForm - globalState={globalState} groupNameValue={groupNameValue} setGroupNameValue={setGroupNameValue} groupDescriptionValue={groupDescriptionValue} @@ -329,7 +347,6 @@ const Groups = () => { users={users} setUsers={setUsers} onGroupAssignment={onGroupAssignment} - globalState={globalState} /> </> ), diff --git a/src/pages/home/Home.js b/src/pages/home/Home.js new file mode 100644 index 0000000000000000000000000000000000000000..76e97bc76088dbfa2c01f728a372315f185de450 --- /dev/null +++ b/src/pages/home/Home.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { + EuiButton, + EuiIcon, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiSpacer, +} from '@elastic/eui'; +import logo from '../../images/logo.png'; +import useStyles from './styles'; + +export default function Home() { + const classes = useStyles(); + const { REACT_APP_VALIDATOR_URL } = process.env; + + return ( + <EuiFlexGroup + direction="column" + justifyContent="center" + alignItems="center" + style={{ minHeight: 'calc(90vh)', textAlign: 'center' }} + > + {/* Logo at the top */} + <EuiFlexItem grow={false}> + <EuiIcon type={logo} size="original" /> + </EuiFlexItem> + + {/* Main content */} + <EuiFlexItem> + <div className={classes.heroContainer}> + <EuiText> + <h1>Welcome to the Portal</h1> + <p> + This is the central hub for managing users, groups, sources, policies, + fields, and relationships between them. + </p> + </EuiText> + <EuiSpacer size="l" /> + <EuiButton color="primary" size="m" href={`${REACT_APP_VALIDATOR_URL}`} target="_blank"> + <EuiIcon type="checkInCircleFilled" size="l" /> + Validate your JSON sources + </EuiButton> + </div> + </EuiFlexItem> + </EuiFlexGroup> + ); +} diff --git a/src/pages/home/package.json b/src/pages/home/package.json new file mode 100644 index 0000000000000000000000000000000000000000..2ecfad44a8b09b088291dfe08041960bb2e75665 --- /dev/null +++ b/src/pages/home/package.json @@ -0,0 +1,7 @@ +{ + "name": "Home", + "version": "1.0.0", + "private": true, + "main": "Home.js" + } + \ No newline at end of file diff --git a/src/pages/home/styles.js b/src/pages/home/styles.js new file mode 100644 index 0000000000000000000000000000000000000000..05e123e9cbd220e3a9ff70fd12d42a755b1ed068 --- /dev/null +++ b/src/pages/home/styles.js @@ -0,0 +1,25 @@ +import { makeStyles } from '@material-ui/styles'; + +export default makeStyles((theme) => ({ + root: { + display: 'flex', + maxWidth: '100vw', + overflowX: 'hidden', + }, + content: { + flexGrow: 1, + padding: theme.spacing(3), + width: `calc(100vw - 240px)`, + minHeight: '100vh', + }, + contentShift: { + width: `calc(100vw - ${240 + theme.spacing(6)}px)`, + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + }, + fakeToolbar: { + ...theme.mixins.toolbar, + }, +})); diff --git a/src/pages/policies/AssignedPolicies.js b/src/pages/policies/AssignedPolicies.js new file mode 100644 index 0000000000000000000000000000000000000000..53ca15c49740d87e4149b4f212858e107b180cc6 --- /dev/null +++ b/src/pages/policies/AssignedPolicies.js @@ -0,0 +1,131 @@ +import React, { memo } from 'react'; +import { EuiBasicTable, EuiComboBox, EuiForm, EuiFormRow } from '@elastic/eui'; + +const AssignedPolicies = memo( + ({ + policies, + selectedPolicy, + onSelectedPolicy, + assignedPolicySources, + assignedPolicyFields, + assignedPolicyGroups, + onDeletePolicySource, + onDeletePolicyField, + onDeletePolicyGroup, + }) => { + const assignedPolicyColumns = [ + { + field: 'field_name', + name: 'Field name', + sortable: true, + }, + { + field: 'definition_and_comment', + name: 'Definition and comment', + truncateText: true, + mobileOptions: { + show: false, + }, + }, + { + field: 'field_type', + name: 'Field type', + }, + { + name: 'Actions', + actions: [ + { + name: 'Delete', + description: 'Delete this policies-field', + icon: 'trash', + type: 'icon', + color: 'danger', + onClick: async (e) => { + await onDeletePolicyField(e); + }, + }, + ], + }, + ]; + + const assignedPolicyGroupColumns = [ + { field: 'group_name', name: 'Group name' }, + { field: 'group_description', name: 'Group description' }, + { + name: 'Actions', + actions: [ + { + name: 'Delete', + description: 'Delete this policies-group', + icon: 'trash', + type: 'icon', + color: 'danger', + onClick: async (e) => { + await onDeletePolicyGroup(e); + }, + }, + ], + }, + ]; + + const assignedPolicySourceColumns = [ + { + field: 'source_name', + name: 'Source name', + }, + { + field: 'source_description', + name: 'Source description', + }, + { + name: 'Actions', + actions: [ + { + name: 'Delete', + description: 'Delete this policy-source', + icon: 'trash', + type: 'icon', + color: 'danger', + onClick: async (e) => { + await onDeletePolicySource(e); + }, + }, + ], + }, + ]; + return ( + <> + <EuiForm component="form"> + <EuiFormRow label="Select a specific policy"> + <EuiComboBox + placeholder="Select a policy" + singleSelection={true} + options={policies} + selectedOptions={selectedPolicy} + onChange={(e) => { + onSelectedPolicy(e); + }} + /> + </EuiFormRow> + <EuiFormRow label="Assigned sources" fullWidth> + <EuiBasicTable + items={assignedPolicySources} + columns={assignedPolicySourceColumns} + /> + </EuiFormRow> + <EuiFormRow label="Assigned standard fields" fullWidth> + <EuiBasicTable items={assignedPolicyFields} columns={assignedPolicyColumns} /> + </EuiFormRow> + <EuiFormRow label="Assigned groups" fullWidth> + <EuiBasicTable + items={assignedPolicyGroups} + columns={assignedPolicyGroupColumns} + /> + </EuiFormRow> + </EuiForm> + </> + ); + } +); + +export default AssignedPolicies; diff --git a/src/pages/policies/NewPolicyForm.js b/src/pages/policies/NewPolicyForm.js new file mode 100644 index 0000000000000000000000000000000000000000..6350ba18bfeea2c03cecef8b48b9312c26751af4 --- /dev/null +++ b/src/pages/policies/NewPolicyForm.js @@ -0,0 +1,59 @@ +import React, { memo } from 'react'; +import { + EuiForm, + EuiFormRow, + EuiFieldText, + EuiButton, + EuiSpacer, + EuiBasicTable, +} from '@elastic/eui'; + +const NewPolicyForm = memo( + ({ policyName, setPolicyName, onSaveNewPolicy, onDeletePolicy, policies }) => { + const policyColumns = [ + { + field: 'policyname', + name: 'Policy name', + }, + { + field: 'sourcename', + name: 'Source name', + }, + { + name: 'Actions', + actions: [ + { + name: 'Delete', + description: 'Delete this policy', + icon: 'trash', + type: 'icon', + color: 'danger', + onClick: onDeletePolicy, + }, + ], + }, + ]; + return ( + <> + <EuiForm component="form"> + <EuiFormRow label="Policy name"> + <EuiFieldText + value={policyName} + onChange={(e) => setPolicyName(e.target.value)} + /> + </EuiFormRow> + <EuiSpacer /> + <EuiButton type="submit" onClick={onSaveNewPolicy} fill> + Save + </EuiButton> + <EuiSpacer /> + <EuiFormRow label="" fullWidth> + <EuiBasicTable items={policies} rowheader="Name" columns={policyColumns} /> + </EuiFormRow> + </EuiForm> + </> + ); + } +); + +export default NewPolicyForm; diff --git a/src/pages/policies/Policies.js b/src/pages/policies/Policies.js index ab3c622cc96049aac3a8fdbc9b721b8f28cd422f..e53c0d549473e3a0238238270ba0ef0d8d1bc407 100644 --- a/src/pages/policies/Policies.js +++ b/src/pages/policies/Policies.js @@ -1,5 +1,4 @@ -import React, { useState, Fragment, useCallback, useEffect, memo } from 'react'; -import store from '../../store/index'; +import React, { useState, useCallback, useEffect } from 'react'; import { EuiForm, EuiPageContent, @@ -8,306 +7,45 @@ import { EuiTitle, EuiPageContentBody, EuiTabbedContent, - EuiFormRow, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiButton, - EuiBasicTable, - EuiSelectable, - EuiComboBox, } from '@elastic/eui'; +import PolicyAssignment from './PolicyAssignment'; +import PolicyGroupAssignment from './PolicyGroupAssignment'; +import PolicySourceAssignment from './PolicySourceAssignment'; +import NewPolicyForm from './NewPolicyForm'; +import AssignedPolicies from './AssignedPolicies'; import { ShowAlert } from '../../components/Common'; - -const NewPolicyForm = memo( - ({ - globalState, - policyName, - setPolicyName, - sources, - isSwitchChecked, - onSwitchChange, - selectedSource, - setSelectedSource, - onSaveNewPolicy, - policies, - policyColumns, - }) => { - return ( - <> - <EuiForm component="form"> - <EuiFormRow label="Policy name"> - <EuiFieldText - value={policyName} - onChange={(e) => setPolicyName(e.target.value)} - /> - </EuiFormRow> - <EuiSpacer /> - <EuiButton - type="submit" - onClick={onSaveNewPolicy} - isLoading={globalState.isLoading} - fill - > - Save - </EuiButton> - <EuiSpacer /> - <EuiFormRow label="" fullWidth> - <EuiBasicTable items={policies} rowheader="Name" columns={policyColumns} /> - </EuiFormRow> - </EuiForm> - </> - ); - } -); - -const PolicyAssignment = memo( - ({ - globalState, - optPolicies, - setOptPolicies, - optStdFields, - setOptStdFields, - onPolicyAssignment, - }) => { - return ( - <> - <EuiForm component="form"> - <EuiFlexGroup component="span"> - <EuiFlexItem component="span"> - <EuiFormRow label="Policies" fullWidth> - <EuiSelectable - searchable - singleSelection={true} - options={optPolicies} - onChange={(newOptions) => setOptPolicies(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem component="span"> - <EuiFormRow label="Standard fields" fullWidth> - <EuiSelectable - searchable - options={optStdFields} - onChange={(newOptions) => setOptStdFields(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> - <EuiButton - type="submit" - onClick={onPolicyAssignment} - isLoading={globalState.isLoading} - fill - > - Save - </EuiButton> - </EuiForm> - </> - ); - } -); - -const PolicyGroupAssignment = memo( - ({ - globalState, - optPolicies, - setOptPolicies, - optGroups, - setOPtGroups, - onPolicyGroupAssignment, - }) => { - return ( - <> - <EuiForm component="form"> - <EuiFlexGroup component="span"> - <EuiFlexItem component="span"> - <EuiFormRow label="Policies" fullWidth> - <EuiSelectable - searchable - singleSelection={true} - options={optPolicies} - onChange={(newOptions) => setOptPolicies(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem component="span"> - <EuiFormRow label="Groups" fullWidth> - <EuiSelectable - searchable - options={optGroups} - onChange={(newOptions) => setOPtGroups(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> - <EuiButton - type="submit" - onClick={onPolicyGroupAssignment} - isLoading={globalState.isLoading} - fill - > - Save - </EuiButton> - </EuiForm> - </> - ); - } -); - -const PolicySourceAssignment = memo( - ({ - globalState, - optPolicies, - setOptPolicies, - optSources, - setOptSources, - onPolicySourceAssignment, - }) => { - return ( - <> - <EuiForm component="form"> - <EuiFlexGroup component="span"> - <EuiFlexItem component="span"> - <EuiFormRow label="Policies" fullWidth> - <EuiSelectable - searchable - singleSelection={true} - options={optPolicies} - onChange={(newOptions) => setOptPolicies(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem component="span"> - <EuiFormRow label="Sources" fullWidth> - <EuiSelectable - searchable - singleSelection={true} - options={optSources} - onChange={(newOptions) => setOptSources(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> - <EuiButton - type="submit" - onClick={onPolicySourceAssignment} - isLoading={globalState.isLoading} - fill - > - Save - </EuiButton> - </EuiForm> - </> - ); - } -); - -const AssignedPolicies = memo( - ({ - policies, - selectedPolicy, - fields, - assignedPolicyColumns, - getRowProps, - getCellProps, - onSelectedPolicy, - assignedPolicyGroups, - assignedPolicyGroupColumns, - }) => { - return ( - <> - <EuiForm component="form"> - <EuiFormRow label="Select a specific policy"> - <EuiComboBox - placeholder="Select a policy" - singleSelection={{ asPlainText: true }} - options={policies} - selectedOptions={selectedPolicy} - onChange={(e) => { - onSelectedPolicy(e); - }} - /> - </EuiFormRow> - <EuiFormRow label="Assigned standard fields" fullWidth> - <EuiBasicTable - items={fields} - columns={assignedPolicyColumns} - rowProps={getRowProps} - cellProps={getCellProps} - /> - </EuiFormRow> - <EuiFormRow label="Assigned groups" fullWidth> - <EuiBasicTable - items={assignedPolicyGroups} - columns={assignedPolicyGroupColumns} - /> - </EuiFormRow> - </EuiForm> - </> - ); - } -); +import { + findUserBySub, + getSources, + getPolicies, + getStdFields, + getGroups, + createPolicy, + deletePolicy, + addSourceToPolicy, + addFieldToPolicy, + addPolicyToGroup, + removeSourceFromPolicy, + removeFieldFromPolicy, + removePolicyFromGroup, + getUser, +} from '../../services/GatekeeperService'; const Policies = () => { - const [globalState, globalActions] = store(); const [selectedTabNumber, setSelectedTabNumber] = useState(0); const [isSwitchChecked, setIsSwitchChecked] = useState(false); const [policyName, setPolicyName] = useState(''); const [selectedSource, setSelectedSource] = useState(); + const [open, setOpen] = useState(false); const [alertMessage, setAlertMessage] = useState(''); const [severity, setSeverity] = useState('info'); + const [optPolicies, setOptPolicies] = useState([]); const [selectedPolicy, setSelectedPolicy] = useState(); const [optStdFields, setOptStdFields] = useState([]); const [assignedPolicyItems, setAssignedPolicyItems] = useState([]); + const [assignedSources, setAssignedSources] = useState([]); const [policies, setPolicies] = useState([]); const [optGroupPolicies, setOptGroupPolicies] = useState([]); const [optGroups, setOPtGroups] = useState([]); @@ -316,125 +54,124 @@ const Policies = () => { const [optSources, setOptSources] = useState([]); const loadSources = useCallback(async () => { - if (sessionStorage.getItem('userId')) { - const user = await globalActions.user.findOneUserWithGroupAndRole( - sessionStorage.getItem('userId') - ); - const role = user[0].roleid; - let result; - if (role === 1) { - result = await globalActions.source.allIndexedSources(); - } else { - result = await globalActions.source.indexedSources( - sessionStorage.getItem('userId') - ); - } - if (result) { - const _sources = []; - for (const source of result) { - _sources.push({ value: source.id, label: source.name }); - } - setOptSources(_sources); + const user = await findUserBySub(getUser().profile.sub); + const role = user.roles[0].roleid; + let result; + if (role === 1) { + result = await getSources(); + } else { + result = await getSources(); // TODO load sources for user + } + if (result) { + const _sources = []; + for (const source of result) { + _sources.push({ value: source.id, label: source.name }); } + setOptSources(_sources); } - }, [globalActions.source, globalActions.user]); + }, [findUserBySub]); const loadPolicies = useCallback(async () => { - if (sessionStorage.getItem('userId')) { - const user = await globalActions.user.findOneUserWithGroupAndRole( - sessionStorage.getItem('userId') - ); - const role = user[0].roleid; - let result; - if (role === 1) { - result = await globalActions.policy.getPolicies(); - } else { - result = await globalActions.policy.getPoliciesByUser( - sessionStorage.getItem('userId') - ); - } - if (result) { - const _policies = []; - for (const policy of result) { - _policies.push({ value: policy.id, label: policy.name }); - } - setOptPolicies(_policies); - setOptGroupPolicies(_policies); - setOptSourcePolicies(_policies); - } + const user = await findUserBySub(getUser().profile.sub); + const role = user.roles[0].roleid; + let result; + if (role === 1) { + result = await getPolicies(); + } else { + result = await getPolicies(); // TODO load policies for user } - }, [globalActions.policy, globalActions.user]); - - const loadPoliciesWithSources = useCallback(async () => { - if (sessionStorage.getItem('userId')) { - const user = await globalActions.user.findOneUserWithGroupAndRole( - sessionStorage.getItem('userId') - ); - const role = user[0].roleid; - let result; - if (role === 1) { - result = await globalActions.policy.getPoliciesWithSources(); - } else { - result = await globalActions.policy.getPoliciesWithSourcesByUser( - sessionStorage.getItem('userId') - ); - } - if (result) { - const policyList = []; - const policyNames = []; - result.forEach((policy) => { - if (policyNames.includes(policy.policyname)) { - policyList[policyNames.indexOf(policy.policyname)].sourcename = - `${policyList[policyNames.indexOf(policy.policyname)].sourcename}, ${policy.sourcename}`; - } else { - policyNames.push(policy.policyname); - policyList.push(policy); - } - }); - setPolicies(policyList); + if (result) { + const _policies = []; + for (const policy of result) { + _policies.push({ value: policy.id, label: policy.name }); } + result = result.map((policy) => { + return { + id: policy.id, + policyname: policy.name, + sourcename: policy.sources.map((source) => source.source.name).join(', '), + }; + }); + setPolicies(result); + setOptPolicies(_policies); + setOptGroupPolicies(_policies); + setOptSourcePolicies(_policies); } - }, [globalActions.policy, globalActions.user]); + }, [getPolicies]); const loadStdFields = useCallback(async () => { - const result = await globalActions.source.privateFields(); + let result = await getStdFields(); if (result) { + result = result.filter((field) => !field.ispublic); const _fields = []; for (const field of result) { _fields.push({ value: field.id, label: field.field_name }); } setOptStdFields(_fields); } - }, [globalActions.source]); + }, [getStdFields]); const loadGroups = useCallback(async () => { - const _groups = await globalActions.group.getGroups(); + const _groups = await getGroups(); if (_groups) { const groups = []; for (const group of _groups) { - groups.push({ value: group.id, label: group.name }); + groups.push({ + value: group.id, + label: group.name, + description: group.description, + }); } setOPtGroups(groups); } - }, [globalActions.group]); + }, [getGroups]); useEffect(() => { - // clean up controller - let isSubscribed = true; + loadSources(); + loadPolicies(); + loadStdFields(); + loadGroups(); + onSelectedPolicy([]); + setPolicyName(''); + }, [loadGroups, loadPolicies, loadSources, loadStdFields, selectedTabNumber]); - if (isSubscribed) { - loadSources(); - loadPolicies(); - loadStdFields(); - loadPoliciesWithSources(); - loadGroups(); + const onSwitchChange = () => { + setIsSwitchChecked(!isSwitchChecked); + }; + + const onSaveNewPolicy = async (e) => { + e.preventDefault(); + if (policyName) { + const result = await createPolicy(policyName); + if (result.id) { + setAlertMessage('Policy created.'); + setSeverity('success'); + setPolicyName(''); + loadPolicies(); + } else { + setAlertMessage('Policy not created.'); + setSeverity('error'); + } + setOpen(true); } + }; - // cancel subscription to useEffect - return () => (isSubscribed = false); - }, [loadGroups, loadPolicies, loadPoliciesWithSources, loadSources, loadStdFields]); + const onDeletePolicy = async (e) => { + if (!e.id) return; + const response = await deletePolicy(e.id); + if (response && response.success) { + setAlertMessage('Policy deleted.'); + setSeverity('success'); + loadPolicies(); + } else { + setAlertMessage('Policy not deleted.'); + setSeverity('error'); + } + setOpen(true); + }; - const onPolicySourceAssignment = async () => { + const onPolicySourceAssignment = async (e) => { + e.preventDefault(); const checkedPolicies = optSourcePolicies.filter((e) => { return e.checked === 'on'; }); @@ -446,41 +183,17 @@ const Policies = () => { if (checkedPolicies && checkedSources) { for (const policy of checkedPolicies) { for (const source of checkedSources) { - await globalActions.policy.createPolicySource(policy.value, source.value); + await addSourceToPolicy(source.value, policy.value); } } - - if (globalState.status === 'SUCCESS') { - loadPoliciesWithSources(); - setOpen(true); - setAlertMessage('Policies-Source assignment completed successfully !'); - setSeverity('success'); - } - } - }; - - const onSwitchChange = () => { - setIsSwitchChecked(!isSwitchChecked); - }; - - const onSaveNewPolicy = async () => { - if (policyName && sessionStorage.getItem('userId')) { - await globalActions.policy.createPolicy( - policyName, - sessionStorage.getItem('userId') - ); - - if (globalState.status === 'SUCCESS') { - setOpen(true); - setAlertMessage('Policy created.'); - setSeverity('success'); - loadPolicies(); - loadPoliciesWithSources(); - } } + setOptSourcePolicies(optSourcePolicies.map((e) => ({ ...e, checked: null }))); + setOptSources(optSources.map((e) => ({ ...e, checked: null }))); + loadPolicies(); }; - const onPolicyAssignment = async () => { + const onPolicyAssignment = async (e) => { + e.preventDefault(); const checkedPolicies = optPolicies.filter((e) => { return e.checked === 'on'; }); @@ -488,22 +201,19 @@ const Policies = () => { const checkedStdFields = optStdFields.filter((e) => { return e.checked === 'on'; }); - for (const policy of checkedPolicies) { for (const stdField of checkedStdFields) { - //to-do: if policies is already defined, do not let insert them into table. - await globalActions.policy.createPolicyField(policy.value, stdField.value); + await addFieldToPolicy(stdField.value, policy.value); } } - if (globalState.status === 'SUCCESS') { - setOpen(true); - setAlertMessage('Policies-Assignment completed.'); - setSeverity('success'); - } + setOptPolicies(optPolicies.map((e) => ({ ...e, checked: null }))); + setOptStdFields(optStdFields.map((e) => ({ ...e, checked: null }))); + loadPolicies(); }; - const onPolicyGroupAssignment = async () => { + const onPolicyGroupAssignment = async (e) => { + e.preventDefault(); const checkedPolicies = optGroupPolicies.filter((e) => { return e.checked === 'on'; }); @@ -515,184 +225,110 @@ const Policies = () => { if (checkedPolicies && checkedGroups) { for (const group of checkedGroups) { for (const policy of checkedPolicies) { - await globalActions.group.createGroupPolicy(group.value, policy.value); + await addPolicyToGroup(policy.value, group.value); } } - if (globalState.status === 'SUCCESS') { - setOpen(true); - setAlertMessage('Policies-Group assignment completed.'); - setSeverity('success'); - } + + setOptGroupPolicies(optGroupPolicies.map((e) => ({ ...e, checked: null }))); + setOPtGroups(optGroups.map((e) => ({ ...e, checked: null }))); + loadPolicies(); } }; const onSelectedPolicy = async (e) => { if (e.length > 0) { - const aPolicies = await globalActions.policy.getAssignedPolicies(e[0].value); - const groupDetailsByPolicy = await globalActions.policy.getGroupDetailsByPolicy( - e[0].value - ); - - if (aPolicies) { - const policyItemsList = []; - const fieldNames = []; - aPolicies.forEach((aPolicy) => { - if (!fieldNames.includes(aPolicy.field_name)) { - fieldNames.push(aPolicy.field_name); - policyItemsList.push(aPolicy); - } - }); - setAssignedPolicyItems(policyItemsList); + const policies = await getPolicies(); + const selected = policies.find((policy) => { + return policy.id === e[0].value; + }); + + const policyItems = selected.std_fields.map((field) => { + return { + id: field.std_field.id, + field_name: field.std_field.field_name, + definition_and_comment: field.std_field.definition_and_comment, + field_type: field.std_field.field_type, + }; + }); + setAssignedPolicyItems(policyItems); + + let assignedSources = []; + for (const source of selected.sources) { + let assignedSource = {}; + assignedSource.id = source.source.id; + assignedSource.source_name = source.source.name; + assignedSource.source_description = source.source.description; + assignedSources.push(assignedSource); } - - if (groupDetailsByPolicy) { - const policyGroupList = []; - const groupIds = []; - groupDetailsByPolicy.forEach((groupDetail) => { - if (groupIds.includes(groupDetail.groupid)) { - policyGroupList[groupIds.indexOf(groupDetail.groupid)].source_name = - `${policyGroupList[groupIds.indexOf(groupDetail.groupid)].source_name}, ${groupDetail.source_name}`; - } else { - groupIds.push(groupDetail.groupid); - policyGroupList.push(groupDetail); - } - }); - setAssignedPolicyGroups(policyGroupList); + setAssignedSources(assignedSources); + + let assignedGroups = []; + for (const group of selected.groups) { + //console.log(group); + let assignedGroup = {}; + assignedGroup.id = group.group.id; + assignedGroup.group_name = group.group.name; + assignedGroup.group_description = group.group.description; + assignedGroups.push(assignedGroup); } - + setAssignedPolicyGroups(assignedGroups); setSelectedPolicy(e); } else { - setSelectedPolicy(e); + setSelectedPolicy([]); + setAssignedPolicyItems([]); + setAssignedSources([]); + setAssignedPolicyGroups([]); } }; - const onDeletePolicyField = async (e) => { - if (e.id) { - await globalActions.policy.deletePolicyField(e.id); - + const onDeletePolicySource = async (e) => { + if (e.id && selectedPolicy.length > 0) { + const result = await removeSourceFromPolicy(e.id, selectedPolicy[0].value); + if (result && result.success) { + setAlertMessage('Policies-Source deleted.'); + setSeverity('success'); + onSelectedPolicy(selectedPolicy); + loadPolicies(); + } else { + setAlertMessage('Policies-Source not deleted.'); + setSeverity('error'); + } setOpen(true); - setAlertMessage('Policies-Field deleted.'); - setSeverity('success'); } }; - const assignedPolicyActions = [ - { - name: 'Delete', - description: 'Delete this policies-field', - icon: 'trash', - type: 'icon', - color: 'danger', - onClick: onDeletePolicyField, - }, - ]; - - const assignedPolicyColumns = [ - { - field: 'field_name', - name: 'Field name', - sortable: true, - }, - { - field: 'definition_and_comment', - name: 'Definition and comment', - truncateText: true, - mobileOptions: { - show: false, - }, - }, - { - field: 'field_type', - name: 'Field type', - }, - { - name: 'Actions', - actions: assignedPolicyActions, - }, - ]; - - const onAssignedPolicyGroup = async (e) => { - if (e.grouppolicyid) { - await globalActions.group.deleteGroupPolicy(e.grouppolicyid); - + const onDeletePolicyField = async (e) => { + if (e.id && selectedPolicy.length > 0) { + const result = await removeFieldFromPolicy(e.id, selectedPolicy[0].value); + if (result && result.success) { + setAlertMessage('Policies-Field deleted.'); + setSeverity('success'); + onSelectedPolicy(selectedPolicy); + loadPolicies(); + } else { + setAlertMessage('Policies-Field not deleted.'); + setSeverity('error'); + } setOpen(true); - setAlertMessage('Policies-groups deleted.'); - setSeverity('success'); } }; - const assignedPolicyGroupActions = [ - { - name: 'Delete', - description: 'Delete this policies-group', - icon: 'trash', - type: 'icon', - color: 'danger', - onClick: onAssignedPolicyGroup, - }, - ]; - - const assignedPolicyGroupColumns = [ - { field: 'group_name', name: 'Group name' }, - { field: 'source_name', name: 'Source name' }, - { field: 'source_description', name: 'Source description' }, - { name: 'Actions', actions: assignedPolicyGroupActions }, - ]; - - const onDeletePolicy = async (e) => { - await globalActions.policy.deletePolicy(e.id); - if (globalState.status === 'SUCCESS') { + const onDeletePolicyGroup = async (e) => { + if (e.id && selectedPolicy.length > 0) { + const result = await removePolicyFromGroup(selectedPolicy[0].value, e.id); + if (result && result.success) { + setAlertMessage('Policies-Group deleted.'); + setSeverity('success'); + onSelectedPolicy(selectedPolicy); + loadPolicies(); + } else { + setAlertMessage('Policies-Group not deleted.'); + setSeverity('error'); + } setOpen(true); - setAlertMessage('Policy deleted.'); - setSeverity('success'); - loadPoliciesWithSources(); - loadPolicies(); } }; - const policyColumnAction = [ - { - name: 'Delete', - description: 'Delete this policy', - icon: 'trash', - type: 'icon', - color: 'danger', - onClick: onDeletePolicy, - }, - ]; - const policyColumns = [ - { - field: 'policyname', - name: 'Policy name', - }, - { - field: 'sourcename', - name: 'Source name', - }, - { - name: 'Actions', - actions: policyColumnAction, - }, - ]; - - const getRowProps = (item) => { - const { id } = item; - return { - 'data-test-subj': `row-${id}`, - className: 'customRowClass', - }; - }; - - const getCellProps = (item, column) => { - const { id } = item; - const { field } = column; - return { - className: 'customCellClass', - 'data-test-subj': `cell-${id}-${field}`, - textOnly: true, - }; - }; - const handleClose = (event, reason) => { if (reason === 'clickaway') { return; @@ -708,7 +344,6 @@ const Policies = () => { <> <br /> <NewPolicyForm - globalState={globalState} policyName={policyName} setPolicyName={setPolicyName} isSwitchChecked={isSwitchChecked} @@ -717,8 +352,8 @@ const Policies = () => { selectedSource={selectedSource} setSelectedSource={setSelectedSource} onSaveNewPolicy={onSaveNewPolicy} + onDeletePolicy={onDeletePolicy} policies={policies} - policyColumns={policyColumns} /> </> ), @@ -730,7 +365,6 @@ const Policies = () => { <> <br /> <PolicySourceAssignment - globalState={globalState} optPolicies={optSourcePolicies} setOptPolicies={setOptSourcePolicies} optSources={optSources} @@ -747,7 +381,6 @@ const Policies = () => { <> <br /> <PolicyAssignment - globalState={globalState} optPolicies={optPolicies} setOptPolicies={setOptPolicies} optStdFields={optStdFields} @@ -764,7 +397,6 @@ const Policies = () => { <> <br /> <PolicyGroupAssignment - globalState={globalState} optPolicies={optGroupPolicies} setOptPolicies={setOptGroupPolicies} optGroups={optGroups} @@ -783,13 +415,13 @@ const Policies = () => { <AssignedPolicies policies={optPolicies} selectedPolicy={selectedPolicy} - fields={assignedPolicyItems} - assignedPolicyColumns={assignedPolicyColumns} - getRowProps={getRowProps} - getCellProps={getCellProps} onSelectedPolicy={onSelectedPolicy} + assignedPolicySources={assignedSources} + assignedPolicyFields={assignedPolicyItems} assignedPolicyGroups={assignedPolicyGroups} - assignedPolicyGroupColumns={assignedPolicyGroupColumns} + onDeletePolicySource={onDeletePolicySource} + onDeletePolicyField={onDeletePolicyField} + onDeletePolicyGroup={onDeletePolicyGroup} /> </> ), diff --git a/src/pages/policies/PolicyAssignment.js b/src/pages/policies/PolicyAssignment.js new file mode 100644 index 0000000000000000000000000000000000000000..44cfbc0c121607bbb6d210c8787f9d70466f1cfa --- /dev/null +++ b/src/pages/policies/PolicyAssignment.js @@ -0,0 +1,68 @@ +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiSelectable, + EuiSpacer, +} from '@elastic/eui'; +import React, { Fragment, memo } from 'react'; + +const PolicyAssignment = memo( + ({ + optPolicies, + setOptPolicies, + optStdFields, + setOptStdFields, + onPolicyAssignment, + }) => { + return ( + <> + <EuiForm component="form"> + <EuiFlexGroup component="span"> + <EuiFlexItem component="span"> + <EuiFormRow label="Policies" fullWidth> + <EuiSelectable + searchable + singleSelection={true} + options={optPolicies} + onChange={(newOptions) => setOptPolicies(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + <EuiFlexItem component="span"> + <EuiFormRow label="Standard fields" fullWidth> + <EuiSelectable + searchable + options={optStdFields} + onChange={(newOptions) => setOptStdFields(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer /> + <EuiButton type="submit" onClick={onPolicyAssignment} fill> + Save + </EuiButton> + </EuiForm> + </> + ); + } +); + +export default PolicyAssignment; diff --git a/src/pages/policies/PolicyGroupAssignment.js b/src/pages/policies/PolicyGroupAssignment.js new file mode 100644 index 0000000000000000000000000000000000000000..7233a2b44237a3a2a408754a9a2e938a06d6bb13 --- /dev/null +++ b/src/pages/policies/PolicyGroupAssignment.js @@ -0,0 +1,62 @@ +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiSelectable, + EuiSpacer, +} from '@elastic/eui'; +import React, { Fragment, memo } from 'react'; + +const PolicyGroupAssignment = memo( + ({ optPolicies, setOptPolicies, optGroups, setOPtGroups, onPolicyGroupAssignment }) => { + return ( + <> + <EuiForm component="form"> + <EuiFlexGroup component="span"> + <EuiFlexItem component="span"> + <EuiFormRow label="Policies" fullWidth> + <EuiSelectable + searchable + singleSelection={true} + options={optPolicies} + onChange={(newOptions) => setOptPolicies(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + <EuiFlexItem component="span"> + <EuiFormRow label="Groups" fullWidth> + <EuiSelectable + searchable + options={optGroups} + onChange={(newOptions) => setOPtGroups(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer /> + <EuiButton type="submit" onClick={onPolicyGroupAssignment} fill> + Save + </EuiButton> + </EuiForm> + </> + ); + } +); + +export default PolicyGroupAssignment; diff --git a/src/pages/policies/PolicySourceAssignment.js b/src/pages/policies/PolicySourceAssignment.js new file mode 100644 index 0000000000000000000000000000000000000000..4db353b55f2a3432b06c8176ff19e059ed5cec57 --- /dev/null +++ b/src/pages/policies/PolicySourceAssignment.js @@ -0,0 +1,69 @@ +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiSelectable, + EuiSpacer, +} from '@elastic/eui'; +import React, { Fragment, memo } from 'react'; + +const PolicySourceAssignment = memo( + ({ + optPolicies, + setOptPolicies, + optSources, + setOptSources, + onPolicySourceAssignment, + }) => { + return ( + <> + <EuiForm component="form"> + <EuiFlexGroup component="span"> + <EuiFlexItem component="span"> + <EuiFormRow label="Policies" fullWidth> + <EuiSelectable + searchable + singleSelection={true} + options={optPolicies} + onChange={(newOptions) => setOptPolicies(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + <EuiFlexItem component="span"> + <EuiFormRow label="Sources" fullWidth> + <EuiSelectable + searchable + singleSelection={false} + options={optSources} + onChange={(newOptions) => setOptSources(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer /> + <EuiButton type="submit" onClick={onPolicySourceAssignment} fill> + Save + </EuiButton> + </EuiForm> + </> + ); + } +); + +export default PolicySourceAssignment; diff --git a/src/pages/requests/Requests.js b/src/pages/requests/Requests.js index 04d3fcd72645f62a93330d81cdd3000799c5fafb..5d2387bd34a7522e2de4109a3a4fed5e9c79ac70 100644 --- a/src/pages/requests/Requests.js +++ b/src/pages/requests/Requests.js @@ -1,5 +1,4 @@ import React, { useState, useCallback, useEffect, memo } from 'react'; -import store from '../../store/index'; import { ShowAlert } from '../../components/Common/Common'; import { EuiForm, @@ -12,6 +11,12 @@ import { EuiFormRow, EuiBasicTable, } from '@elastic/eui'; +import { + getRequests, + getPendingRequests, + deleteUserRequest, + updateUserRequest, +} from '../../services/GatekeeperService'; const RequestList = memo(({ requests, requestColumns }) => { return ( @@ -31,7 +36,6 @@ const RequestList = memo(({ requests, requestColumns }) => { }); const Requests = () => { - const [, globalActions] = store(); const [selectedTabNumber, setSelectedTabNumber] = useState(0); const [open, setOpen] = useState(false); const [alertMessage, setAlertMessage] = useState(''); @@ -40,64 +44,54 @@ const Requests = () => { const [pendingRequests, setPendingRequests] = useState([]); const loadRequests = useCallback(async () => { - const requestList = await globalActions.user.fetchRequests(); + const requestList = await getRequests(); if (requestList) { setRequests(requestList); } - }, [globalActions.user]); + }, [getRequests]); const loadPendingRequests = useCallback(async () => { - const requestList = await globalActions.user.fetchPendingRequests(); + const requestList = await getPendingRequests(); if (requestList) { setPendingRequests(requestList); } - }, [globalActions.user]); + }, [getPendingRequests]); useEffect(() => { - // clean up controller - let isSubscribed = true; - if (isSubscribed) { - loadRequests(); - loadPendingRequests(); - } - // cancel subscription to useEffect - return () => (isSubscribed = false); + loadRequests(); + loadPendingRequests(); }, [loadRequests, loadPendingRequests]); const onDeleteRequest = async (request) => { - if (!request) { - return; - } - const result = await globalActions.user.deleteUserRequest(request.id); - if (result && !result.error) { - setAlertMessage('Request has been deleted.'); - setSeverity('success'); - setOpen(true); - } else { - setAlertMessage(`Error: ${result.error}`); - setSeverity('error'); + if (request) { + const result = await deleteUserRequest(request.id); + if (result) { + setAlertMessage('Request has been deleted.'); + setSeverity('success'); + await loadRequests(); + await loadPendingRequests(); + } else { + setAlertMessage(`Error: ${result.error}`); + setSeverity('error'); + } setOpen(true); } - await loadRequests(); - await loadPendingRequests(); }; const onProcessRequest = async (request) => { - if (!request) { - return; - } - const result = await globalActions.user.processUserRequest(request.id); - if (result && !result.error) { - setAlertMessage('Request has been processed.'); - setSeverity('success'); - setOpen(true); - } else { - setAlertMessage(`Error: ${result.error}`); - setSeverity('error'); + if (request) { + const result = await updateUserRequest(request.id, true); + if (result) { + setAlertMessage('Request has been processed.'); + setSeverity('success'); + await loadRequests(); + await loadPendingRequests(); + } else { + setAlertMessage(`Error: ${result.error}`); + setSeverity('error'); + } setOpen(true); } - await loadRequests(); - await loadPendingRequests(); }; const requestActions = [ diff --git a/src/pages/roles/Roles.js b/src/pages/roles/Roles.js index 4706c0f98399ada46f5ceca129fa42db132bfbda..4f59de7f88f11ad3c6dcc7761269cc35aeda78db 100644 --- a/src/pages/roles/Roles.js +++ b/src/pages/roles/Roles.js @@ -1,5 +1,4 @@ import React, { useState, Fragment, useCallback, useEffect } from 'react'; -import store from '../../store/index'; import { ShowAlert } from '../../components/Common'; import { EuiForm, @@ -19,104 +18,107 @@ import { EuiSelectable, EuiComboBox, } from '@elastic/eui'; +import { + getRoles, + getUsers, + createRole, + addUserToRole, + deleteRole, +} from '../../services/GatekeeperService'; const newRoleForm = ({ roleNameValue, setRoleNameValue, roleDescriptionValue, setRoleDescriptionValue, - globalState, + roles, onSaveRole, roleColumns, }) => { return ( - <> - <EuiForm component="form"> - <EuiFormRow label="Name"> - <EuiFieldText - value={roleNameValue} - onChange={(e) => setRoleNameValue(e.target.value)} - /> - </EuiFormRow> - <EuiFormRow label="Description"> - <EuiFieldText - value={roleDescriptionValue} - onChange={(e) => setRoleDescriptionValue(e.target.value)} - /> - </EuiFormRow> - <EuiSpacer /> - { - <EuiButton fill onClick={onSaveRole} isLoading={globalState.isLoading}> - Save - </EuiButton> - } - <EuiSpacer /> - <EuiFormRow label="" fullWidth> - <EuiBasicTable - items={globalState.roles} - rowheader="Name" - columns={roleColumns} - // rowProps={getRowProps} - // cellProps={getCellProps} - /> - </EuiFormRow> - </EuiForm> - </> + roles && + roles.length > 0 && ( + <> + <EuiForm component="form"> + <EuiFormRow label="Name"> + <EuiFieldText + value={roleNameValue || ''} + onChange={(e) => setRoleNameValue(e.target.value)} + /> + </EuiFormRow> + <EuiFormRow label="Description"> + <EuiFieldText + value={roleDescriptionValue || ''} + onChange={(e) => setRoleDescriptionValue(e.target.value)} + /> + </EuiFormRow> + <EuiSpacer /> + { + <EuiButton fill onClick={onSaveRole}> + Save + </EuiButton> + } + <EuiSpacer /> + <EuiFormRow label="" fullWidth> + <EuiBasicTable items={roles} rowheader="Name" columns={roleColumns} /> + </EuiFormRow> + </EuiForm> + </> + ) ); }; -const roleAssignment = ( - globalState, - roles, - setRoles, - users, - setUsers, - onRoleAssignment -) => { + +const roleAssignment = (roles, setRoles, users, setUsers, onRoleAssignment) => { return ( - <> - <EuiForm component="form"> - <EuiFlexGroup component="span"> - <EuiFlexItem component="span"> - <EuiFormRow label="Roles" fullWidth> - <EuiSelectable - searchable - singleSelection={true} - options={roles} - onChange={(newOptions) => setRoles(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem component="span"> - <EuiFormRow label="Users" fullWidth> - <EuiSelectable - searchable - options={users} - onChange={(newOptions) => setUsers(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> + roles && + users && + roles.length > 0 && + users.length > 0 && ( + <> + <EuiForm component="form"> + <EuiFlexGroup component="span"> + <EuiFlexItem component="span"> + <EuiFormRow label="Roles" fullWidth> + <EuiSelectable + searchable + singleSelection={true} + options={roles} + onChange={(newOptions) => setRoles(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + <EuiFlexItem component="span"> + <EuiFormRow label="Users" fullWidth> + <EuiSelectable + searchable + options={users} + onChange={(newOptions) => setUsers(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer /> - <EuiButton type="submit" onClick={onRoleAssignment} fill> - Save - </EuiButton> - </EuiForm> - </> + <EuiButton type="submit" onClick={onRoleAssignment} fill> + Save + </EuiButton> + </EuiForm> + </> + ) ); }; const assignedRoles = ({ @@ -132,7 +134,7 @@ const assignedRoles = ({ <EuiFormRow label="Select specific roles"> <EuiComboBox placeholder="Select a role" - singleSelection={{ asPlainText: true }} + singleSelection={true} options={roles} selectedOptions={selectedRole} onChange={(e) => { @@ -145,8 +147,6 @@ const assignedRoles = ({ items={assignedRoleToUsers} rowheader="" columns={assignedRolesColumns} - // rowProps={getRowProps} - // cellProps={getCellProps} /> </EuiFormRow> </EuiForm> @@ -155,56 +155,71 @@ const assignedRoles = ({ }; const Roles = () => { - const [globalState, globalActions] = store(); const [selectedTabNumber, setSelectedTabNumber] = useState(0); const [roleNameValue, setRoleNameValue] = useState(); const [roleDescriptionValue, setRoleDescriptionValue] = useState(); const [roles, setRoles] = useState([]); const [users, setUsers] = useState([]); - const [open, setOpen] = useState(false); + const [alertMessage, setAlertMessage] = useState(''); const [severity, setSeverity] = useState('info'); + const [open, setOpen] = useState(false); + const [selectedRole, setSelectedRole] = useState(); const [assignedRoleToUsers, setAssignedRoleToUsers] = useState([]); const loads = useCallback(async () => { - const roles = await globalActions.user.findRole(); - const users = await globalActions.user.findUser(); - if (globalState.user.id) { - await globalActions.user.allocatedRoles(globalState.user.id); - } + const roles = await getRoles(); + const users = await getUsers(); if (roles && users) { const _roles = []; for (const role of roles) { - _roles.push({ value: role.id, label: role.name }); + _roles.push({ + value: role.id, + label: role.name, + description: role.description, + users: role.users, + }); } setRoles(_roles); const _users = []; for (const user of users) { - _users.push({ value: user.id, label: user.username }); + _users.push({ value: user.id, label: user.email, sub: user.kc_id }); } setUsers(_users); } - }, [globalActions.user, globalState.user.id]); + }, []); useEffect(() => { - // clean up controller - let isSubscribed = true; - if (isSubscribed) { - loads(); - } - // cancel subscription to useEffect - return () => (isSubscribed = false); + setSelectedRole([]); + setAssignedRoleToUsers([]); + loads(); + }, [loads, selectedTabNumber]); + + useEffect(() => { + loads(); }, [loads]); - const onSaveRole = async () => { - await globalActions.user.createRole(roleNameValue, roleDescriptionValue); - await globalActions.user.findRole(); + const onSaveRole = async (e) => { + e.preventDefault(); + const result = await createRole(roleNameValue, roleDescriptionValue); + if (result) { + setAlertMessage('Role has been created.'); + setSeverity('success'); + } else { + setAlertMessage('Role has not been created.'); + setSeverity('error'); + } + setOpen(true); + setRoleNameValue(''); + setRoleDescriptionValue(''); + await loads(); }; - const onRoleAssignment = async () => { + const onRoleAssignment = async (e) => { + e.preventDefault(); const checkedRoles = roles.filter((e) => { return e.checked === 'on'; }); @@ -215,37 +230,61 @@ const Roles = () => { for (const user of checkedUsers) { for (const role of checkedRoles) { - await globalActions.user.allocateRolesToUser(user.value, role.value); + await addUserToRole(user.sub, role.value); } } + await loads(); + setRoles(roles.map((role) => ({ ...role, checked: null }))); + setUsers(users.map((user) => ({ ...user, checked: null }))); + }; - if (globalState.status === 'SUCCESS') { - setOpen(true); - setAlertMessage('Role assignment was completed.'); + const removeUserFromRole = async (e) => { + const result = await removeUserFromRole(e.sub, e.roleId); + if (result) { + setAlertMessage('User has been removed from role.'); setSeverity('success'); + } else { + setAlertMessage('User has not been removed from role.'); + setSeverity('error'); } + setOpen(true); + onSelectedRole([]); + await loads(); }; const onSelectedRole = async (e) => { if (e.length > 0) { setSelectedRole(e); - const assignedUserByRoleItems = await globalActions.user.getAssignedUserByRole( - e[0].value - ); + const assignedUserByRoleItems = roles + .find((role) => { + return role.value === e[0].value; + }) + .users.map((user) => { + return { + email: user.user.email, + rolename: e[0].label, + roleId: e[0].value, + sub: user.user.kc_id, + }; + }); setAssignedRoleToUsers(assignedUserByRoleItems); } else { - setSelectedRole(e); + setSelectedRole([]); + setAssignedRoleToUsers([]); } }; const onDeleteRole = async (e) => { - await globalActions.user.deleteRole(e.id); - if (globalState.status === 'SUCCESS') { - setOpen(true); + const result = await deleteRole(e.value); + if (result) { setAlertMessage('Role has been deleted.'); setSeverity('success'); - await globalActions.user.findRole(); + } else { + setAlertMessage('Role has not been deleted.'); + setSeverity('error'); } + setOpen(true); + await loads(); }; const roleActions = [ @@ -258,17 +297,28 @@ const Roles = () => { onClick: onDeleteRole, }, ]; + + const assignedRoleActions = [ + { + name: 'Remove', + description: 'Remove this user from this role', + icon: 'trash', + type: 'icon', + color: 'danger', + onClick: removeUserFromRole, + }, + ]; + const roleColumns = [ - { field: 'name', name: 'Name' }, + { field: 'label', name: 'Name' }, { field: 'description', name: 'Description' }, { name: 'Actions', actions: roleActions }, ]; const assignedRolesColumns = [ - { field: 'username', name: 'Username' }, - { field: 'useremail', name: 'E-mail' }, + { field: 'email', name: 'E-mail' }, { field: 'rolename', name: 'Role' }, - { name: 'Actions', actions: roleActions }, + { name: 'Actions', actions: assignedRoleActions }, ]; const handleClose = (event, reason) => { @@ -290,7 +340,7 @@ const Roles = () => { setRoleNameValue, roleDescriptionValue, setRoleDescriptionValue, - globalState, + roles, onSaveRole, roleColumns, })} @@ -303,14 +353,7 @@ const Roles = () => { content: ( <> <br /> - {roleAssignment( - globalState, - roles, - setRoles, - users, - setUsers, - onRoleAssignment - )} + {roleAssignment(roles, setRoles, users, setUsers, onRoleAssignment)} </> ), }, diff --git a/src/pages/sources/Sources.js b/src/pages/sources/Sources.js index 97187650b81164ba2d4c26f1b1dd5b553e3f624e..11a8d3d7ca0d5b1c18ced34ed1b84b3814a69db5 100644 --- a/src/pages/sources/Sources.js +++ b/src/pages/sources/Sources.js @@ -1,5 +1,4 @@ import React, { useState, useCallback, useEffect, memo, useRef } from 'react'; -import store from '../../store/index'; import { EuiForm, EuiPageContent, @@ -18,7 +17,6 @@ import { EuiFilePicker, EuiBasicTable, EuiHealth, - EuiProgress, EuiConfirmModal, EuiOverlayMask, EuiModal, @@ -31,11 +29,17 @@ import { } from '@elastic/eui'; import { ShowAlert } from '../../components/Common'; -import JsonView from '@in-sylva/json-view'; +import ReactJson from '@microlink/react-json-view'; +import { + getUser, + deleteSource, + createSource, + findUserBySub, + getSources, +} from '../../services/GatekeeperService'; const NewSourceForm = memo( ({ - globalState, nameValue, setNameValue, descriptionValue, @@ -55,14 +59,14 @@ const NewSourceForm = memo( <EuiFormRow label="Source or file name"> <EuiFieldText id="nameValue" - value={nameValue} + value={nameValue || ''} onChange={(e) => setNameValue(e.target.value)} /> </EuiFormRow> <EuiFormRow label="Source description"> <EuiFieldText id="descriptionValue" - value={descriptionValue} + value={descriptionValue || ''} onChange={(e) => setDescriptionValue(e.target.value)} /> </EuiFormRow> @@ -89,7 +93,7 @@ const NewSourceForm = memo( <EuiButton fill onClick={onSaveSource} - isLoading={globalState.isLoading} + disabled={!nameValue || !descriptionValue || !metaUrfms} > Save </EuiButton> @@ -99,7 +103,7 @@ const NewSourceForm = memo( <EuiFlexItem grow={3}> <> <br /> - <JsonView + <ReactJson name="Metadata records" collapsed={true} iconStyle={'triangle'} @@ -152,22 +156,6 @@ const SourcesForm = memo( } ); -const ShareSources = memo(() => { - return ( - <> - <br /> - </> - ); -}); - -const SharedSources = memo(() => { - return ( - <> - <br /> - </> - ); -}); - const renderFiles = (files) => { if (files.length > 0) { return ( @@ -185,9 +173,7 @@ const renderFiles = (files) => { }; const Sources = () => { const [metaUrfms, setMetaUrfms] = useState([]); - // const [metaUrfmsCol, setMetaUrfmsCol] = useState([]) const [selectedTabNumber, setSelectedTabNumber] = useState(0); - const [globalState, globalActions] = store(); const [files, setFiles] = useState([]); const [nameValue, setNameValue] = useState(); const [descriptionValue, setDescriptionValue] = useState(); @@ -246,19 +232,11 @@ const Sources = () => { pageSizeOptions: [3, 5, 8], }; - // const unsentSources = (typeof sources === ([] || null) ? [] : sources.filter(e => { return e.is_send === false })) - const selection = { selectable: (source) => !source.is_send, onSelectionChange: onSelectionChange, - // initialSelected: unsentSources, }; - /* - const onSelection = () => { - tableref.current.setSelection(typeof sources === ([] || null) ? [] : sources.filter(e => { return e.is_send === false })); - }; */ - const sorting = { sort: { field: sortField, @@ -280,14 +258,11 @@ const Sources = () => { const onDeleteSourceConfirm = async () => { closeModal(); - if (source.id && sessionStorage.getItem('userId')) { - await globalActions.source.deleteSource( - source.id, - sessionStorage.getItem('userId') - ); - setOpen(true); + if (source.id) { + const response = await deleteSource(source.id); setAlertMessage('Source is deleted successfully'); setSeverity('success'); + setOpen(true); await loadSources(); } else { @@ -301,51 +276,48 @@ const Sources = () => { const onMergeAndSendSourcesConfirmed = async () => { if (sourceName && sourceDescription && selectedSources) { - const kcId = sessionStorage.getItem('userId'); - const result = await globalActions.source.mergeAndSendSource( - kcId, - sourceName, - sourceDescription, - selectedSources - ); - if (result.status === 201) { - setOpen(true); - setAlertMessage('Sources are merged and send to elasticsearch successfully !'); - setSeverity('success'); - - await loadSources(); - } else { - setOpen(true); - setAlertMessage('While merging the sources, unexpected error has been occurred.'); - setSeverity('error'); - } - closeMergeSourceModal(); + //const kcId = sessionStorage.getItem('userId'); + // const result = await globalActions.source.mergeAndSendSource( + // kcId, + // sourceName, + // sourceDescription, + // selectedSources + // ); + // if (result.status === 201) { + // setOpen(true); + // setAlertMessage('Sources are merged and send to elasticsearch successfully !'); + // setSeverity('success'); + // await loadSources(); + // } else { + // setOpen(true); + // setAlertMessage('While merging the sources, unexpected error has been occurred.'); + // setSeverity('error'); + // } + // closeMergeSourceModal(); } }; - const updateSource = async (kc_id, source_id) => { - await globalActions.source.updateSource(kc_id, source_id); - }; + // const updateSource = async (kc_id, source_id) => { + // await globalActions.source.updateSource(kc_id, source_id); + // }; // import source document to elasticsearch const onSendSource = async (source) => { if (sessionStorage.getItem('userId') && source) { - const kc_id = sessionStorage.getItem('userId'); - const source_id = source.id; - - await updateSource(kc_id, source_id); - if (globalState.status === 'SUCCESS') { - setOpen(true); - setAlertMessage('Source imported to elastic stack successfully'); - setSeverity('success'); - - await loadSources(); - } else { - setOpen(true); - setAlertMessage( - 'While importation the sources to elastic stack, unexpected error has been occurred.' - ); - setSeverity('error'); - } + // const kc_id = sessionStorage.getItem('userId'); + // const source_id = source.id; + // const updatedSource = await updateSource(kc_id, source_id); + // if (updatedSource) { + // setOpen(true); + // setAlertMessage('Source imported to elastic stack successfully'); + // setSeverity('success'); + // await loadSources(); + // } else { + // setOpen(true); + // setAlertMessage( + // 'While importation the sources to elastic stack, unexpected error has been occurred.' + // ); + // setSeverity('error'); + // } } }; @@ -387,8 +359,13 @@ const Sources = () => { ]; const onFilePickerChange = async (files) => { - setFiles(files); - await handleSelectedFile(files); + if (files.length > 0) { + setFiles(files); + await handleSelectedFile(files); + } else { + setFiles([]); + setMetaUrfms([]); + } }; const handleSelectedFile = async (files) => { @@ -401,47 +378,24 @@ const Sources = () => { const handleData = (file) => { const { metadataRecords } = JSON.parse(file); - if (metadataRecords) { - const columns = []; - const rows = []; - - const prop = Object.keys(metadataRecords[0]); - - prop.forEach((item) => { - const column = { - name: item, - options: { - display: true, - }, - }; - columns.push(column); - }); - - metadataRecords.forEach((row) => { - rows.push(row); - }); - - if (columns && rows) { - // setMetaUrfmsCol(columns) - setMetaUrfms(rows); - } + setMetaUrfms(metadataRecords); } }; - const onSaveSource = async () => { + const onSaveSource = async (e) => { + e.preventDefault(); if (nameValue && descriptionValue && metaUrfms) { - await globalActions.source.createSource( - metaUrfms, - nameValue, - descriptionValue, - sessionStorage.getItem('userId') - ); + const result = await createSource(metaUrfms, nameValue, descriptionValue); - if (globalState.status === 'SUCCESS') { + if (result?.id) { setOpen(true); setAlertMessage('Source created.'); setSeverity('success'); + setNameValue(''); + setDescriptionValue(''); + setFiles([]); + setMetaUrfms([]); await loadSources(); } else { @@ -453,22 +407,30 @@ const Sources = () => { }; const loadSources = useCallback(async () => { - if (sessionStorage.getItem('userId')) { - const user = await globalActions.user.findOneUserWithGroupAndRole( - sessionStorage.getItem('userId') - ); - const role = user[0].roleid; - let result; - if (role === 1) { - result = await globalActions.source.allSources(); - } else { - result = await globalActions.source.sources(sessionStorage.getItem('userId')); - } - if (result) { - setSources(result); - } + const userInfo = getUser(); + const sub = userInfo.profile?.sub; + const user = await findUserBySub(sub); + const role = user.roles[0].role_id; + let result; + if (role === 1) { + result = await getSources(); + } else { + result = await getSources(); + } + if (result) { + result = result.map((source) => { + return { + id: source.id, + name: source.name, + description: source.description, + index_id: source.source_indices[0].index_id, + mng_id: source.source_indices[0].mng_id, + is_send: source.source_indices[0].is_send, + }; + }); + setSources(result); } - }, [globalActions.source, globalActions.user]); + }, [getSources, getUser, findUserBySub]); useEffect(() => { // clean up controller @@ -499,7 +461,6 @@ const Sources = () => { setNameValue={setNameValue} descriptionValue={descriptionValue} setDescriptionValue={setDescriptionValue} - globalState={globalState} onFilePickerChange={onFilePickerChange} files={files} renderFiles={renderFiles} @@ -527,24 +488,6 @@ const Sources = () => { </> ), }, - { - id: 'tab3', - name: 'Share sources', - content: ( - <> - <ShareSources /> - </> - ), - }, - { - id: 'tab4', - name: 'Shared sources', - content: ( - <> - <SharedSources /> - </> - ), - }, ]; const mergeModalForm = ( @@ -637,9 +580,6 @@ const Sources = () => { /> </EuiForm> </EuiForm> - {globalState.isLoading && ( - <EuiProgress postion="fixed" size="l" color="accent" /> - )} </EuiPageContentBody> </EuiPageContent> {ShowAlert(open, handleClose, alertMessage, severity)} diff --git a/src/pages/users/Users.js b/src/pages/users/Users.js index 2ccf6fdf3062db40555dd6f3a6b055c7acf3e104..49100005c0a3b9cd178c5dd85ff5d12b3a54fec8 100644 --- a/src/pages/users/Users.js +++ b/src/pages/users/Users.js @@ -1,5 +1,4 @@ -import React, { useState, useCallback, useEffect, memo, useRef } from 'react'; -import store from '../../store/index'; +import React, { useState, useEffect, useCallback, memo, useRef } from 'react'; import { ShowAlert } from '../../components/Common'; import { EuiForm, @@ -8,70 +7,10 @@ import { EuiPageContentHeaderSection, EuiTitle, EuiPageContentBody, - EuiTabbedContent, EuiFormRow, - EuiFieldText, - EuiFieldPassword, - EuiSelect, - EuiButton, EuiBasicTable, } from '@elastic/eui'; - -const NewUserForm = memo( - ({ - globalState, - usernameValue, - setUsername, - passwordValue, - setPasswordValue, - email, - setEmail, - roles, - selectedRole, - setSelectedRole, - onSaveUser, - }) => { - return ( - <> - <EuiForm component="form"> - <EuiFormRow label="Username"> - <EuiFieldText - value={usernameValue} - onChange={(e) => setUsername(e.target.value)} - /> - </EuiFormRow> - <EuiFormRow label="Password"> - <EuiFieldPassword - fullWidth - placeholder="Password" - type={'dual'} - value={passwordValue} - onChange={(e) => setPasswordValue(e.target.value)} - aria-label="Use aria labels when no actual label is in use" - /> - </EuiFormRow> - <EuiFormRow label="e-mail"> - <EuiFieldText value={email} onChange={(e) => setEmail(e.target.value)} /> - </EuiFormRow> - <EuiFormRow label="Select specific roles"> - <EuiSelect - options={roles} - value={selectedRole} - onChange={(e) => setSelectedRole(e.target.value)} - /> - </EuiFormRow> - <EuiFormRow label=""> - { - <EuiButton fill onClick={onSaveUser} isLoading={globalState.isLoading}> - Save - </EuiButton> - } - </EuiFormRow> - </EuiForm> - </> - ); - } -); +import { getUsers, deleteUser } from '../../services/GatekeeperService'; const UserList = memo( ({ users, userColumns, onTableChange, tableRef, pagination, sorting }) => { @@ -88,7 +27,6 @@ const UserList = memo( ref={tableRef} pagination={pagination} sorting={sorting} - // selection={selection} /> </EuiFormRow> </EuiForm> @@ -98,17 +36,10 @@ const UserList = memo( ); const Users = () => { - const [globalState, globalActions] = store(); - const [selectedTabNumber, setSelectedTabNumber] = useState(0); const [open, setOpen] = useState(false); const [alertMessage, setAlertMessage] = useState(''); const [severity, setSeverity] = useState('info'); - const [dual] = useState(true); - const [passwordValue, setPasswordValue] = useState(''); - const [usernameValue, setUsername] = useState(''); - const [email, setEmail] = useState(''); - const [roles, setRoles] = useState([{ value: 0, text: 'select a role' }]); - const [selectedRole, setSelectedRole] = useState(); + const [users, setUsers] = useState([]); const [sortDirection, setSortDirection] = useState('asc'); const [sortField, setSortField] = useState('username'); @@ -116,69 +47,46 @@ const Users = () => { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(5); - const loadRoles = useCallback(async () => { - const _roles = []; - const roles = await globalActions.user.findRole(); - if (roles) { - for (const role of roles) { - _roles.push({ value: role.id, text: role.name }); - } - } - setRoles((prevRoles) => [...prevRoles, ..._roles]); - }, [globalActions.user]); - const loadUsers = useCallback(async () => { - const users = await globalActions.user.usersWithGroupAndRole(); - - if (users) { - const userList = []; - const userNameList = []; - users.forEach((user) => { - if (userNameList.includes(user.username)) { - userList[userNameList.indexOf(user.username)].groupname = - `${userList[userNameList.indexOf(user.username)].groupname}, ${user.groupname}`; - } else { - userNameList.push(user.username); - userList.push(user); - } + const response = await getUsers(); + if (response) { + const users = response.map((user) => { + return { + id: user.kc_id, + email: user.email, + groupname: user.groups + .map((element) => { + const { group } = element; + return group.name; + }) + .join(', '), + rolename: user.roles.map((element) => { + const { role } = element; + return role.name; + }), + }; }); - setUsers(userList); + setUsers(users); } - }, [globalActions.user]); + }, [getUsers]); useEffect(() => { - // clean up controller - let isSubscribed = true; - if (isSubscribed) { - loadRoles(); - loadUsers(); - } - // cancel subscription to useEffect - return () => (isSubscribed = false); - }, [loadRoles, loadUsers]); - - const onSaveUser = async () => { - if (usernameValue && passwordValue && email && selectedRole) { - await globalActions.user.createUser( - usernameValue, - email, - passwordValue, - selectedRole - ); - setOpen(true); - setAlertMessage('User has been created.'); - setSeverity('success'); - loadUsers(); - } - }; - - const onDeleteUser = async (user) => { - if (user) { - await globalActions.user.deleteUser(user.kc_id); - setOpen(true); - setAlertMessage('Users has been deleted.'); - setSeverity('success'); - await loadUsers(); + loadUsers(); + }, [loadUsers]); + + const onDeleteUser = async (id) => { + if (id) { + const response = await deleteUser(id); + if (!response) { + setAlertMessage('User has not been deleted.'); + setSeverity('error'); + setOpen(true); + } else { + setAlertMessage('User has been deleted.'); + setSeverity('success'); + setOpen(true); + await loadUsers(); + } } }; @@ -189,20 +97,12 @@ const Users = () => { icon: 'trash', type: 'icon', color: 'danger', - onClick: onDeleteUser, + onClick: async (user) => { + onDeleteUser(user.id); + }, }, ]; - const onTableChange = ({ page = {}, sort = {} }) => { - const { index: pageIndex, size: pageSize } = page; - - const { field: sortField, direction: sortDirection } = sort; - - setPageIndex(pageIndex); - setPageSize(pageSize); - setSortField(sortField); - setSortDirection(sortDirection); - }; const totalItemCount = typeof USERS === typeof undefined ? 0 : users.length; const pagination = { @@ -220,56 +120,22 @@ const Users = () => { }; const userColumns = [ - { field: 'username', name: 'Username' }, { field: 'email', name: 'Email' }, - { field: 'groupname', name: 'Group' }, - { field: 'rolename', name: 'Role' }, - { field: 'roledescription', name: 'Role description' }, + { field: 'groupname', name: 'Groups' }, + { field: 'rolename', name: 'Roles' }, { name: 'Actions', actions: userActions }, ]; - const tabContents = [ - { - id: 'tab1', - name: 'New user', - content: ( - <> - <br /> - <NewUserForm - globalState={globalState} - dual={dual} - usernameValue={usernameValue} - setUsername={setUsername} - passwordValue={passwordValue} - setPasswordValue={setPasswordValue} - email={email} - setEmail={setEmail} - onSaveUser={onSaveUser} - selectedRole={selectedRole} - setSelectedRole={setSelectedRole} - roles={roles} - /> - </> - ), - }, - { - id: 'tab2', - name: 'Users list', - content: ( - <> - <br /> - <UserList - users={users} - userColumns={userColumns} - onTableChange={onTableChange} - tableRef={tableRef} - pagination={pagination} - sorting={sorting} - /> - </> - ), - }, - ]; + const onTableChange = ({ page = {}, sort = {} }) => { + const { index: pageIndex, size: pageSize } = page; + + const { field: sortField, direction: sortDirection } = sort; + + setPageIndex(pageIndex); + setPageSize(pageSize); + setSortField(sortField); + setSortDirection(sortDirection); + }; const handleClose = (event, reason) => { if (reason === 'clickaway') { @@ -289,12 +155,13 @@ const Users = () => { </EuiPageContentHeader> <EuiPageContentBody> <EuiForm> - <EuiTabbedContent - tabs={tabContents} - selectedTab={tabContents[selectedTabNumber]} - onTabClick={(tab) => { - setSelectedTabNumber(tabContents.indexOf(tab)); - }} + <UserList + users={users} + userColumns={userColumns} + onTableChange={onTableChange} + tableRef={tableRef} + pagination={pagination} + sorting={sorting} /> </EuiForm> </EuiPageContentBody> diff --git a/src/services/GatekeeperService.js b/src/services/GatekeeperService.js new file mode 100644 index 0000000000000000000000000000000000000000..3563f9bde3f381015d202a921d9124efd0dec17d --- /dev/null +++ b/src/services/GatekeeperService.js @@ -0,0 +1,374 @@ +import { User, UserManager, WebStorageStateStore } from 'oidc-client-ts'; + +export const userManager = new UserManager({ + authority: process.env.REACT_APP_KEYCLOAK_BASE_URL, + client_id: process.env.REACT_APP_KEYCLOAK_CLIENT_ID, + client_secret: process.env.REACT_APP_KEYCLOAK_CLIENT_SECRET, + redirect_uri: process.env.REACT_APP_BASE_URL, + userStore: new WebStorageStateStore({ store: window.localStorage }), +}); + +export function getUser() { + const oidcStorage = localStorage.getItem( + `oidc.user:${process.env.REACT_APP_KEYCLOAK_BASE_URL}:${process.env.REACT_APP_KEYCLOAK_CLIENT_ID}` + ); + if (!oidcStorage) { + return null; + } + + return User.fromStorageString(oidcStorage); +} + +const get = async (path, payload) => { + const user = getUser(); + const access_token = user?.access_token; + const headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${access_token}`, + }; + const response = await fetch(`${process.env.REACT_APP_GATEKEEPER_BASE_URL}${path}`, { + method: 'GET', + headers, + mode: 'cors', + body: JSON.stringify(payload), + }); + if (response.status === 401) { + try { + await userManager.signinSilent(); + return await this.get(path, payload); + } catch (error) { + const currentHash = window.location.hash; + await userManager.signoutRedirect({ + post_logout_redirect_uri: `${process.env.REACT_APP_BASE_URL}${currentHash}`, + }); + } + } + return await response.json(); +}; + +const post = async (path, payload) => { + const user = getUser(); + const access_token = user?.access_token; + const headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${access_token}`, + }; + const response = await fetch(`${process.env.REACT_APP_GATEKEEPER_BASE_URL}${path}`, { + method: 'POST', + headers, + body: JSON.stringify(payload), + mode: 'cors', + }); + if (response.status === 401) { + try { + await userManager.signinSilent(); + return await this.post(path, payload); + } catch (error) { + const currentHash = window.location.hash; + await userManager.signoutRedirect({ + post_logout_redirect_uri: `${process.env.REACT_APP_BASE_URL}${currentHash}`, + }); + } + } + return await response.json(); +}; + +const deleteRequest = async (path, payload) => { + const user = getUser(); + const access_token = user?.access_token; + const headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${access_token}`, + }; + const response = await fetch(`${process.env.REACT_APP_GATEKEEPER_BASE_URL}${path}`, { + method: 'DELETE', + headers, + body: JSON.stringify(payload), + mode: 'cors', + }); + if (response.status === 401) { + try { + await userManager.signinSilent(); + return await this.post(path, payload); + } catch (error) { + const currentHash = window.location.hash; + await userManager.signoutRedirect({ + post_logout_redirect_uri: `${process.env.REACT_APP_BASE_URL}${currentHash}`, + }); + } + } + if (response.status === 204) { + return { + success: true, + message: 'Request deleted successfully', + }; + } else { + return { + success: false, + message: 'Request not deleted', + }; + } +}; + +export const createUser = async (sub, email) => { + const path = `/users`; + return await post(path, { + kc_id: sub, + email, + }); +}; + +export const deleteUser = async (sub) => { + const path = `/users/${sub}`; + return await deleteRequest(path, {}); +}; + +export const getUsers = async () => { + const path = `/users`; + return await get(path); +}; + +export const findUserBySub = async (sub) => { + const path = `/users/${sub}`; + return await get(path); +}; + +export const getRoles = async () => { + const path = `/roles`; + return await get(path); +}; + +export const createRole = async (name, description) => { + const path = `/roles`; + return await post(path, { + name: name, + description: description, + }); +}; + +export const addUserToRole = async (sub, role_id) => { + const path = `/roles/${role_id}/users`; + return await post(path, { + kc_id: sub, + }); +}; + +export const removeUserFromRole = async (sub, role_id) => { + const path = `/roles/${role_id}/users/${sub}`; + return await deleteRequest(path, {}); +}; + +export const deleteRole = async (id) => { + const path = `/roles/${id}`; + return await deleteRequest(path, {}); +}; + +export const createUserRequest = async (message, sub) => { + const path = `/users/${sub}/requests`; + return await post(path, { + message: message, + }); +}; + +export const deleteUserRequest = async (id) => { + const path = `/user-requests/${id}`; + return await deleteRequest(path, {}); +}; + +export const getGroups = async () => { + const path = `/groups`; + return await get(path); +}; + +export const createGroup = async (name, description) => { + const path = `/groups`; + const user = getUser(); + const sub = user?.profile?.sub; + return await post(path, { + kc_id: sub, + name: name, + description: description, + }); +}; + +export const deleteGroup = async (id) => { + const path = `/groups/${id}`; + return await deleteRequest(path, {}); +}; + +export const addUserToGroup = async (sub, group_id) => { + const path = `/groups/${group_id}/users`; + return await post(path, { + kc_id: sub, + }); +}; + +export const removeUserFromGroup = async (sub, group_id) => { + const path = `/groups/${group_id}/users/${sub}`; + return await deleteRequest(path, {}); +}; + +export const getUserFieldsDisplaySettings = async (sub) => { + const path = `/users/${sub}/fields`; + return await get(path); +}; + +export const setUserFieldsDisplaySettings = async (sub, fields) => { + const path = `/users/${sub}/fields`; + return await post(path, { + fields_id: fields, + }); +}; + +export const createSource = async (metaUrfms, name, description) => { + const path = `/sources`; + const user = getUser(); + const sub = user.profile?.sub; + return await post(path, { + name: name, + description: description, + metaUrfms: metaUrfms, + kc_id: sub, + }); +}; + +export const getSources = async () => { + const path = `/sources`; + return await get(path); +}; + +export const deleteSource = async (source_id) => { + const path = `/sources/${source_id}`; + return await deleteRequest(path, {}); +}; + +export const getRequests = async () => { + const path = `/user-requests`; + return await get(path); +}; + +export const getPendingRequests = async () => { + const path = `/user-requests/pending`; + return await get(path); +}; + +export const deleteRequestById = async (id) => { + const path = `/user-requests/${id}`; + return await deleteRequest(path, {}); +}; + +export const updateUserRequest = async (id, is_processed) => { + const path = `/user-requests/${id}`; + return await post(path, { + is_processed: is_processed, + }); +}; + +export const getStdFields = async () => { + const path = `/std_fields`; + return await get(path); +}; + +export const createStdField = async ( + cardinality, + category, + definition_and_comment, + field_name, + field_type, + is_optional, + is_public, + obligation_or_condition, + values, + list_url, + default_display_fields +) => { + const path = `/std_fields`; + return await post(path, { + cardinality, + category, + definition_and_comment, + field_name, + field_type, + isoptional: is_optional, + ispublic: is_public, + obligation_or_condition, + values, + list_url, + default_display_fields + }); +}; + +export const addFieldToPolicy = async (field_id, policy_id) => { + const path = `/policies/${policy_id}/std_fields`; + return await post(path, { + field_id, + }); +}; + +export const removeFieldFromPolicy = async (field_id, policy_id) => { + const path = `/policies/${policy_id}/std_fields/${field_id}`; + return await deleteRequest(path, {}); +}; + +export const deleteAllStdFields = async () => { + const path = `/std_fields`; + return await deleteRequest(path, {}); +}; + +export const getPublicFields = async () => { + const path = `/public_std_fields`; + return await get(path); +}; + +export const createPolicy = async (name) => { + const path = `/policies`; + const kc_id = getUser().profile?.sub; + return await post(path, { + name, + kc_id, + }); +}; + +export const addSourceToPolicy = async (source_id, policy_id) => { + const path = `/policies/${policy_id}/sources`; + return await post(path, { + source_id, + }); +}; + +export const removeSourceFromPolicy = async (source_id, policy_id) => { + const path = `/policies/${policy_id}/sources/${source_id}`; + return await deleteRequest(path, {}); +}; + +export const addPolicyToGroup = async (policy_id, group_id) => { + const path = `/groups/${group_id}/policies`; + return await post(path, { + policy_id, + }); +}; + +export const removePolicyFromGroup = async (policy_id, group_id) => { + const path = `/groups/${group_id}/policies/${policy_id}`; + return await deleteRequest(path, {}); +}; + +export const deletePolicy = async (id) => { + const path = `/policies/${id}`; + return await deleteRequest(path, {}); +}; + +export const getPolicies = async () => { + const path = `/policies`; + return await get(path); +}; + +export const getUserPolicies = async (sub) => { + const path = `/users/${sub}/policies`; + return await get(path); +}; + +export const searchQuery = async (payload) => { + const path = `/search`; + return await post(path, payload); +}; diff --git a/src/store/CustomConnector.js b/src/store/CustomConnector.js deleted file mode 100644 index 28b24cab07c945dab23cac07aacc0c118f47d250..0000000000000000000000000000000000000000 --- a/src/store/CustomConnector.js +++ /dev/null @@ -1,53 +0,0 @@ -import React, { Component } from 'react'; - -export const CustomConnectorContext = React.createContext(); - -export class CustomConnectorProvider extends Component { - render() { - return ( - <CustomConnectorContext.Provider value={this.props.dataStore}> - {this.props.children} - </CustomConnectorContext.Provider> - ); - } -} - -export class CustomConnector extends React.Component { - static contextType = CustomConnectorContext; - - constructor(props, context) { - super(props, context); - this.state = this.selectData(); - this.functionProps = Object.entries(this.props.dispatchers) - .map(([k, v]) => [k, (...args) => this.context.dispatch(v(...args))]) - .reduce((result, [k, v]) => ({ ...result, [k]: v }), {}); - } - - render() { - return React.Children.map(this.props.children, (c) => - React.cloneElement(c, { ...this.state, ...this.functionProps }) - ); - } - - selectData() { - let storeState = this.context.getState(); - return Object.entries(this.props.selectors) - .map(([k, v]) => [k, v(storeState)]) - .reduce((result, [k, v]) => ({ ...result, [k]: v }), {}); - } - - handleDataStoreChange() { - let newData = this.selectData(); - Object.keys(this.props.selectors) - .filter((key) => this.state[key] !== newData[key]) - .forEach((key) => this.setState({ [key]: newData[key] })); - } - - componentDidMount() { - this.unsubscriber = this.context.subscribe(() => this.handleDataStoreChange()); - } - - componentWillUnmount() { - this.unsubscriber(); - } -} diff --git a/src/store/asyncEnhancer.js b/src/store/asyncEnhancer.js deleted file mode 100644 index 7f9e47e559c489ddd745c44186aed0eb0d2e8ca1..0000000000000000000000000000000000000000 --- a/src/store/asyncEnhancer.js +++ /dev/null @@ -1,16 +0,0 @@ -export const asyncEnhancer = - (delay) => - (createStoreFunction) => - (...args) => { - const store = createStoreFunction(...args); - return { - ...store, - dispatchAsync: (action) => - new Promise((resolve, reject) => { - setTimeout(() => { - store.dispatch(action); - resolve(); - }, delay); - }), - }; - }; diff --git a/src/store/index.js b/src/store/index.js deleted file mode 100644 index 905e71ea56e2310a94a1552813aeeb2c1066d9e5..0000000000000000000000000000000000000000 --- a/src/store/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import useStore from './useStore'; -import * as actions from '../actions'; - -const initialState = { - isLoading: false, - status: 'INITIAL', - users: [], - roles: [], - role: {}, - user: {}, - allocatedRoles: [], - error: false, - resources: [], -}; -const store = useStore(React, initialState, actions); -export default store; diff --git a/src/store/useStore.js b/src/store/useStore.js deleted file mode 100644 index bb16c7eed93d73f62df8d480cc41645a7c2edd8d..0000000000000000000000000000000000000000 --- a/src/store/useStore.js +++ /dev/null @@ -1,60 +0,0 @@ -import { useSessionStorage } from '@in-sylva/react-use-storage'; - -function setState(store, newState, afterUpdateCallback) { - store.state = { ...store.state, ...newState }; - store.listeners.forEach((listener) => { - listener.run(store.state); - }); - afterUpdateCallback && afterUpdateCallback(); -} - -function useCustom(store, React, mapState, mapActions) { - const [, originalHook] = useSessionStorage('portal', Object.create(null)); - const state = mapState ? mapState(store.state) : store.state; - const actions = React.useMemo( - () => (mapActions ? mapActions(store.actions) : store.actions), - [mapActions, store.actions] - ); - - React.useEffect(() => { - const newListener = { oldState: {} }; - newListener.run = mapState - ? (newState) => { - const mappedState = mapState(newState); - if (mappedState !== newListener.oldState) { - newListener.oldState = mappedState; - originalHook(mappedState); - } - } - : originalHook; - store.listeners.push(newListener); - newListener.run(store.state); - return () => { - store.listeners = store.listeners.filter((listener) => listener !== newListener); - }; - }, []); // eslint-disable-line - return [state, actions]; -} - -function associateActions(store, actions) { - const associatedActions = {}; - Object.keys(actions).forEach((key) => { - if (typeof actions[key] === 'function') { - associatedActions[key] = actions[key].bind(null, store); - } - if (typeof actions[key] === 'object') { - associatedActions[key] = associateActions(store, actions[key]); - } - }); - return associatedActions; -} - -const useStore = (React, initialState, actions, initializer) => { - const store = { state: initialState, listeners: [] }; - store.setState = setState.bind(null, store); - store.actions = associateActions(store, actions); - if (initializer) initializer(store); - return useCustom.bind(null, store, React); -}; - -export default useStore; diff --git a/src/utils.js b/src/utils.js index 9ad2ab2d0483dfc75eba6b73fedc75e4c70eb1d8..6b39314a8e41c1a26d64c42405eabd8acd3cb146 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,39 +1,5 @@ -import { EuiIcon } from '@elastic/eui'; import React from 'react'; - -export const getGatekeeperBaseUrl = () => { - return process.env.REACT_APP_IN_SYLVA_GATEKEEPER_PORT - ? `${process.env.REACT_APP_IN_SYLVA_GATEKEEPER_HOST}:${process.env.REACT_APP_IN_SYLVA_GATEKEEPER_PORT}` - : `${window._env_.REACT_APP_IN_SYLVA_GATEKEEPER_HOST}`; -}; - -export const getLoginUrl = () => { - return process.env.REACT_APP_IN_SYLVA_LOGIN_PORT - ? `${process.env.REACT_APP_IN_SYLVA_LOGIN_HOST}:${process.env.REACT_APP_IN_SYLVA_LOGIN_PORT}` - : `${window._env_.REACT_APP_IN_SYLVA_LOGIN_HOST}`; -}; - -export const redirect = (url, condition = true) => { - if (condition) { - window.location.replace(url); - } -}; - -export const getUrlParam = (parameter, defaultValue) => { - let urlParameter = defaultValue; - if (window.location.href.indexOf(parameter) > -1) { - urlParameter = getUrlParams()[parameter]; - } - return urlParameter; -}; - -export const getUrlParams = () => { - let lets = {}; - window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) { - lets[key] = value; - }); - return lets; -}; +import { EuiIcon } from '@elastic/eui'; // Function used to mock statistics for dashboard page export const getRandomData = (arrayLength) => { @@ -44,11 +10,19 @@ export const getRandomData = (arrayLength) => { }); }; -export const getUserRoleId = () => { - if (sessionStorage.getItem('roleId').split('#')[0]) { - return parseInt(sessionStorage.getItem('roleId').split('#')[0]); - } else { - return parseInt(sessionStorage.getItem('roleId')); +export const safeJsonStringify = (str) => { + try { + return JSON.stringify(str); + } catch (e) { + return null; + } +}; + +export const safeJsonParse = (str) => { + try { + return JSON.parse(str); + } catch (e) { + return null; } }; @@ -56,70 +30,66 @@ export const getSideBarItems = () => { return [ { id: 0, + label: 'Home', + link: '/home', + roles: [1, 2, 3], + icon: <EuiIcon type="home" size="l" />, + }, + { + id: 1, label: 'Dashboard', - link: '/app/dashboard', + link: '/dashboard', roles: [1], icon: <EuiIcon type="dashboardApp" size="l" />, }, { - id: 1, + id: 2, label: 'Users', - link: '/app/users', + link: '/users', roles: [1], icon: <EuiIcon type="user" size="l" />, }, { - id: 2, + id: 3, label: 'Users roles', - link: '/app/roles', + link: '/roles', roles: [1], icon: <EuiIcon type="usersRolesApp" size="l" />, }, { - id: 3, + id: 4, label: 'Groups', - link: '/app/groups', + link: '/groups', roles: [1], icon: <EuiIcon type="users" size="l" />, }, { - id: 4, + id: 5, label: 'Requests', - link: '/app/requests', + link: '/requests', roles: [1], icon: <EuiIcon type="email" size="l" />, }, { - id: 5, + id: 6, label: 'Policies', - link: '/app/policies', - roles: [1, 2], + link: '/policies', + roles: [1], icon: <EuiIcon type="lockOpen" size="l" />, }, { - id: 6, + id: 7, label: 'Sources', - link: '/app/sources', + link: '/sources', roles: [1, 2], icon: <EuiIcon type="indexManagementApp" size="l" />, }, { - id: 7, + id: 8, label: 'Fields', - link: '/app/fields', - roles: [1], + link: '/fields', + roles: [1, 2], icon: <EuiIcon type="visTable" size="l" />, }, ]; }; - -export const changeNameToLabel = (object) => { - object.label = object.name; - delete object.name; - return object; -}; - -export const tokenTimedOut = (validityDurationInMin) => { - const timeSinceLastRefresh = Date.now() - sessionStorage.getItem('token_refresh_time'); - return timeSinceLastRefresh > validityDurationInMin * 60000; -};