mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ui.git
				synced 2025-10-31 18:57:59 +00:00 
			
		
		
		
	Compare commits
	
		
			289 Commits
		
	
	
		
			hotfix/tes
			...
			release/v1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 196a8607cc | ||
|   | e38c672f91 | ||
|   | 7810f51baf | ||
|   | 300f00cc85 | ||
|   | 0b83cce523 | ||
|   | 5de121330e | ||
|   | 252047aad4 | ||
|   | cf7ed39120 | ||
|   | a7c7b0f295 | ||
|   | 8d14212702 | ||
|   | 70d609643c | ||
|   | a6e2f9b5c7 | ||
|   | 569ce9d5f9 | ||
|   | a0506eaf07 | ||
|   | 31074ac42e | ||
|   | 2eebd4ac3d | ||
|   | 153e4f7dcf | ||
|   | 325f86ff42 | ||
|   | 5bff3379d5 | ||
|   | a2cf42e90c | ||
|   | 0fd5803584 | ||
|   | af98ac522a | ||
|   | 6baf3785da | ||
|   | eba4850ae1 | ||
|   | 36682cd207 | ||
|   | 3cfdcf15c6 | ||
|   | 63ac433ad8 | ||
|   | 2ed09f903c | ||
|   | 2c0fa53896 | ||
|   | 9bf737c1eb | ||
|   | 1f018cbd04 | ||
|   | bc57402aa9 | ||
|   | 2692e6527f | ||
|   | f6d03b7a1b | ||
|   | 3f1a85a9c0 | ||
|   | f1b0c54fc3 | ||
|   | 7659407562 | ||
|   | cf5be15402 | ||
|   | f3ee2f5a7c | ||
|   | 6ddd9b30f3 | ||
|   | 8acdd0ca4a | ||
|   | 8670e74b77 | ||
|   | c8798dd6b3 | ||
|   | 82b5688503 | ||
|   | fadb618c6a | ||
|   | dbf54d26b6 | ||
|   | 11e7b92902 | ||
|   | cb8364b1ab | ||
|   | 796bf24ef9 | ||
|   | 444a9a929d | ||
|   | efea725ce9 | ||
|   | 7a3bcbb2cb | ||
|   | a3e304323d | ||
|   | af182c93c6 | ||
|   | 78fde75678 | ||
|   | 7eda2074b2 | ||
|   | ded1b2d088 | ||
|   | e1f0357c1f | ||
|   | b84e156f74 | ||
|   | cae23638ee | ||
|   | 569154ff95 | ||
|   | 78146969f3 | ||
|   | c39ee3e2c0 | ||
|   | 017a3de539 | ||
|   | 85ef801831 | ||
|   | 9582ca56af | ||
|   | 26c11502e3 | ||
|   | 83e4bc5e33 | ||
|   | a89ac6e550 | ||
|   | 3910e75712 | ||
|   | 2bf34a62e3 | ||
|   | 2d06e56ffb | ||
|   | f13ec45465 | ||
|   | d2fe2c7dc3 | ||
|   | 2aeaaab3bc | ||
|   | 4dbed9922b | ||
|   | 7d2b1ea5fc | ||
|   | 6dd251dd94 | ||
|   | 285444f0b9 | ||
|   | 7c16a50e81 | ||
|   | 8f2a33b625 | ||
|   | 3be405a577 | ||
|   | 940eeb72fd | ||
|   | 0c14857dac | ||
|   | 612e9eb449 | ||
|   | e02bd79c26 | ||
|   | 0b1e2a7427 | ||
|   | ea605f333c | ||
|   | 1e8306ad4b | ||
|   | 965dca7e7f | ||
|   | de2570662a | ||
|   | 64a1ecae8a | ||
|   | 0ceff9e42e | ||
|   | b287aa4005 | ||
|   | 23cc799d71 | ||
|   | 4ea1e55cdd | ||
|   | cca9fc3667 | ||
|   | 883379c48c | ||
|   | 514100c6bc | ||
|   | 0f463832a7 | ||
|   | 157b4ecb33 | ||
|   | 155aeb27e0 | ||
|   | 6b43f8967d | ||
|   | e744fed5d8 | ||
|   | 1f9da3f314 | ||
|   | 0d6b2f05af | ||
|   | 98d6fb6dbd | ||
|   | 3f3c81f5db | ||
|   | 76acff8b89 | ||
|   | 4bab1f3c1c | ||
|   | 2f12264af3 | ||
|   | 016d1ff439 | ||
|   | 1f06e0b89c | ||
|   | cacb2a3833 | ||
|   | bf39c6b6dc | ||
|   | 8d2de52870 | ||
|   | 8b0dc34ac4 | ||
|   | 6646226cb7 | ||
|   | 120ae073fb | ||
|   | 834b7b28a7 | ||
|   | d9a970350b | ||
|   | 60c2c21997 | ||
|   | 26d74f6059 | ||
|   | 1682a2ca21 | ||
|   | 1fa7080593 | ||
|   | ac405ae1c4 | ||
|   | c1264c3ea0 | ||
|   | cfee505a5b | ||
|   | ba49ca4e16 | ||
|   | c40a8ca157 | ||
|   | 0dfb7a7901 | ||
|   | 153394cad0 | ||
|   | 1507320fae | ||
|   | 673002b685 | ||
|   | c014ba5e40 | ||
|   | b20caa666d | ||
|   | c75e5ea934 | ||
|   | 631482ab6f | ||
|   | 2d42d59c54 | ||
|   | 229d50f1a8 | ||
|   | f6078182ff | ||
|   | 3f0c1c0e33 | ||
|   | 6dbaddc021 | ||
|   | 64096d040d | ||
|   | 5a634b2e8a | ||
|   | 69972e0033 | ||
|   | 743c4f3665 | ||
|   | d70df54986 | ||
|   | b40b29f73d | ||
|   | 2206a9bf3f | ||
|   | 450fe6280f | ||
|   | d0424480fe | ||
|   | ea2b4f90b7 | ||
|   | fbfd4895b1 | ||
|   | eafc05b645 | ||
|   | 0009bd5ca6 | ||
|   | e38dd84f11 | ||
|   | e6a0dc9f12 | ||
|   | 327c4ea3d1 | ||
|   | 01dbd0a887 | ||
|   | 5b46dbf657 | ||
|   | 46c860f310 | ||
|   | 51d34055ba | ||
|   | be3582514c | ||
|   | afc6ddc30e | ||
|   | 4314239e1d | ||
|   | bac628ea67 | ||
|   | bac2f43eb0 | ||
|   | f7681e9bd3 | ||
|   | 5d822bdd78 | ||
|   | cbfce876b3 | ||
|   | 16d4933190 | ||
|   | 85f3386c23 | ||
|   | c929a1eff5 | ||
|   | 401f22b10a | ||
|   | 40f2ef31ce | ||
|   | 2813057c31 | ||
|   | 8d1339c412 | ||
|   | 54ca94cefa | ||
|   | d750a0e798 | ||
|   | 09f2e91f4c | ||
|   | 4f18a93375 | ||
|   | 5a41840f1d | ||
|   | f5f30f3784 | ||
|   | 6edc98af47 | ||
|   | 15df3caf6b | ||
|   | 94a26fef43 | ||
|   | b44290a28a | ||
|   | 823d7d0fef | ||
|   | d33caa8885 | ||
|   | c4d9f76c2e | ||
|   | c416d87106 | ||
|   | 5023ed9d96 | ||
|   | 125e988f25 | ||
|   | 4069374d58 | ||
|   | ded926c98b | ||
|   | 7b0becec58 | ||
|   | 9be76979ea | ||
|   | cf9cc4d6ac | ||
|   | 0ef102e28e | ||
|   | 41f27c76b1 | ||
|   | 124a84b75f | ||
|   | 1b5a09636f | ||
|   | 81cc493aa3 | ||
|   | 41a27ff9ef | ||
|   | 1c6f8dfdba | ||
|   | 5911395fac | ||
|   | 941b952ff6 | ||
|   | 4065a9272c | ||
|   | 333b37a139 | ||
|   | 0939aacdbe | ||
|   | bb1da8f0c9 | ||
|   | a11c7d651d | ||
|   | 07f1f64537 | ||
|   | 8bde2ad4bc | ||
|   | ac848713ea | ||
|   | 4bebfe68f6 | ||
|   | 7bf25f4edb | ||
|   | 0ff45e9d87 | ||
|   | f877b4c3c1 | ||
|   | ecf2c2fa8c | ||
|   | d5c094d749 | ||
|   | 26dba66470 | ||
|   | 5ae31d7198 | ||
|   | 2bd50d4560 | ||
|   | a8843d4be7 | ||
|   | 644cdd2c45 | ||
|   | 035a714fa6 | ||
|   | 2d7514fbde | ||
|   | 0c5f0842e9 | ||
|   | 86087930da | ||
|   | e60ec0c713 | ||
|   | fd4f90173b | ||
|   | a4d4dce73a | ||
|   | 7b8f45d9f2 | ||
|   | 3affd7b6a6 | ||
|   | 5854c1a0a0 | ||
|   | a2352e2d01 | ||
|   | c0074b6610 | ||
|   | dfb4bf21cd | ||
|   | 2514d9e77c | ||
|   | f2c5b5c5f3 | ||
|   | 8b5af07f87 | ||
|   | a1d4081f0b | ||
|   | d6d054f53e | ||
|   | 5a49a0301e | ||
|   | 7936777344 | ||
|   | 057e7c2d0a | ||
|   | 7911c630b4 | ||
|   | 0108229ddb | ||
|   | 51750b83c8 | ||
|   | aaf14b0d0a | ||
|   | b2bee67551 | ||
|   | afc222bb4a | ||
|   | c595424269 | ||
|   | 69fbceefff | ||
|   | f9c66adf00 | ||
|   | 56c0329bdb | ||
|   | 06cb3feac3 | ||
|   | a159705cae | ||
|   | 02c20ffd6e | ||
|   | dc8374300e | ||
|   | c001ce21ba | ||
|   | 2dd3bff53c | ||
|   | 04bbc5fbed | ||
|   | 902de8bcb0 | ||
|   | 6c6635017a | ||
|   | 8891b5b3f9 | ||
|   | 8d2e5bb8a5 | ||
|   | f29f31558a | ||
|   | 6e0cf15616 | ||
|   | 49d5771fca | ||
|   | 3794869adb | ||
|   | 3d105340af | ||
|   | c8ee2138ad | ||
|   | 2e9dd99f3a | ||
|   | ad0a6d299b | ||
|   | 0d58a8bb6a | ||
|   | bcadd1f8d4 | ||
|   | af809974b6 | ||
|   | 573718bc1e | ||
|   | c7a1ec1dc3 | ||
|   | 19c0bb4393 | ||
|   | 2a521ce036 | ||
|   | 85e1ee16ee | ||
|   | 195518a137 | ||
|   | 6ea3c778cd | ||
|   | 3b6d1a57ca | ||
|   | 6498e2a984 | 
							
								
								
									
										37
									
								
								.github/workflows/dockerpublish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										37
									
								
								.github/workflows/dockerpublish.yml
									
									
									
									
										vendored
									
									
								
							| @@ -5,6 +5,7 @@ on: | |||||||
|     # Publish `master` as Docker `latest` image. |     # Publish `master` as Docker `latest` image. | ||||||
|     branches: |     branches: | ||||||
|       - master |       - master | ||||||
|  |       - 'release/**' | ||||||
|  |  | ||||||
|     # Publish `v1.2.3` tags as releases. |     # Publish `v1.2.3` tags as releases. | ||||||
|     tags: |     tags: | ||||||
| @@ -13,6 +14,10 @@ on: | |||||||
|   # Run tests for any PRs. |   # Run tests for any PRs. | ||||||
|   pull_request: |   pull_request: | ||||||
|  |  | ||||||
|  |   schedule: | ||||||
|  |     # runs nightly build at 5AM | ||||||
|  |     - cron: '00 09 * * *' | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   IMAGE_NAME: wlan-cloud-ui |   IMAGE_NAME: wlan-cloud-ui | ||||||
|   DOCKER_REPO: tip-tip-wlan-cloud-docker-repo.jfrog.io |   DOCKER_REPO: tip-tip-wlan-cloud-docker-repo.jfrog.io | ||||||
| @@ -39,11 +44,31 @@ jobs: | |||||||
|     needs: test |     needs: test | ||||||
|  |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     if: github.event_name == 'push' |     if: github.event_name == 'push' || github.event_name == 'schedule' | ||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|  |  | ||||||
|  |       - name: Adding property file with component version and commit hash | ||||||
|  |         run: | | ||||||
|  |           # Strip git ref prefix from version | ||||||
|  |           VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') | ||||||
|  |  | ||||||
|  |           # Strip "v" prefix from tag name | ||||||
|  |           [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') | ||||||
|  |  | ||||||
|  |           # Create a release snapshot if we are on release branch | ||||||
|  |           [[ "${{ github.ref }}" == "refs/heads/release/"* ]] && VERSION=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\/release\/[v]//' | awk '{print $1"-SNAPSHOT"}') | ||||||
|  |  | ||||||
|  |           # Use Docker `latest` tag convention | ||||||
|  |           [ "$VERSION" == "master" ] && VERSION=0.0.1-SNAPSHOT | ||||||
|  |  | ||||||
|  |           TIMESTAMP=$(date +'%Y-%m-%d') | ||||||
|  |  | ||||||
|  |           echo date=$TIMESTAMP > app/commit.properties | ||||||
|  |           echo commitId=$GITHUB_SHA >> app/commit.properties | ||||||
|  |           echo projectVersion=$VERSION>> app/commit.properties | ||||||
|  |  | ||||||
|       - name: Build image |       - name: Build image | ||||||
|         run: docker build . -f Dockerfile -t image --build-arg NPM_TOKEN=${{secrets.NPM_REPO_AUTH_TOKEN}} |         run: docker build . -f Dockerfile -t image --build-arg NPM_TOKEN=${{secrets.NPM_REPO_AUTH_TOKEN}} | ||||||
|  |  | ||||||
| @@ -56,6 +81,7 @@ jobs: | |||||||
|       - name: Push image |       - name: Push image | ||||||
|         run: | |         run: | | ||||||
|           IMAGE_ID=$DOCKER_REPO/$IMAGE_NAME |           IMAGE_ID=$DOCKER_REPO/$IMAGE_NAME | ||||||
|  |           TIMESTAMP=$(date +'%Y-%m-%d') | ||||||
|  |  | ||||||
|           # Change all uppercase to lowercase |           # Change all uppercase to lowercase | ||||||
|           IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') |           IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') | ||||||
| @@ -66,11 +92,18 @@ jobs: | |||||||
|           # Strip "v" prefix from tag name |           # Strip "v" prefix from tag name | ||||||
|           [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') |           [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') | ||||||
|  |  | ||||||
|  |           # Create a release snapshot if we are on release branch | ||||||
|  |           [[ "${{ github.ref }}" == "refs/heads/release/"* ]] && VERSION=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\/release\/[v]//' | awk '{print $1"-SNAPSHOT"}') | ||||||
|  |  | ||||||
|           # Use Docker `latest` tag convention |           # Use Docker `latest` tag convention | ||||||
|           [ "$VERSION" == "master" ] && VERSION=latest |           [ "$VERSION" == "master" ] && VERSION=0.0.1-SNAPSHOT | ||||||
|  |  | ||||||
|           echo IMAGE_ID=$IMAGE_ID |           echo IMAGE_ID=$IMAGE_ID | ||||||
|           echo VERSION=$VERSION |           echo VERSION=$VERSION | ||||||
|  |           echo TIMESTAMP=$TIMESTAMP | ||||||
|  |  | ||||||
|           docker tag image $IMAGE_ID:$VERSION |           docker tag image $IMAGE_ID:$VERSION | ||||||
|           docker push $IMAGE_ID:$VERSION |           docker push $IMAGE_ID:$VERSION | ||||||
|  |  | ||||||
|  |           docker tag image $IMAGE_ID:$VERSION-$TIMESTAMP | ||||||
|  |           docker push $IMAGE_ID:$VERSION-$TIMESTAMP | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -4,6 +4,7 @@ | |||||||
| /node_modules | /node_modules | ||||||
| /.pnp | /.pnp | ||||||
| .pnp.js | .pnp.js | ||||||
|  | package-lock.json | ||||||
|  |  | ||||||
| # testing | # testing | ||||||
| /coverage | /coverage | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ ENV PATH /app/node_modules/.bin:$PATH | |||||||
| # where available (npm@5+) | # where available (npm@5+) | ||||||
| COPY package*.json ./ | COPY package*.json ./ | ||||||
|  |  | ||||||
|  |  | ||||||
| #RUN npm install | #RUN npm install | ||||||
| # If you are building your code for production | # If you are building your code for production | ||||||
| RUN (echo "@tip-wlan:registry=https://tip.jfrog.io/artifactory/api/npm/tip-wlan-cloud-npm-repo/" && echo "//tip.jfrog.io/artifactory/api/npm/tip-wlan-cloud-npm-repo/:_authToken=$NPM_TOKEN") > .npmrc | RUN (echo "@tip-wlan:registry=https://tip.jfrog.io/artifactory/api/npm/tip-wlan-cloud-npm-repo/" && echo "//tip.jfrog.io/artifactory/api/npm/tip-wlan-cloud-npm-repo/:_authToken=$NPM_TOKEN") > .npmrc | ||||||
| @@ -24,7 +25,12 @@ RUN npm run build | |||||||
|  |  | ||||||
| # production environment | # production environment | ||||||
| FROM nginx:stable-alpine | FROM nginx:stable-alpine | ||||||
|  | RUN apk add --no-cache jq | ||||||
| COPY --from=build /app/dist /usr/share/nginx/html | COPY --from=build /app/dist /usr/share/nginx/html | ||||||
| COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf | COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf | ||||||
|  | COPY docker_entrypoint.sh generate_config_js.sh / | ||||||
|  | COPY app/commit.properties / | ||||||
|  | RUN chmod +x docker_entrypoint.sh generate_config_js.sh | ||||||
|  |   | ||||||
| EXPOSE 80 | EXPOSE 80 | ||||||
| CMD ["nginx", "-g", "daemon off;"] | ENTRYPOINT ["/docker_entrypoint.sh"] | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								README.md
									
									
									
									
									
								
							| @@ -2,12 +2,30 @@ | |||||||
|  |  | ||||||
| ## Set up environment: | ## Set up environment: | ||||||
|  |  | ||||||
| Delete from package.json (undo this delete after all steps) | Install Dependencies: | ||||||
| `"@tip-wlan/wlan-cloud-ui-library": X.X.X,` |  | ||||||
|  |  | ||||||
| Install Dependencies |  | ||||||
| `npm install` | `npm install` | ||||||
|  |  | ||||||
|  | You will get an error installing the package [wlan-cloud-ui-library](https://github.com/Telecominfraproject/wlan-cloud-ui-library) because it is in a private npm registry. | ||||||
|  | You can either install the package with the commands below or clone the repo [wlan-cloud-ui-library](https://github.com/Telecominfraproject/wlan-cloud-ui-library) and skip to the section Set up Full Development. | ||||||
|  |  | ||||||
|  | Run | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | npm login --registry=https://tip.jfrog.io/artifactory/api/npm/tip-wlan-cloud-npm-repo/ | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | And enter the supplied credentials. Ask @sean-macfarlane for credentials if you don't have. | ||||||
|  |  | ||||||
|  | Install package: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | npm i --registry=https://tip.jfrog.io/artifactory/api/npm/tip-wlan-cloud-npm-repo/ @tip-wlan/wlan-cloud-ui-library | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Set up Full Development | ||||||
|  |  | ||||||
|  | _Skip this section if you are not using a local [wlan-cloud-ui-library](https://github.com/Telecominfraproject/wlan-cloud-ui-library)_ | ||||||
|  |  | ||||||
| Clone [wlan-cloud-ui-library](https://github.com/Telecominfraproject/wlan-cloud-ui-library) in parent folder | Clone [wlan-cloud-ui-library](https://github.com/Telecominfraproject/wlan-cloud-ui-library) in parent folder | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| @@ -28,15 +46,25 @@ If `npm link` fails due to Permissions run with `sudo` | |||||||
| sudo npm link ../wlan-cloud-ui-library | sudo npm link ../wlan-cloud-ui-library | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | To run Full Development you must clone [wlan-cloud-graphql-gw](https://github.com/Telecominfraproject/wlan-cloud-graphql-gw), follow it's README, and run it. Or you use the live production build by setting the environment variable `API` to the GraphQL domain. | ||||||
|  |  | ||||||
| ## Run: | ## Run: | ||||||
|  |  | ||||||
| ### Development | ### Bare Development | ||||||
|  |  | ||||||
|  | This is if you only want to run this repo locally, and want to use the live production builds of [wlan-cloud-ui-library](https://github.com/Telecominfraproject/wlan-cloud-ui-library) and [wlan-cloud-graphql-gw](https://github.com/Telecominfraproject/wlan-cloud-graphql-gw) | ||||||
|  |  | ||||||
|  | `npm run start:bare` | ||||||
|  |  | ||||||
|  | ### Full Development | ||||||
|  |  | ||||||
|  | If you have cloned [wlan-cloud-ui-library](https://github.com/Telecominfraproject/wlan-cloud-ui-library) and [wlan-cloud-graphql-gw](https://github.com/Telecominfraproject/wlan-cloud-graphql-gw) | ||||||
|  |  | ||||||
| `npm start` | `npm start` | ||||||
|  |  | ||||||
| ### Tests | ### Tests | ||||||
|  |  | ||||||
| `npm run test` | `npm test` | ||||||
|  |  | ||||||
| ### Production | ### Production | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								app/commit.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								app/commit.properties
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | #This file will be used when running the docker locally | ||||||
|  | date=${date} | ||||||
|  | commitId=${commit.id} | ||||||
|  | projectVersion=${project.version} | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,6 +1,5 @@ | |||||||
| import React, { useContext } from 'react'; | import React, { useContext } from 'react'; | ||||||
| import gql from 'graphql-tag'; | import { useQuery, useMutation, gql } from '@apollo/client'; | ||||||
| import { useQuery, useMutation } from '@apollo/react-hooks'; |  | ||||||
| import { Alert, notification } from 'antd'; | import { Alert, notification } from 'antd'; | ||||||
|  |  | ||||||
| import { Accounts as AccountsPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | import { Accounts as AccountsPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
| @@ -8,28 +7,25 @@ import { Accounts as AccountsPage, Loading } from '@tip-wlan/wlan-cloud-ui-libra | |||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
|  |  | ||||||
| const GET_ALL_USERS = gql` | const GET_ALL_USERS = gql` | ||||||
|   query GetAllUsers($customerId: ID!, $cursor: String) { |   query GetAllUsers($customerId: ID!, $context: JSONObject) { | ||||||
|     getAllUsers(customerId: $customerId, cursor: $cursor) { |     getAllUsers(customerId: $customerId, context: $context) { | ||||||
|       items { |       items { | ||||||
|         id |         id | ||||||
|         email: username |         email: username | ||||||
|         role |         roles | ||||||
|         lastModifiedTimestamp |         lastModifiedTimestamp | ||||||
|         customerId |         customerId | ||||||
|       } |       } | ||||||
|       context { |       context | ||||||
|         cursor |  | ||||||
|         lastPage |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| `; | `; | ||||||
|  |  | ||||||
| const CREATE_USER = gql` | const CREATE_USER = gql` | ||||||
|   mutation CreateUser($username: String!, $password: String!, $role: String!, $customerId: ID!) { |   mutation CreateUser($username: String!, $password: String!, $roles: [String], $customerId: ID!) { | ||||||
|     createUser(username: $username, password: $password, role: $role, customerId: $customerId) { |     createUser(username: $username, password: $password, roles: $roles, customerId: $customerId) { | ||||||
|       username |       username | ||||||
|       role |       roles | ||||||
|       customerId |       customerId | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -40,7 +36,7 @@ const UPDATE_USER = gql` | |||||||
|     $id: ID! |     $id: ID! | ||||||
|     $username: String! |     $username: String! | ||||||
|     $password: String! |     $password: String! | ||||||
|     $role: String! |     $roles: [String] | ||||||
|     $customerId: ID! |     $customerId: ID! | ||||||
|     $lastModifiedTimestamp: String |     $lastModifiedTimestamp: String | ||||||
|   ) { |   ) { | ||||||
| @@ -48,13 +44,13 @@ const UPDATE_USER = gql` | |||||||
|       id: $id |       id: $id | ||||||
|       username: $username |       username: $username | ||||||
|       password: $password |       password: $password | ||||||
|       role: $role |       roles: $roles | ||||||
|       customerId: $customerId |       customerId: $customerId | ||||||
|       lastModifiedTimestamp: $lastModifiedTimestamp |       lastModifiedTimestamp: $lastModifiedTimestamp | ||||||
|     ) { |     ) { | ||||||
|       id |       id | ||||||
|       username |       username | ||||||
|       role |       roles | ||||||
|       customerId |       customerId | ||||||
|       lastModifiedTimestamp |       lastModifiedTimestamp | ||||||
|     } |     } | ||||||
| @@ -70,7 +66,7 @@ const DELETE_USER = gql` | |||||||
| `; | `; | ||||||
|  |  | ||||||
| const Accounts = () => { | const Accounts = () => { | ||||||
|   const { customerId } = useContext(UserContext); |   const { customerId, id: currentUserId } = useContext(UserContext); | ||||||
|  |  | ||||||
|   const { data, loading, error, refetch, fetchMore } = useQuery(GET_ALL_USERS, { |   const { data, loading, error, refetch, fetchMore } = useQuery(GET_ALL_USERS, { | ||||||
|     variables: { customerId }, |     variables: { customerId }, | ||||||
| @@ -82,7 +78,7 @@ const Accounts = () => { | |||||||
|   const handleLoadMore = () => { |   const handleLoadMore = () => { | ||||||
|     if (!data.getAllUsers.context.lastPage) { |     if (!data.getAllUsers.context.lastPage) { | ||||||
|       fetchMore({ |       fetchMore({ | ||||||
|         variables: { cursor: data.getAllUsers.context.cursor }, |         variables: { context: data.getAllUsers.context }, | ||||||
|         updateQuery: (previousResult, { fetchMoreResult }) => { |         updateQuery: (previousResult, { fetchMoreResult }) => { | ||||||
|           const previousEntry = previousResult.getAllUsers; |           const previousEntry = previousResult.getAllUsers; | ||||||
|           const newItems = fetchMoreResult.getAllUsers.items; |           const newItems = fetchMoreResult.getAllUsers.items; | ||||||
| @@ -99,12 +95,12 @@ const Accounts = () => { | |||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleCreateUser = (email, password, role) => { |   const handleCreateUser = (email, password, roles) => { | ||||||
|     createUser({ |     createUser({ | ||||||
|       variables: { |       variables: { | ||||||
|         username: email, |         username: email, | ||||||
|         password, |         password, | ||||||
|         role, |         roles: [roles], | ||||||
|         customerId, |         customerId, | ||||||
|       }, |       }, | ||||||
|     }) |     }) | ||||||
| @@ -123,13 +119,13 @@ const Accounts = () => { | |||||||
|       ); |       ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleEditUser = (id, email, password, role, lastModifiedTimestamp) => { |   const handleEditUser = (id, email, password, roles, lastModifiedTimestamp) => { | ||||||
|     updateUser({ |     updateUser({ | ||||||
|       variables: { |       variables: { | ||||||
|         id, |         id, | ||||||
|         username: email, |         username: email, | ||||||
|         password, |         password, | ||||||
|         role, |         roles: [roles], | ||||||
|         customerId, |         customerId, | ||||||
|         lastModifiedTimestamp, |         lastModifiedTimestamp, | ||||||
|       }, |       }, | ||||||
| @@ -177,6 +173,7 @@ const Accounts = () => { | |||||||
|   return ( |   return ( | ||||||
|     <AccountsPage |     <AccountsPage | ||||||
|       data={data.getAllUsers.items} |       data={data.getAllUsers.items} | ||||||
|  |       currentUserId={currentUserId} | ||||||
|       onLoadMore={handleLoadMore} |       onLoadMore={handleLoadMore} | ||||||
|       onCreateUser={handleCreateUser} |       onCreateUser={handleCreateUser} | ||||||
|       onEditUser={handleEditUser} |       onEditUser={handleEditUser} | ||||||
|   | |||||||
| @@ -1,11 +1,13 @@ | |||||||
| import React, { useContext } from 'react'; | import React, { useContext } from 'react'; | ||||||
| import { AddProfile as AddProfilePage } from '@tip-wlan/wlan-cloud-ui-library'; | import { AddProfile as AddProfilePage } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
| import gql from 'graphql-tag'; | import { useMutation, useQuery, gql } from '@apollo/client'; | ||||||
| import { useMutation, useQuery } from '@apollo/react-hooks'; |  | ||||||
| import { notification } from 'antd'; | import { notification } from 'antd'; | ||||||
|  | import { useHistory } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import { ROUTES } from 'constants/index'; | ||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
| import { GET_ALL_PROFILES } from 'graphql/queries'; | import { GET_ALL_PROFILES } from 'graphql/queries'; | ||||||
|  | import { fetchMoreProfiles } from 'graphql/functions'; | ||||||
|  |  | ||||||
| const CREATE_PROFILE = gql` | const CREATE_PROFILE = gql` | ||||||
|   mutation CreateProfile( |   mutation CreateProfile( | ||||||
| @@ -33,10 +35,48 @@ const CREATE_PROFILE = gql` | |||||||
|  |  | ||||||
| const AddProfile = () => { | const AddProfile = () => { | ||||||
|   const { customerId } = useContext(UserContext); |   const { customerId } = useContext(UserContext); | ||||||
|   const { data: ssidProfiles } = useQuery(GET_ALL_PROFILES, { |   const { data: ssidProfiles, fetchMore } = useQuery(GET_ALL_PROFILES(), { | ||||||
|     variables: { customerId, type: 'ssid' }, |     variables: { customerId, type: 'ssid' }, | ||||||
|  |     fetchPolicy: 'network-only', | ||||||
|  |   }); | ||||||
|  |   const { data: radiusProfiles, fetchMore: fetchMoreRadiusProfiles } = useQuery( | ||||||
|  |     GET_ALL_PROFILES(), | ||||||
|  |     { | ||||||
|  |       variables: { customerId, type: 'radius' }, | ||||||
|  |       fetchPolicy: 'network-only', | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   const { data: captiveProfiles, fetchMore: fetchMoreCaptiveProfiles } = useQuery( | ||||||
|  |     GET_ALL_PROFILES(), | ||||||
|  |     { | ||||||
|  |       variables: { customerId, type: 'captive_portal' }, | ||||||
|  |       fetchPolicy: 'network-only', | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   const { data: venueProfiles, fetchMore: fetchMoreVenueProfiles } = useQuery(GET_ALL_PROFILES(), { | ||||||
|  |     variables: { customerId, type: 'passpoint_venue' }, | ||||||
|  |     fetchPolicy: 'network-only', | ||||||
|  |   }); | ||||||
|  |   const { data: operatorProfiles, fetchMore: fetchMoreOperatorProfiles } = useQuery( | ||||||
|  |     GET_ALL_PROFILES(), | ||||||
|  |     { | ||||||
|  |       variables: { customerId, type: 'passpoint_operator' }, | ||||||
|  |       fetchPolicy: 'network-only', | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   const { data: idProviderProfiles, fetchMore: fetchMoreIdProviderProfiles } = useQuery( | ||||||
|  |     GET_ALL_PROFILES(), | ||||||
|  |     { | ||||||
|  |       variables: { customerId, type: 'passpoint_osu_id_provider' }, | ||||||
|  |       fetchPolicy: 'network-only', | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   const { data: rfProfiles, fetchMore: fetchMoreRfProfiles } = useQuery(GET_ALL_PROFILES(), { | ||||||
|  |     variables: { customerId, type: 'rf' }, | ||||||
|  |     fetchPolicy: 'network-only', | ||||||
|   }); |   }); | ||||||
|   const [createProfile] = useMutation(CREATE_PROFILE); |   const [createProfile] = useMutation(CREATE_PROFILE); | ||||||
|  |   const history = useHistory(); | ||||||
|  |  | ||||||
|   const handleAddProfile = (profileType, name, details, childProfileIds = []) => { |   const handleAddProfile = (profileType, name, details, childProfileIds = []) => { | ||||||
|     createProfile({ |     createProfile({ | ||||||
| @@ -53,6 +93,7 @@ const AddProfile = () => { | |||||||
|           message: 'Success', |           message: 'Success', | ||||||
|           description: 'Profile successfully created.', |           description: 'Profile successfully created.', | ||||||
|         }); |         }); | ||||||
|  |         history.push(ROUTES.profiles, { refetch: true }); | ||||||
|       }) |       }) | ||||||
|       .catch(() => |       .catch(() => | ||||||
|         notification.error({ |         notification.error({ | ||||||
| @@ -62,12 +103,30 @@ const AddProfile = () => { | |||||||
|       ); |       ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const handleFetchMoreProfiles = (e, key) => { | ||||||
|  |     if (key === 'radius') fetchMoreProfiles(e, radiusProfiles, fetchMoreRadiusProfiles); | ||||||
|  |     else if (key === 'captive_portal') | ||||||
|  |       fetchMoreProfiles(e, captiveProfiles, fetchMoreCaptiveProfiles); | ||||||
|  |     else if (key === 'rf') fetchMoreProfiles(e, rfProfiles, fetchMoreRfProfiles); | ||||||
|  |     else if (key === 'passpoint_venue') fetchMoreProfiles(e, venueProfiles, fetchMoreVenueProfiles); | ||||||
|  |     else if (key === 'passpoint_operator') | ||||||
|  |       fetchMoreProfiles(e, operatorProfiles, fetchMoreOperatorProfiles); | ||||||
|  |     else if (key === 'passpoint_osu_id_provider') | ||||||
|  |       fetchMoreProfiles(e, idProviderProfiles, fetchMoreIdProviderProfiles); | ||||||
|  |     else fetchMoreProfiles(e, ssidProfiles, fetchMore); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <AddProfilePage |     <AddProfilePage | ||||||
|       onCreateProfile={handleAddProfile} |       onCreateProfile={handleAddProfile} | ||||||
|       ssidProfiles={ |       ssidProfiles={ssidProfiles?.getAllProfiles?.items} | ||||||
|         (ssidProfiles && ssidProfiles.getAllProfiles && ssidProfiles.getAllProfiles.items) || [] |       radiusProfiles={radiusProfiles?.getAllProfiles?.items} | ||||||
|       } |       captiveProfiles={captiveProfiles?.getAllProfiles?.items} | ||||||
|  |       venueProfiles={venueProfiles?.getAllProfiles?.items} | ||||||
|  |       operatorProfiles={operatorProfiles?.getAllProfiles?.items} | ||||||
|  |       idProviderProfiles={idProviderProfiles?.getAllProfiles?.items} | ||||||
|  |       rfProfiles={rfProfiles?.getAllProfiles?.items} | ||||||
|  |       onFetchMoreProfiles={handleFetchMoreProfiles} | ||||||
|     /> |     /> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,14 +1,13 @@ | |||||||
| import React, { useContext } from 'react'; | import React, { useContext } from 'react'; | ||||||
| import gql from 'graphql-tag'; | import { useQuery, gql } from '@apollo/client'; | ||||||
| import { useQuery } from '@apollo/react-hooks'; |  | ||||||
| import { Alert, notification } from 'antd'; | import { Alert, notification } from 'antd'; | ||||||
| import { Alarms as AlarmsPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | import { Alarms as AlarmsPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
|  |  | ||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
|  |  | ||||||
| const GET_ALL_ALARMS = gql` | const GET_ALL_ALARMS = gql` | ||||||
|   query GetAllAlarms($customerId: ID!, $cursor: String) { |   query GetAllAlarms($customerId: ID!, $context: JSONObject) { | ||||||
|     getAllAlarms(customerId: $customerId, cursor: $cursor) { |     getAllAlarms(customerId: $customerId, context: $context) { | ||||||
|       items { |       items { | ||||||
|         severity |         severity | ||||||
|         alarmCode |         alarmCode | ||||||
| @@ -19,10 +18,7 @@ const GET_ALL_ALARMS = gql` | |||||||
|           name |           name | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       context { |       context | ||||||
|         cursor |  | ||||||
|         lastPage |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| `; | `; | ||||||
| @@ -31,6 +27,7 @@ const Alarms = () => { | |||||||
|   const { customerId } = useContext(UserContext); |   const { customerId } = useContext(UserContext); | ||||||
|   const { loading, error, data, refetch, fetchMore } = useQuery(GET_ALL_ALARMS, { |   const { loading, error, data, refetch, fetchMore } = useQuery(GET_ALL_ALARMS, { | ||||||
|     variables: { customerId }, |     variables: { customerId }, | ||||||
|  |     errorPolicy: 'all', | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   const handleOnReload = () => { |   const handleOnReload = () => { | ||||||
| @@ -52,7 +49,7 @@ const Alarms = () => { | |||||||
|   const handleLoadMore = () => { |   const handleLoadMore = () => { | ||||||
|     if (!data.getAllAlarms.context.lastPage) { |     if (!data.getAllAlarms.context.lastPage) { | ||||||
|       fetchMore({ |       fetchMore({ | ||||||
|         variables: { cursor: data.getAllAlarms.context.cursor }, |         variables: { context: data.getAllAlarms.context }, | ||||||
|         updateQuery: (previousResult, { fetchMoreResult }) => { |         updateQuery: (previousResult, { fetchMoreResult }) => { | ||||||
|           const previousEntry = previousResult.getAllAlarms; |           const previousEntry = previousResult.getAllAlarms; | ||||||
|           const newItems = fetchMoreResult.getAllAlarms.items; |           const newItems = fetchMoreResult.getAllAlarms.items; | ||||||
| @@ -73,9 +70,10 @@ const Alarms = () => { | |||||||
|     return <Loading />; |     return <Loading />; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (error) { |   if (error && !data?.getAllAlarms?.items) { | ||||||
|     return <Alert message="Error" description="Failed to load alarms." type="error" showIcon />; |     return <Alert message="Error" description="Failed to load alarms." type="error" showIcon />; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <AlarmsPage |     <AlarmsPage | ||||||
|       data={data.getAllAlarms.items} |       data={data.getAllAlarms.items} | ||||||
|   | |||||||
| @@ -2,12 +2,12 @@ import React, { useState } from 'react'; | |||||||
| import { Helmet } from 'react-helmet'; | import { Helmet } from 'react-helmet'; | ||||||
| import { Switch, Redirect } from 'react-router-dom'; | import { Switch, Redirect } from 'react-router-dom'; | ||||||
|  |  | ||||||
| import { ThemeProvider } from '@tip-wlan/wlan-cloud-ui-library'; | import { ThemeProvider, GenericNotFound } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
|  |  | ||||||
| import logo from 'images/tip-logo.png'; | import logo from 'images/tip-logo.png'; | ||||||
| import logoMobile from 'images/tip-logo-mobile.png'; | import logoMobile from 'images/tip-logo-mobile.png'; | ||||||
|  |  | ||||||
| import { AUTH_TOKEN, COMPANY } from 'constants/index'; | import { AUTH_TOKEN, COMPANY, ROUTES, USER_FRIENDLY_RADIOS } from 'constants/index'; | ||||||
| import Login from 'containers/Login'; | import Login from 'containers/Login'; | ||||||
|  |  | ||||||
| import Network from 'containers/Network'; | import Network from 'containers/Network'; | ||||||
| @@ -32,7 +32,7 @@ import ProtectedRouteWithLayout from './components/ProtectedRouteWithLayout'; | |||||||
| const RedirectToDashboard = () => ( | const RedirectToDashboard = () => ( | ||||||
|   <Redirect |   <Redirect | ||||||
|     to={{ |     to={{ | ||||||
|       pathname: '/dashboard', |       pathname: ROUTES.dashboard, | ||||||
|     }} |     }} | ||||||
|   /> |   /> | ||||||
| ); | ); | ||||||
| @@ -42,7 +42,7 @@ const App = () => { | |||||||
|   let initialUser = {}; |   let initialUser = {}; | ||||||
|   if (token) { |   if (token) { | ||||||
|     const { userId, userName, userRole, customerId } = parseJwt(token.access_token); |     const { userId, userName, userRole, customerId } = parseJwt(token.access_token); | ||||||
|     initialUser = { id: userId, email: userName, role: userRole, customerId }; |     initialUser = { id: userId, email: userName, roles: userRole, customerId }; | ||||||
|   } |   } | ||||||
|   const [user, setUser] = useState(initialUser); |   const [user, setUser] = useState(initialUser); | ||||||
|  |  | ||||||
| @@ -50,7 +50,7 @@ const App = () => { | |||||||
|     setItem(AUTH_TOKEN, newToken); |     setItem(AUTH_TOKEN, newToken); | ||||||
|     if (newToken) { |     if (newToken) { | ||||||
|       const { userId, userName, userRole, customerId } = parseJwt(newToken.access_token); |       const { userId, userName, userRole, customerId } = parseJwt(newToken.access_token); | ||||||
|       setUser({ id: userId, email: userName, role: userRole, customerId }); |       setUser({ id: userId, email: userName, roles: userRole, customerId }); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
| @@ -60,32 +60,45 @@ const App = () => { | |||||||
|     <UserProvider |     <UserProvider | ||||||
|       id={user.id} |       id={user.id} | ||||||
|       email={user.email} |       email={user.email} | ||||||
|       role={user.role} |       roles={user.roles} | ||||||
|       customerId={user.customerId} |       customerId={user.customerId} | ||||||
|       updateUser={updateUser} |       updateUser={updateUser} | ||||||
|       updateToken={updateToken} |       updateToken={updateToken} | ||||||
|     > |     > | ||||||
|       <ThemeProvider company={COMPANY} logo={logo} logoMobile={logoMobile}> |       <ThemeProvider | ||||||
|  |         company={COMPANY} | ||||||
|  |         logo={logo} | ||||||
|  |         logoMobile={logoMobile} | ||||||
|  |         routes={ROUTES} | ||||||
|  |         radioTypes={USER_FRIENDLY_RADIOS} | ||||||
|  |       > | ||||||
|         <Helmet titleTemplate={`%s - ${COMPANY}`} defaultTitle={COMPANY}> |         <Helmet titleTemplate={`%s - ${COMPANY}`} defaultTitle={COMPANY}> | ||||||
|           <meta name="description" content={COMPANY} /> |           <meta name="description" content={COMPANY} /> | ||||||
|         </Helmet> |         </Helmet> | ||||||
|  |  | ||||||
|         <Switch> |         <Switch> | ||||||
|           <UnauthenticatedRoute exact path="/login" component={Login} /> |           <UnauthenticatedRoute exact path={ROUTES.login} component={Login} /> | ||||||
|           <ProtectedRouteWithLayout exact path="/" component={RedirectToDashboard} /> |           <ProtectedRouteWithLayout exact path={ROUTES.root} component={RedirectToDashboard} /> | ||||||
|           <ProtectedRouteWithLayout exact path="/dashboard" component={Dashboard} /> |           <ProtectedRouteWithLayout exact path={ROUTES.dashboard} component={Dashboard} /> | ||||||
|           <ProtectedRouteWithLayout path="/network" component={Network} /> |           <ProtectedRouteWithLayout path={ROUTES.network} component={Network} /> | ||||||
|           <ProtectedRouteWithLayout path="/system" component={System} /> |           <ProtectedRouteWithLayout path={ROUTES.system} component={System} /> | ||||||
|  |  | ||||||
|           <ProtectedRouteWithLayout exact path="/profiles" component={Profiles} /> |           <ProtectedRouteWithLayout exact path={ROUTES.profiles} component={Profiles} /> | ||||||
|           <ProtectedRouteWithLayout exact path="/profiles/:id" component={ProfileDetails} /> |           <ProtectedRouteWithLayout | ||||||
|           <ProtectedRouteWithLayout exact path="/addprofile" component={AddProfile} /> |             exact | ||||||
|  |             path={`${ROUTES.profiles}/:id`} | ||||||
|  |             component={ProfileDetails} | ||||||
|  |           /> | ||||||
|  |           <ProtectedRouteWithLayout exact path={ROUTES.addprofile} component={AddProfile} /> | ||||||
|  |  | ||||||
|           <ProtectedRouteWithLayout exact path="/alarms" component={Alarms} /> |           <ProtectedRouteWithLayout exact path={ROUTES.alarms} component={Alarms} /> | ||||||
|           <ProtectedRouteWithLayout exact path="/account/edit" component={EditAccount} /> |           {user?.id !== 0 && ( | ||||||
|           {user.role === 'SuperUser' && ( |             <ProtectedRouteWithLayout exact path={ROUTES.account} component={EditAccount} /> | ||||||
|             <ProtectedRouteWithLayout exact path="/accounts" component={Accounts} /> |  | ||||||
|           )} |           )} | ||||||
|  |           {user?.roles?.[0] === 'SuperUser' && ( | ||||||
|  |             <ProtectedRouteWithLayout exact path={ROUTES.users} component={Accounts} /> | ||||||
|  |           )} | ||||||
|  |           <ProtectedRouteWithLayout component={GenericNotFound} /> | ||||||
|         </Switch> |         </Switch> | ||||||
|       </ThemeProvider> |       </ThemeProvider> | ||||||
|     </UserProvider> |     </UserProvider> | ||||||
|   | |||||||
| @@ -1,10 +1,11 @@ | |||||||
| import React, { useContext, useMemo, useState } from 'react'; | import React, { useContext, useEffect, useMemo, useState, useRef } from 'react'; | ||||||
| import { Alert } from 'antd'; | import { Alert } from 'antd'; | ||||||
| import moment from 'moment'; | import moment from 'moment'; | ||||||
| import { useQuery } from '@apollo/react-hooks'; | import { useQuery } from '@apollo/client'; | ||||||
| import { Dashboard as DashboardPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | import { Dashboard as DashboardPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
| import { FILTER_SYSTEM_EVENTS, GET_ALL_STATUS } from 'graphql/queries'; | import { FILTER_SYSTEM_EVENTS, GET_ALL_STATUS } from 'graphql/queries'; | ||||||
|  | import { USER_FRIENDLY_RADIOS } from 'constants/index'; | ||||||
|  |  | ||||||
| function formatBytes(bytes, decimals = 2) { | function formatBytes(bytes, decimals = 2) { | ||||||
|   if (bytes === 0) return '0 Bytes'; |   if (bytes === 0) return '0 Bytes'; | ||||||
| @@ -13,7 +14,7 @@ function formatBytes(bytes, decimals = 2) { | |||||||
|   const dm = decimals < 0 ? 0 : decimals; |   const dm = decimals < 0 ? 0 : decimals; | ||||||
|   const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; |   const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; | ||||||
|  |  | ||||||
|   const i = Math.floor(Math.log(bytes) / Math.log(k)); |   const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k)); | ||||||
|  |  | ||||||
|   // eslint-disable-next-line no-restricted-properties |   // eslint-disable-next-line no-restricted-properties | ||||||
|   return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i] || ''}`; |   return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i] || ''}`; | ||||||
| @@ -29,64 +30,82 @@ function trafficTooltipFormatter() { | |||||||
|   )}</b><br/>`; |   )}</b><br/>`; | ||||||
| } | } | ||||||
|  |  | ||||||
| const USER_FRIENDLY_RADIOS = { | const lineChartConfig = [ | ||||||
|   is2dot4GHz: '2.4GHz', |   { key: 'inservicesAPs', title: 'Inservice APs (24 hours)' }, | ||||||
|   is5GHzL: '5GHz (L)', |   { key: 'clientDevices', title: 'Client Devices (24 hours)' }, | ||||||
|   is5GHzU: '5GHz (U)', |   { | ||||||
|   is5GHz: '5GHz', |     key: 'traffic', | ||||||
| }; |     title: 'Traffic (24 hours)', | ||||||
|  |     options: { formatter: trafficLabelFormatter, tooltipFormatter: trafficTooltipFormatter }, | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  |  | ||||||
| const Dashboard = () => { | const Dashboard = () => { | ||||||
|  |   const initialGraphTime = useRef({ | ||||||
|  |     toTime: moment() | ||||||
|  |       .valueOf() | ||||||
|  |       .toString(), | ||||||
|  |     fromTime: moment() | ||||||
|  |       .subtract(24, 'hours') | ||||||
|  |       .valueOf() | ||||||
|  |       .toString(), | ||||||
|  |   }); | ||||||
|   const { customerId } = useContext(UserContext); |   const { customerId } = useContext(UserContext); | ||||||
|   const { loading, error, data } = useQuery(GET_ALL_STATUS, { |   const { loading, error, data } = useQuery(GET_ALL_STATUS, { | ||||||
|     variables: { customerId, statusDataTypes: ['CUSTOMER_DASHBOARD'] }, |     variables: { customerId, statusDataTypes: ['CUSTOMER_DASHBOARD'] }, | ||||||
|   }); |   }); | ||||||
|   const [toTime] = useState( |  | ||||||
|     moment() |  | ||||||
|       .valueOf() |  | ||||||
|       .toString() |  | ||||||
|   ); |  | ||||||
|   const [fromTime] = useState( |  | ||||||
|     moment() |  | ||||||
|       .subtract(24, 'hours') |  | ||||||
|       .valueOf() |  | ||||||
|       .toString() |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   const { |   const [lineChartData, setLineChartData] = useState({ | ||||||
|     loading: metricsLoading, |     inservicesAPs: { | ||||||
|     error: metricsError, |       key: 'Inservice APs', | ||||||
|     data: metricsData, |       value: [], | ||||||
|     // refetch: metricsRefetch, |     }, | ||||||
|   } = useQuery(FILTER_SYSTEM_EVENTS, { |     clientDevices: { | ||||||
|     variables: { |       is2dot4GHz: { | ||||||
|       customerId, |         key: USER_FRIENDLY_RADIOS.is2dot4GHz, | ||||||
|       fromTime, |         value: [], | ||||||
|       toTime, |       }, | ||||||
|       equipmentIds: [0], |       is5GHz: { | ||||||
|       dataTypes: ['StatusChangedEvent'], |         key: USER_FRIENDLY_RADIOS.is5GHz, | ||||||
|       limit: 1000, |         value: [], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     traffic: { | ||||||
|  |       trafficBytesDownstream: { | ||||||
|  |         key: 'Down Stream', | ||||||
|  |         value: [], | ||||||
|  |       }, | ||||||
|  |       trafficBytesUpstream: { | ||||||
|  |         key: 'Up Stream', | ||||||
|  |         value: [], | ||||||
|  |       }, | ||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   const { loading: metricsLoading, error: metricsError, data: metricsData, fetchMore } = useQuery( | ||||||
|  |     FILTER_SYSTEM_EVENTS, | ||||||
|  |     { | ||||||
|  |       variables: { | ||||||
|  |         customerId, | ||||||
|  |         fromTime: initialGraphTime.current.fromTime, | ||||||
|  |         toTime: initialGraphTime.current.toTime, | ||||||
|  |         equipmentIds: [0], | ||||||
|  |         dataTypes: ['StatusChangedEvent'], | ||||||
|  |         limit: 3000, // TODO: make get all in GraphQL | ||||||
|  |       }, | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |  | ||||||
|   const formatLineChartData = (list = []) => { |   const formatLineChartData = (list = []) => { | ||||||
|     const lineChartData = { |     if (list.length) { | ||||||
|       inservicesAPs: { |       setLineChartData(prev => { | ||||||
|         title: 'Inservice APs (24 hours)', |         const inservicesAPs = []; | ||||||
|         data: { key: 'Inservice APs', value: [] }, |         const clientDevices2dot4GHz = []; | ||||||
|       }, |         const clientDevices5GHz = []; | ||||||
|       clientDevices: { title: 'Client Devices (24 hours)' }, |         const trafficBytesDownstreamData = []; | ||||||
|       traffic: { |         const trafficBytesUpstreamData = []; | ||||||
|         title: 'Traffic (24 hours)', |         let totalDown = 0; | ||||||
|         formatter: trafficLabelFormatter, |         let totalUp = 0; | ||||||
|         tooltipFormatter: trafficTooltipFormatter, |  | ||||||
|         data: { |  | ||||||
|           trafficBytesDownstream: { key: 'Down Stream', value: [] }, |  | ||||||
|           trafficBytesUpstream: { key: 'Up Stream', value: [] }, |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|     }; |  | ||||||
|     const clientDevicesData = {}; |  | ||||||
|  |  | ||||||
|         list.forEach( |         list.forEach( | ||||||
|           ({ |           ({ | ||||||
| @@ -102,40 +121,88 @@ const Dashboard = () => { | |||||||
|               }, |               }, | ||||||
|             }, |             }, | ||||||
|           }) => { |           }) => { | ||||||
|         lineChartData.inservicesAPs.data.value.push([eventTimestamp, equipmentInServiceCount]); |             inservicesAPs.push([eventTimestamp, equipmentInServiceCount]); | ||||||
|         Object.keys(radios).forEach(key => { |  | ||||||
|           if (!clientDevicesData[key]) { |  | ||||||
|             clientDevicesData[key] = { |  | ||||||
|               key: USER_FRIENDLY_RADIOS[key] || key, |  | ||||||
|               value: [], |  | ||||||
|             }; |  | ||||||
|           } |  | ||||||
|           clientDevicesData[key].value.push([eventTimestamp, radios[key]]); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         lineChartData.traffic.data.trafficBytesDownstream.value.push([ |             let total5GHz = 0; | ||||||
|  |             total5GHz += (radios?.is5GHz || 0) + (radios?.is5GHzL || 0) + (radios?.is5GHzU || 0); // combine all 5GHz radios | ||||||
|  |  | ||||||
|  |             clientDevices2dot4GHz.push([eventTimestamp, radios.is2dot4GHz || 0]); | ||||||
|  |             clientDevices5GHz.push([eventTimestamp, total5GHz || 0]); | ||||||
|  |  | ||||||
|  |             trafficBytesDownstreamData.push([ | ||||||
|               eventTimestamp, |               eventTimestamp, | ||||||
|           trafficBytesDownstream, |               (trafficBytesDownstream > 0 && trafficBytesDownstream) || 0, | ||||||
|             ]); |             ]); | ||||||
|         lineChartData.traffic.data.trafficBytesUpstream.value.push([ |             trafficBytesUpstreamData.push([ | ||||||
|               eventTimestamp, |               eventTimestamp, | ||||||
|           trafficBytesUpstream, |               (trafficBytesUpstream > 0 && trafficBytesUpstream) || 0, | ||||||
|             ]); |             ]); | ||||||
|  |  | ||||||
|  |             totalDown += (trafficBytesDownstream > 0 && trafficBytesDownstream) || 0; | ||||||
|  |             totalUp += (trafficBytesUpstream > 0 && trafficBytesUpstream) || 0; | ||||||
|           } |           } | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         return { |         return { | ||||||
|       ...lineChartData, |           inservicesAPs: { | ||||||
|       clientDevices: { ...lineChartData.clientDevices, data: { ...clientDevicesData } }, |             ...prev.inservicesAPs, | ||||||
|  |             value: [...prev.inservicesAPs.value, ...inservicesAPs], | ||||||
|  |           }, | ||||||
|  |           clientDevices: { | ||||||
|  |             is2dot4GHz: { | ||||||
|  |               ...prev.clientDevices.is2dot4GHz, | ||||||
|  |               value: [...prev.clientDevices.is2dot4GHz.value, ...clientDevices2dot4GHz], | ||||||
|  |             }, | ||||||
|  |             is5GHz: { | ||||||
|  |               ...prev.clientDevices.is5GHz, | ||||||
|  |               value: [...prev.clientDevices.is5GHz.value, ...clientDevices5GHz], | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |           traffic: { | ||||||
|  |             trafficBytesDownstream: { | ||||||
|  |               ...prev.traffic.trafficBytesDownstream, | ||||||
|  |               value: [...prev.traffic.trafficBytesDownstream.value, ...trafficBytesDownstreamData], | ||||||
|  |             }, | ||||||
|  |             trafficBytesUpstream: { | ||||||
|  |               ...prev.traffic.trafficBytesUpstream, | ||||||
|  |               value: [...prev.traffic.trafficBytesUpstream.value, ...trafficBytesUpstreamData], | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |           totalDownstreamTraffic: totalDown, | ||||||
|  |           totalUpstreamTraffic: totalUp, | ||||||
|         }; |         }; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const lineChartsData = useMemo( |   useEffect(() => { | ||||||
|     () => formatLineChartData(metricsData?.filterSystemEvents?.items), |     const interval = setInterval(() => { | ||||||
|     [metricsData] |       const toTime = moment() | ||||||
|   ); |         .valueOf() | ||||||
|  |         .toString(); | ||||||
|  |       const fromTime = moment() | ||||||
|  |         .subtract(5, 'minutes') | ||||||
|  |         .valueOf() | ||||||
|  |         .toString(); | ||||||
|  |       fetchMore({ | ||||||
|  |         variables: { | ||||||
|  |           fromTime, | ||||||
|  |           toTime, | ||||||
|  |         }, | ||||||
|  |         updateQuery: (_, { fetchMoreResult }) => { | ||||||
|  |           formatLineChartData(fetchMoreResult?.filterSystemEvents?.items); | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     }, 300000); | ||||||
|  |  | ||||||
|   const statsArr = useMemo(() => { |     return () => clearInterval(interval); | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     formatLineChartData(metricsData?.filterSystemEvents?.items); | ||||||
|  |   }, [metricsData]); | ||||||
|  |  | ||||||
|  |   const statsData = useMemo(() => { | ||||||
|     const status = data?.getAllStatus?.items[0]?.detailsJSON || {}; |     const status = data?.getAllStatus?.items[0]?.detailsJSON || {}; | ||||||
|  |  | ||||||
|     const { |     const { | ||||||
| @@ -143,8 +210,6 @@ const Dashboard = () => { | |||||||
|       totalProvisionedEquipment, |       totalProvisionedEquipment, | ||||||
|       equipmentInServiceCount, |       equipmentInServiceCount, | ||||||
|       equipmentWithClientsCount, |       equipmentWithClientsCount, | ||||||
|       trafficBytesDownstream, |  | ||||||
|       trafficBytesUpstream, |  | ||||||
|     } = status; |     } = status; | ||||||
|  |  | ||||||
|     const clientRadios = {}; |     const clientRadios = {}; | ||||||
| @@ -164,24 +229,13 @@ const Dashboard = () => { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return [ |     return { | ||||||
|       { |       totalProvisionedEquipment, | ||||||
|         title: 'Access Point', |       equipmentInServiceCount, | ||||||
|         'Total Provisioned': totalProvisionedEquipment, |       equipmentWithClientsCount, | ||||||
|         'In Service': equipmentInServiceCount, |       totalAssociated, | ||||||
|         'With Clients': equipmentWithClientsCount, |       clientRadios, | ||||||
|       }, |     }; | ||||||
|       { |  | ||||||
|         title: 'Client Devices', |  | ||||||
|         'Total Associated': totalAssociated, |  | ||||||
|         ...clientRadios, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         title: 'Usage Information', |  | ||||||
|         'Total Traffic (US)': formatBytes(trafficBytesUpstream), |  | ||||||
|         'Total Traffic (DS)': formatBytes(trafficBytesDownstream), |  | ||||||
|       }, |  | ||||||
|     ]; |  | ||||||
|   }, [data]); |   }, [data]); | ||||||
|  |  | ||||||
|   const pieChartsData = useMemo(() => { |   const pieChartsData = useMemo(() => { | ||||||
| @@ -203,9 +257,27 @@ const Dashboard = () => { | |||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <DashboardPage |     <DashboardPage | ||||||
|       statsCardDetails={statsArr} |       statsCardDetails={[ | ||||||
|  |         { | ||||||
|  |           title: 'Access Point', | ||||||
|  |           'Total Provisioned': statsData?.totalProvisionedEquipment, | ||||||
|  |           'In Service': statsData?.equipmentInServiceCount, | ||||||
|  |           'With Clients': statsData?.equipmentWithClientsCount, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           title: 'Client Devices', | ||||||
|  |           'Total Associated': statsData?.totalAssociated, | ||||||
|  |           ...statsData?.clientRadios, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           title: 'Usage Information (24 hours)', | ||||||
|  |           'Total Traffic (US)': formatBytes(lineChartData?.totalUpstreamTraffic), | ||||||
|  |           'Total Traffic (DS)': formatBytes(lineChartData?.totalDownstreamTraffic), | ||||||
|  |         }, | ||||||
|  |       ]} | ||||||
|       pieChartDetails={pieChartsData} |       pieChartDetails={pieChartsData} | ||||||
|       lineChartDetails={lineChartsData} |       lineChartData={lineChartData} | ||||||
|  |       lineChartConfig={lineChartConfig} | ||||||
|       lineChartLoading={metricsLoading} |       lineChartLoading={metricsLoading} | ||||||
|       lineChartError={metricsError} |       lineChartError={metricsError} | ||||||
|     /> |     /> | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| import React, { useContext } from 'react'; | import React, { useContext } from 'react'; | ||||||
| import gql from 'graphql-tag'; | import { useMutation, useQuery, gql } from '@apollo/client'; | ||||||
| import { useMutation, useQuery } from '@apollo/react-hooks'; |  | ||||||
| import { notification, Alert } from 'antd'; | import { notification, Alert } from 'antd'; | ||||||
| import { EditAccount as EditAccountPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | import { EditAccount as EditAccountPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
|  |  | ||||||
| @@ -11,7 +10,7 @@ const GET_USER = gql` | |||||||
|     getUser(id: $id) { |     getUser(id: $id) { | ||||||
|       id |       id | ||||||
|       username |       username | ||||||
|       role |       roles | ||||||
|       customerId |       customerId | ||||||
|       lastModifiedTimestamp |       lastModifiedTimestamp | ||||||
|     } |     } | ||||||
| @@ -23,7 +22,7 @@ const UPDATE_USER = gql` | |||||||
|     $id: ID! |     $id: ID! | ||||||
|     $username: String! |     $username: String! | ||||||
|     $password: String! |     $password: String! | ||||||
|     $role: String! |     $roles: [String] | ||||||
|     $customerId: ID! |     $customerId: ID! | ||||||
|     $lastModifiedTimestamp: String |     $lastModifiedTimestamp: String | ||||||
|   ) { |   ) { | ||||||
| @@ -31,13 +30,13 @@ const UPDATE_USER = gql` | |||||||
|       id: $id |       id: $id | ||||||
|       username: $username |       username: $username | ||||||
|       password: $password |       password: $password | ||||||
|       role: $role |       roles: $roles | ||||||
|       customerId: $customerId |       customerId: $customerId | ||||||
|       lastModifiedTimestamp: $lastModifiedTimestamp |       lastModifiedTimestamp: $lastModifiedTimestamp | ||||||
|     ) { |     ) { | ||||||
|       id |       id | ||||||
|       username |       username | ||||||
|       role |       roles | ||||||
|       customerId |       customerId | ||||||
|       lastModifiedTimestamp |       lastModifiedTimestamp | ||||||
|     } |     } | ||||||
| @@ -50,14 +49,14 @@ const EditAccount = () => { | |||||||
|   const [updateUser] = useMutation(UPDATE_USER); |   const [updateUser] = useMutation(UPDATE_USER); | ||||||
|  |  | ||||||
|   const handleSubmit = newPassword => { |   const handleSubmit = newPassword => { | ||||||
|     const { role, customerId, lastModifiedTimestamp } = data.getUser; |     const { roles, customerId, lastModifiedTimestamp } = data.getUser; | ||||||
|  |  | ||||||
|     updateUser({ |     updateUser({ | ||||||
|       variables: { |       variables: { | ||||||
|         id, |         id, | ||||||
|         username: email, |         username: email, | ||||||
|         password: newPassword, |         password: newPassword, | ||||||
|         role, |         roles, | ||||||
|         customerId, |         customerId, | ||||||
|         lastModifiedTimestamp, |         lastModifiedTimestamp, | ||||||
|       }, |       }, | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| import React, { useContext } from 'react'; | import React, { useContext } from 'react'; | ||||||
| import gql from 'graphql-tag'; | import { useMutation, useApolloClient, gql } from '@apollo/client'; | ||||||
| import { useMutation, useApolloClient } from '@apollo/react-hooks'; |  | ||||||
| import { useHistory } from 'react-router-dom'; | import { useHistory } from 'react-router-dom'; | ||||||
| import { notification } from 'antd'; | import { notification } from 'antd'; | ||||||
|  |  | ||||||
| @@ -27,9 +26,10 @@ const Login = () => { | |||||||
|   const handleLogin = (email, password) => { |   const handleLogin = (email, password) => { | ||||||
|     authenticateUser({ variables: { email, password } }) |     authenticateUser({ variables: { email, password } }) | ||||||
|       .then(({ data }) => { |       .then(({ data }) => { | ||||||
|         client.resetStore(); |         client.resetStore().then(() => { | ||||||
|           updateToken(data.authenticateUser); |           updateToken(data.authenticateUser); | ||||||
|           history.push('/'); |           history.push('/'); | ||||||
|  |         }); | ||||||
|       }) |       }) | ||||||
|       .catch(() => |       .catch(() => | ||||||
|         notification.error({ |         notification.error({ | ||||||
|   | |||||||
| @@ -1,19 +1,19 @@ | |||||||
| import React, { useContext } from 'react'; | import React, { useContext } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useLocation } from 'react-router-dom'; | import { useLocation } from 'react-router-dom'; | ||||||
| import { useApolloClient, useQuery } from '@apollo/react-hooks'; | import { useApolloClient, useQuery } from '@apollo/client'; | ||||||
| import { AppLayout as Layout } from '@tip-wlan/wlan-cloud-ui-library'; | import { AppLayout as Layout } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
|  |  | ||||||
| import { GET_ALARM_COUNT } from 'graphql/queries'; | import { GET_ALARM_COUNT } from 'graphql/queries'; | ||||||
|  |  | ||||||
| import { AUTH_TOKEN } from 'constants/index'; | import { AUTH_TOKEN, ROUTES } from 'constants/index'; | ||||||
|  |  | ||||||
| import { removeItem } from 'utils/localStorage'; | import { removeItem } from 'utils/localStorage'; | ||||||
|  |  | ||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
|  |  | ||||||
| const MasterLayout = ({ children }) => { | const MasterLayout = ({ children }) => { | ||||||
|   const { role, customerId } = useContext(UserContext); |   const { roles, customerId, id: currentUserId } = useContext(UserContext); | ||||||
|  |  | ||||||
|   const client = useApolloClient(); |   const client = useApolloClient(); | ||||||
|   const location = useLocation(); |   const location = useLocation(); | ||||||
| @@ -30,75 +30,60 @@ const MasterLayout = ({ children }) => { | |||||||
|   const menuItems = [ |   const menuItems = [ | ||||||
|     { |     { | ||||||
|       key: 'dashboard', |       key: 'dashboard', | ||||||
|       path: '/dashboard', |       path: ROUTES.dashboard, | ||||||
|       text: 'Dashboard', |       text: 'Dashboard', | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       key: 'network', |       key: 'network', | ||||||
|       path: '/network', |       path: ROUTES.network, | ||||||
|       text: 'Network', |       text: 'Network', | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       key: 'profiles', |       key: 'profiles', | ||||||
|       path: '/profiles', |       path: ROUTES.profiles, | ||||||
|       text: 'Profiles', |       text: 'Profiles', | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       key: 'system', |       key: 'system', | ||||||
|       path: '/system', |       path: ROUTES.system, | ||||||
|       text: 'System', |       text: 'System', | ||||||
|     }, |     }, | ||||||
|   ]; |   ]; | ||||||
|  |  | ||||||
|   const mobileMenuItems = [ |   const mobileMenuItems = [ | ||||||
|     { |     ...menuItems, | ||||||
|       key: 'dashboard', |  | ||||||
|       path: '/dashboard', |  | ||||||
|       text: 'Dashboard', |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       key: 'network', |  | ||||||
|       path: '/network', |  | ||||||
|       text: 'Network', |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       key: 'profiles', |  | ||||||
|       path: '/profiles', |  | ||||||
|       text: 'Profiles', |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       key: 'system', |  | ||||||
|       path: '/system', |  | ||||||
|       text: 'System', |  | ||||||
|     }, |  | ||||||
|     { |     { | ||||||
|       key: 'settings', |       key: 'settings', | ||||||
|       text: 'Settings', |       text: 'Settings', | ||||||
|       children: [ |       children: [ | ||||||
|  |         ...(currentUserId !== 0 | ||||||
|  |           ? [ | ||||||
|               { |               { | ||||||
|                 key: 'editAccount', |                 key: 'editAccount', | ||||||
|           path: '/account/edit', |                 path: ROUTES.account, | ||||||
|                 text: 'Edit Account', |                 text: 'Edit Account', | ||||||
|               }, |               }, | ||||||
|  |             ] | ||||||
|  |           : []), | ||||||
|         { |         { | ||||||
|           key: 'logout', |           key: 'logout', | ||||||
|           path: '/', |           path: ROUTES.root, | ||||||
|           text: 'Log Out', |           text: 'Log Out', | ||||||
|         }, |         }, | ||||||
|       ], |       ], | ||||||
|     }, |     }, | ||||||
|   ]; |   ]; | ||||||
|  |  | ||||||
|   if (role === 'SuperUser') { |   if (roles?.[0] === 'SuperUser') { | ||||||
|     menuItems.push({ |     menuItems.push({ | ||||||
|       key: 'accounts', |       key: 'users', | ||||||
|       path: '/accounts', |       path: ROUTES.users, | ||||||
|       text: 'Accounts', |       text: 'Users', | ||||||
|     }); |     }); | ||||||
|     mobileMenuItems.push({ |     mobileMenuItems.push({ | ||||||
|       key: 'accounts', |       key: 'users', | ||||||
|       path: '/accounts', |       path: ROUTES.users, | ||||||
|       text: 'Accounts', |       text: 'Users', | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -109,6 +94,7 @@ const MasterLayout = ({ children }) => { | |||||||
|       menuItems={menuItems} |       menuItems={menuItems} | ||||||
|       mobileMenuItems={mobileMenuItems} |       mobileMenuItems={mobileMenuItems} | ||||||
|       totalAlarms={data && data.getAlarmCount} |       totalAlarms={data && data.getAlarmCount} | ||||||
|  |       currentUserId={currentUserId} | ||||||
|     > |     > | ||||||
|       {children} |       {children} | ||||||
|     </Layout> |     </Layout> | ||||||
|   | |||||||
| @@ -1,172 +1,71 @@ | |||||||
| import React, { useContext } from 'react'; | import React, { useContext } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams, useHistory } from 'react-router-dom'; | ||||||
| import gql from 'graphql-tag'; | import { useQuery, useMutation } from '@apollo/client'; | ||||||
| import { useQuery, useMutation } from '@apollo/react-hooks'; | import { Alert, notification } from 'antd'; | ||||||
| import { Alert, Spin, notification } from 'antd'; |  | ||||||
| import moment from 'moment'; | import moment from 'moment'; | ||||||
| import { AccessPointDetails as AccessPointDetailsPage } from '@tip-wlan/wlan-cloud-ui-library'; | import { | ||||||
|  |   AccessPointDetails as AccessPointDetailsPage, | ||||||
|  |   Loading, | ||||||
|  | } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
|  |  | ||||||
| import { FILTER_SERVICE_METRICS } from 'graphql/queries'; | import { | ||||||
| import { UPDATE_EQUIPMENT_FIRMWARE } from 'graphql/mutations'; |   GET_EQUIPMENT, | ||||||
|  |   FILTER_SERVICE_METRICS, | ||||||
|  |   GET_ALL_FIRMWARE, | ||||||
|  |   GET_ALL_PROFILES, | ||||||
|  | } from 'graphql/queries'; | ||||||
|  | import { | ||||||
|  |   UPDATE_EQUIPMENT, | ||||||
|  |   DELETE_EQUIPMENT, | ||||||
|  |   UPDATE_EQUIPMENT_FIRMWARE, | ||||||
|  |   REQUEST_EQUIPMENT_SWITCH_BANK, | ||||||
|  |   REQUEST_EQUIPMENT_REBOOT, | ||||||
|  | } from 'graphql/mutations'; | ||||||
|  | import { fetchMoreProfiles } from 'graphql/functions'; | ||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
|  |  | ||||||
| const GET_EQUIPMENT = gql` |  | ||||||
|   query GetEquipment($id: ID!) { |  | ||||||
|     getEquipment(id: $id) { |  | ||||||
|       id |  | ||||||
|       equipmentType |  | ||||||
|       inventoryId |  | ||||||
|       customerId |  | ||||||
|       profileId |  | ||||||
|       locationId |  | ||||||
|       name |  | ||||||
|       latitude |  | ||||||
|       longitude |  | ||||||
|       serial |  | ||||||
|       lastModifiedTimestamp |  | ||||||
|       details |  | ||||||
|       profile { |  | ||||||
|         name |  | ||||||
|         childProfiles { |  | ||||||
|           id |  | ||||||
|           name |  | ||||||
|           details |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       status { |  | ||||||
|         firmware { |  | ||||||
|           detailsJSON |  | ||||||
|         } |  | ||||||
|         protocol { |  | ||||||
|           detailsJSON |  | ||||||
|           details { |  | ||||||
|             reportedMacAddr |  | ||||||
|             manufacturer |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|         radioUtilization { |  | ||||||
|           detailsJSON |  | ||||||
|         } |  | ||||||
|         clientDetails { |  | ||||||
|           detailsJSON |  | ||||||
|           details { |  | ||||||
|             numClientsPerRadio |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|         osPerformance { |  | ||||||
|           detailsJSON |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       model |  | ||||||
|       alarmsCount |  | ||||||
|       alarms { |  | ||||||
|         severity |  | ||||||
|         alarmCode |  | ||||||
|         details |  | ||||||
|         createdTimestamp |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| `; |  | ||||||
|  |  | ||||||
| export const GET_ALL_FIRMWARE = gql` |  | ||||||
|   query GetAllFirmware { |  | ||||||
|     getAllFirmware { |  | ||||||
|       id |  | ||||||
|       modelId |  | ||||||
|       versionName |  | ||||||
|       description |  | ||||||
|       filename |  | ||||||
|       commit |  | ||||||
|       releaseDate |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| `; |  | ||||||
|  |  | ||||||
| const UPDATE_EQUIPMENT = gql` |  | ||||||
|   mutation UpdateEquipment( |  | ||||||
|     $id: ID! |  | ||||||
|     $equipmentType: String! |  | ||||||
|     $inventoryId: String! |  | ||||||
|     $customerId: ID! |  | ||||||
|     $profileId: ID! |  | ||||||
|     $locationId: ID! |  | ||||||
|     $name: String! |  | ||||||
|     $latitude: String |  | ||||||
|     $longitude: String |  | ||||||
|     $serial: String |  | ||||||
|     $lastModifiedTimestamp: String |  | ||||||
|     $details: JSONObject |  | ||||||
|   ) { |  | ||||||
|     updateEquipment( |  | ||||||
|       id: $id |  | ||||||
|       equipmentType: $equipmentType |  | ||||||
|       inventoryId: $inventoryId |  | ||||||
|       customerId: $customerId |  | ||||||
|       profileId: $profileId |  | ||||||
|       locationId: $locationId |  | ||||||
|       name: $name |  | ||||||
|       latitude: $latitude |  | ||||||
|       longitude: $longitude |  | ||||||
|       serial: $serial |  | ||||||
|       lastModifiedTimestamp: $lastModifiedTimestamp |  | ||||||
|       details: $details |  | ||||||
|     ) { |  | ||||||
|       id |  | ||||||
|       equipmentType |  | ||||||
|       inventoryId |  | ||||||
|       customerId |  | ||||||
|       profileId |  | ||||||
|       locationId |  | ||||||
|       name |  | ||||||
|       latitude |  | ||||||
|       longitude |  | ||||||
|       serial |  | ||||||
|       lastModifiedTimestamp |  | ||||||
|       details |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| `; |  | ||||||
|  |  | ||||||
| export const GET_ALL_PROFILES = gql` |  | ||||||
|   query GetAllProfiles($customerId: ID!, $cursor: String, $type: String) { |  | ||||||
|     getAllProfiles(customerId: $customerId, cursor: $cursor, type: $type) { |  | ||||||
|       items { |  | ||||||
|         id |  | ||||||
|         name |  | ||||||
|         profileType |  | ||||||
|         details |  | ||||||
|         childProfiles { |  | ||||||
|           id |  | ||||||
|           name |  | ||||||
|           details |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       context { |  | ||||||
|         cursor |  | ||||||
|         lastPage |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| `; |  | ||||||
|  |  | ||||||
| const toTime = moment(); | const toTime = moment(); | ||||||
| const fromTime = moment().subtract(1, 'hour'); | const fromTime = moment().subtract(1, 'hour'); | ||||||
|  |  | ||||||
| const AccessPointDetails = ({ locations }) => { | const AccessPointDetails = ({ locations }) => { | ||||||
|   const { id } = useParams(); |   const { id } = useParams(); | ||||||
|   const { customerId } = useContext(UserContext); |   const { customerId } = useContext(UserContext); | ||||||
|  |   const history = useHistory(); | ||||||
|  |  | ||||||
|   const { loading, error, data, refetch } = useQuery(GET_EQUIPMENT, { |   const { loading, error, data, refetch } = useQuery(GET_EQUIPMENT, { | ||||||
|     variables: { id }, |     variables: { | ||||||
|  |       id, | ||||||
|  |     }, | ||||||
|  |     fetchPolicy: 'network-only', | ||||||
|  |     errorPolicy: 'all', | ||||||
|   }); |   }); | ||||||
|   const { data: dataProfiles, error: errorProfiles, loading: landingProfiles } = useQuery( |  | ||||||
|     GET_ALL_PROFILES, |   const { data: dataFirmware, error: errorFirmware, loading: loadingFirmware } = useQuery( | ||||||
|  |     GET_ALL_FIRMWARE, | ||||||
|  |     { | ||||||
|  |       skip: !data?.getEquipment?.model, | ||||||
|  |       variables: { modelId: data?.getEquipment?.model }, | ||||||
|  |       errorPolicy: 'all', | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   const { | ||||||
|  |     data: dataProfiles, | ||||||
|  |     error: errorProfiles, | ||||||
|  |     loading: loadingProfiles, | ||||||
|  |     fetchMore, | ||||||
|  |   } = useQuery( | ||||||
|  |     GET_ALL_PROFILES(` | ||||||
|  |     childProfiles { | ||||||
|  |       id | ||||||
|  |       name | ||||||
|  |       details | ||||||
|  |     }`), | ||||||
|     { |     { | ||||||
|       variables: { customerId, type: 'equipment_ap' }, |       variables: { customerId, type: 'equipment_ap' }, | ||||||
|     } |     } | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   const { |   const { | ||||||
|     loading: metricsLoading, |     loading: metricsLoading, | ||||||
|     error: metricsError, |     error: metricsError, | ||||||
| @@ -185,30 +84,30 @@ const AccessPointDetails = ({ locations }) => { | |||||||
|  |  | ||||||
|   const [updateEquipment] = useMutation(UPDATE_EQUIPMENT); |   const [updateEquipment] = useMutation(UPDATE_EQUIPMENT); | ||||||
|   const [updateEquipmentFirmware] = useMutation(UPDATE_EQUIPMENT_FIRMWARE); |   const [updateEquipmentFirmware] = useMutation(UPDATE_EQUIPMENT_FIRMWARE); | ||||||
|  |   const [requestEquipmentSwitchBank] = useMutation(REQUEST_EQUIPMENT_SWITCH_BANK); | ||||||
|   const { data: dataFirmware, error: errorFirmware, loading: landingFirmware } = useQuery( |   const [requestEquipmentReboot] = useMutation(REQUEST_EQUIPMENT_REBOOT); | ||||||
|     GET_ALL_FIRMWARE |   const [deleteEquipment] = useMutation(DELETE_EQUIPMENT); | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   const refetchData = () => { |   const refetchData = () => { | ||||||
|     refetch(); |     refetch(); | ||||||
|     metricsRefetch(); |     metricsRefetch(); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleUpdateEquipment = ( |   const handleUpdateEquipment = ({ | ||||||
|     equipmentId, |     id: equipmentId, | ||||||
|     equipmentType, |     equipmentType, | ||||||
|     inventoryId, |     inventoryId, | ||||||
|     custId, |     customerId: custId, | ||||||
|     profileId, |     profileId, | ||||||
|     locationId, |     locationId, | ||||||
|     name, |     name, | ||||||
|  |     baseMacAddress, | ||||||
|     latitude, |     latitude, | ||||||
|     longitude, |     longitude, | ||||||
|     serial, |     serial, | ||||||
|     lastModifiedTimestamp, |     lastModifiedTimestamp, | ||||||
|     details |     formattedData, | ||||||
|   ) => { |   }) => { | ||||||
|     updateEquipment({ |     updateEquipment({ | ||||||
|       variables: { |       variables: { | ||||||
|         id: equipmentId, |         id: equipmentId, | ||||||
| @@ -218,11 +117,12 @@ const AccessPointDetails = ({ locations }) => { | |||||||
|         profileId, |         profileId, | ||||||
|         locationId, |         locationId, | ||||||
|         name, |         name, | ||||||
|  |         baseMacAddress, | ||||||
|         latitude, |         latitude, | ||||||
|         longitude, |         longitude, | ||||||
|         serial, |         serial, | ||||||
|         lastModifiedTimestamp, |         lastModifiedTimestamp, | ||||||
|         details, |         details: formattedData, | ||||||
|       }, |       }, | ||||||
|     }) |     }) | ||||||
|       .then(() => { |       .then(() => { | ||||||
| @@ -240,19 +140,38 @@ const AccessPointDetails = ({ locations }) => { | |||||||
|       ); |       ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const handleDeleteEquipment = () => { | ||||||
|  |     deleteEquipment({ | ||||||
|  |       variables: { id }, | ||||||
|  |     }) | ||||||
|  |       .then(() => { | ||||||
|  |         history.push('/network/access-points'); | ||||||
|  |         notification.success({ | ||||||
|  |           message: 'Success', | ||||||
|  |           description: 'Equipment successfully deleted', | ||||||
|  |         }); | ||||||
|  |       }) | ||||||
|  |       .catch(() => | ||||||
|  |         notification.error({ | ||||||
|  |           message: 'Error', | ||||||
|  |           description: 'Equipment could not be deleted.', | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const handleUpdateEquipmentFirmware = (equipmentId, firmwareVersionId) => |   const handleUpdateEquipmentFirmware = (equipmentId, firmwareVersionId) => | ||||||
|     updateEquipmentFirmware({ variables: { equipmentId, firmwareVersionId } }) |     updateEquipmentFirmware({ variables: { equipmentId, firmwareVersionId } }) | ||||||
|       .then(firmwareResp => { |       .then(firmwareResp => { | ||||||
|         if (firmwareResp && firmwareResp.updateEquipmentFirmware.success === false) { |         if (firmwareResp?.data?.updateEquipmentFirmware?.success === true) { | ||||||
|           notification.error({ |  | ||||||
|             message: 'Error', |  | ||||||
|             description: 'Equipment Firmware Upgrade could not be updated.', |  | ||||||
|           }); |  | ||||||
|         } else { |  | ||||||
|           notification.success({ |           notification.success({ | ||||||
|             message: 'Success', |             message: 'Success', | ||||||
|             description: 'Equipment Firmware Upgrade in progress', |             description: 'Equipment Firmware Upgrade in progress', | ||||||
|           }); |           }); | ||||||
|  |         } else { | ||||||
|  |           notification.error({ | ||||||
|  |             message: 'Error', | ||||||
|  |             description: 'Equipment Firmware Upgrade could not be updated.', | ||||||
|  |           }); | ||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|       .catch(() => |       .catch(() => | ||||||
| @@ -262,11 +181,59 @@ const AccessPointDetails = ({ locations }) => { | |||||||
|         }) |         }) | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|   if (loading || landingProfiles || landingFirmware) { |   const handleRequestEquipmentSwitchBank = equipmentId => | ||||||
|     return <Spin size="large" />; |     requestEquipmentSwitchBank({ variables: { equipmentId } }) | ||||||
|  |       .then(firmwareResp => { | ||||||
|  |         if (firmwareResp?.data?.requestEquipmentSwitchBank?.success === true) { | ||||||
|  |           notification.success({ | ||||||
|  |             message: 'Success', | ||||||
|  |             description: 'Equipment Firmware in progress', | ||||||
|  |           }); | ||||||
|  |         } else { | ||||||
|  |           notification.error({ | ||||||
|  |             message: 'Error', | ||||||
|  |             description: 'Equipment Firmware could not be updated.', | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |       .catch(() => | ||||||
|  |         notification.error({ | ||||||
|  |           message: 'Error', | ||||||
|  |           description: 'Equipment Firmware could not be updated.', | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   const handleRequestEquipmentReboot = equipmentId => | ||||||
|  |     requestEquipmentReboot({ variables: { equipmentId } }) | ||||||
|  |       .then(firmwareResp => { | ||||||
|  |         if (firmwareResp?.data?.requestEquipmentReboot?.success === true) { | ||||||
|  |           notification.success({ | ||||||
|  |             message: 'Success', | ||||||
|  |             description: 'Equipment Firmware in progress', | ||||||
|  |           }); | ||||||
|  |         } else { | ||||||
|  |           notification.error({ | ||||||
|  |             message: 'Error', | ||||||
|  |             description: 'Equipment Firmware could not be updated.', | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |       .catch(() => | ||||||
|  |         notification.error({ | ||||||
|  |           message: 'Error', | ||||||
|  |           description: 'Equipment Firmware could not be updated.', | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   const handleFetchProfiles = e => { | ||||||
|  |     fetchMoreProfiles(e, dataProfiles, fetchMore); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   if (loading) { | ||||||
|  |     return <Loading />; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (error) { |   if (error && !data?.getEquipment) { | ||||||
|     return ( |     return ( | ||||||
|       <Alert |       <Alert | ||||||
|         message="Error" |         message="Error" | ||||||
| @@ -277,42 +244,29 @@ const AccessPointDetails = ({ locations }) => { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (errorProfiles) { |  | ||||||
|     return ( |  | ||||||
|       <Alert |  | ||||||
|         message="Error" |  | ||||||
|         description="Failed to load Access Point profiles." |  | ||||||
|         type="error" |  | ||||||
|         showIcon |  | ||||||
|       /> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (errorFirmware) { |  | ||||||
|     return ( |  | ||||||
|       <Alert |  | ||||||
|         message="Error" |  | ||||||
|         description="Failed to load Access Point firmware." |  | ||||||
|         type="error" |  | ||||||
|         showIcon |  | ||||||
|       /> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <AccessPointDetailsPage |     <AccessPointDetailsPage | ||||||
|       handleRefresh={refetchData} |       handleRefresh={refetchData} | ||||||
|       onUpdateEquipment={handleUpdateEquipment} |       onUpdateEquipment={handleUpdateEquipment} | ||||||
|       data={data.getEquipment} |       onDeleteEquipment={handleDeleteEquipment} | ||||||
|       profiles={dataProfiles.getAllProfiles.items} |       data={data?.getEquipment} | ||||||
|  |       profiles={dataProfiles?.getAllProfiles?.items} | ||||||
|       osData={{ |       osData={{ | ||||||
|         loading: metricsLoading, |         loading: metricsLoading, | ||||||
|         error: metricsError, |         error: metricsError, | ||||||
|         data: metricsData && metricsData.filterServiceMetrics.items, |         data: metricsData && metricsData.filterServiceMetrics.items, | ||||||
|       }} |       }} | ||||||
|       firmware={dataFirmware.getAllFirmware} |       firmware={dataFirmware?.getAllFirmware} | ||||||
|       locations={locations} |       locations={locations} | ||||||
|       onUpdateEquipmentFirmware={handleUpdateEquipmentFirmware} |       onUpdateEquipmentFirmware={handleUpdateEquipmentFirmware} | ||||||
|  |       onRequestEquipmentSwitchBank={handleRequestEquipmentSwitchBank} | ||||||
|  |       onRequestEquipmentReboot={handleRequestEquipmentReboot} | ||||||
|  |       loadingProfiles={loadingProfiles} | ||||||
|  |       errorProfiles={errorProfiles} | ||||||
|  |       loadingFirmware={loadingFirmware} | ||||||
|  |       errorFirmware={errorFirmware} | ||||||
|  |       onFetchMoreProfiles={handleFetchProfiles} | ||||||
|  |       isLastProfilesPage={dataProfiles?.getAllProfiles?.context?.lastPage} | ||||||
|     /> |     /> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,27 +1,41 @@ | |||||||
| import React, { useEffect, useContext } from 'react'; | import React, { useEffect, useContext } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useLazyQuery } from '@apollo/react-hooks'; | import moment from 'moment'; | ||||||
| import { Alert } from 'antd'; | import { useLazyQuery } from '@apollo/client'; | ||||||
| import { NetworkTable, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | import { notification } from 'antd'; | ||||||
|  | import { floor, padStart } from 'lodash'; | ||||||
|  | import { NetworkTableContainer } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
|  |  | ||||||
|  | import { ROUTES } from 'constants/index'; | ||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
| import { FILTER_EQUIPMENT } from 'graphql/queries'; | import { FILTER_EQUIPMENT } from 'graphql/queries'; | ||||||
|  |  | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
| const renderTableCell = tabCell => { | const renderTableCell = (text, _record, _index, defaultValue = 'N/A') => { | ||||||
|   if (Array.isArray(tabCell)) { |   if (Array.isArray(text)) { | ||||||
|  |     if (text.length < 1) { | ||||||
|  |       return defaultValue; | ||||||
|  |     } | ||||||
|     return ( |     return ( | ||||||
|       <div className={styles.tabColumn}> |       <div className={styles.tabColumn}> | ||||||
|         {tabCell.map((i, key) => ( |         {text.map((i, key) => ( | ||||||
|           // eslint-disable-next-line react/no-array-index-key |           // eslint-disable-next-line react/no-array-index-key | ||||||
|           <span key={key}>{i}</span> |           <span key={key}>{i}</span> | ||||||
|         ))} |         ))} | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   return tabCell; |   return text !== null ? text : defaultValue; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | const durationToString = duration => | ||||||
|  |   `${floor(duration.asDays())}d ${floor(duration.hours())}h ${padStart( | ||||||
|  |     duration.minutes(), | ||||||
|  |     2, | ||||||
|  |     0 | ||||||
|  |   )}m ${padStart(duration.seconds(), 2, 0)}s`; | ||||||
|  |  | ||||||
| const accessPointsTableColumns = [ | const accessPointsTableColumns = [ | ||||||
|   { |   { | ||||||
|     title: 'NAME', |     title: 'NAME', | ||||||
| @@ -45,12 +59,17 @@ const accessPointsTableColumns = [ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     title: 'MAC', |     title: 'MAC', | ||||||
|     dataIndex: ['status', 'protocol', 'details', 'reportedMacAddr'], |     dataIndex: 'baseMacAddress', | ||||||
|     render: renderTableCell, |     render: renderTableCell, | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     title: 'MANUFACTURER', |     title: 'MANUFACTURER', | ||||||
|     dataIndex: ['status', 'protocol', 'details', 'manufacturer'], |     dataIndex: 'manufacturer', | ||||||
|  |     render: renderTableCell, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     title: 'FIRMWARE', | ||||||
|  |     dataIndex: ['status', 'firmware', 'detailsJSON', 'activeSwVersion'], | ||||||
|     render: renderTableCell, |     render: renderTableCell, | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
| @@ -61,7 +80,7 @@ const accessPointsTableColumns = [ | |||||||
|   { |   { | ||||||
|     title: 'UP TIME', |     title: 'UP TIME', | ||||||
|     dataIndex: ['status', 'osPerformance', 'details', 'uptimeInSeconds'], |     dataIndex: ['status', 'osPerformance', 'details', 'uptimeInSeconds'], | ||||||
|     render: renderTableCell, |     render: upTimeInSeconds => durationToString(moment.duration(upTimeInSeconds, 'seconds')), | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     title: 'PROFILE', |     title: 'PROFILE', | ||||||
| @@ -70,11 +89,11 @@ const accessPointsTableColumns = [ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     title: 'CHANNEL', |     title: 'CHANNEL', | ||||||
|     dataIndex: 'channel', |     dataIndex: ['status', 'channel', 'detailsJSON', 'channelNumberStatusDataMap'], | ||||||
|     render: renderTableCell, |     render: text => renderTableCell(Object.values(text ?? [])), | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     title: 'CAPACITY', |     title: 'OCCUPANCY', | ||||||
|     dataIndex: ['status', 'radioUtilization', 'details', 'capacityDetails'], |     dataIndex: ['status', 'radioUtilization', 'details', 'capacityDetails'], | ||||||
|     render: renderTableCell, |     render: renderTableCell, | ||||||
|   }, |   }, | ||||||
| @@ -86,23 +105,39 @@ const accessPointsTableColumns = [ | |||||||
|   { |   { | ||||||
|     title: 'DEVICES', |     title: 'DEVICES', | ||||||
|     dataIndex: ['status', 'clientDetails', 'details', 'numClientsPerRadio'], |     dataIndex: ['status', 'clientDetails', 'details', 'numClientsPerRadio'], | ||||||
|     render: renderTableCell, |     render: (text, record, index) => renderTableCell(text, record, index, 0), | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const AccessPoints = ({ checkedLocations }) => { | const AccessPoints = ({ checkedLocations }) => { | ||||||
|   const { customerId } = useContext(UserContext); |   const { customerId } = useContext(UserContext); | ||||||
|   const [filterEquipment, { loading, error, data: equipData, fetchMore }] = useLazyQuery( |   const [filterEquipment, { loading, error, data: equipData, refetch, fetchMore }] = useLazyQuery( | ||||||
|     FILTER_EQUIPMENT, |     FILTER_EQUIPMENT, | ||||||
|     { |     { | ||||||
|       errorPolicy: 'all', |       errorPolicy: 'all', | ||||||
|     } |     } | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  |   const handleOnRefresh = () => { | ||||||
|  |     refetch() | ||||||
|  |       .then(() => { | ||||||
|  |         notification.success({ | ||||||
|  |           message: 'Success', | ||||||
|  |           description: 'Access points reloaded.', | ||||||
|  |         }); | ||||||
|  |       }) | ||||||
|  |       .catch(() => | ||||||
|  |         notification.error({ | ||||||
|  |           message: 'Error', | ||||||
|  |           description: 'Access points could not be reloaded.', | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const handleLoadMore = () => { |   const handleLoadMore = () => { | ||||||
|     if (!equipData.filterEquipment.context.lastPage) { |     if (!equipData.filterEquipment.context.lastPage) { | ||||||
|       fetchMore({ |       fetchMore({ | ||||||
|         variables: { cursor: equipData.filterEquipment.context.cursor }, |         variables: { context: equipData.filterEquipment.context }, | ||||||
|         updateQuery: (previousResult, { fetchMoreResult }) => { |         updateQuery: (previousResult, { fetchMoreResult }) => { | ||||||
|           const previousEntry = previousResult.filterEquipment; |           const previousEntry = previousResult.filterEquipment; | ||||||
|           const newItems = fetchMoreResult.filterEquipment.items; |           const newItems = fetchMoreResult.filterEquipment.items; | ||||||
| @@ -129,22 +164,18 @@ const AccessPoints = ({ checkedLocations }) => { | |||||||
|     fetchFilterEquipment(); |     fetchFilterEquipment(); | ||||||
|   }, [checkedLocations]); |   }, [checkedLocations]); | ||||||
|  |  | ||||||
|   if (loading) { |  | ||||||
|     return <Loading />; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (error && !equipData?.filterEquipment?.items) { |  | ||||||
|     return <Alert message="Error" description="Failed to load equipment." type="error" showIcon />; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <NetworkTable |     <NetworkTableContainer | ||||||
|  |       activeTab={ROUTES.accessPoints} | ||||||
|  |       onRefresh={handleOnRefresh} | ||||||
|       tableColumns={accessPointsTableColumns} |       tableColumns={accessPointsTableColumns} | ||||||
|       tableData={equipData && equipData.filterEquipment && equipData.filterEquipment.items} |       tableData={equipData && equipData.filterEquipment && equipData.filterEquipment.items} | ||||||
|       onLoadMore={handleLoadMore} |       onLoadMore={handleLoadMore} | ||||||
|       isLastPage={ |       isLastPage={ | ||||||
|         equipData && equipData.filterEquipment && equipData.filterEquipment.context.lastPage |         equipData && equipData.filterEquipment && equipData.filterEquipment.context.lastPage | ||||||
|       } |       } | ||||||
|  |       loading={loading} | ||||||
|  |       error={error && !equipData?.filterEquipment?.items && 'Failed to load equipment.'} | ||||||
|     /> |     /> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| .tabColumn { | .tabColumn { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   padding: 0 30px; |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import React, { useContext, useMemo } from 'react'; | |||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { Alert, notification } from 'antd'; | import { Alert, notification } from 'antd'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
| import { useQuery, useMutation } from '@apollo/react-hooks'; | import { useQuery, useMutation } from '@apollo/client'; | ||||||
| import { BulkEditAccessPoints, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | import { BulkEditAccessPoints, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
|  |  | ||||||
| import { FILTER_EQUIPMENT_BULK_EDIT_APS } from 'graphql/queries'; | import { FILTER_EQUIPMENT_BULK_EDIT_APS } from 'graphql/queries'; | ||||||
| @@ -11,6 +11,8 @@ import { UPDATE_EQUIPMENT_BULK } from 'graphql/mutations'; | |||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
|  | const defaultAppliedRadios = { is5GHzL: 'is5GHzL', is2dot4GHz: 'is2dot4GHz', is5GHzU: 'is5GHzU' }; | ||||||
|  |  | ||||||
| const renderTableCell = tabCell => { | const renderTableCell = tabCell => { | ||||||
|   if (Array.isArray(tabCell)) { |   if (Array.isArray(tabCell)) { | ||||||
|     return ( |     return ( | ||||||
| @@ -25,47 +27,63 @@ const renderTableCell = tabCell => { | |||||||
|   return <span>{tabCell}</span>; |   return <span>{tabCell}</span>; | ||||||
| }; | }; | ||||||
| const accessPointsChannelTableColumns = [ | const accessPointsChannelTableColumns = [ | ||||||
|   { title: 'NAME', dataIndex: 'name', key: 'name', render: renderTableCell }, |   { title: 'Name', dataIndex: 'name', key: 'name', width: 250, render: renderTableCell }, | ||||||
|   { |   { | ||||||
|     title: 'CHANNEL', |     title: 'Manual Active Channel', | ||||||
|     dataIndex: 'channel', |     dataIndex: 'manualChannelNumber', | ||||||
|     key: 'channel', |     key: 'manualChannelNumber', | ||||||
|     editable: true, |     editable: true, | ||||||
|  |     width: 200, | ||||||
|  |     render: renderTableCell, | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   { | ||||||
|  |     title: 'Manual Backup Channel', | ||||||
|  |     dataIndex: 'manualBackupChannelNumber', | ||||||
|  |     key: 'manualBackupChannelNumber', | ||||||
|  |     editable: true, | ||||||
|  |     width: 210, | ||||||
|     render: renderTableCell, |     render: renderTableCell, | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     title: 'CELL SIZE', |     title: 'Cell Size', | ||||||
|     dataIndex: 'cellSize', |     dataIndex: 'cellSize', | ||||||
|     key: 'cellSize', |     key: 'cellSize', | ||||||
|     editable: true, |     editable: true, | ||||||
|  |     width: 150, | ||||||
|     render: renderTableCell, |     render: renderTableCell, | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     title: 'PROB RESPONSE THRESHOLD', |     title: 'Probe Response Threshold', | ||||||
|     dataIndex: 'probeResponseThreshold', |     dataIndex: 'probeResponseThreshold', | ||||||
|     key: 'probeResponseThreshold', |     key: 'probeResponseThreshold', | ||||||
|     editable: true, |     editable: true, | ||||||
|  |     width: 210, | ||||||
|     render: renderTableCell, |     render: renderTableCell, | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     title: 'CLIENT DISCONNECT THRESHOLD', |     title: 'Client Disconnect Threshold', | ||||||
|     dataIndex: 'clientDisconnectThreshold', |     dataIndex: 'clientDisconnectThreshold', | ||||||
|     key: 'clientDisconnectThreshold', |     key: 'clientDisconnectThreshold', | ||||||
|     editable: true, |     editable: true, | ||||||
|  |     width: 210, | ||||||
|  |  | ||||||
|     render: renderTableCell, |     render: renderTableCell, | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     title: 'SNR (% DROP)', |     title: 'SNR (% Drop)', | ||||||
|     dataIndex: 'snrDrop', |     dataIndex: 'snrDrop', | ||||||
|     key: 'snrDrop', |     key: 'snrDrop', | ||||||
|     editable: true, |     editable: true, | ||||||
|  |     width: 150, | ||||||
|     render: renderTableCell, |     render: renderTableCell, | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     title: 'MIN LOAD', |     title: 'Min Load', | ||||||
|     dataIndex: 'minLoad', |     dataIndex: 'minLoad', | ||||||
|     key: 'minLoad', |     key: 'minLoad', | ||||||
|     editable: true, |     editable: true, | ||||||
|  |     width: 150, | ||||||
|     render: renderTableCell, |     render: renderTableCell, | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
| @@ -160,68 +178,84 @@ const BulkEditAPs = ({ locations, checkedLocations }) => { | |||||||
|   const [updateEquipmentBulk] = useMutation(UPDATE_EQUIPMENT_BULK); |   const [updateEquipmentBulk] = useMutation(UPDATE_EQUIPMENT_BULK); | ||||||
|  |  | ||||||
|   const getRadioDetails = (radioDetails, type) => { |   const getRadioDetails = (radioDetails, type) => { | ||||||
|  |     if (type === 'manualChannelNumber') { | ||||||
|  |       const manualChannelNumbers = []; | ||||||
|  |       Object.keys(radioDetails?.radioMap || {}).map(i => { | ||||||
|  |         return manualChannelNumbers.push(radioDetails.radioMap[i]?.manualChannelNumber); | ||||||
|  |       }); | ||||||
|  |       return manualChannelNumbers; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (type === 'manualBackupChannelNumber') { | ||||||
|  |       const manualBackupChannelNumbers = []; | ||||||
|  |       Object.keys(radioDetails?.radioMap || {}).map(i => { | ||||||
|  |         return manualBackupChannelNumbers.push(radioDetails.radioMap[i]?.manualBackupChannelNumber); | ||||||
|  |       }); | ||||||
|  |       return manualBackupChannelNumbers; | ||||||
|  |     } | ||||||
|     if (type === 'cellSize') { |     if (type === 'cellSize') { | ||||||
|       const cellSizeValues = []; |       const cellSizeValues = []; | ||||||
|       Object.keys(radioDetails.radioMap).map(i => { |       Object.keys(radioDetails?.radioMap || {}).map(i => { | ||||||
|         return cellSizeValues.push(radioDetails.radioMap[i].rxCellSizeDb.value); |         return cellSizeValues.push(radioDetails.radioMap[i]?.rxCellSizeDb?.value); | ||||||
|       }); |       }); | ||||||
|       return cellSizeValues; |       return cellSizeValues; | ||||||
|     } |     } | ||||||
|     if (type === 'probeResponseThreshold') { |     if (type === 'probeResponseThreshold') { | ||||||
|       const probeResponseThresholdValues = []; |       const probeResponseThresholdValues = []; | ||||||
|       Object.keys(radioDetails.radioMap).map(i => { |       Object.keys(radioDetails?.radioMap || {}).map(i => { | ||||||
|         return probeResponseThresholdValues.push( |         return probeResponseThresholdValues.push( | ||||||
|           radioDetails.radioMap[i].probeResponseThresholdDb.value |           radioDetails.radioMap[i]?.probeResponseThresholdDb?.value | ||||||
|         ); |         ); | ||||||
|       }); |       }); | ||||||
|       return probeResponseThresholdValues; |       return probeResponseThresholdValues; | ||||||
|     } |     } | ||||||
|     if (type === 'clientDisconnectThreshold') { |     if (type === 'clientDisconnectThreshold') { | ||||||
|       const clientDisconnectThresholdValues = []; |       const clientDisconnectThresholdValues = []; | ||||||
|       Object.keys(radioDetails.radioMap).map(i => { |       Object.keys(radioDetails?.radioMap || {}).map(i => { | ||||||
|         return clientDisconnectThresholdValues.push( |         return clientDisconnectThresholdValues.push( | ||||||
|           radioDetails.radioMap[i].clientDisconnectThresholdDb.value |           radioDetails.radioMap[i]?.clientDisconnectThresholdDb?.value | ||||||
|         ); |         ); | ||||||
|       }); |       }); | ||||||
|       return clientDisconnectThresholdValues; |       return clientDisconnectThresholdValues; | ||||||
|     } |     } | ||||||
|     if (type === 'snrDrop') { |     if (type === 'snrDrop') { | ||||||
|       const snrDropValues = []; |       const snrDropValues = []; | ||||||
|       Object.keys(radioDetails.advancedRadioMap).map(i => { |       Object.keys(radioDetails?.radioMap || {}).map(i => { | ||||||
|         return snrDropValues.push( |         return snrDropValues.push( | ||||||
|           radioDetails.advancedRadioMap[i].bestApSettings.dropInSnrPercentage |           radioDetails.advancedRadioMap[i]?.bestApSettings?.value?.dropInSnrPercentage | ||||||
|         ); |         ); | ||||||
|       }); |       }); | ||||||
|       return snrDropValues; |       return snrDropValues; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const minLoadValue = []; |     const minLoadValue = []; | ||||||
|     Object.keys(radioDetails.advancedRadioMap).map(i => { |     Object.keys(radioDetails?.radioMap || {}).map(i => { | ||||||
|       return minLoadValue.push(radioDetails.advancedRadioMap[i].bestApSettings.minLoadFactor); |       return minLoadValue.push( | ||||||
|  |         radioDetails.advancedRadioMap[i]?.bestApSettings?.value?.minLoadFactor | ||||||
|  |       ); | ||||||
|     }); |     }); | ||||||
|     return minLoadValue; |     return minLoadValue; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const setAccessPointsBulkEditTableData = (dataSource = []) => { |   const setAccessPointsBulkEditTableData = (dataSource = []) => | ||||||
|     const tableData = dataSource.items.map(({ id: key, name, channel, details }) => { |     dataSource.items.map(({ id: key, name, details }) => ({ | ||||||
|       return { |  | ||||||
|       key, |       key, | ||||||
|       id: key, |       id: key, | ||||||
|       name, |       name, | ||||||
|         channel, |       manualChannelNumber: getRadioDetails(details, 'manualChannelNumber'), | ||||||
|  |       manualBackupChannelNumber: getRadioDetails(details, 'manualBackupChannelNumber'), | ||||||
|       cellSize: getRadioDetails(details, 'cellSize'), |       cellSize: getRadioDetails(details, 'cellSize'), | ||||||
|       probeResponseThreshold: getRadioDetails(details, 'probeResponseThreshold'), |       probeResponseThreshold: getRadioDetails(details, 'probeResponseThreshold'), | ||||||
|       clientDisconnectThreshold: getRadioDetails(details, 'clientDisconnectThreshold'), |       clientDisconnectThreshold: getRadioDetails(details, 'clientDisconnectThreshold'), | ||||||
|       snrDrop: getRadioDetails(details, 'snrDrop'), |       snrDrop: getRadioDetails(details, 'snrDrop'), | ||||||
|       minLoad: getRadioDetails(details, 'minLoad'), |       minLoad: getRadioDetails(details, 'minLoad'), | ||||||
|       }; |       radioMap: Object.keys(details?.radioMap || {}), | ||||||
|     }); |     })); | ||||||
|     return tableData; |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   const setUpdatedBulkEditTableData = ( |   const setUpdatedBulkEditTableData = ( | ||||||
|     equipmentId, |     equipmentId, | ||||||
|     channel, |     manualChannelNumber, | ||||||
|  |     manualBackupChannelNumber, | ||||||
|     cellSize, |     cellSize, | ||||||
|     probeResponseThreshold, |     probeResponseThreshold, | ||||||
|     clientDisconnectThreshold, |     clientDisconnectThreshold, | ||||||
| @@ -234,13 +268,14 @@ const BulkEditAPs = ({ locations, checkedLocations }) => { | |||||||
|     let minLoadFactor; |     let minLoadFactor; | ||||||
|     dataSource.items.forEach(({ id: itemId, details }) => { |     dataSource.items.forEach(({ id: itemId, details }) => { | ||||||
|       if (equipmentId === itemId) { |       if (equipmentId === itemId) { | ||||||
|         Object.keys(details.radioMap).forEach((i, dataIndex) => { |         Object.keys(details?.radioMap || defaultAppliedRadios).forEach((i, dataIndex) => { | ||||||
|           const frequencies = {}; |           const frequencies = {}; | ||||||
|           dropInSnrPercentage = snrDrop[dataIndex]; |           dropInSnrPercentage = snrDrop[dataIndex]; | ||||||
|           minLoadFactor = minLoad[dataIndex]; |           minLoadFactor = minLoad[dataIndex]; | ||||||
|  |  | ||||||
|           frequencies[`${i}`] = { |           frequencies[`${i}`] = { | ||||||
|             channelNumber: channel[dataIndex], |             channelNumber: manualChannelNumber[dataIndex], | ||||||
|  |             backupChannelNumber: manualBackupChannelNumber[dataIndex], | ||||||
|             rxCellSizeDb: { |             rxCellSizeDb: { | ||||||
|               auto: true, |               auto: true, | ||||||
|               value: cellSize[dataIndex], |               value: cellSize[dataIndex], | ||||||
| @@ -286,20 +321,21 @@ const BulkEditAPs = ({ locations, checkedLocations }) => { | |||||||
|  |  | ||||||
|   const handleSaveChanges = updatedRows => { |   const handleSaveChanges = updatedRows => { | ||||||
|     const editedRowsArr = []; |     const editedRowsArr = []; | ||||||
|     if (updatedRows.length > 0) { |     Object.keys(updatedRows).forEach(key => { | ||||||
|       updatedRows.map( |       const { | ||||||
|         ({ |  | ||||||
|         id: equipmentId, |         id: equipmentId, | ||||||
|           channel, |         manualChannelNumber, | ||||||
|  |         manualBackupChannelNumber, | ||||||
|         cellSize, |         cellSize, | ||||||
|         probeResponseThreshold, |         probeResponseThreshold, | ||||||
|         clientDisconnectThreshold, |         clientDisconnectThreshold, | ||||||
|         snrDrop, |         snrDrop, | ||||||
|         minLoad, |         minLoad, | ||||||
|         }) => { |       } = updatedRows[key]; | ||||||
|       const updatedEuips = setUpdatedBulkEditTableData( |       const updatedEuips = setUpdatedBulkEditTableData( | ||||||
|         equipmentId, |         equipmentId, | ||||||
|             channel, |         manualChannelNumber, | ||||||
|  |         manualBackupChannelNumber, | ||||||
|         cellSize, |         cellSize, | ||||||
|         probeResponseThreshold, |         probeResponseThreshold, | ||||||
|         clientDisconnectThreshold, |         clientDisconnectThreshold, | ||||||
| @@ -318,16 +354,14 @@ const BulkEditAPs = ({ locations, checkedLocations }) => { | |||||||
|         return tempObj; |         return tempObj; | ||||||
|       }); |       }); | ||||||
|       return editedRowsArr.push(tempObj); |       return editedRowsArr.push(tempObj); | ||||||
|         } |     }); | ||||||
|       ); |  | ||||||
|     updateEquipments(editedRowsArr); |     updateEquipments(editedRowsArr); | ||||||
|     } |  | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleLoadMore = () => { |   const handleLoadMore = () => { | ||||||
|     if (!equipData.filterEquipment.context.lastPage) { |     if (!equipData.filterEquipment.context.lastPage) { | ||||||
|       fetchMore({ |       fetchMore({ | ||||||
|         variables: { cursor: equipData.filterEquipment.context.cursor }, |         variables: { context: equipData.filterEquipment.context }, | ||||||
|         updateQuery: (previousResult, { fetchMoreResult }) => { |         updateQuery: (previousResult, { fetchMoreResult }) => { | ||||||
|           const previousEntry = previousResult.filterEquipment; |           const previousEntry = previousResult.filterEquipment; | ||||||
|           const newItems = fetchMoreResult.filterEquipment.items; |           const newItems = fetchMoreResult.filterEquipment.items; | ||||||
| @@ -356,18 +390,9 @@ const BulkEditAPs = ({ locations, checkedLocations }) => { | |||||||
|   return ( |   return ( | ||||||
|     <BulkEditAccessPoints |     <BulkEditAccessPoints | ||||||
|       tableColumns={accessPointsChannelTableColumns} |       tableColumns={accessPointsChannelTableColumns} | ||||||
|       tableData={ |       tableData={setAccessPointsBulkEditTableData(equipData?.filterEquipment)} | ||||||
|         equipData && |  | ||||||
|         equipData.filterEquipment && |  | ||||||
|         setAccessPointsBulkEditTableData(equipData && equipData.filterEquipment) |  | ||||||
|       } |  | ||||||
|       onLoadMore={handleLoadMore} |       onLoadMore={handleLoadMore} | ||||||
|       isLastPage={ |       isLastPage={equipData?.filterEquipment?.context?.lastPage} | ||||||
|         equipData && |  | ||||||
|         equipData.filterEquipment && |  | ||||||
|         equipData.filterEquipment.context && |  | ||||||
|         equipData.filterEquipment.context.lastPage |  | ||||||
|       } |  | ||||||
|       onSaveChanges={handleSaveChanges} |       onSaveChanges={handleSaveChanges} | ||||||
|       breadcrumbPath={getBreadcrumbPath(id, locations)} |       breadcrumbPath={getBreadcrumbPath(id, locations)} | ||||||
|     /> |     /> | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| .tabColumn { | .tabColumn { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   padding: 0 30px; |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import React, { useContext } from 'react'; | import React, { useContext } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
| import { useQuery } from '@apollo/react-hooks'; | import { useQuery } from '@apollo/client'; | ||||||
| import moment from 'moment'; | import moment from 'moment'; | ||||||
| import { Alert, notification } from 'antd'; | import { Alert, notification } from 'antd'; | ||||||
| import { | import { | ||||||
| @@ -12,7 +12,7 @@ import UserContext from 'contexts/UserContext'; | |||||||
| import { GET_CLIENT_SESSION, FILTER_SERVICE_METRICS } from 'graphql/queries'; | import { GET_CLIENT_SESSION, FILTER_SERVICE_METRICS } from 'graphql/queries'; | ||||||
|  |  | ||||||
| const toTime = moment(); | const toTime = moment(); | ||||||
| const fromTime = moment().subtract(1, 'hour'); | const fromTime = moment().subtract(4, 'hours'); | ||||||
|  |  | ||||||
| const ClientDeviceDetails = () => { | const ClientDeviceDetails = () => { | ||||||
|   const { id } = useParams(); |   const { id } = useParams(); | ||||||
| @@ -33,7 +33,7 @@ const ClientDeviceDetails = () => { | |||||||
|       toTime: toTime.valueOf().toString(), |       toTime: toTime.valueOf().toString(), | ||||||
|       clientMacs: [id], |       clientMacs: [id], | ||||||
|       dataTypes: ['Client'], |       dataTypes: ['Client'], | ||||||
|       limit: 100, |       limit: 1000, | ||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
| @@ -73,7 +73,7 @@ const ClientDeviceDetails = () => { | |||||||
|       metricsData={ |       metricsData={ | ||||||
|         metricsData && metricsData.filterServiceMetrics && metricsData.filterServiceMetrics.items |         metricsData && metricsData.filterServiceMetrics && metricsData.filterServiceMetrics.items | ||||||
|       } |       } | ||||||
|       historyDate={toTime} |       historyDate={{ toTime, fromTime }} | ||||||
|     /> |     /> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,9 +1,11 @@ | |||||||
| import React, { useEffect, useContext } from 'react'; | import React, { useEffect, useContext } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useLazyQuery } from '@apollo/react-hooks'; | import { useLazyQuery } from '@apollo/client'; | ||||||
| import { Alert } from 'antd'; | import { notification } from 'antd'; | ||||||
| import { NetworkTable, Loading } from '@tip-wlan/wlan-cloud-ui-library'; |  | ||||||
|  |  | ||||||
|  | import { NetworkTableContainer } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
|  |  | ||||||
|  | import { ROUTES, USER_FRIENDLY_RADIOS } from 'constants/index'; | ||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
| import { FILTER_CLIENT_SESSIONS } from 'graphql/queries'; | import { FILTER_CLIENT_SESSIONS } from 'graphql/queries'; | ||||||
|  |  | ||||||
| @@ -21,14 +23,30 @@ const clientDevicesTableColumns = [ | |||||||
|   { title: 'HOST NAME', dataIndex: 'hostname' }, |   { title: 'HOST NAME', dataIndex: 'hostname' }, | ||||||
|   { title: 'ACCESS POINT', dataIndex: ['equipment', 'name'] }, |   { title: 'ACCESS POINT', dataIndex: ['equipment', 'name'] }, | ||||||
|   { title: 'SSID', dataIndex: 'ssid' }, |   { title: 'SSID', dataIndex: 'ssid' }, | ||||||
|   { title: 'BAND', dataIndex: 'radioType' }, |   { | ||||||
|  |     title: 'BAND', | ||||||
|  |     dataIndex: 'radioType', | ||||||
|  |     render: band => USER_FRIENDLY_RADIOS[band], | ||||||
|  |   }, | ||||||
|   { title: 'SIGNAL', dataIndex: 'signal' }, |   { title: 'SIGNAL', dataIndex: 'signal' }, | ||||||
|   { title: 'STATUS', dataIndex: 'status' }, |   { | ||||||
|  |     title: 'STATUS', | ||||||
|  |     dataIndex: ['details', 'associationState'], | ||||||
|  |     render: text => { | ||||||
|  |       if (text === 'Active_Data') { | ||||||
|  |         return 'Connected'; | ||||||
|  |       } | ||||||
|  |       if (text === 'Disconnected') { | ||||||
|  |         return 'Disconnected'; | ||||||
|  |       } | ||||||
|  |       return 'N/A'; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const ClientDevices = ({ checkedLocations }) => { | const ClientDevices = ({ checkedLocations }) => { | ||||||
|   const { customerId } = useContext(UserContext); |   const { customerId } = useContext(UserContext); | ||||||
|   const [filterClientSessions, { loading, error, data, fetchMore }] = useLazyQuery( |   const [filterClientSessions, { loading, error, data, refetch, fetchMore }] = useLazyQuery( | ||||||
|     FILTER_CLIENT_SESSIONS, |     FILTER_CLIENT_SESSIONS, | ||||||
|     { |     { | ||||||
|       errorPolicy: 'all', |       errorPolicy: 'all', | ||||||
| @@ -38,7 +56,7 @@ const ClientDevices = ({ checkedLocations }) => { | |||||||
|   const handleLoadMore = () => { |   const handleLoadMore = () => { | ||||||
|     if (!data.filterClientSessions.context.lastPage) { |     if (!data.filterClientSessions.context.lastPage) { | ||||||
|       fetchMore({ |       fetchMore({ | ||||||
|         variables: { cursor: data.filterClientSessions.context.cursor }, |         variables: { context: data.filterClientSessions.context }, | ||||||
|         updateQuery: (previousResult, { fetchMoreResult }) => { |         updateQuery: (previousResult, { fetchMoreResult }) => { | ||||||
|           const previousEntry = previousResult.filterClientSessions; |           const previousEntry = previousResult.filterClientSessions; | ||||||
|           const newItems = fetchMoreResult.filterClientSessions.items; |           const newItems = fetchMoreResult.filterClientSessions.items; | ||||||
| @@ -61,26 +79,36 @@ const ClientDevices = ({ checkedLocations }) => { | |||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const handleOnRefresh = () => { | ||||||
|  |     refetch() | ||||||
|  |       .then(() => { | ||||||
|  |         notification.success({ | ||||||
|  |           message: 'Success', | ||||||
|  |           description: 'Client devices reloaded.', | ||||||
|  |         }); | ||||||
|  |       }) | ||||||
|  |       .catch(() => | ||||||
|  |         notification.error({ | ||||||
|  |           message: 'Error', | ||||||
|  |           description: 'Client devices could not be reloaded.', | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     fetchFilterClientSessions(); |     fetchFilterClientSessions(); | ||||||
|   }, [checkedLocations]); |   }, [checkedLocations]); | ||||||
|  |  | ||||||
|   if (loading) { |  | ||||||
|     return <Loading />; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (error && !data?.filterClientSessions?.items) { |  | ||||||
|   return ( |   return ( | ||||||
|       <Alert message="Error" description="Failed to load client devices." type="error" showIcon /> |     <NetworkTableContainer | ||||||
|     ); |       activeTab={ROUTES.clientDevices} | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <NetworkTable |  | ||||||
|       tableColumns={clientDevicesTableColumns} |       tableColumns={clientDevicesTableColumns} | ||||||
|       tableData={data && data.filterClientSessions && data.filterClientSessions.items} |       tableData={data?.filterClientSessions?.items} | ||||||
|       onLoadMore={handleLoadMore} |       onLoadMore={handleLoadMore} | ||||||
|  |       onRefresh={handleOnRefresh} | ||||||
|       isLastPage={data && data.filterClientSessions && data.filterClientSessions.context.lastPage} |       isLastPage={data && data.filterClientSessions && data.filterClientSessions.context.lastPage} | ||||||
|  |       loading={loading} | ||||||
|  |       error={error && !data?.filterClientSessions?.items && 'Failed to load client devices.'} | ||||||
|     /> |     /> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import React, { useMemo, useContext, useState } from 'react'; | import React, { useMemo, useContext, useState } from 'react'; | ||||||
| import { useLocation, Switch, Route, useRouteMatch, Redirect } from 'react-router-dom'; | import { Switch, Route, useRouteMatch, Redirect } from 'react-router-dom'; | ||||||
| import { useQuery, useMutation, useLazyQuery } from '@apollo/react-hooks'; | import { useQuery, useMutation, useLazyQuery } from '@apollo/client'; | ||||||
| import { Alert, notification } from 'antd'; | import { Alert, notification } from 'antd'; | ||||||
| import _ from 'lodash'; | import _ from 'lodash'; | ||||||
| import { Network as NetworkPage, PopoverMenu, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | import { Network as NetworkPage, PopoverMenu, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
| @@ -12,23 +12,28 @@ import ClientDeviceDetails from 'containers/Network/containers/ClientDeviceDetai | |||||||
| import BulkEditAccessPoints from 'containers/Network/containers/BulkEditAccessPoints'; | import BulkEditAccessPoints from 'containers/Network/containers/BulkEditAccessPoints'; | ||||||
|  |  | ||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
| import { GET_ALL_LOCATIONS, GET_LOCATION, GET_ALL_PROFILES } from 'graphql/queries'; | import { | ||||||
|  |   GET_ALL_LOCATIONS, | ||||||
|  |   GET_LOCATION, | ||||||
|  |   GET_ALL_PROFILES, | ||||||
|  |   FILTER_EQUIPMENT, | ||||||
|  | } from 'graphql/queries'; | ||||||
| import { | import { | ||||||
|   CREATE_LOCATION, |   CREATE_LOCATION, | ||||||
|   UPDATE_LOCATION, |   UPDATE_LOCATION, | ||||||
|   DELETE_LOCATION, |   DELETE_LOCATION, | ||||||
|   CREATE_EQUIPMENT, |   CREATE_EQUIPMENT, | ||||||
| } from 'graphql/mutations'; | } from 'graphql/mutations'; | ||||||
|  | import { updateQueryGetAllProfiles } from 'graphql/functions'; | ||||||
|  |  | ||||||
| const Network = () => { | const Network = () => { | ||||||
|   const { path } = useRouteMatch(); |   const { path } = useRouteMatch(); | ||||||
|   const { customerId } = useContext(UserContext); |   const { customerId } = useContext(UserContext); | ||||||
|   const location = useLocation(); |  | ||||||
|   const { loading, error, refetch, data } = useQuery(GET_ALL_LOCATIONS, { |   const { loading, error, refetch, data } = useQuery(GET_ALL_LOCATIONS, { | ||||||
|     variables: { customerId }, |     variables: { customerId }, | ||||||
|   }); |   }); | ||||||
|   const { loading: loadingProfile, error: errorProfile, data: apProfiles } = useQuery( |   const { loading: loadingProfile, error: errorProfile, data: apProfiles, fetchMore } = useQuery( | ||||||
|     GET_ALL_PROFILES, |     GET_ALL_PROFILES(), | ||||||
|     { |     { | ||||||
|       variables: { customerId, type: 'equipment_ap' }, |       variables: { customerId, type: 'equipment_ap' }, | ||||||
|     } |     } | ||||||
| @@ -38,13 +43,21 @@ const Network = () => { | |||||||
|   const [createLocation] = useMutation(CREATE_LOCATION); |   const [createLocation] = useMutation(CREATE_LOCATION); | ||||||
|   const [updateLocation] = useMutation(UPDATE_LOCATION); |   const [updateLocation] = useMutation(UPDATE_LOCATION); | ||||||
|   const [deleteLocation] = useMutation(DELETE_LOCATION); |   const [deleteLocation] = useMutation(DELETE_LOCATION); | ||||||
|   const [createEquipment] = useMutation(CREATE_EQUIPMENT); |  | ||||||
|   const [checkedLocations, setCheckedLocations] = useState([]); |   const [checkedLocations, setCheckedLocations] = useState([]); | ||||||
|   const [deleteModal, setDeleteModal] = useState(false); |   const [deleteModal, setDeleteModal] = useState(false); | ||||||
|   const [editModal, setEditModal] = useState(false); |   const [editModal, setEditModal] = useState(false); | ||||||
|   const [addModal, setAddModal] = useState(false); |   const [addModal, setAddModal] = useState(false); | ||||||
|   const [apModal, setApModal] = useState(false); |   const [apModal, setApModal] = useState(false); | ||||||
|  |  | ||||||
|  |   const [createEquipment] = useMutation(CREATE_EQUIPMENT, { | ||||||
|  |     refetchQueries: [ | ||||||
|  |       { | ||||||
|  |         query: FILTER_EQUIPMENT, | ||||||
|  |         variables: { customerId, locationIds: checkedLocations, equipmentType: 'AP' }, | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   const handleGetSingleLocation = id => { |   const handleGetSingleLocation = id => { | ||||||
|     getLocation({ |     getLocation({ | ||||||
|       variables: { id }, |       variables: { id }, | ||||||
| @@ -52,7 +65,7 @@ const Network = () => { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const formatLocationListForTree = (list = []) => { |   const formatLocationListForTree = (list = []) => { | ||||||
|     const checkedTreeLocations = []; |     const checkedTreeLocations = ['0']; | ||||||
|     list.forEach(ele => { |     list.forEach(ele => { | ||||||
|       checkedTreeLocations.push(ele.id); |       checkedTreeLocations.push(ele.id); | ||||||
|     }); |     }); | ||||||
| @@ -92,26 +105,38 @@ const Network = () => { | |||||||
|     return [ |     return [ | ||||||
|       { |       { | ||||||
|         title: ( |         title: ( | ||||||
|           <PopoverMenu locationType="NETWORK" setAddModal={setAddModal}> |           <PopoverMenu locationId="0" locationType="NETWORK" setAddModal={setAddModal}> | ||||||
|             Network |             Network | ||||||
|           </PopoverMenu> |           </PopoverMenu> | ||||||
|         ), |         ), | ||||||
|         id: '0', |         id: '0', | ||||||
|         value: '0', |  | ||||||
|         key: '0', |         key: '0', | ||||||
|  |         value: '0', | ||||||
|         children: unflatten(list), |         children: unflatten(list), | ||||||
|       }, |       }, | ||||||
|     ]; |     ]; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleAddLocation = (name, parentId, locationType) => { |   const handleAddLocation = ({ location }) => { | ||||||
|     setAddModal(false); |     setAddModal(false); | ||||||
|  |     let id; | ||||||
|  |     let locationType; | ||||||
|  |  | ||||||
|  |     // adding location from root makes selecetedLocation null so we check for that | ||||||
|  |     if (selectedLocation && selectedLocation.getLocation) { | ||||||
|  |       id = selectedLocation.getLocation.id; | ||||||
|  |       locationType = 'SITE'; | ||||||
|  |     } else { | ||||||
|  |       id = '0'; | ||||||
|  |       locationType = 'COUNTRY'; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     createLocation({ |     createLocation({ | ||||||
|       variables: { |       variables: { | ||||||
|         locationType, |         locationType, | ||||||
|         customerId, |         customerId, | ||||||
|         parentId, |         parentId: id, | ||||||
|         name, |         name: location, | ||||||
|       }, |       }, | ||||||
|     }) |     }) | ||||||
|       .then(() => { |       .then(() => { | ||||||
| @@ -129,8 +154,10 @@ const Network = () => { | |||||||
|       ); |       ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleEditLocation = (id, parentId, name, locationType, lastModifiedTimestamp) => { |   const handleEditLocation = ({ name }) => { | ||||||
|     setEditModal(false); |     setEditModal(false); | ||||||
|  |     const { id, parentId, locationType, lastModifiedTimestamp } = selectedLocation.getLocation; | ||||||
|  |  | ||||||
|     updateLocation({ |     updateLocation({ | ||||||
|       variables: { |       variables: { | ||||||
|         customerId, |         customerId, | ||||||
| @@ -156,8 +183,10 @@ const Network = () => { | |||||||
|       ); |       ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleDeleteLocation = id => { |   const handleDeleteLocation = () => { | ||||||
|     setDeleteModal(false); |     setDeleteModal(false); | ||||||
|  |     const { id } = selectedLocation.getLocation; | ||||||
|  |  | ||||||
|     deleteLocation({ variables: { id } }) |     deleteLocation({ variables: { id } }) | ||||||
|       .then(() => { |       .then(() => { | ||||||
|         notification.success({ |         notification.success({ | ||||||
| @@ -174,8 +203,10 @@ const Network = () => { | |||||||
|       ); |       ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleCreateEquipment = (inventoryId, locationId, name, profileId) => { |   const handleCreateEquipment = ({ inventoryId, name, profileId }) => { | ||||||
|     setApModal(false); |     setApModal(false); | ||||||
|  |     const { id: locationId } = selectedLocation.getLocation; | ||||||
|  |  | ||||||
|     createEquipment({ variables: { customerId, inventoryId, locationId, name, profileId } }) |     createEquipment({ variables: { customerId, inventoryId, locationId, name, profileId } }) | ||||||
|       .then(() => { |       .then(() => { | ||||||
|         notification.success({ |         notification.success({ | ||||||
| @@ -201,10 +232,27 @@ const Network = () => { | |||||||
|     setCheckedLocations(checkedKeys.checked); |     setCheckedLocations(checkedKeys.checked); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const locationsTree = useMemo( |   const handleFetchProfiles = e => { | ||||||
|     () => formatLocationListForTree(data && data.getAllLocations)[0].children, |     if (apProfiles.getAllProfiles.context.lastPage) { | ||||||
|     [data] |       return false; | ||||||
|   ); |     } | ||||||
|  |  | ||||||
|  |     e.persist(); | ||||||
|  |     const { target } = e; | ||||||
|  |  | ||||||
|  |     if (target.scrollTop + target.offsetHeight === target.scrollHeight) { | ||||||
|  |       fetchMore({ | ||||||
|  |         variables: { context: { ...apProfiles.getAllProfiles.context } }, | ||||||
|  |         updateQuery: updateQueryGetAllProfiles, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const locationsTree = useMemo(() => formatLocationListForTree(data && data.getAllLocations), [ | ||||||
|  |     data, | ||||||
|  |   ]); | ||||||
|  |  | ||||||
|   if (loading) { |   if (loading) { | ||||||
|     return <Loading />; |     return <Loading />; | ||||||
| @@ -220,7 +268,6 @@ const Network = () => { | |||||||
|       onCheck={onCheck} |       onCheck={onCheck} | ||||||
|       checkedLocations={checkedLocations} |       checkedLocations={checkedLocations} | ||||||
|       locations={locationsTree} |       locations={locationsTree} | ||||||
|       activeTab={location.pathname} |  | ||||||
|       selectedLocation={selectedLocation && selectedLocation.getLocation} |       selectedLocation={selectedLocation && selectedLocation.getLocation} | ||||||
|       addModal={addModal} |       addModal={addModal} | ||||||
|       editModal={editModal} |       editModal={editModal} | ||||||
| @@ -237,6 +284,8 @@ const Network = () => { | |||||||
|       profiles={(apProfiles && apProfiles.getAllProfiles && apProfiles.getAllProfiles.items) || []} |       profiles={(apProfiles && apProfiles.getAllProfiles && apProfiles.getAllProfiles.items) || []} | ||||||
|       loadingProfile={loadingProfile} |       loadingProfile={loadingProfile} | ||||||
|       errorProfile={errorProfile} |       errorProfile={errorProfile} | ||||||
|  |       onFetchMoreProfiles={handleFetchProfiles} | ||||||
|  |       isLastProfilesPage={apProfiles?.getAllProfiles?.context?.lastPage} | ||||||
|     > |     > | ||||||
|       <Switch> |       <Switch> | ||||||
|         <Route |         <Route | ||||||
| @@ -257,15 +306,17 @@ const Network = () => { | |||||||
|         /> |         /> | ||||||
|         <Route |         <Route | ||||||
|           exact |           exact | ||||||
|           path={`${path}/access-points/:id`} |           path={`${path}/access-points/:id/:tab`} | ||||||
|           render={props => <AccessPointDetails locations={locationsTree} {...props} />} |           render={props => <AccessPointDetails locations={locationsTree} {...props} />} | ||||||
|         /> |         /> | ||||||
|  |  | ||||||
|         <Route |         <Route | ||||||
|           exact |           exact | ||||||
|           path={`${path}/client-devices`} |           path={`${path}/client-devices`} | ||||||
|           render={props => <ClientDevices checkedLocations={checkedLocations} {...props} />} |           render={props => <ClientDevices checkedLocations={checkedLocations} {...props} />} | ||||||
|         /> |         /> | ||||||
|         <Route exact path={`${path}/client-devices/:id`} component={ClientDeviceDetails} /> |         <Route exact path={`${path}/client-devices/:id`} component={ClientDeviceDetails} /> | ||||||
|  |         <Redirect from={`${path}/access-points/:id`} to={`${path}/access-points/:id/general`} /> | ||||||
|         <Redirect from={path} to={`${path}/access-points`} /> |         <Redirect from={path} to={`${path}/access-points`} /> | ||||||
|       </Switch> |       </Switch> | ||||||
|     </NetworkPage> |     </NetworkPage> | ||||||
|   | |||||||
| @@ -1,13 +1,14 @@ | |||||||
| import React, { useState, useContext } from 'react'; | import React, { useState, useContext } from 'react'; | ||||||
| import { useParams, Redirect } from 'react-router-dom'; | import { useParams, Redirect } from 'react-router-dom'; | ||||||
| import gql from 'graphql-tag'; | import { useQuery, useMutation, gql } from '@apollo/client'; | ||||||
| import { useQuery, useMutation } from '@apollo/react-hooks'; | import { Alert, notification } from 'antd'; | ||||||
| import { Alert, Spin, notification } from 'antd'; | import { ProfileDetails as ProfileDetailsPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
| import { ProfileDetails as ProfileDetailsPage } from '@tip-wlan/wlan-cloud-ui-library'; |  | ||||||
|  |  | ||||||
|  | import { ROUTES, AUTH_TOKEN } from 'constants/index'; | ||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
| import { GET_ALL_PROFILES } from 'graphql/queries'; | import { GET_ALL_PROFILES, GET_API_URL } from 'graphql/queries'; | ||||||
| import { FILE_UPLOAD } from 'graphql/mutations'; | import { fetchMoreProfiles } from 'graphql/functions'; | ||||||
|  | import { getItem } from 'utils/localStorage'; | ||||||
|  |  | ||||||
| const GET_PROFILE = gql` | const GET_PROFILE = gql` | ||||||
|   query GetProfile($id: ID!) { |   query GetProfile($id: ID!) { | ||||||
| @@ -22,6 +23,16 @@ const GET_PROFILE = gql` | |||||||
|         profileType |         profileType | ||||||
|         details |         details | ||||||
|       } |       } | ||||||
|  |       associatedSsidProfiles { | ||||||
|  |         id | ||||||
|  |         name | ||||||
|  |         profileType | ||||||
|  |         details | ||||||
|  |       } | ||||||
|  |       osuSsidProfile { | ||||||
|  |         id | ||||||
|  |         name | ||||||
|  |       } | ||||||
|       childProfileIds |       childProfileIds | ||||||
|       createdTimestamp |       createdTimestamp | ||||||
|       lastModifiedTimestamp |       lastModifiedTimestamp | ||||||
| @@ -74,17 +85,63 @@ const ProfileDetails = () => { | |||||||
|  |  | ||||||
|   const [redirect, setRedirect] = useState(false); |   const [redirect, setRedirect] = useState(false); | ||||||
|  |  | ||||||
|  |   const { data: apiUrl } = useQuery(GET_API_URL); | ||||||
|  |  | ||||||
|   const { loading, error, data } = useQuery(GET_PROFILE, { |   const { loading, error, data } = useQuery(GET_PROFILE, { | ||||||
|     variables: { id }, |     variables: { id }, | ||||||
|  |     fetchPolicy: 'network-only', | ||||||
|   }); |   }); | ||||||
|   const { data: ssidProfiles } = useQuery(GET_ALL_PROFILES, { |  | ||||||
|  |   const { data: ssidProfiles, fetchMore } = useQuery(GET_ALL_PROFILES(), { | ||||||
|     variables: { customerId, type: 'ssid' }, |     variables: { customerId, type: 'ssid' }, | ||||||
|  |     fetchPolicy: 'network-only', | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   const { data: radiusProfiles, fetchMore: fetchMoreRadiusProfiles } = useQuery( | ||||||
|  |     GET_ALL_PROFILES(), | ||||||
|  |     { | ||||||
|  |       variables: { customerId, type: 'radius' }, | ||||||
|  |       fetchPolicy: 'network-only', | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const { data: captiveProfiles, fetchMore: fetchMoreCaptiveProfiles } = useQuery( | ||||||
|  |     GET_ALL_PROFILES(), | ||||||
|  |     { | ||||||
|  |       variables: { customerId, type: 'captive_portal' }, | ||||||
|  |       fetchPolicy: 'network-only', | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const { data: venueProfiles, fetchMore: fetchMoreVenueProfiles } = useQuery(GET_ALL_PROFILES(), { | ||||||
|  |     variables: { customerId, type: 'passpoint_venue' }, | ||||||
|  |     fetchPolicy: 'network-only', | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const { data: operatorProfiles, fetchMore: fetchMoreOperatorProfiles } = useQuery( | ||||||
|  |     GET_ALL_PROFILES(), | ||||||
|  |     { | ||||||
|  |       variables: { customerId, type: 'passpoint_operator' }, | ||||||
|  |       fetchPolicy: 'network-only', | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const { data: idProviderProfiles, fetchMore: fetchMoreIdProviderProfiles } = useQuery( | ||||||
|  |     GET_ALL_PROFILES(), | ||||||
|  |     { | ||||||
|  |       variables: { customerId, type: 'passpoint_osu_id_provider' }, | ||||||
|  |       fetchPolicy: 'network-only', | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const { data: rfProfiles, fetchMore: fetchMoreRfProfiles } = useQuery(GET_ALL_PROFILES(), { | ||||||
|  |     variables: { customerId, type: 'rf' }, | ||||||
|  |     fetchPolicy: 'network-only', | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   const [updateProfile] = useMutation(UPDATE_PROFILE); |   const [updateProfile] = useMutation(UPDATE_PROFILE); | ||||||
|   const [deleteProfile] = useMutation(DELETE_PROFILE); |   const [deleteProfile] = useMutation(DELETE_PROFILE); | ||||||
|  |  | ||||||
|   const [fileUpload] = useMutation(FILE_UPLOAD); |  | ||||||
|  |  | ||||||
|   const handleDeleteProfile = () => { |   const handleDeleteProfile = () => { | ||||||
|     deleteProfile({ variables: { id } }) |     deleteProfile({ variables: { id } }) | ||||||
|       .then(() => { |       .then(() => { | ||||||
| @@ -130,23 +187,93 @@ const ProfileDetails = () => { | |||||||
|       ); |       ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleFileUpload = (fileName, file) => |   const handleFileUpload = async (fileName, file) => { | ||||||
|     fileUpload({ variables: { fileName, file } }) |     const token = getItem(AUTH_TOKEN); | ||||||
|       .then(() => { |  | ||||||
|  |     if (apiUrl?.getApiUrl) { | ||||||
|  |       fetch(`${apiUrl?.getApiUrl}filestore/${fileName}`, { | ||||||
|  |         method: 'POST', | ||||||
|  |         headers: { | ||||||
|  |           Authorization: token ? `Bearer ${token.access_token}` : '', | ||||||
|  |           'Content-Type': 'application/octet-stream', | ||||||
|  |         }, | ||||||
|  |         body: file, | ||||||
|  |       }) | ||||||
|  |         .then(response => response.json()) | ||||||
|  |         .then(resp => { | ||||||
|  |           if (resp?.success) { | ||||||
|             notification.success({ |             notification.success({ | ||||||
|               message: 'Success', |               message: 'Success', | ||||||
|               description: 'File successfully uploaded.', |               description: 'File successfully uploaded.', | ||||||
|             }); |             }); | ||||||
|       }) |           } else { | ||||||
|       .catch(() => |  | ||||||
|             notification.error({ |             notification.error({ | ||||||
|               message: 'Error', |               message: 'Error', | ||||||
|               description: 'File could not be uploaded.', |               description: 'File could not be uploaded.', | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|         }) |         }) | ||||||
|       ); |         .catch(() => { | ||||||
|  |           notification.error({ | ||||||
|  |             message: 'Error', | ||||||
|  |             description: 'File could not be uploaded.', | ||||||
|  |           }); | ||||||
|  |         }); | ||||||
|  |     } else { | ||||||
|  |       notification.error({ | ||||||
|  |         message: 'Error', | ||||||
|  |         description: 'File could not be uploaded.', | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleDownloadFile = async name => { | ||||||
|  |     const token = getItem(AUTH_TOKEN); | ||||||
|  |     if (apiUrl?.getApiUrl) { | ||||||
|  |       return fetch(`${apiUrl?.getApiUrl}filestore/${encodeURIComponent(name)}`, { | ||||||
|  |         method: 'GET', | ||||||
|  |         headers: { | ||||||
|  |           'Content-Type': 'application/octet-stream', | ||||||
|  |           Authorization: token ? `Bearer ${token.access_token}` : '', | ||||||
|  |         }, | ||||||
|  |       }) | ||||||
|  |         .then(response => response.blob()) | ||||||
|  |         .then(blob => { | ||||||
|  |           return new Promise((resolve, reject) => { | ||||||
|  |             const reader = new FileReader(); | ||||||
|  |             reader.onloadend = () => resolve(reader.result); | ||||||
|  |             reader.onerror = reject; | ||||||
|  |             reader.readAsDataURL(blob); | ||||||
|  |           }); | ||||||
|  |         }) | ||||||
|  |         .catch(() => { | ||||||
|  |           notification.error({ | ||||||
|  |             message: 'Error', | ||||||
|  |             description: 'File could not be retrieved.', | ||||||
|  |           }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     return notification.error({ | ||||||
|  |       message: 'Error', | ||||||
|  |       description: 'File could not be retrieved.', | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleFetchMoreProfiles = (e, key) => { | ||||||
|  |     if (key === 'radius') fetchMoreProfiles(e, radiusProfiles, fetchMoreRadiusProfiles); | ||||||
|  |     else if (key === 'captive_portal') | ||||||
|  |       fetchMoreProfiles(e, captiveProfiles, fetchMoreCaptiveProfiles); | ||||||
|  |     else if (key === 'rf') fetchMoreProfiles(e, rfProfiles, fetchMoreRfProfiles); | ||||||
|  |     else if (key === 'passpoint_venue') fetchMoreProfiles(e, venueProfiles, fetchMoreVenueProfiles); | ||||||
|  |     else if (key === 'passpoint_operator') | ||||||
|  |       fetchMoreProfiles(e, operatorProfiles, fetchMoreOperatorProfiles); | ||||||
|  |     else if (key === 'passpoint_osu_id_provider') | ||||||
|  |       fetchMoreProfiles(e, idProviderProfiles, fetchMoreIdProviderProfiles); | ||||||
|  |     else fetchMoreProfiles(e, ssidProfiles, fetchMore); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   if (loading) { |   if (loading) { | ||||||
|     return <Spin size="large" />; |     return <Loading />; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (error) { |   if (error) { | ||||||
| @@ -156,7 +283,7 @@ const ProfileDetails = () => { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (redirect) { |   if (redirect) { | ||||||
|     return <Redirect to="/profiles" />; |     return <Redirect to={ROUTES.profiles} />; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
| @@ -164,13 +291,22 @@ const ProfileDetails = () => { | |||||||
|       name={data.getProfile.name} |       name={data.getProfile.name} | ||||||
|       profileType={data.getProfile.profileType} |       profileType={data.getProfile.profileType} | ||||||
|       details={data.getProfile.details} |       details={data.getProfile.details} | ||||||
|  |       childProfiles={data.getProfile.childProfiles} | ||||||
|       childProfileIds={data.getProfile.childProfileIds} |       childProfileIds={data.getProfile.childProfileIds} | ||||||
|       onDeleteProfile={handleDeleteProfile} |       onDeleteProfile={handleDeleteProfile} | ||||||
|       onUpdateProfile={handleUpdateProfile} |       onUpdateProfile={handleUpdateProfile} | ||||||
|       ssidProfiles={ |       ssidProfiles={ssidProfiles?.getAllProfiles?.items} | ||||||
|         (ssidProfiles && ssidProfiles.getAllProfiles && ssidProfiles.getAllProfiles.items) || [] |       rfProfiles={rfProfiles?.getAllProfiles?.items} | ||||||
|       } |       radiusProfiles={radiusProfiles?.getAllProfiles?.items} | ||||||
|  |       captiveProfiles={captiveProfiles?.getAllProfiles?.items} | ||||||
|  |       venueProfiles={venueProfiles?.getAllProfiles?.items} | ||||||
|  |       operatorProfiles={operatorProfiles?.getAllProfiles?.items} | ||||||
|  |       idProviderProfiles={idProviderProfiles?.getAllProfiles?.items} | ||||||
|  |       associatedSsidProfiles={data.getProfile?.associatedSsidProfiles} | ||||||
|  |       osuSsidProfile={data.getProfile?.osuSsidProfile} | ||||||
|       fileUpload={handleFileUpload} |       fileUpload={handleFileUpload} | ||||||
|  |       onFetchMoreProfiles={handleFetchMoreProfiles} | ||||||
|  |       onDownloadFile={handleDownloadFile} | ||||||
|     /> |     /> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,29 +1,13 @@ | |||||||
| import React, { useContext } from 'react'; | import React, { useContext, useEffect } from 'react'; | ||||||
| import gql from 'graphql-tag'; | import { useQuery, useMutation, gql } from '@apollo/client'; | ||||||
| import { useQuery, useMutation } from '@apollo/react-hooks'; | import { useLocation } from 'react-router-dom'; | ||||||
|  | import { Alert, notification } from 'antd'; | ||||||
|  | import { Profile as ProfilePage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
|  |  | ||||||
| import { Alert, Spin, notification } from 'antd'; | import { GET_ALL_PROFILES } from 'graphql/queries'; | ||||||
|  | import { updateQueryGetAllProfiles } from 'graphql/functions'; | ||||||
| import { Profile as ProfilePage } from '@tip-wlan/wlan-cloud-ui-library'; |  | ||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
|  |  | ||||||
| const GET_ALL_PROFILES = gql` |  | ||||||
|   query GetAllProfiles($customerId: ID!, $cursor: String) { |  | ||||||
|     getAllProfiles(customerId: $customerId, cursor: $cursor) { |  | ||||||
|       items { |  | ||||||
|         id |  | ||||||
|         name |  | ||||||
|         profileType |  | ||||||
|         details |  | ||||||
|       } |  | ||||||
|       context { |  | ||||||
|         cursor |  | ||||||
|         lastPage |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| `; |  | ||||||
|  |  | ||||||
| const DELETE_PROFILE = gql` | const DELETE_PROFILE = gql` | ||||||
|   mutation DeleteProfile($id: ID!) { |   mutation DeleteProfile($id: ID!) { | ||||||
|     deleteProfile(id: $id) { |     deleteProfile(id: $id) { | ||||||
| @@ -34,13 +18,28 @@ const DELETE_PROFILE = gql` | |||||||
|  |  | ||||||
| const Profiles = () => { | const Profiles = () => { | ||||||
|   const { customerId } = useContext(UserContext); |   const { customerId } = useContext(UserContext); | ||||||
|   const { loading, error, data, refetch, fetchMore } = useQuery(GET_ALL_PROFILES, { |   const { loading, error, data, refetch, fetchMore } = useQuery( | ||||||
|  |     GET_ALL_PROFILES(`equipmentCount`), | ||||||
|  |     { | ||||||
|       variables: { customerId }, |       variables: { customerId }, | ||||||
|   }); |       fetchPolicy: 'network-only', | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|   const [deleteProfile] = useMutation(DELETE_PROFILE); |   const [deleteProfile] = useMutation(DELETE_PROFILE); | ||||||
|  |   const location = useLocation(); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (location.state && location.state.refetch) { | ||||||
|  |       refetch({ | ||||||
|  |         variables: { refresh: Date.now() }, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|   const reloadTable = () => { |   const reloadTable = () => { | ||||||
|     refetch() |     refetch({ | ||||||
|  |       variables: { refresh: Date.now() }, | ||||||
|  |     }) | ||||||
|       .then(() => { |       .then(() => { | ||||||
|         notification.success({ |         notification.success({ | ||||||
|           message: 'Success', |           message: 'Success', | ||||||
| @@ -58,25 +57,22 @@ const Profiles = () => { | |||||||
|   const handleLoadMore = () => { |   const handleLoadMore = () => { | ||||||
|     if (!data.getAllProfiles.context.lastPage) { |     if (!data.getAllProfiles.context.lastPage) { | ||||||
|       fetchMore({ |       fetchMore({ | ||||||
|         variables: { cursor: data.getAllProfiles.context.cursor }, |         variables: { context: { ...data.getAllProfiles.context } }, | ||||||
|         updateQuery: (previousResult, { fetchMoreResult }) => { |         updateQuery: updateQueryGetAllProfiles, | ||||||
|           const previousEntry = previousResult.getAllProfiles; |  | ||||||
|           const newItems = fetchMoreResult.getAllProfiles.items; |  | ||||||
|  |  | ||||||
|           return { |  | ||||||
|             getAllProfiles: { |  | ||||||
|               context: fetchMoreResult.getAllProfiles.context, |  | ||||||
|               items: [...previousEntry.items, ...newItems], |  | ||||||
|               __typename: previousEntry.__typename, |  | ||||||
|             }, |  | ||||||
|           }; |  | ||||||
|         }, |  | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleDeleteProfile = id => { |   const handleDeleteProfile = id => { | ||||||
|     deleteProfile({ variables: { id } }) |     deleteProfile({ | ||||||
|  |       variables: { id }, | ||||||
|  |       refetchQueries: [ | ||||||
|  |         { | ||||||
|  |           query: GET_ALL_PROFILES(`equipmentCount`), | ||||||
|  |           variables: { customerId }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }) | ||||||
|       .then(() => { |       .then(() => { | ||||||
|         notification.success({ |         notification.success({ | ||||||
|           message: 'Success', |           message: 'Success', | ||||||
| @@ -92,7 +88,7 @@ const Profiles = () => { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   if (loading) { |   if (loading) { | ||||||
|     return <Spin size="large" />; |     return <Loading />; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (error) { |   if (error) { | ||||||
| @@ -103,7 +99,7 @@ const Profiles = () => { | |||||||
|     <ProfilePage |     <ProfilePage | ||||||
|       data={data.getAllProfiles.items} |       data={data.getAllProfiles.items} | ||||||
|       onReload={reloadTable} |       onReload={reloadTable} | ||||||
|       isLastPage={data.getAllProfiles.context.lastPage} |       isLastPage={data?.getAllProfiles?.context?.lastPage} | ||||||
|       onDeleteProfile={handleDeleteProfile} |       onDeleteProfile={handleDeleteProfile} | ||||||
|       onLoadMore={handleLoadMore} |       onLoadMore={handleLoadMore} | ||||||
|     /> |     /> | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import React, { useContext } from 'react'; | import React, { useContext } from 'react'; | ||||||
| import { useQuery, useMutation } from '@apollo/react-hooks'; | import { useQuery, useMutation } from '@apollo/client'; | ||||||
| import { Alert, notification } from 'antd'; | import { Alert, notification } from 'antd'; | ||||||
| import { AutoProvision as AutoProvisionPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | import { AutoProvision as AutoProvisionPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
|  |  | ||||||
| @@ -21,9 +21,9 @@ const AutoProvision = () => { | |||||||
|     } |     } | ||||||
|   ); |   ); | ||||||
|   const { data: dataProfile, loading: loadingProfile, error: errorProfile } = useQuery( |   const { data: dataProfile, loading: loadingProfile, error: errorProfile } = useQuery( | ||||||
|     GET_ALL_PROFILES, |     GET_ALL_PROFILES(), | ||||||
|     { |     { | ||||||
|       variables: { customerId, type: 'equipment_ap' }, |       variables: { customerId, type: 'equipment_ap', limit: 100 }, | ||||||
|     } |     } | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import React, { useContext } from 'react'; | import React, { useContext } from 'react'; | ||||||
| import { useQuery, useMutation } from '@apollo/react-hooks'; | import { useQuery, useMutation } from '@apollo/client'; | ||||||
| import { Alert, notification } from 'antd'; | import { Alert, notification } from 'antd'; | ||||||
| import { BlockedList as BlockedListPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | import { BlockedList as BlockedListPage, Loading } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
| import { GET_BLOCKED_CLIENTS } from 'graphql/queries'; | import { GET_BLOCKED_CLIENTS } from 'graphql/queries'; | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import { useQuery, useMutation, useLazyQuery } from '@apollo/react-hooks'; | import { useQuery, useMutation, useLazyQuery } from '@apollo/client'; | ||||||
| import { notification } from 'antd'; | import { notification } from 'antd'; | ||||||
| import { | import { | ||||||
|   GET_ALL_FIRMWARE, |   GET_ALL_FIRMWARE, | ||||||
| @@ -22,7 +22,7 @@ const Firmware = () => { | |||||||
|   const [ |   const [ | ||||||
|     getAllFirmware, |     getAllFirmware, | ||||||
|     { data: firmwareVersionData, loading: firmwareVersionLoading }, |     { data: firmwareVersionData, loading: firmwareVersionLoading }, | ||||||
|   ] = useLazyQuery(GET_ALL_FIRMWARE); |   ] = useLazyQuery(GET_ALL_FIRMWARE, { fetchPolicy: 'network-only' }); | ||||||
|  |  | ||||||
|   const { |   const { | ||||||
|     data: trackAssignmentData, |     data: trackAssignmentData, | ||||||
| @@ -83,8 +83,18 @@ const Firmware = () => { | |||||||
|     firmwareVersionRecordId, |     firmwareVersionRecordId, | ||||||
|     modelId, |     modelId, | ||||||
|     createdTimestamp, |     createdTimestamp, | ||||||
|     lastModifiedTimestamp |     lastModifiedTimestamp, | ||||||
|  |     prevFirmwareVersionRecordId | ||||||
|   ) => { |   ) => { | ||||||
|  |     if (prevFirmwareVersionRecordId !== firmwareVersionRecordId) { | ||||||
|  |       deleteTrackAssignment({ | ||||||
|  |         variables: { | ||||||
|  |           firmwareTrackId: firmwareTrackData.getFirmwareTrack.recordId, | ||||||
|  |           firmwareVersionId: prevFirmwareVersionRecordId, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     updateTrackAssignment({ |     updateTrackAssignment({ | ||||||
|       variables: { |       variables: { | ||||||
|         trackRecordId: firmwareTrackData.getFirmwareTrack.recordId, |         trackRecordId: firmwareTrackData.getFirmwareTrack.recordId, | ||||||
| @@ -130,6 +140,7 @@ const Firmware = () => { | |||||||
|         }) |         }) | ||||||
|       ); |       ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleCreateFirmware = ( |   const handleCreateFirmware = ( | ||||||
|     modelId, |     modelId, | ||||||
|     versionName, |     versionName, | ||||||
| @@ -237,7 +248,7 @@ const Firmware = () => { | |||||||
|       onCreateTrackAssignment={handleCreateTrackAssignment} |       onCreateTrackAssignment={handleCreateTrackAssignment} | ||||||
|       onUpdateTrackAssignment={handleUpdateTrackAssignment} |       onUpdateTrackAssignment={handleUpdateTrackAssignment} | ||||||
|       onDeleteTrackAssignment={handleDeleteTrackAssignment} |       onDeleteTrackAssignment={handleDeleteTrackAssignment} | ||||||
|       onCreateFirnware={handleCreateFirmware} |       onCreateFirmware={handleCreateFirmware} | ||||||
|       onUpdateFirmware={handleUpdateFirmware} |       onUpdateFirmware={handleUpdateFirmware} | ||||||
|       onDeleteFirmware={handleDeleteFirmware} |       onDeleteFirmware={handleDeleteFirmware} | ||||||
|       firmwareError={error} |       firmwareError={error} | ||||||
|   | |||||||
| @@ -1,9 +1,11 @@ | |||||||
| import React from 'react'; | import React, { useState } from 'react'; | ||||||
| import gql from 'graphql-tag'; | import { useQuery, useMutation, useLazyQuery, gql } from '@apollo/client'; | ||||||
| import { useMutation, useLazyQuery } from '@apollo/react-hooks'; |  | ||||||
| import { notification } from 'antd'; | import { notification } from 'antd'; | ||||||
| import { Manufacturer as ManufacturerPage } from '@tip-wlan/wlan-cloud-ui-library'; | import { Manufacturer as ManufacturerPage } from '@tip-wlan/wlan-cloud-ui-library'; | ||||||
| import { OUI_UPLOAD } from 'graphql/mutations'; |  | ||||||
|  | import { AUTH_TOKEN } from 'constants/index'; | ||||||
|  | import { GET_API_URL } from 'graphql/queries'; | ||||||
|  | import { getItem } from 'utils/localStorage'; | ||||||
|  |  | ||||||
| const GET_OUI = gql` | const GET_OUI = gql` | ||||||
|   query GetOui($oui: String!) { |   query GetOui($oui: String!) { | ||||||
| @@ -28,7 +30,12 @@ const UPDATE_OUI = gql` | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| `; | `; | ||||||
|  |  | ||||||
| const System = () => { | const System = () => { | ||||||
|  |   const token = getItem(AUTH_TOKEN); | ||||||
|  |   const [loadingFileUpload, setLoadingFileUpload] = useState(false); | ||||||
|  |  | ||||||
|  |   const { data: apiUrl } = useQuery(GET_API_URL); | ||||||
|   const [updateOUI] = useMutation(UPDATE_OUI); |   const [updateOUI] = useMutation(UPDATE_OUI); | ||||||
|   const [searchOUI, { data }] = useLazyQuery(GET_OUI, { |   const [searchOUI, { data }] = useLazyQuery(GET_OUI, { | ||||||
|     onError: () => { |     onError: () => { | ||||||
| @@ -37,8 +44,16 @@ const System = () => { | |||||||
|         description: 'No matching manufacturer found for OUI', |         description: 'No matching manufacturer found for OUI', | ||||||
|       }); |       }); | ||||||
|     }, |     }, | ||||||
|  |     onCompleted: () => { | ||||||
|  |       if (!data?.getOui?.oui) { | ||||||
|  |         notification.error({ | ||||||
|  |           message: 'Error', | ||||||
|  |           description: 'No matching manufacturer found for OUI', | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     fetchPolicy: 'no-cache', | ||||||
|   }); |   }); | ||||||
|   const [fileUpload] = useMutation(OUI_UPLOAD); |  | ||||||
|  |  | ||||||
|   const handleUpdateOUI = (oui, manufacturerAlias, manufacturerName) => { |   const handleUpdateOUI = (oui, manufacturerAlias, manufacturerName) => { | ||||||
|     updateOUI({ |     updateOUI({ | ||||||
| @@ -66,10 +81,21 @@ const System = () => { | |||||||
|     searchOUI({ variables: { oui } }); |     searchOUI({ variables: { oui } }); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleFileUpload = (fileName, file) => |   const handleFileUpload = (fileName, file) => { | ||||||
|     fileUpload({ variables: { fileName, file } }) |     if (apiUrl?.getApiUrl) { | ||||||
|  |       setLoadingFileUpload(true); | ||||||
|  |  | ||||||
|  |       fetch(`${apiUrl?.getApiUrl}portal/manufacturer/oui/upload?fileName=${fileName}`, { | ||||||
|  |         method: 'POST', | ||||||
|  |         headers: { | ||||||
|  |           Authorization: token ? `Bearer ${token.access_token}` : '', | ||||||
|  |           'Content-Type': 'application/octet-stream', | ||||||
|  |         }, | ||||||
|  |         body: file, | ||||||
|  |       }) | ||||||
|  |         .then(response => response.json()) | ||||||
|         .then(resp => { |         .then(resp => { | ||||||
|         if (resp?.ouiUpload?.success) { |           if (resp?.success) { | ||||||
|             notification.success({ |             notification.success({ | ||||||
|               message: 'Success', |               message: 'Success', | ||||||
|               description: 'File successfully uploaded.', |               description: 'File successfully uploaded.', | ||||||
| @@ -81,18 +107,28 @@ const System = () => { | |||||||
|             }); |             }); | ||||||
|           } |           } | ||||||
|         }) |         }) | ||||||
|       .catch(() => |         .catch(() => { | ||||||
|           notification.error({ |           notification.error({ | ||||||
|             message: 'Error', |             message: 'Error', | ||||||
|             description: 'File could not be uploaded.', |             description: 'File could not be uploaded.', | ||||||
|  |           }); | ||||||
|         }) |         }) | ||||||
|       ); |         .finally(() => setLoadingFileUpload(false)); | ||||||
|  |     } else { | ||||||
|  |       notification.error({ | ||||||
|  |         message: 'Error', | ||||||
|  |         description: 'File could not be uploaded.', | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <ManufacturerPage |     <ManufacturerPage | ||||||
|       onSearchOUI={handleSearchOUI} |       onSearchOUI={handleSearchOUI} | ||||||
|       onUpdateOUI={handleUpdateOUI} |       onUpdateOUI={handleUpdateOUI} | ||||||
|       returnedOUI={data && data.getOui} |       returnedOUI={data && data.getOui} | ||||||
|       fileUpload={handleFileUpload} |       fileUpload={handleFileUpload} | ||||||
|  |       loadingFileUpload={loadingFileUpload} | ||||||
|     /> |     /> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; | |||||||
|  |  | ||||||
| import UserContext from 'contexts/UserContext'; | import UserContext from 'contexts/UserContext'; | ||||||
|  |  | ||||||
| const UserProvider = ({ children, id, email, role, customerId, updateUser, updateToken }) => ( | const UserProvider = ({ children, id, email, roles, customerId, updateUser, updateToken }) => ( | ||||||
|   <UserContext.Provider value={{ id, email, role, customerId, updateUser, updateToken }}> |   <UserContext.Provider value={{ id, email, roles, customerId, updateUser, updateToken }}> | ||||||
|     {children} |     {children} | ||||||
|   </UserContext.Provider> |   </UserContext.Provider> | ||||||
| ); | ); | ||||||
| @@ -15,14 +15,14 @@ UserProvider.propTypes = { | |||||||
|   updateToken: PropTypes.func.isRequired, |   updateToken: PropTypes.func.isRequired, | ||||||
|   id: PropTypes.number, |   id: PropTypes.number, | ||||||
|   email: PropTypes.string, |   email: PropTypes.string, | ||||||
|   role: PropTypes.string, |   roles: PropTypes.instanceOf(Array), | ||||||
|   customerId: PropTypes.number, |   customerId: PropTypes.number, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| UserProvider.defaultProps = { | UserProvider.defaultProps = { | ||||||
|   id: null, |   id: null, | ||||||
|   email: null, |   email: null, | ||||||
|   role: null, |   roles: [], | ||||||
|   customerId: null, |   customerId: null, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								app/graphql/functions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								app/graphql/functions.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | export const updateQueryGetAllProfiles = (previousResult, { fetchMoreResult }) => { | ||||||
|  |   const previousEntry = previousResult.getAllProfiles; | ||||||
|  |   const newItems = fetchMoreResult.getAllProfiles.items; | ||||||
|  |  | ||||||
|  |   return { | ||||||
|  |     getAllProfiles: { | ||||||
|  |       context: { ...fetchMoreResult.getAllProfiles.context }, | ||||||
|  |       items: [...previousEntry.items, ...newItems], | ||||||
|  |       __typename: previousEntry.__typename, | ||||||
|  |     }, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const fetchMoreProfiles = (e, profile, fetchMore) => { | ||||||
|  |   if (profile.getAllProfiles.context.lastPage) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   e.persist(); | ||||||
|  |   const { target } = e; | ||||||
|  |  | ||||||
|  |   if (target.scrollTop + target.offsetHeight + 10 >= target.scrollHeight) { | ||||||
|  |     fetchMore({ | ||||||
|  |       variables: { context: { ...profile.getAllProfiles.context } }, | ||||||
|  |       updateQuery: updateQueryGetAllProfiles, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | }; | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import gql from 'graphql-tag'; | import { gql } from '@apollo/client'; | ||||||
|  |  | ||||||
| export const REFRESH_TOKEN = gql` | export const REFRESH_TOKEN = gql` | ||||||
|   mutation UpdateToken($refreshToken: String!) { |   mutation UpdateToken($refreshToken: String!) { | ||||||
| @@ -99,6 +99,22 @@ export const UPDATE_EQUIPMENT_FIRMWARE = gql` | |||||||
|   } |   } | ||||||
| `; | `; | ||||||
|  |  | ||||||
|  | export const REQUEST_EQUIPMENT_SWITCH_BANK = gql` | ||||||
|  |   mutation RequestEquipmentSwitchBank($equipmentId: ID) { | ||||||
|  |     requestEquipmentSwitchBank(equipmentId: $equipmentId) { | ||||||
|  |       success | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | export const REQUEST_EQUIPMENT_REBOOT = gql` | ||||||
|  |   mutation RequestEquipmentReboot($equipmentId: ID) { | ||||||
|  |     requestEquipmentReboot(equipmentId: $equipmentId) { | ||||||
|  |       success | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | `; | ||||||
|  |  | ||||||
| export const UPDATE_TRACK_ASSIGNMENT = gql` | export const UPDATE_TRACK_ASSIGNMENT = gql` | ||||||
|   mutation UpdateFirmwareTrackAssignment( |   mutation UpdateFirmwareTrackAssignment( | ||||||
|     $trackRecordId: ID! |     $trackRecordId: ID! | ||||||
| @@ -239,6 +255,62 @@ export const CREATE_EQUIPMENT = gql` | |||||||
|   } |   } | ||||||
| `; | `; | ||||||
|  |  | ||||||
|  | export const UPDATE_EQUIPMENT = gql` | ||||||
|  |   mutation UpdateEquipment( | ||||||
|  |     $id: ID! | ||||||
|  |     $equipmentType: String! | ||||||
|  |     $inventoryId: String! | ||||||
|  |     $customerId: ID! | ||||||
|  |     $profileId: ID! | ||||||
|  |     $locationId: ID! | ||||||
|  |     $name: String! | ||||||
|  |     $baseMacAddress: String | ||||||
|  |     $latitude: String | ||||||
|  |     $longitude: String | ||||||
|  |     $serial: String | ||||||
|  |     $lastModifiedTimestamp: String | ||||||
|  |     $details: JSONObject | ||||||
|  |   ) { | ||||||
|  |     updateEquipment( | ||||||
|  |       id: $id | ||||||
|  |       equipmentType: $equipmentType | ||||||
|  |       inventoryId: $inventoryId | ||||||
|  |       customerId: $customerId | ||||||
|  |       profileId: $profileId | ||||||
|  |       locationId: $locationId | ||||||
|  |       name: $name | ||||||
|  |       baseMacAddress: $baseMacAddress | ||||||
|  |       latitude: $latitude | ||||||
|  |       longitude: $longitude | ||||||
|  |       serial: $serial | ||||||
|  |       lastModifiedTimestamp: $lastModifiedTimestamp | ||||||
|  |       details: $details | ||||||
|  |     ) { | ||||||
|  |       id | ||||||
|  |       equipmentType | ||||||
|  |       inventoryId | ||||||
|  |       customerId | ||||||
|  |       profileId | ||||||
|  |       locationId | ||||||
|  |       name | ||||||
|  |       baseMacAddress | ||||||
|  |       latitude | ||||||
|  |       longitude | ||||||
|  |       serial | ||||||
|  |       lastModifiedTimestamp | ||||||
|  |       details | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | export const DELETE_EQUIPMENT = gql` | ||||||
|  |   mutation DeleteEquipment($id: ID!) { | ||||||
|  |     deleteEquipment(id: $id) { | ||||||
|  |       id | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | `; | ||||||
|  |  | ||||||
| export const UPDATE_CUSTOMER = gql` | export const UPDATE_CUSTOMER = gql` | ||||||
|   mutation UpdateCustomer( |   mutation UpdateCustomer( | ||||||
|     $id: ID! |     $id: ID! | ||||||
|   | |||||||
| @@ -1,4 +1,10 @@ | |||||||
| import gql from 'graphql-tag'; | import { gql } from '@apollo/client'; | ||||||
|  |  | ||||||
|  | export const GET_API_URL = gql` | ||||||
|  |   query GetApiUrl { | ||||||
|  |     getApiUrl | ||||||
|  |   } | ||||||
|  | `; | ||||||
|  |  | ||||||
| export const GET_ALL_LOCATIONS = gql` | export const GET_ALL_LOCATIONS = gql` | ||||||
|   query GetAllLocations($customerId: ID!) { |   query GetAllLocations($customerId: ID!) { | ||||||
| @@ -16,13 +22,13 @@ export const FILTER_EQUIPMENT = gql` | |||||||
|     $locationIds: [ID] |     $locationIds: [ID] | ||||||
|     $customerId: ID! |     $customerId: ID! | ||||||
|     $equipmentType: String |     $equipmentType: String | ||||||
|     $cursor: String |     $context: JSONObject | ||||||
|   ) { |   ) { | ||||||
|     filterEquipment( |     filterEquipment( | ||||||
|       customerId: $customerId |       customerId: $customerId | ||||||
|       locationIds: $locationIds |       locationIds: $locationIds | ||||||
|       equipmentType: $equipmentType |       equipmentType: $equipmentType | ||||||
|       cursor: $cursor |       context: $context | ||||||
|     ) { |     ) { | ||||||
|       items { |       items { | ||||||
|         name |         name | ||||||
| @@ -36,12 +42,12 @@ export const FILTER_EQUIPMENT = gql` | |||||||
|         profile { |         profile { | ||||||
|           name |           name | ||||||
|         } |         } | ||||||
|  |         baseMacAddress | ||||||
|  |         manufacturer | ||||||
|         status { |         status { | ||||||
|           protocol { |           protocol { | ||||||
|             details { |             details { | ||||||
|               reportedIpV4Addr |               reportedIpV4Addr | ||||||
|               reportedMacAddr |  | ||||||
|               manufacturer |  | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|           osPerformance { |           osPerformance { | ||||||
| @@ -61,11 +67,75 @@ export const FILTER_EQUIPMENT = gql` | |||||||
|               numClientsPerRadio |               numClientsPerRadio | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|  |           firmware { | ||||||
|  |             detailsJSON | ||||||
|  |           } | ||||||
|  |           channel { | ||||||
|  |             detailsJSON | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       context { |       } | ||||||
|         lastPage |       context | ||||||
|         cursor |     } | ||||||
|  |   } | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | export const GET_EQUIPMENT = gql` | ||||||
|  |   query GetEquipment($id: ID!) { | ||||||
|  |     getEquipment(id: $id) { | ||||||
|  |       id | ||||||
|  |       equipmentType | ||||||
|  |       inventoryId | ||||||
|  |       customerId | ||||||
|  |       profileId | ||||||
|  |       locationId | ||||||
|  |       name | ||||||
|  |       latitude | ||||||
|  |       longitude | ||||||
|  |       serial | ||||||
|  |       lastModifiedTimestamp | ||||||
|  |       details | ||||||
|  |       profile { | ||||||
|  |         id | ||||||
|  |         name | ||||||
|  |         childProfiles { | ||||||
|  |           id | ||||||
|  |           name | ||||||
|  |           details | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       baseMacAddress | ||||||
|  |       manufacturer | ||||||
|  |       status { | ||||||
|  |         firmware { | ||||||
|  |           detailsJSON | ||||||
|  |         } | ||||||
|  |         protocol { | ||||||
|  |           detailsJSON | ||||||
|  |         } | ||||||
|  |         radioUtilization { | ||||||
|  |           detailsJSON | ||||||
|  |         } | ||||||
|  |         clientDetails { | ||||||
|  |           detailsJSON | ||||||
|  |           details { | ||||||
|  |             numClientsPerRadio | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         osPerformance { | ||||||
|  |           detailsJSON | ||||||
|  |         } | ||||||
|  |         channel { | ||||||
|  |           detailsJSON | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       model | ||||||
|  |       alarmsCount | ||||||
|  |       alarms { | ||||||
|  |         severity | ||||||
|  |         alarmCode | ||||||
|  |         details | ||||||
|  |         createdTimestamp | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -76,13 +146,13 @@ export const FILTER_EQUIPMENT_BULK_EDIT_APS = gql` | |||||||
|     $locationIds: [ID] |     $locationIds: [ID] | ||||||
|     $customerId: ID! |     $customerId: ID! | ||||||
|     $equipmentType: String |     $equipmentType: String | ||||||
|     $cursor: String |     $context: JSONObject | ||||||
|   ) { |   ) { | ||||||
|     filterEquipment( |     filterEquipment( | ||||||
|       customerId: $customerId |       customerId: $customerId | ||||||
|       locationIds: $locationIds |       locationIds: $locationIds | ||||||
|       equipmentType: $equipmentType |       equipmentType: $equipmentType | ||||||
|       cursor: $cursor |       context: $context | ||||||
|     ) { |     ) { | ||||||
|       items { |       items { | ||||||
|         name |         name | ||||||
| @@ -91,10 +161,7 @@ export const FILTER_EQUIPMENT_BULK_EDIT_APS = gql` | |||||||
|         channel |         channel | ||||||
|         details |         details | ||||||
|       } |       } | ||||||
|       context { |       context | ||||||
|         lastPage |  | ||||||
|         cursor |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| `; | `; | ||||||
| @@ -112,8 +179,8 @@ export const GET_LOCATION = gql` | |||||||
| `; | `; | ||||||
|  |  | ||||||
| export const FILTER_CLIENT_SESSIONS = gql` | export const FILTER_CLIENT_SESSIONS = gql` | ||||||
|   query FilterClientSessions($customerId: ID!, $locationIds: [ID], $cursor: String) { |   query FilterClientSessions($customerId: ID!, $locationIds: [ID], $context: JSONObject) { | ||||||
|     filterClientSessions(customerId: $customerId, locationIds: $locationIds, cursor: $cursor) { |     filterClientSessions(customerId: $customerId, locationIds: $locationIds, context: $context) { | ||||||
|       items { |       items { | ||||||
|         id |         id | ||||||
|         macAddress |         macAddress | ||||||
| @@ -123,14 +190,12 @@ export const FILTER_CLIENT_SESSIONS = gql` | |||||||
|         radioType |         radioType | ||||||
|         signal |         signal | ||||||
|         manufacturer |         manufacturer | ||||||
|  |         details | ||||||
|         equipment { |         equipment { | ||||||
|           name |           name | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       context { |       context | ||||||
|         lastPage |  | ||||||
|         cursor |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| `; | `; | ||||||
| @@ -157,7 +222,7 @@ export const GET_CLIENT_SESSION = gql` | |||||||
| export const FILTER_SERVICE_METRICS = gql` | export const FILTER_SERVICE_METRICS = gql` | ||||||
|   query FilterServiceMetrics( |   query FilterServiceMetrics( | ||||||
|     $customerId: ID! |     $customerId: ID! | ||||||
|     $cursor: String |     $context: JSONObject | ||||||
|     $fromTime: String! |     $fromTime: String! | ||||||
|     $toTime: String! |     $toTime: String! | ||||||
|     $clientMacs: [String] |     $clientMacs: [String] | ||||||
| @@ -167,7 +232,7 @@ export const FILTER_SERVICE_METRICS = gql` | |||||||
|   ) { |   ) { | ||||||
|     filterServiceMetrics( |     filterServiceMetrics( | ||||||
|       customerId: $customerId |       customerId: $customerId | ||||||
|       cursor: $cursor |       context: $context | ||||||
|       fromTime: $fromTime |       fromTime: $fromTime | ||||||
|       toTime: $toTime |       toTime: $toTime | ||||||
|       clientMacs: $clientMacs |       clientMacs: $clientMacs | ||||||
| @@ -181,28 +246,36 @@ export const FILTER_SERVICE_METRICS = gql` | |||||||
|         rssi |         rssi | ||||||
|         rxBytes |         rxBytes | ||||||
|         txBytes |         txBytes | ||||||
|  |         detailsJSON | ||||||
|       } |       } | ||||||
|       context { |       context | ||||||
|         lastPage |  | ||||||
|         cursor |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| `; | `; | ||||||
|  |  | ||||||
| export const GET_ALL_PROFILES = gql` | export const GET_ALL_PROFILES = (fields = '') => gql` | ||||||
|   query GetAllProfiles($customerId: ID!, $cursor: String, $type: String) { |   query GetAllProfiles( | ||||||
|     getAllProfiles(customerId: $customerId, cursor: $cursor, type: $type) { |     $customerId: ID! | ||||||
|  |     $cursor: String | ||||||
|  |     $limit: Int | ||||||
|  |     $type: String | ||||||
|  |     $context: JSONObject | ||||||
|  |   ) { | ||||||
|  |     getAllProfiles( | ||||||
|  |       customerId: $customerId | ||||||
|  |       cursor: $cursor | ||||||
|  |       limit: $limit | ||||||
|  |       type: $type | ||||||
|  |       context: $context | ||||||
|  |     ) { | ||||||
|       items { |       items { | ||||||
|         id |         id | ||||||
|         name |         name | ||||||
|         profileType |         profileType | ||||||
|         details |         details | ||||||
|  |         ${fields} | ||||||
|       } |       } | ||||||
|       context { |       context | ||||||
|         cursor |  | ||||||
|         lastPage |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| `; | `; | ||||||
| @@ -218,10 +291,7 @@ export const GET_ALL_STATUS = gql` | |||||||
|           clientCountPerOui |           clientCountPerOui | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       context { |       context | ||||||
|         lastPage |  | ||||||
|         cursor |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| `; | `; | ||||||
| @@ -297,7 +367,7 @@ export const FILTER_SYSTEM_EVENTS = gql` | |||||||
|     $toTime: String! |     $toTime: String! | ||||||
|     $equipmentIds: [ID] |     $equipmentIds: [ID] | ||||||
|     $dataTypes: [String] |     $dataTypes: [String] | ||||||
|     $cursor: String |     $context: JSONObject | ||||||
|     $limit: Int |     $limit: Int | ||||||
|   ) { |   ) { | ||||||
|     filterSystemEvents( |     filterSystemEvents( | ||||||
| @@ -306,14 +376,11 @@ export const FILTER_SYSTEM_EVENTS = gql` | |||||||
|       toTime: $toTime |       toTime: $toTime | ||||||
|       dataTypes: $dataTypes |       dataTypes: $dataTypes | ||||||
|       equipmentIds: $equipmentIds |       equipmentIds: $equipmentIds | ||||||
|       cursor: $cursor |       context: $context | ||||||
|       limit: $limit |       limit: $limit | ||||||
|     ) { |     ) { | ||||||
|       items |       items | ||||||
|       context { |       context | ||||||
|         lastPage |  | ||||||
|         cursor |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| `; | `; | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
|  |  | ||||||
|     <!-- Allow installing the app to the homescreen --> |     <!-- Allow installing the app to the homescreen --> | ||||||
|     <meta name="mobile-web-app-capable" content="yes" /> |     <meta name="mobile-web-app-capable" content="yes" /> | ||||||
|  |     <script type="text/javascript" src="/config.js"></script> | ||||||
|   </head> |   </head> | ||||||
|  |  | ||||||
|   <body> |   <body> | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								app/index.js
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								app/index.js
									
									
									
									
									
								
							| @@ -1,12 +1,10 @@ | |||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ReactDOM from 'react-dom'; | import ReactDOM from 'react-dom'; | ||||||
| import { Router } from 'react-router-dom'; | import { Router } from 'react-router-dom'; | ||||||
| import { ApolloClient } from 'apollo-client'; | import { ApolloClient, ApolloProvider, ApolloLink, Observable } from '@apollo/client'; | ||||||
| import { InMemoryCache } from 'apollo-cache-inmemory'; | import { InMemoryCache } from '@apollo/client/cache'; | ||||||
| import { onError } from 'apollo-link-error'; | import { onError } from '@apollo/client/link/error'; | ||||||
| import { createUploadLink } from 'apollo-upload-client'; | import { createUploadLink } from 'apollo-upload-client'; | ||||||
| import { ApolloLink, Observable } from 'apollo-link'; |  | ||||||
| import { ApolloProvider } from '@apollo/react-hooks'; |  | ||||||
|  |  | ||||||
| import 'styles/antd.less'; | import 'styles/antd.less'; | ||||||
| import 'styles/index.scss'; | import 'styles/index.scss'; | ||||||
| @@ -17,16 +15,13 @@ import { REFRESH_TOKEN } from 'graphql/mutations'; | |||||||
| import { getItem, setItem, removeItem } from 'utils/localStorage'; | import { getItem, setItem, removeItem } from 'utils/localStorage'; | ||||||
| import history from 'utils/history'; | import history from 'utils/history'; | ||||||
|  |  | ||||||
| const API_URI = | const API_URI = process.env.NODE_ENV === 'production' ? window.REACT_APP_API : process.env.API; | ||||||
|   process.env.NODE_ENV === 'development' |  | ||||||
|     ? 'http://localhost:4000/' |  | ||||||
|     : 'https://wlan-graphql.zone3.lab.connectus.ai/'; |  | ||||||
| const MOUNT_NODE = document.getElementById('root'); | const MOUNT_NODE = document.getElementById('root'); | ||||||
|  |  | ||||||
| const cache = new InMemoryCache(); | const cache = new InMemoryCache(); | ||||||
|  |  | ||||||
| const uploadLink = createUploadLink({ | const uploadLink = createUploadLink({ | ||||||
|   uri: API_URI, |   uri: `${API_URI}/graphql`, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const request = async operation => { | const request = async operation => { | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								docker_entrypoint.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docker_entrypoint.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | #!/bin/sh -eu | ||||||
|  | ./generate_config_js.sh >/usr/share/nginx/html/config.js | ||||||
|  | nginx -g "daemon off;" | ||||||
							
								
								
									
										10
									
								
								generate_config_js.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								generate_config_js.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | #!/bin/sh -eu | ||||||
|  | if [ -z "${API:-}" ]; then | ||||||
|  |     API_URL_JSON=undefined | ||||||
|  | else | ||||||
|  |     API_URL_JSON=$(jq -n --arg api "$API" '$api') | ||||||
|  | fi | ||||||
|  |   | ||||||
|  | cat <<EOF | ||||||
|  | window.REACT_APP_API = $API_URL_JSON; | ||||||
|  | EOF | ||||||
							
								
								
									
										9921
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9921
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										40
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "wlan-cloud-ui", |   "name": "wlan-cloud-ui", | ||||||
|   "version": "0.2.3", |   "version": "1.0.7", | ||||||
|   "author": "ConnectUs", |   "author": "ConnectUs", | ||||||
|   "description": "React Portal", |   "description": "React Portal", | ||||||
|   "engines": { |   "engines": { | ||||||
| @@ -10,28 +10,23 @@ | |||||||
|   "scripts": { |   "scripts": { | ||||||
|     "test": "jest --passWithNoTests --coverage", |     "test": "jest --passWithNoTests --coverage", | ||||||
|     "start": "cross-env NODE_ENV=development webpack-dev-server", |     "start": "cross-env NODE_ENV=development webpack-dev-server", | ||||||
|  |     "start:bare": "cross-env API=ttps://portal.rtl.lab.netexperience.com NODE_ENV=bare webpack-dev-server", | ||||||
|  |     "start:dev": "cross-env API=https://portal.rtl.lab.netexperience.com NODE_ENV=development webpack-dev-server", | ||||||
|     "build": "webpack --mode=production", |     "build": "webpack --mode=production", | ||||||
|     "format": "prettier --write \"app/**/*.js\"", |     "format": "prettier --write 'app/**/*{.js,.scss}'", | ||||||
|     "eslint-fix": "eslint --fix \"app/**/*.js\"", |     "eslint-fix": "eslint --fix 'app/**/*.js'", | ||||||
|     "eslint": "eslint \"app/**/*.js\" --max-warnings=0" |     "eslint": "eslint 'app/**/*.js' --max-warnings=0" | ||||||
|   }, |   }, | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@ant-design/icons": "^4.2.1", |     "@ant-design/icons": "^4.2.1", | ||||||
|     "@apollo/react-hoc": "^3.1.4", |     "@apollo/client": "^3.1.3", | ||||||
|     "@apollo/react-hooks": "^3.1.3", |     "@tip-wlan/wlan-cloud-ui-library": "^1.0.10", | ||||||
|     "@tip-wlan/wlan-cloud-ui-library": "^0.2.3", |     "antd": "^4.5.2", | ||||||
|     "antd": "^4.3.1", |  | ||||||
|     "apollo-cache-inmemory": "^1.6.6", |  | ||||||
|     "apollo-client": "^2.6.10", |  | ||||||
|     "apollo-link": "^1.2.14", |  | ||||||
|     "apollo-link-error": "^1.1.13", |  | ||||||
|     "apollo-link-http": "^1.5.17", |  | ||||||
|     "apollo-upload-client": "^13.0.0", |     "apollo-upload-client": "^13.0.0", | ||||||
|     "clean-webpack-plugin": "^3.0.0", |     "clean-webpack-plugin": "^3.0.0", | ||||||
|     "graphql": "^14.6.0", |     "graphql": "^14.6.0", | ||||||
|     "graphql-tag": "^2.10.3", |     "highcharts": "^8.1.0", | ||||||
|     "highcharts": "^8.1.1", |  | ||||||
|     "highcharts-react-official": "^3.0.0", |     "highcharts-react-official": "^3.0.0", | ||||||
|     "history": "^4.10.1", |     "history": "^4.10.1", | ||||||
|     "html-webpack-plugin": "^3.2.0", |     "html-webpack-plugin": "^3.2.0", | ||||||
| @@ -80,7 +75,6 @@ | |||||||
|     "lint-staged": "^10.0.8", |     "lint-staged": "^10.0.8", | ||||||
|     "node-sass": "^4.13.1", |     "node-sass": "^4.13.1", | ||||||
|     "prettier": "^1.19.1", |     "prettier": "^1.19.1", | ||||||
|     "pretty-quick": "^2.0.1", |  | ||||||
|     "react-test-renderer": "^16.13.1", |     "react-test-renderer": "^16.13.1", | ||||||
|     "sass-loader": "^8.0.2", |     "sass-loader": "^8.0.2", | ||||||
|     "style-loader": "^1.1.3", |     "style-loader": "^1.1.3", | ||||||
| @@ -89,22 +83,20 @@ | |||||||
|     "webpack-dev-server": "^3.11.0", |     "webpack-dev-server": "^3.11.0", | ||||||
|     "webpack-merge": "^4.2.2" |     "webpack-merge": "^4.2.2" | ||||||
|   }, |   }, | ||||||
|   "precommit": "NODE_ENV=production lint-staged", |  | ||||||
|   "browserslist": [ |   "browserslist": [ | ||||||
|     "last 2 versions", |     "last 2 versions", | ||||||
|     "> 1%", |     "> 1%", | ||||||
|     "IE 10" |     "IE 10" | ||||||
|   ], |   ], | ||||||
|   "lint-staged": { |  | ||||||
|     "*.{js,jsx}": [ |  | ||||||
|       "pretty-quick --staged", |  | ||||||
|       "eslint . --fix \"app/**/*.js\" --max-warnings=0", |  | ||||||
|       "git add" |  | ||||||
|     ] |  | ||||||
|   }, |  | ||||||
|   "husky": { |   "husky": { | ||||||
|     "hooks": { |     "hooks": { | ||||||
|       "pre-commit": "lint-staged" |       "pre-commit": "lint-staged" | ||||||
|     } |     } | ||||||
|  |   }, | ||||||
|  |   "lint-staged": { | ||||||
|  |     "*.{js,jsx}": [ | ||||||
|  |       "eslint --fix 'app/**/*.js' --max-warnings=0", | ||||||
|  |       "prettier --write" | ||||||
|  |     ] | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ const common = require('./webpack/webpack.common'); | |||||||
| const envs = { | const envs = { | ||||||
|   development: 'dev', |   development: 'dev', | ||||||
|   production: 'prod', |   production: 'prod', | ||||||
|  |   bare: 'bare', | ||||||
| }; | }; | ||||||
| /* eslint-disable global-require,import/no-dynamic-require */ | /* eslint-disable global-require,import/no-dynamic-require */ | ||||||
| const env = envs[process.env.NODE_ENV || 'production']; | const env = envs[process.env.NODE_ENV || 'production']; | ||||||
|   | |||||||
							
								
								
									
										73
									
								
								webpack/webpack.bare.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								webpack/webpack.bare.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | const path = require('path'); | ||||||
|  |  | ||||||
|  | const commonPaths = require('./paths'); | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |   mode: 'development', | ||||||
|  |   output: { | ||||||
|  |     path: commonPaths.outputPath, | ||||||
|  |     publicPath: '/', | ||||||
|  |     filename: '[name].js', | ||||||
|  |     chunkFilename: '[name].js', | ||||||
|  |   }, | ||||||
|  |   devtool: 'inline-source-map', | ||||||
|  |   devServer: { | ||||||
|  |     port: 3000, | ||||||
|  |     historyApiFallback: true, | ||||||
|  |     contentBase: commonPaths.outputPath, | ||||||
|  |   }, | ||||||
|  |   module: { | ||||||
|  |     rules: [ | ||||||
|  |       { | ||||||
|  |         test: /\.(css|scss)$/, | ||||||
|  |         use: [ | ||||||
|  |           { | ||||||
|  |             loader: 'style-loader', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             loader: 'css-loader', | ||||||
|  |             options: { | ||||||
|  |               modules: { | ||||||
|  |                 localIdentName: '[name]__[local]___[hash:base64:5]', | ||||||
|  |               }, | ||||||
|  |               sourceMap: true, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             loader: 'sass-loader', | ||||||
|  |             options: { | ||||||
|  |               sourceMap: true, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         test: /\.less$/, | ||||||
|  |         use: [ | ||||||
|  |           { | ||||||
|  |             loader: 'style-loader', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             loader: 'css-loader', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             loader: 'less-loader', // compiles Less to CSS | ||||||
|  |             options: { | ||||||
|  |               lessOptions: { | ||||||
|  |                 javascriptEnabled: true, | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |   }, | ||||||
|  |   resolve: { | ||||||
|  |     modules: ['node_modules', 'app'], | ||||||
|  |     alias: { | ||||||
|  |       app: path.resolve(__dirname, '../', 'app'), | ||||||
|  |       react: path.resolve(__dirname, '../', 'node_modules', 'react'), | ||||||
|  |       'react-router-dom': path.resolve('./node_modules/react-router-dom'), | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -22,23 +22,6 @@ module.exports = { | |||||||
|         exclude: /node_modules/, |         exclude: /node_modules/, | ||||||
|         use: ['babel-loader', 'eslint-loader'], |         use: ['babel-loader', 'eslint-loader'], | ||||||
|       }, |       }, | ||||||
|       { |  | ||||||
|         test: /\.less$/, |  | ||||||
|         use: [ |  | ||||||
|           { |  | ||||||
|             loader: 'style-loader', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             loader: 'css-loader', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             loader: 'less-loader', |  | ||||||
|             options: { |  | ||||||
|               javascriptEnabled: true, |  | ||||||
|             }, |  | ||||||
|           }, |  | ||||||
|         ], |  | ||||||
|       }, |  | ||||||
|     ], |     ], | ||||||
|   }, |   }, | ||||||
|   plugins: [ |   plugins: [ | ||||||
| @@ -47,7 +30,7 @@ module.exports = { | |||||||
|       favicon: './app/images/favicon.ico', |       favicon: './app/images/favicon.ico', | ||||||
|     }), |     }), | ||||||
|     new webpack.DefinePlugin({ |     new webpack.DefinePlugin({ | ||||||
|       'process.env.GRAPHQL_URL': JSON.stringify(process.env.GRAPHQL_URL), |       'process.env.API': JSON.stringify(process.env.API || 'http://localhost:4000'), | ||||||
|     }), |     }), | ||||||
|   ], |   ], | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -34,6 +34,26 @@ module.exports = { | |||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             loader: 'sass-loader', |             loader: 'sass-loader', | ||||||
|  |             options: { | ||||||
|  |               sourceMap: true, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         test: /\.less$/, | ||||||
|  |         use: [ | ||||||
|  |           { | ||||||
|  |             loader: 'style-loader', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             loader: 'css-loader', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             loader: 'less-loader', // compiles Less to CSS | ||||||
|  |             options: { | ||||||
|  |               javascriptEnabled: true, | ||||||
|  |             }, | ||||||
|           }, |           }, | ||||||
|         ], |         ], | ||||||
|       }, |       }, | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ module.exports = { | |||||||
|     chunkFilename: `${commonPaths.jsFolder}/[name].[chunkhash].js`, |     chunkFilename: `${commonPaths.jsFolder}/[name].[chunkhash].js`, | ||||||
|   }, |   }, | ||||||
|   optimization: { |   optimization: { | ||||||
|  |     minimize: true, | ||||||
|     minimizer: [ |     minimizer: [ | ||||||
|       new TerserPlugin({ |       new TerserPlugin({ | ||||||
|         // Use multi-process parallel running to improve the build speed |         // Use multi-process parallel running to improve the build speed | ||||||
| @@ -67,6 +68,21 @@ module.exports = { | |||||||
|           'sass-loader', |           'sass-loader', | ||||||
|         ], |         ], | ||||||
|       }, |       }, | ||||||
|  |       { | ||||||
|  |         test: /\.less$/, | ||||||
|  |         use: [ | ||||||
|  |           MiniCssExtractPlugin.loader, | ||||||
|  |           { | ||||||
|  |             loader: 'css-loader', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             loader: 'less-loader', // compiles Less to CSS | ||||||
|  |             options: { | ||||||
|  |               javascriptEnabled: true, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|     ], |     ], | ||||||
|   }, |   }, | ||||||
|   resolve: { |   resolve: { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user