mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
				synced 2025-10-30 18:27:53 +00:00 
			
		
		
		
	Compare commits
	
		
			39 Commits
		
	
	
		
			v2.6.0-RC4
			...
			release/v2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 4b79a0b74c | ||
|   | c158f0aef8 | ||
|   | 4e5c6a9426 | ||
|   | 7ad184cb48 | ||
|   | 41a7d5d0a8 | ||
|   | 78c48e004c | ||
|   | 7106d61881 | ||
|   | 8ead4c4708 | ||
|   | 52ca7d3503 | ||
|   | 7d504da0a8 | ||
|   | c6dee2252b | ||
|   | 680c4a9ec4 | ||
|   | 3887d57fa4 | ||
|   | d733daed9d | ||
|   | de8651ab52 | ||
|   | 0ce641d10b | ||
|   | 316224b424 | ||
|   | cf9bbce284 | ||
|   | 6eae6c046e | ||
|   | 837a430228 | ||
|   | 71431f8fb5 | ||
|   | 0c7cd1f299 | ||
|   | 674682e919 | ||
|   | a5ca8115af | ||
|   | d4338fce42 | ||
|   | 14e8135f81 | ||
|   | e925f07505 | ||
|   | b792b51bd0 | ||
|   | fb64813b2a | ||
|   | b16e0e33ab | ||
|   | 818921e4a2 | ||
|   | 6c437459ca | ||
|   | b276901874 | ||
|   | 53a3de1ebc | ||
|   | 2d35747e75 | ||
|   | 71feebea6d | ||
|   | c8c75e7a70 | ||
|   | 7b2263e9a5 | ||
|   | 9cd216bbba | 
							
								
								
									
										8
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,4 +17,10 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - run: | |       - run: | | ||||||
|           export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-') |           export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-') | ||||||
|           curl -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owgw-ui/$PR_BRANCH_TAG" |  | ||||||
|  |           if [[ ! $PR_BRANCH_TAG =~ (main|master|release-*) ]]; then | ||||||
|  |             echo "PR branch is $PR_BRANCH_TAG, deleting Docker image" | ||||||
|  |             curl -s -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owgw-ui/$PR_BRANCH_TAG" | ||||||
|  |           else | ||||||
|  |             echo "PR branch is $PR_BRANCH_TAG, not deleting Docker image" | ||||||
|  |           fi | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| FROM node:14-alpine3.11 AS build | FROM node:18.7.0-alpine3.15 AS build | ||||||
|  |  | ||||||
| COPY package.json package-lock.json / | COPY package.json package-lock.json / | ||||||
|  |  | ||||||
| @@ -8,7 +8,7 @@ COPY . . | |||||||
|  |  | ||||||
| RUN npm run build | RUN npm run build | ||||||
|  |  | ||||||
| FROM nginx:1.20.1-alpine AS runtime | FROM nginx:1.22.0-alpine AS runtime | ||||||
|  |  | ||||||
| COPY --from=build /build/ /usr/share/nginx/html/ | COPY --from=build /build/ /usr/share/nginx/html/ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ fullnameOverride: "" | |||||||
| images: | images: | ||||||
|   owgwui: |   owgwui: | ||||||
|     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui |     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui | ||||||
|     tag: main |     tag: v2.7.0 | ||||||
|     pullPolicy: Always |     pullPolicy: Always | ||||||
|  |  | ||||||
| services: | services: | ||||||
|   | |||||||
							
								
								
									
										347
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										347
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-client", |   "name": "ucentral-client", | ||||||
|   "version": "2.6.29", |   "version": "2.7.0(8)", | ||||||
|   "lockfileVersion": 2, |   "lockfileVersion": 2, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "packages": { |   "packages": { | ||||||
|     "": { |     "": { | ||||||
|       "name": "ucentral-client", |       "name": "ucentral-client", | ||||||
|       "version": "2.6.29", |       "version": "2.7.0(8)", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@coreui/coreui": "^3.4.0", |         "@coreui/coreui": "^3.4.0", | ||||||
|         "@coreui/icons": "^2.0.1", |         "@coreui/icons": "^2.0.1", | ||||||
| @@ -2069,6 +2069,64 @@ | |||||||
|       "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", |       "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/@jridgewell/gen-mapping": { | ||||||
|  |       "version": "0.3.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", | ||||||
|  |       "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", | ||||||
|  |       "dev": true, | ||||||
|  |       "dependencies": { | ||||||
|  |         "@jridgewell/set-array": "^1.0.1", | ||||||
|  |         "@jridgewell/sourcemap-codec": "^1.4.10", | ||||||
|  |         "@jridgewell/trace-mapping": "^0.3.9" | ||||||
|  |       }, | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=6.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/@jridgewell/resolve-uri": { | ||||||
|  |       "version": "3.1.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", | ||||||
|  |       "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", | ||||||
|  |       "dev": true, | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=6.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/@jridgewell/set-array": { | ||||||
|  |       "version": "1.1.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", | ||||||
|  |       "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", | ||||||
|  |       "dev": true, | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=6.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/@jridgewell/source-map": { | ||||||
|  |       "version": "0.3.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", | ||||||
|  |       "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", | ||||||
|  |       "dev": true, | ||||||
|  |       "dependencies": { | ||||||
|  |         "@jridgewell/gen-mapping": "^0.3.0", | ||||||
|  |         "@jridgewell/trace-mapping": "^0.3.9" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/@jridgewell/sourcemap-codec": { | ||||||
|  |       "version": "1.4.14", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", | ||||||
|  |       "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "node_modules/@jridgewell/trace-mapping": { | ||||||
|  |       "version": "0.3.14", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", | ||||||
|  |       "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", | ||||||
|  |       "dev": true, | ||||||
|  |       "dependencies": { | ||||||
|  |         "@jridgewell/resolve-uri": "^3.0.3", | ||||||
|  |         "@jridgewell/sourcemap-codec": "^1.4.10" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/@nodelib/fs.scandir": { |     "node_modules/@nodelib/fs.scandir": { | ||||||
|       "version": "2.1.5", |       "version": "2.1.5", | ||||||
|       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", |       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", | ||||||
| @@ -7320,60 +7378,6 @@ | |||||||
|         "node": ">=12" |         "node": ">=12" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/html-minifier-terser/node_modules/acorn": { |  | ||||||
|       "version": "8.7.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", |  | ||||||
|       "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", |  | ||||||
|       "dev": true, |  | ||||||
|       "optional": true, |  | ||||||
|       "peer": true, |  | ||||||
|       "bin": { |  | ||||||
|         "acorn": "bin/acorn" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=0.4.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/html-minifier-terser/node_modules/source-map": { |  | ||||||
|       "version": "0.7.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", |  | ||||||
|       "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", |  | ||||||
|       "dev": true, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">= 8" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/html-minifier-terser/node_modules/terser": { |  | ||||||
|       "version": "5.10.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", |  | ||||||
|       "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", |  | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |  | ||||||
|         "commander": "^2.20.0", |  | ||||||
|         "source-map": "~0.7.2", |  | ||||||
|         "source-map-support": "~0.5.20" |  | ||||||
|       }, |  | ||||||
|       "bin": { |  | ||||||
|         "terser": "bin/terser" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=10" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "acorn": "^8.5.0" |  | ||||||
|       }, |  | ||||||
|       "peerDependenciesMeta": { |  | ||||||
|         "acorn": { |  | ||||||
|           "optional": true |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": { |  | ||||||
|       "version": "2.20.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", |  | ||||||
|       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", |  | ||||||
|       "dev": true |  | ||||||
|     }, |  | ||||||
|     "node_modules/html-parse-stringify": { |     "node_modules/html-parse-stringify": { | ||||||
|       "version": "3.0.1", |       "version": "3.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", |       "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", | ||||||
| @@ -9201,9 +9205,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/moment": { |     "node_modules/moment": { | ||||||
|       "version": "2.29.3", |       "version": "2.29.4", | ||||||
|       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", |       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", | ||||||
|       "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==", |       "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", | ||||||
|       "engines": { |       "engines": { | ||||||
|         "node": "*" |         "node": "*" | ||||||
|       } |       } | ||||||
| @@ -13073,6 +13077,24 @@ | |||||||
|         "node": ">=6" |         "node": ">=6" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/terser": { | ||||||
|  |       "version": "5.14.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", | ||||||
|  |       "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", | ||||||
|  |       "dev": true, | ||||||
|  |       "dependencies": { | ||||||
|  |         "@jridgewell/source-map": "^0.3.2", | ||||||
|  |         "acorn": "^8.5.0", | ||||||
|  |         "commander": "^2.20.0", | ||||||
|  |         "source-map-support": "~0.5.20" | ||||||
|  |       }, | ||||||
|  |       "bin": { | ||||||
|  |         "terser": "bin/terser" | ||||||
|  |       }, | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=10" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/terser-webpack-plugin": { |     "node_modules/terser-webpack-plugin": { | ||||||
|       "version": "5.3.0", |       "version": "5.3.0", | ||||||
|       "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz", |       "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz", | ||||||
| @@ -13107,26 +13129,6 @@ | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/terser-webpack-plugin/node_modules/acorn": { |  | ||||||
|       "version": "8.7.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", |  | ||||||
|       "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", |  | ||||||
|       "dev": true, |  | ||||||
|       "optional": true, |  | ||||||
|       "peer": true, |  | ||||||
|       "bin": { |  | ||||||
|         "acorn": "bin/acorn" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=0.4.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/terser-webpack-plugin/node_modules/commander": { |  | ||||||
|       "version": "2.20.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", |  | ||||||
|       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", |  | ||||||
|       "dev": true |  | ||||||
|     }, |  | ||||||
|     "node_modules/terser-webpack-plugin/node_modules/has-flag": { |     "node_modules/terser-webpack-plugin/node_modules/has-flag": { | ||||||
|       "version": "4.0.0", |       "version": "4.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", |       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", | ||||||
| @@ -13192,39 +13194,23 @@ | |||||||
|         "url": "https://github.com/chalk/supports-color?sponsor=1" |         "url": "https://github.com/chalk/supports-color?sponsor=1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/terser-webpack-plugin/node_modules/terser": { |     "node_modules/terser/node_modules/acorn": { | ||||||
|       "version": "5.10.0", |       "version": "8.8.0", | ||||||
|       "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", |       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", | ||||||
|       "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", |       "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "dependencies": { |  | ||||||
|         "commander": "^2.20.0", |  | ||||||
|         "source-map": "~0.7.2", |  | ||||||
|         "source-map-support": "~0.5.20" |  | ||||||
|       }, |  | ||||||
|       "bin": { |       "bin": { | ||||||
|         "terser": "bin/terser" |         "acorn": "bin/acorn" | ||||||
|       }, |       }, | ||||||
|       "engines": { |       "engines": { | ||||||
|         "node": ">=10" |         "node": ">=0.4.0" | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "acorn": "^8.5.0" |  | ||||||
|       }, |  | ||||||
|       "peerDependenciesMeta": { |  | ||||||
|         "acorn": { |  | ||||||
|           "optional": true |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/terser-webpack-plugin/node_modules/terser/node_modules/source-map": { |     "node_modules/terser/node_modules/commander": { | ||||||
|       "version": "0.7.3", |       "version": "2.20.3", | ||||||
|       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", |       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", | ||||||
|       "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", |       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", | ||||||
|       "dev": true, |       "dev": true | ||||||
|       "engines": { |  | ||||||
|         "node": ">= 8" |  | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
|     "node_modules/text-table": { |     "node_modules/text-table": { | ||||||
|       "version": "0.2.0", |       "version": "0.2.0", | ||||||
| @@ -16414,6 +16400,55 @@ | |||||||
|       "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", |       "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "@jridgewell/gen-mapping": { | ||||||
|  |       "version": "0.3.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", | ||||||
|  |       "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "@jridgewell/set-array": "^1.0.1", | ||||||
|  |         "@jridgewell/sourcemap-codec": "^1.4.10", | ||||||
|  |         "@jridgewell/trace-mapping": "^0.3.9" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "@jridgewell/resolve-uri": { | ||||||
|  |       "version": "3.1.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", | ||||||
|  |       "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "@jridgewell/set-array": { | ||||||
|  |       "version": "1.1.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", | ||||||
|  |       "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "@jridgewell/source-map": { | ||||||
|  |       "version": "0.3.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", | ||||||
|  |       "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "@jridgewell/gen-mapping": "^0.3.0", | ||||||
|  |         "@jridgewell/trace-mapping": "^0.3.9" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "@jridgewell/sourcemap-codec": { | ||||||
|  |       "version": "1.4.14", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", | ||||||
|  |       "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "@jridgewell/trace-mapping": { | ||||||
|  |       "version": "0.3.14", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", | ||||||
|  |       "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "@jridgewell/resolve-uri": "^3.0.3", | ||||||
|  |         "@jridgewell/sourcemap-codec": "^1.4.10" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "@nodelib/fs.scandir": { |     "@nodelib/fs.scandir": { | ||||||
|       "version": "2.1.5", |       "version": "2.1.5", | ||||||
|       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", |       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", | ||||||
| @@ -20424,41 +20459,6 @@ | |||||||
|         "param-case": "^3.0.4", |         "param-case": "^3.0.4", | ||||||
|         "relateurl": "^0.2.7", |         "relateurl": "^0.2.7", | ||||||
|         "terser": "^5.10.0" |         "terser": "^5.10.0" | ||||||
|       }, |  | ||||||
|       "dependencies": { |  | ||||||
|         "acorn": { |  | ||||||
|           "version": "8.7.0", |  | ||||||
|           "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", |  | ||||||
|           "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", |  | ||||||
|           "dev": true, |  | ||||||
|           "optional": true, |  | ||||||
|           "peer": true |  | ||||||
|         }, |  | ||||||
|         "source-map": { |  | ||||||
|           "version": "0.7.3", |  | ||||||
|           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", |  | ||||||
|           "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", |  | ||||||
|           "dev": true |  | ||||||
|         }, |  | ||||||
|         "terser": { |  | ||||||
|           "version": "5.10.0", |  | ||||||
|           "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", |  | ||||||
|           "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", |  | ||||||
|           "dev": true, |  | ||||||
|           "requires": { |  | ||||||
|             "commander": "^2.20.0", |  | ||||||
|             "source-map": "~0.7.2", |  | ||||||
|             "source-map-support": "~0.5.20" |  | ||||||
|           }, |  | ||||||
|           "dependencies": { |  | ||||||
|             "commander": { |  | ||||||
|               "version": "2.20.3", |  | ||||||
|               "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", |  | ||||||
|               "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", |  | ||||||
|               "dev": true |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "html-parse-stringify": { |     "html-parse-stringify": { | ||||||
| @@ -21815,9 +21815,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "moment": { |     "moment": { | ||||||
|       "version": "2.29.3", |       "version": "2.29.4", | ||||||
|       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", |       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", | ||||||
|       "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==" |       "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" | ||||||
|     }, |     }, | ||||||
|     "mrmime": { |     "mrmime": { | ||||||
|       "version": "1.0.0", |       "version": "1.0.0", | ||||||
| @@ -24722,6 +24722,32 @@ | |||||||
|       "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", |       "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "terser": { | ||||||
|  |       "version": "5.14.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", | ||||||
|  |       "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "@jridgewell/source-map": "^0.3.2", | ||||||
|  |         "acorn": "^8.5.0", | ||||||
|  |         "commander": "^2.20.0", | ||||||
|  |         "source-map-support": "~0.5.20" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "acorn": { | ||||||
|  |           "version": "8.8.0", | ||||||
|  |           "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", | ||||||
|  |           "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", | ||||||
|  |           "dev": true | ||||||
|  |         }, | ||||||
|  |         "commander": { | ||||||
|  |           "version": "2.20.3", | ||||||
|  |           "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", | ||||||
|  |           "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", | ||||||
|  |           "dev": true | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "terser-webpack-plugin": { |     "terser-webpack-plugin": { | ||||||
|       "version": "5.3.0", |       "version": "5.3.0", | ||||||
|       "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz", |       "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz", | ||||||
| @@ -24735,20 +24761,6 @@ | |||||||
|         "terser": "^5.7.2" |         "terser": "^5.7.2" | ||||||
|       }, |       }, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "acorn": { |  | ||||||
|           "version": "8.7.0", |  | ||||||
|           "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", |  | ||||||
|           "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", |  | ||||||
|           "dev": true, |  | ||||||
|           "optional": true, |  | ||||||
|           "peer": true |  | ||||||
|         }, |  | ||||||
|         "commander": { |  | ||||||
|           "version": "2.20.3", |  | ||||||
|           "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", |  | ||||||
|           "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", |  | ||||||
|           "dev": true |  | ||||||
|         }, |  | ||||||
|         "has-flag": { |         "has-flag": { | ||||||
|           "version": "4.0.0", |           "version": "4.0.0", | ||||||
|           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", |           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", | ||||||
| @@ -24791,25 +24803,6 @@ | |||||||
|           "requires": { |           "requires": { | ||||||
|             "has-flag": "^4.0.0" |             "has-flag": "^4.0.0" | ||||||
|           } |           } | ||||||
|         }, |  | ||||||
|         "terser": { |  | ||||||
|           "version": "5.10.0", |  | ||||||
|           "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", |  | ||||||
|           "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", |  | ||||||
|           "dev": true, |  | ||||||
|           "requires": { |  | ||||||
|             "commander": "^2.20.0", |  | ||||||
|             "source-map": "~0.7.2", |  | ||||||
|             "source-map-support": "~0.5.20" |  | ||||||
|           }, |  | ||||||
|           "dependencies": { |  | ||||||
|             "source-map": { |  | ||||||
|               "version": "0.7.3", |  | ||||||
|               "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", |  | ||||||
|               "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", |  | ||||||
|               "dev": true |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-client", |   "name": "ucentral-client", | ||||||
|   "version": "2.6.29", |   "version": "2.7.0(8)", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@coreui/coreui": "^3.4.0", |     "@coreui/coreui": "^3.4.0", | ||||||
|     "@coreui/icons": "^2.0.1", |     "@coreui/icons": "^2.0.1", | ||||||
|   | |||||||
| @@ -326,6 +326,7 @@ | |||||||
| 	"device": { | 	"device": { | ||||||
| 		"add_to_blacklist": "Gerät zur Blacklist hinzufügen", | 		"add_to_blacklist": "Gerät zur Blacklist hinzufügen", | ||||||
| 		"all_devices": "Alle Geräte", | 		"all_devices": "Alle Geräte", | ||||||
|  | 		"already_running_command": "Gerät führt bereits einen Befehl aus, bitte versuchen Sie es später erneut", | ||||||
| 		"blacklisted_on": "Datum", | 		"blacklisted_on": "Datum", | ||||||
| 		"capabilities": "Fähigkeiten", | 		"capabilities": "Fähigkeiten", | ||||||
| 		"certificate_explanation": "Zertifikate der angeschlossenen Geräte", | 		"certificate_explanation": "Zertifikate der angeschlossenen Geräte", | ||||||
| @@ -337,6 +338,7 @@ | |||||||
| 		"error_fetching_devices": "Fehler beim Abrufen von Geräten: {{error}}", | 		"error_fetching_devices": "Fehler beim Abrufen von Geräten: {{error}}", | ||||||
| 		"firmware_count_explanation": "Dies ist die Gesamtzahl der Geräte, die diesem Firmware-Server hinzugefügt wurden, einschließlich der Geräte, die derzeit nicht auf den zugehörigen Gateway-Server verweisen.", | 		"firmware_count_explanation": "Dies ist die Gesamtzahl der Geräte, die diesem Firmware-Server hinzugefügt wurden, einschließlich der Geräte, die derzeit nicht auf den zugehörigen Gateway-Server verweisen.", | ||||||
| 		"health_explanation": "Zustand der verbundenen Geräte ((Geräte = 100 % * 100 + Geräte > 90 % * 95 + Geräte > 60 % * 75 + Geräte < 60 % * 35) / Verbundene Geräte)", | 		"health_explanation": "Zustand der verbundenen Geräte ((Geräte = 100 % * 100 + Geräte > 90 % * 95 + Geräte > 60 % * 75 + Geräte < 60 % * 35) / Verbundene Geräte)", | ||||||
|  | 		"mac_not_found": "Seriennummer nicht gefunden, Sie werden zur Seite „Geräte“ weitergeleitet", | ||||||
| 		"memory_explanation": "Anzahl verbundener Geräte mit entsprechendem belegtem Speicher %", | 		"memory_explanation": "Anzahl verbundener Geräte mit entsprechendem belegtem Speicher %", | ||||||
| 		"remove_from_blacklist": "Von der schwarzen Liste entfernen", | 		"remove_from_blacklist": "Von der schwarzen Liste entfernen", | ||||||
| 		"success_added_blacklist": "Gerät erfolgreich zur Blacklist hinzugefügt!", | 		"success_added_blacklist": "Gerät erfolgreich zur Blacklist hinzugefügt!", | ||||||
|   | |||||||
| @@ -326,6 +326,7 @@ | |||||||
| 	"device": { | 	"device": { | ||||||
| 		"add_to_blacklist": "Add Device To Blacklist", | 		"add_to_blacklist": "Add Device To Blacklist", | ||||||
| 		"all_devices": "All Devices", | 		"all_devices": "All Devices", | ||||||
|  | 		"already_running_command": "Device is already executing a command, please try later", | ||||||
| 		"blacklisted_on": "Date", | 		"blacklisted_on": "Date", | ||||||
| 		"capabilities": "Capabilities", | 		"capabilities": "Capabilities", | ||||||
| 		"certificate_explanation": "Certificates of connected devices", | 		"certificate_explanation": "Certificates of connected devices", | ||||||
| @@ -337,6 +338,7 @@ | |||||||
| 		"error_fetching_devices": "Error while fetching devices: {{error}}", | 		"error_fetching_devices": "Error while fetching devices: {{error}}", | ||||||
| 		"firmware_count_explanation": "This is the total amount of devices that were added to this firmware server, including devices not currently pointing at the related gateway server.", | 		"firmware_count_explanation": "This is the total amount of devices that were added to this firmware server, including devices not currently pointing at the related gateway server.", | ||||||
| 		"health_explanation": "Health of connected devices ((Devices=100% * 100 + Devices>90% * 95 + Devices>60% * 75 + Devices<60% * 35) / ConnectedDevices)", | 		"health_explanation": "Health of connected devices ((Devices=100% * 100 + Devices>90% * 95 + Devices>60% * 75 + Devices<60% * 35) / ConnectedDevices)", | ||||||
|  | 		"mac_not_found": "Serial number not found, redirecting you to the Devices page", | ||||||
| 		"memory_explanation": "Amount of connected devices with corresponding memory used percentage", | 		"memory_explanation": "Amount of connected devices with corresponding memory used percentage", | ||||||
| 		"remove_from_blacklist": "Remove from blacklist", | 		"remove_from_blacklist": "Remove from blacklist", | ||||||
| 		"success_added_blacklist": "Device successfully added to blacklist!", | 		"success_added_blacklist": "Device successfully added to blacklist!", | ||||||
|   | |||||||
| @@ -326,6 +326,7 @@ | |||||||
| 	"device": { | 	"device": { | ||||||
| 		"add_to_blacklist": "Agregar dispositivo a la lista negra", | 		"add_to_blacklist": "Agregar dispositivo a la lista negra", | ||||||
| 		"all_devices": "Todos los dispositivos", | 		"all_devices": "Todos los dispositivos", | ||||||
|  | 		"already_running_command": "El dispositivo ya está ejecutando un comando, intente más tarde", | ||||||
| 		"blacklisted_on": "Fecha", | 		"blacklisted_on": "Fecha", | ||||||
| 		"capabilities": "capacidades", | 		"capabilities": "capacidades", | ||||||
| 		"certificate_explanation": "Certificados de dispositivos conectados", | 		"certificate_explanation": "Certificados de dispositivos conectados", | ||||||
| @@ -337,6 +338,7 @@ | |||||||
| 		"error_fetching_devices": "Error al recuperar dispositivos: {{error}}", | 		"error_fetching_devices": "Error al recuperar dispositivos: {{error}}", | ||||||
| 		"firmware_count_explanation": "Esta es la cantidad total de dispositivos que se agregaron a este servidor de firmware, incluidos los dispositivos que actualmente no apuntan al servidor de puerta de enlace relacionado.", | 		"firmware_count_explanation": "Esta es la cantidad total de dispositivos que se agregaron a este servidor de firmware, incluidos los dispositivos que actualmente no apuntan al servidor de puerta de enlace relacionado.", | ||||||
| 		"health_explanation": "Estado de los dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos conectados)", | 		"health_explanation": "Estado de los dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos conectados)", | ||||||
|  | 		"mac_not_found": "Número de serie no encontrado, lo redirige a la página Dispositivos", | ||||||
| 		"memory_explanation": "Cantidad de dispositivos conectados con la memoria correspondiente utilizada%", | 		"memory_explanation": "Cantidad de dispositivos conectados con la memoria correspondiente utilizada%", | ||||||
| 		"remove_from_blacklist": "ELIMINAR DE LA LISTA NEGRA", | 		"remove_from_blacklist": "ELIMINAR DE LA LISTA NEGRA", | ||||||
| 		"success_added_blacklist": "¡Dispositivo agregado exitosamente a la lista negra!", | 		"success_added_blacklist": "¡Dispositivo agregado exitosamente a la lista negra!", | ||||||
|   | |||||||
| @@ -326,6 +326,7 @@ | |||||||
| 	"device": { | 	"device": { | ||||||
| 		"add_to_blacklist": "Ajouter un appareil à la liste noire", | 		"add_to_blacklist": "Ajouter un appareil à la liste noire", | ||||||
| 		"all_devices": "Tous les dispositifs", | 		"all_devices": "Tous les dispositifs", | ||||||
|  | 		"already_running_command": "L'appareil exécute déjà une commande, veuillez réessayer plus tard", | ||||||
| 		"blacklisted_on": "Rendez-vous amoureux", | 		"blacklisted_on": "Rendez-vous amoureux", | ||||||
| 		"capabilities": "Capacités", | 		"capabilities": "Capacités", | ||||||
| 		"certificate_explanation": "Certificats des appareils connectés", | 		"certificate_explanation": "Certificats des appareils connectés", | ||||||
| @@ -337,6 +338,7 @@ | |||||||
| 		"error_fetching_devices": "Erreur lors de la récupération des appareils : {{error}}", | 		"error_fetching_devices": "Erreur lors de la récupération des appareils : {{error}}", | ||||||
| 		"firmware_count_explanation": "Il s'agit du nombre total d'appareils qui ont été ajoutés à ce serveur de micrologiciel, y compris les appareils qui ne pointent pas actuellement vers le serveur de passerelle associé.", | 		"firmware_count_explanation": "Il s'agit du nombre total d'appareils qui ont été ajoutés à ce serveur de micrologiciel, y compris les appareils qui ne pointent pas actuellement vers le serveur de passerelle associé.", | ||||||
| 		"health_explanation": "Santé des appareils connectés ((Appareils = 100 % * 100 + Appareils> 90 % * 95 + Appareils> 60 % * 75 + Appareils < 60 % * 35) / Appareils connectés)", | 		"health_explanation": "Santé des appareils connectés ((Appareils = 100 % * 100 + Appareils> 90 % * 95 + Appareils> 60 % * 75 + Appareils < 60 % * 35) / Appareils connectés)", | ||||||
|  | 		"mac_not_found": "Numéro de série introuvable, vous redirigeant vers la page Appareils", | ||||||
| 		"memory_explanation": "Nombre d'appareils connectés avec la mémoire correspondante utilisée %", | 		"memory_explanation": "Nombre d'appareils connectés avec la mémoire correspondante utilisée %", | ||||||
| 		"remove_from_blacklist": "Supprimer de la liste noire", | 		"remove_from_blacklist": "Supprimer de la liste noire", | ||||||
| 		"success_added_blacklist": "Appareil ajouté avec succès à la liste noire !", | 		"success_added_blacklist": "Appareil ajouté avec succès à la liste noire !", | ||||||
|   | |||||||
| @@ -326,6 +326,7 @@ | |||||||
| 	"device": { | 	"device": { | ||||||
| 		"add_to_blacklist": "Adicionar dispositivo à lista negra", | 		"add_to_blacklist": "Adicionar dispositivo à lista negra", | ||||||
| 		"all_devices": "Todos os dispositivos", | 		"all_devices": "Todos os dispositivos", | ||||||
|  | 		"already_running_command": "O dispositivo já está executando um comando, tente mais tarde", | ||||||
| 		"blacklisted_on": "Encontro", | 		"blacklisted_on": "Encontro", | ||||||
| 		"capabilities": "Recursos", | 		"capabilities": "Recursos", | ||||||
| 		"certificate_explanation": "Certificados de dispositivos conectados", | 		"certificate_explanation": "Certificados de dispositivos conectados", | ||||||
| @@ -337,6 +338,7 @@ | |||||||
| 		"error_fetching_devices": "Erro ao buscar dispositivos: {{error}}", | 		"error_fetching_devices": "Erro ao buscar dispositivos: {{error}}", | ||||||
| 		"firmware_count_explanation": "Esta é a quantidade total de dispositivos que foram adicionados a este servidor de firmware, incluindo dispositivos que não estão apontando para o servidor de gateway relacionado.", | 		"firmware_count_explanation": "Esta é a quantidade total de dispositivos que foram adicionados a este servidor de firmware, incluindo dispositivos que não estão apontando para o servidor de gateway relacionado.", | ||||||
| 		"health_explanation": "Integridade dos dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos Conectados)", | 		"health_explanation": "Integridade dos dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos Conectados)", | ||||||
|  | 		"mac_not_found": "Número de série não encontrado, redirecionando você para a página Dispositivos", | ||||||
| 		"memory_explanation": "Quantidade de dispositivos conectados com a memória correspondente usada%", | 		"memory_explanation": "Quantidade de dispositivos conectados com a memória correspondente usada%", | ||||||
| 		"remove_from_blacklist": "Remover da lista negra", | 		"remove_from_blacklist": "Remover da lista negra", | ||||||
| 		"success_added_blacklist": "Dispositivo adicionado à lista negra com sucesso!", | 		"success_added_blacklist": "Dispositivo adicionado à lista negra com sucesso!", | ||||||
|   | |||||||
| @@ -72,7 +72,18 @@ const BlinkModal = ({ show, toggleModal }) => { | |||||||
|         } |         } | ||||||
|         toggleModal(); |         toggleModal(); | ||||||
|       }) |       }) | ||||||
|       .catch(() => { |       .catch((e) => { | ||||||
|  |         if (e.response?.data?.ErrorDescription !== undefined) { | ||||||
|  |           const split = e.response?.data?.ErrorDescription.split(':'); | ||||||
|  |           if (split !== undefined && split.length >= 2) { | ||||||
|  |             addToast({ | ||||||
|  |               title: t('common.error'), | ||||||
|  |               body: split[1], | ||||||
|  |               color: 'danger', | ||||||
|  |               autohide: true, | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|         setResult('error'); |         setResult('error'); | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
|   | |||||||
| @@ -205,10 +205,11 @@ const DeviceCommands = () => { | |||||||
|  |  | ||||||
|   const columns = [ |   const columns = [ | ||||||
|     { key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } }, |     { key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } }, | ||||||
|     { key: 'command', label: t('common.command'), _style: { width: '15%' } }, |     { key: 'command', label: t('common.command'), _style: { width: '0%' } }, | ||||||
|  |     { key: 'status', label: t('common.status'), _style: { width: '0%' } }, | ||||||
|     { key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } }, |     { key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } }, | ||||||
|     { key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } }, |     { key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } }, | ||||||
|     { key: 'errorCode', label: t('common.error_code'), filter: false, _style: { width: '8%' } }, |     { key: 'errorCode', label: t('common.error_code'), filter: false }, | ||||||
|     { |     { | ||||||
|       key: 'show_buttons', |       key: 'show_buttons', | ||||||
|       label: '', |       label: '', | ||||||
| @@ -317,16 +318,17 @@ const DeviceCommands = () => { | |||||||
|                     {item.completed && item.completed !== 0 ? ( |                     {item.completed && item.completed !== 0 ? ( | ||||||
|                       <FormattedDate date={item.completed} /> |                       <FormattedDate date={item.completed} /> | ||||||
|                     ) : ( |                     ) : ( | ||||||
|                       'Pending' |                       '-' | ||||||
|                     )} |                     )} | ||||||
|                   </td> |                   </td> | ||||||
|                 ), |                 ), | ||||||
|  |                 status: (item) => <td className="align-middle">{item.status}</td>, | ||||||
|                 executed: (item) => ( |                 executed: (item) => ( | ||||||
|                   <td className="align-middle"> |                   <td className="align-middle"> | ||||||
|                     {item.executed && item.executed !== 0 ? ( |                     {item.executed && item.executed !== 0 ? ( | ||||||
|                       <FormattedDate date={item.executed} /> |                       <FormattedDate date={item.executed} /> | ||||||
|                     ) : ( |                     ) : ( | ||||||
|                       'Pending' |                       '-' | ||||||
|                     )} |                     )} | ||||||
|                   </td> |                   </td> | ||||||
|                 ), |                 ), | ||||||
| @@ -335,7 +337,7 @@ const DeviceCommands = () => { | |||||||
|                     {item.submitted && item.submitted !== '' ? ( |                     {item.submitted && item.submitted !== '' ? ( | ||||||
|                       <FormattedDate date={item.submitted} /> |                       <FormattedDate date={item.submitted} /> | ||||||
|                     ) : ( |                     ) : ( | ||||||
|                       'Pending' |                       '-' | ||||||
|                     )} |                     )} | ||||||
|                   </td> |                   </td> | ||||||
|                 ), |                 ), | ||||||
|   | |||||||
| @@ -103,12 +103,17 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|       }) |       }) | ||||||
|       .catch((e) => { |       .catch((e) => { | ||||||
|         setResponseBody('Error while submitting command!'); |         setResponseBody('Error while submitting command!'); | ||||||
|         addToast({ |         if (e.response?.data?.ErrorDescription !== undefined) { | ||||||
|           title: t('common.error'), |           const split = e.response?.data?.ErrorDescription.split(':'); | ||||||
|           body: `${t('common.general_error')}: ${e.response?.data?.ErrorDescription}`, |           if (split !== undefined && split.length >= 2) { | ||||||
|           color: 'danger', |             addToast({ | ||||||
|           autohide: true, |               title: t('common.error'), | ||||||
|         }); |               body: split[1], | ||||||
|  |               color: 'danger', | ||||||
|  |               autohide: true, | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|         setHadFailure(true); |         setHadFailure(true); | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
|   | |||||||
| @@ -54,12 +54,17 @@ const DeviceActions = ({ device }) => { | |||||||
|         if (newWindow) newWindow.opener = null; |         if (newWindow) newWindow.opener = null; | ||||||
|       }) |       }) | ||||||
|       .catch((e) => { |       .catch((e) => { | ||||||
|         addToast({ |         if (e.response?.data?.ErrorDescription !== undefined) { | ||||||
|           title: t('common.error'), |           const split = e.response?.data?.ErrorDescription.split(':'); | ||||||
|           body: t('connect.error_trying_to_connect', { error: e.response?.data?.ErrorDescription }), |           if (split !== undefined && split.length >= 2) { | ||||||
|           color: 'danger', |             addToast({ | ||||||
|           autohide: true, |               title: t('common.error'), | ||||||
|         }); |               body: split[1], | ||||||
|  |               color: 'danger', | ||||||
|  |               autohide: true, | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
|         setConnectLoading(false); |         setConnectLoading(false); | ||||||
| @@ -68,18 +73,20 @@ const DeviceActions = ({ device }) => { | |||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (upgradeStatus.result !== undefined) { |     if (upgradeStatus.result !== undefined) { | ||||||
|       addToast({ |       if (upgradeStatus.result.success) { | ||||||
|         title: upgradeStatus.result.success ? t('common.success') : t('common.error'), |         addToast({ | ||||||
|         body: upgradeStatus.result.success |           title: upgradeStatus.result.success ? t('common.success') : t('common.error'), | ||||||
|           ? t('firmware.upgrade_command_submitted') |           body: upgradeStatus.result.success | ||||||
|           : upgradeStatus.result.error, |             ? t('firmware.upgrade_command_submitted') | ||||||
|         color: upgradeStatus.result.success ? 'success' : 'danger', |             : upgradeStatus.result.error, | ||||||
|         autohide: true, |           color: upgradeStatus.result.success ? 'success' : 'danger', | ||||||
|       }); |           autohide: true, | ||||||
|  |         }); | ||||||
|  |         setShowUpgradeModal(false); | ||||||
|  |       } | ||||||
|       setUpgradeStatus({ |       setUpgradeStatus({ | ||||||
|         loading: false, |         loading: false, | ||||||
|       }); |       }); | ||||||
|       setShowUpgradeModal(false); |  | ||||||
|     } |     } | ||||||
|   }, [upgradeStatus]); |   }, [upgradeStatus]); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -111,7 +111,18 @@ const DeviceFirmwareModal = ({ | |||||||
|           }, |           }, | ||||||
|         }); |         }); | ||||||
|       }) |       }) | ||||||
|       .catch(() => { |       .catch((e) => { | ||||||
|  |         if (e.response?.data?.ErrorDescription !== undefined) { | ||||||
|  |           const split = e.response?.data?.ErrorDescription.split(':'); | ||||||
|  |           if (split !== undefined && split.length >= 2) { | ||||||
|  |             addToast({ | ||||||
|  |               title: t('common.error'), | ||||||
|  |               body: split[1], | ||||||
|  |               color: 'danger', | ||||||
|  |               autohide: true, | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|         setUpgradeStatus({ |         setUpgradeStatus({ | ||||||
|           loading: false, |           loading: false, | ||||||
|           result: { |           result: { | ||||||
|   | |||||||
							
								
								
									
										75
									
								
								src/components/DeviceSearchBar/Input.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/components/DeviceSearchBar/Input.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import Select, { components } from 'react-select'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  |  | ||||||
|  | const DeviceSearchBarInput = ({ search, results, history, action, isDisabled }) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const [selected, setSelected] = useState(''); | ||||||
|  |   const NoOptionsMessage = (props) => ( | ||||||
|  |     <components.NoOptionsMessage {...props}> | ||||||
|  |       <span>{t('common.no_devices_found')}</span> | ||||||
|  |     </components.NoOptionsMessage> | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const onInputChange = (value) => { | ||||||
|  |     if (value === '' || value.match('^[a-fA-F0-9-*]+$')) { | ||||||
|  |       setSelected(value); | ||||||
|  |       search(value); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <Select | ||||||
|  |       components={{ NoOptionsMessage }} | ||||||
|  |       options={results.map((serial) => ({ label: serial, value: serial }))} | ||||||
|  |       filterOption={() => true} | ||||||
|  |       inputValue={selected} | ||||||
|  |       placeholder={t('common.search')} | ||||||
|  |       isDisabled={isDisabled} | ||||||
|  |       styles={{ | ||||||
|  |         placeholder: (provided) => ({ | ||||||
|  |           ...provided, | ||||||
|  |           // disable placeholder mouse events | ||||||
|  |           pointerEvents: 'none', | ||||||
|  |           userSelect: 'none', | ||||||
|  |           MozUserSelect: 'none', | ||||||
|  |           WebkitUserSelect: 'none', | ||||||
|  |           msUserSelect: 'none', | ||||||
|  |         }), | ||||||
|  |         input: (css) => ({ | ||||||
|  |           ...css, | ||||||
|  |           /* expand the Input Component div */ | ||||||
|  |           flex: '1 1 auto', | ||||||
|  |           /* expand the Input Component child div */ | ||||||
|  |           '> div': { | ||||||
|  |             width: '100%', | ||||||
|  |           }, | ||||||
|  |           /* expand the Input Component input */ | ||||||
|  |           input: { | ||||||
|  |             width: '100% !important', | ||||||
|  |             textAlign: 'left', | ||||||
|  |           }, | ||||||
|  |         }), | ||||||
|  |       }} | ||||||
|  |       onInputChange={onInputChange} | ||||||
|  |       onChange={(property) => | ||||||
|  |         action === null ? history.push(`/devices/${property.value}`) : action(property.value) | ||||||
|  |       } | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | DeviceSearchBarInput.propTypes = { | ||||||
|  |   search: PropTypes.func.isRequired, | ||||||
|  |   results: PropTypes.instanceOf(Array).isRequired, | ||||||
|  |   history: PropTypes.instanceOf(Object).isRequired, | ||||||
|  |   isDisabled: PropTypes.bool.isRequired, | ||||||
|  |   action: PropTypes.func, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | DeviceSearchBarInput.defaultProps = { | ||||||
|  |   action: null, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default DeviceSearchBarInput; | ||||||
| @@ -1,12 +1,11 @@ | |||||||
| import React, { useEffect, useState } from 'react'; | import React, { useEffect, useState } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useTranslation } from 'react-i18next'; |  | ||||||
| import { useHistory } from 'react-router-dom'; | import { useHistory } from 'react-router-dom'; | ||||||
| import { useAuth, DeviceSearchBar as SearchBar } from 'ucentral-libs'; | import { useAuth } from 'ucentral-libs'; | ||||||
| import { toJson } from 'utils/helper'; | import { toJson } from 'utils/helper'; | ||||||
|  | import DeviceSearchBarInput from './Input'; | ||||||
|  |  | ||||||
| const DeviceSearchBar = ({ action }) => { | const DeviceSearchBar = ({ action }) => { | ||||||
|   const { t } = useTranslation(); |  | ||||||
|   const history = useHistory(); |   const history = useHistory(); | ||||||
|   const { currentToken, endpoints } = useAuth(); |   const { currentToken, endpoints } = useAuth(); | ||||||
|   const [socket, setSocket] = useState(null); |   const [socket, setSocket] = useState(null); | ||||||
| @@ -14,20 +13,22 @@ const DeviceSearchBar = ({ action }) => { | |||||||
|   const [waitingSearch, setWaitingSearch] = useState(''); |   const [waitingSearch, setWaitingSearch] = useState(''); | ||||||
|  |  | ||||||
|   const search = (value) => { |   const search = (value) => { | ||||||
|     if (socket.readyState === WebSocket.OPEN) { |     if (socket) { | ||||||
|       if (value.length > 1 && value.match('^[a-fA-F0-9-*]+$')) { |       if (socket.readyState === WebSocket.OPEN) { | ||||||
|         setWaitingSearch(''); |         if (value.length > 1 && value.match('^[a-fA-F0-9-*]+$')) { | ||||||
|         socket.send( |           setWaitingSearch(''); | ||||||
|           JSON.stringify({ command: 'serial_number_search', serial_prefix: value.toLowerCase() }), |           socket.send( | ||||||
|         ); |             JSON.stringify({ command: 'serial_number_search', serial_prefix: value.toLowerCase() }), | ||||||
|  |           ); | ||||||
|  |         } else { | ||||||
|  |           setResults([]); | ||||||
|  |         } | ||||||
|  |       } else if (socket.readyState !== WebSocket.CONNECTING && endpoints?.owgw !== undefined) { | ||||||
|  |         setWaitingSearch(value); | ||||||
|  |         setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`)); | ||||||
|       } else { |       } else { | ||||||
|         setResults([]); |         setWaitingSearch(value); | ||||||
|       } |       } | ||||||
|     } else if (socket.readyState !== WebSocket.CONNECTING) { |  | ||||||
|       setWaitingSearch(value); |  | ||||||
|       setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`)); |  | ||||||
|     } else { |  | ||||||
|       setWaitingSearch(value); |  | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
| @@ -59,12 +60,20 @@ const DeviceSearchBar = ({ action }) => { | |||||||
|   }, [socket]); |   }, [socket]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (socket === null && endpoints?.owgw) { |     if (socket === null && endpoints?.owgw !== undefined) { | ||||||
|       setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`)); |       setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`)); | ||||||
|     } |     } | ||||||
|   }, []); |   }, []); | ||||||
|  |  | ||||||
|   return <SearchBar t={t} search={search} results={results} history={history} action={action} />; |   return ( | ||||||
|  |     <DeviceSearchBarInput | ||||||
|  |       search={search} | ||||||
|  |       results={results} | ||||||
|  |       history={history} | ||||||
|  |       action={action} | ||||||
|  |       isDisabled={endpoints.owgw === undefined} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| DeviceSearchBar.propTypes = { | DeviceSearchBar.propTypes = { | ||||||
|   | |||||||
| @@ -34,12 +34,17 @@ const EventQueueModal = ({ show, toggle }) => { | |||||||
|         setResult(response.data); |         setResult(response.data); | ||||||
|       }) |       }) | ||||||
|       .catch((e) => { |       .catch((e) => { | ||||||
|         addToast({ |         if (e.response?.data?.ErrorDescription !== undefined) { | ||||||
|           title: t('common.error'), |           const split = e.response?.data?.ErrorDescription.split(':'); | ||||||
|           body: t('commands.unable_queue', { error: e.response?.data?.ErrorDescription }), |           if (split !== undefined && split.length >= 2) { | ||||||
|           color: 'danger', |             addToast({ | ||||||
|           autohide: true, |               title: t('common.error'), | ||||||
|         }); |               body: split[1], | ||||||
|  |               color: 'danger', | ||||||
|  |               autohide: true, | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
|         setLoading(false); |         setLoading(false); | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ import React, { useState, useEffect } from 'react'; | |||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import 'react-widgets/styles.css'; | import 'react-widgets/styles.css'; | ||||||
| import { useAuth, useDevice } from 'ucentral-libs'; | import { useAuth, useDevice, useToast } from 'ucentral-libs'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
| import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | ||||||
|  |  | ||||||
| @@ -26,6 +26,7 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const { currentToken, endpoints } = useAuth(); |   const { currentToken, endpoints } = useAuth(); | ||||||
|   const { deviceSerialNumber } = useDevice(); |   const { deviceSerialNumber } = useDevice(); | ||||||
|  |   const { addToast } = useToast(); | ||||||
|   const [hadSuccess, setHadSuccess] = useState(false); |   const [hadSuccess, setHadSuccess] = useState(false); | ||||||
|   const [hadFailure, setHadFailure] = useState(false); |   const [hadFailure, setHadFailure] = useState(false); | ||||||
|   const [doingNow, setDoingNow] = useState(false); |   const [doingNow, setDoingNow] = useState(false); | ||||||
| @@ -74,7 +75,18 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|       .then(() => { |       .then(() => { | ||||||
|         setHadSuccess(true); |         setHadSuccess(true); | ||||||
|       }) |       }) | ||||||
|       .catch(() => { |       .catch((e) => { | ||||||
|  |         if (e.response?.data?.ErrorDescription !== undefined) { | ||||||
|  |           const split = e.response?.data?.ErrorDescription.split(':'); | ||||||
|  |           if (split !== undefined && split.length >= 2) { | ||||||
|  |             addToast({ | ||||||
|  |               title: t('common.error'), | ||||||
|  |               body: split[1], | ||||||
|  |               color: 'danger', | ||||||
|  |               autohide: true, | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|         setResponseBody(t('commands.error')); |         setResponseBody(t('commands.error')); | ||||||
|         setHadFailure(true); |         setHadFailure(true); | ||||||
|       }) |       }) | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								src/components/FormattedDate/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/components/FormattedDate/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { CPopover } from '@coreui/react'; | ||||||
|  | import { formatDaysAgo, prettyDate } from 'utils/helper'; | ||||||
|  |  | ||||||
|  | const FormattedDate = ({ date, size }) => { | ||||||
|  |   if (size === 'lg') { | ||||||
|  |     return ( | ||||||
|  |       <CPopover content={prettyDate(date)} advancedOptions={{ animation: false }}> | ||||||
|  |         <h2 className="d-inline-block">{date === 0 ? '-' : formatDaysAgo(date)}</h2> | ||||||
|  |       </CPopover> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CPopover content={prettyDate(date)} advancedOptions={{ animation: false }}> | ||||||
|  |       <span className="d-inline-block">{date === 0 ? '-' : formatDaysAgo(date)}</span> | ||||||
|  |     </CPopover> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | FormattedDate.propTypes = { | ||||||
|  |   date: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | ||||||
|  |   size: PropTypes.string, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | FormattedDate.defaultProps = { | ||||||
|  |   date: 0, | ||||||
|  |   size: 'md', | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default FormattedDate; | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import React, { useState, useEffect } from 'react'; | import React, { useState, useEffect, useMemo } from 'react'; | ||||||
| import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react'; | import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react'; | ||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import { cilX } from '@coreui/icons'; | import { cilX } from '@coreui/icons'; | ||||||
| @@ -32,6 +32,17 @@ const LatestStatisticsModal = ({ show, toggle }) => { | |||||||
|       .catch(() => {}); |       .catch(() => {}); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const latestStatsString = useMemo(() => { | ||||||
|  |     if (latestStats) { | ||||||
|  |       try { | ||||||
|  |         return JSON.stringify(latestStats, null, 2); | ||||||
|  |       } catch (e) { | ||||||
|  |         return ''; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return ''; | ||||||
|  |   }, [latestStats]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (show) { |     if (show) { | ||||||
|       getLatestStats(); |       getLatestStats(); | ||||||
| @@ -52,13 +63,9 @@ const LatestStatisticsModal = ({ show, toggle }) => { | |||||||
|       </CModalHeader> |       </CModalHeader> | ||||||
|       <CModalBody> |       <CModalBody> | ||||||
|         <div style={{ textAlign: 'right' }}> |         <div style={{ textAlign: 'right' }}> | ||||||
|           <CopyToClipboardButton |           <CopyToClipboardButton t={t} size="lg" content={latestStatsString} /> | ||||||
|             t={t} |  | ||||||
|             size="lg" |  | ||||||
|             content={JSON.stringify(latestStats ?? {}, null, 4)} |  | ||||||
|           /> |  | ||||||
|         </div> |         </div> | ||||||
|         <pre className="ignore">{JSON.stringify(latestStats, null, 2)}</pre> |         <pre className="ignore">{latestStatsString}</pre> | ||||||
|       </CModalBody> |       </CModalBody> | ||||||
|     </CModal> |     </CModal> | ||||||
|   ); |   ); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import React, { useState, useEffect, useCallback } from 'react'; | import React, { useState, useEffect, useCallback } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { CSpinner } from '@coreui/react'; | import { CSpinner, CAlert } from '@coreui/react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import { v4 as createUuid } from 'uuid'; | import { v4 as createUuid } from 'uuid'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
| @@ -23,230 +23,258 @@ const StatisticsChartList = ({ deviceSerialNumber, setOptions, section, time }) | |||||||
|     memory: [], |     memory: [], | ||||||
|     settings: {}, |     settings: {}, | ||||||
|   }); |   }); | ||||||
|  |   const [error, setError] = useState(false); | ||||||
|  |  | ||||||
|   const transformIntoDataset = (data) => { |   const transformIntoDataset = (data) => { | ||||||
|     let sortedData = data.sort((a, b) => { |     try { | ||||||
|       if (a.recorded > b.recorded) return 1; |       let sortedData = data.sort((a, b) => { | ||||||
|       if (b.recorded > a.recorded) return -1; |         if (a.recorded > b.recorded) return 1; | ||||||
|       return 0; |         if (b.recorded > a.recorded) return -1; | ||||||
|     }); |         return 0; | ||||||
|  |       }); | ||||||
|  |  | ||||||
|     const dataLength = sortedData.length; |       const dataLength = sortedData.length; | ||||||
|     if (dataLength > 1000 && dataLength < 3000) { |       if (dataLength > 1000 && dataLength < 3000) { | ||||||
|       sortedData = sortedData.filter((dat, index) => index % 4 === 0); |         sortedData = sortedData.filter((dat, index) => index % 4 === 0); | ||||||
|     } else if (dataLength >= 3000 && dataLength < 5000) { |       } else if (dataLength >= 3000 && dataLength < 5000) { | ||||||
|       sortedData = sortedData.filter((dat, index) => index % 8 === 0); |         sortedData = sortedData.filter((dat, index) => index % 8 === 0); | ||||||
|     } else if (dataLength >= 5000 && dataLength < 7000) { |       } else if (dataLength >= 5000 && dataLength < 7000) { | ||||||
|       sortedData = sortedData.filter((dat, index) => index % 12 === 0); |         sortedData = sortedData.filter((dat, index) => index % 12 === 0); | ||||||
|     } else if (dataLength > 7000) { |       } else if (dataLength > 7000) { | ||||||
|       sortedData = sortedData.filter((dat, index) => index % 20 === 0); |         sortedData = sortedData.filter((dat, index) => index % 20 === 0); | ||||||
|     } |       } | ||||||
|  |  | ||||||
|     // Looping through data to build our memory graph data |       // Looping through data to build our memory graph data | ||||||
|     const memoryUsed = [ |       const memoryUsed = [ | ||||||
|       { |         { | ||||||
|         titleName: t('statistics.memory'), |           titleName: t('statistics.memory'), | ||||||
|         name: 'Used', |           name: 'Used', | ||||||
|         backgroundColor: 'rgb(228,102,81,0.9)', |           backgroundColor: 'rgb(228,102,81,0.9)', | ||||||
|         data: [], |           data: [], | ||||||
|         fill: true, |           fill: true, | ||||||
|       }, |         }, | ||||||
|       { |         { | ||||||
|         titleName: t('statistics.memory'), |           titleName: t('statistics.memory'), | ||||||
|         name: 'Buffered', |           name: 'Buffered', | ||||||
|         backgroundColor: 'rgb(228,102,81,0.9)', |           backgroundColor: 'rgb(228,102,81,0.9)', | ||||||
|         data: [], |           data: [], | ||||||
|         fill: true, |           fill: true, | ||||||
|       }, |         }, | ||||||
|       { |         { | ||||||
|         titleName: t('statistics.memory'), |           titleName: t('statistics.memory'), | ||||||
|         name: 'Cached', |           name: 'Cached', | ||||||
|         backgroundColor: 'rgb(228,102,81,0.9)', |           backgroundColor: 'rgb(228,102,81,0.9)', | ||||||
|         data: [], |           data: [], | ||||||
|         fill: true, |           fill: true, | ||||||
|       }, |         }, | ||||||
|     ]; |       ]; | ||||||
|  |  | ||||||
|     for (const log of sortedData) { |       for (const log of sortedData) { | ||||||
|       memoryUsed[0].data.push( |         memoryUsed[0].data.push( | ||||||
|         Math.floor((log.data.unit.memory.total - log.data.unit.memory.free) / 1024 / 1024), |           Math.floor((log.data.unit.memory.total - log.data.unit.memory.free) / 1024 / 1024), | ||||||
|  |         ); | ||||||
|  |         memoryUsed[1].data.push(Math.floor(log.data.unit.memory.buffered / 1024 / 1024)); | ||||||
|  |         memoryUsed[2].data.push(Math.floor(log.data.unit.memory.cached / 1024 / 1024)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       const newUsed = memoryUsed[0].data; | ||||||
|  |       if (newUsed.length > 0) newUsed.shift(); | ||||||
|  |       memoryUsed[0].data = newUsed; | ||||||
|  |       const newBuff = memoryUsed[1].data; | ||||||
|  |       if (newBuff.length > 0) newBuff.shift(); | ||||||
|  |       memoryUsed[1].data = newBuff; | ||||||
|  |       const newCached = memoryUsed[2].data; | ||||||
|  |       if (newCached.length > 0) newCached.shift(); | ||||||
|  |       memoryUsed[2].data = newCached; | ||||||
|  |  | ||||||
|  |       // This dictionary will have a key that is the interface name and a value of it's index in the final array | ||||||
|  |       const interfaceTypes = {}; | ||||||
|  |       const interfaceList = []; | ||||||
|  |       const categories = []; | ||||||
|  |       let i = 0; | ||||||
|  |       const areSameDay = datesSameDay( | ||||||
|  |         new Date(sortedData[0].recorded * 1000), | ||||||
|  |         new Date(sortedData[sortedData.length - 1].recorded * 1000), | ||||||
|       ); |       ); | ||||||
|       memoryUsed[1].data.push(Math.floor(log.data.unit.memory.buffered / 1024 / 1024)); |  | ||||||
|       memoryUsed[2].data.push(Math.floor(log.data.unit.memory.cached / 1024 / 1024)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const newUsed = memoryUsed[0].data; |       // Just building the array for all the interfaces | ||||||
|     if (newUsed.length > 0) newUsed.shift(); |       for (const log of sortedData) { | ||||||
|     memoryUsed[0].data = newUsed; |         categories.push(areSameDay ? unixToTime(log.recorded) : prettyDate(log.recorded)); | ||||||
|     const newBuff = memoryUsed[1].data; |         for (const logInterface of log.data.interfaces) { | ||||||
|     if (newBuff.length > 0) newBuff.shift(); |           if (interfaceTypes[logInterface.name] === undefined) { | ||||||
|     memoryUsed[1].data = newBuff; |             interfaceTypes[logInterface.name] = i; | ||||||
|     const newCached = memoryUsed[2].data; |             interfaceList.push([ | ||||||
|     if (newCached.length > 0) newCached.shift(); |               { | ||||||
|     memoryUsed[2].data = newCached; |                 titleName: logInterface.name, | ||||||
|  |                 name: 'Tx', | ||||||
|     // This dictionary will have a key that is the interface name and a value of it's index in the final array |                 backgroundColor: 'rgb(228,102,81,0.9)', | ||||||
|     const interfaceTypes = {}; |                 data: [], | ||||||
|     const interfaceList = []; |                 fill: false, | ||||||
|     const categories = []; |               }, | ||||||
|     let i = 0; |               { | ||||||
|     const areSameDay = datesSameDay( |                 titleName: logInterface.name, | ||||||
|       new Date(sortedData[0].recorded * 1000), |                 name: 'Rx', | ||||||
|       new Date(sortedData[sortedData.length - 1].recorded * 1000), |                 backgroundColor: 'rgb(0,216,255,0.9)', | ||||||
|     ); |                 data: [], | ||||||
|  |                 fill: false, | ||||||
|     // Just building the array for all the interfaces |               }, | ||||||
|     for (const log of sortedData) { |             ]); | ||||||
|       categories.push(areSameDay ? unixToTime(log.recorded) : prettyDate(log.recorded)); |             i += 1; | ||||||
|       for (const logInterface of log.data.interfaces) { |           } | ||||||
|         if (interfaceTypes[logInterface.name] === undefined) { |  | ||||||
|           interfaceTypes[logInterface.name] = i; |  | ||||||
|           interfaceList.push([ |  | ||||||
|             { |  | ||||||
|               titleName: logInterface.name, |  | ||||||
|               name: 'Tx', |  | ||||||
|               backgroundColor: 'rgb(228,102,81,0.9)', |  | ||||||
|               data: [], |  | ||||||
|               fill: false, |  | ||||||
|             }, |  | ||||||
|             { |  | ||||||
|               titleName: logInterface.name, |  | ||||||
|               name: 'Rx', |  | ||||||
|               backgroundColor: 'rgb(0,216,255,0.9)', |  | ||||||
|               data: [], |  | ||||||
|               fill: false, |  | ||||||
|             }, |  | ||||||
|           ]); |  | ||||||
|           i += 1; |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Looping through all the data |       // Looping through all the data | ||||||
|     const prevTxObj = {}; |       const prevTxObj = {}; | ||||||
|     const prevRxObj = {}; |       const prevRxObj = {}; | ||||||
|     for (const log of sortedData) { |       for (const log of sortedData) { | ||||||
|       // Looping through the interfaces of the log |         // Looping through the interfaces of the log | ||||||
|       const version = log.data.version ?? 0; |         const version = log.data.version ?? 0; | ||||||
|       for (const inter of log.data.interfaces) { |         for (const inter of log.data.interfaces) { | ||||||
|         if (version > 0) { |           if (version > 0) { | ||||||
|           const prevTx = prevTxObj[inter.name] !== undefined ? prevTxObj[inter.name] : 0; |             const prevTx = prevTxObj[inter.name] !== undefined ? prevTxObj[inter.name] : 0; | ||||||
|           const prevRx = prevTxObj[inter.name] !== undefined ? prevRxObj[inter.name] : 0; |             const prevRx = prevTxObj[inter.name] !== undefined ? prevRxObj[inter.name] : 0; | ||||||
|           const tx = inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0; |             const tx = inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0; | ||||||
|           const rx = inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0; |             const rx = inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0; | ||||||
|           interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(0, tx - prevTx)); |             interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(0, tx - prevTx)); | ||||||
|           interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(0, rx - prevRx)); |             interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(0, rx - prevRx)); | ||||||
|           prevTxObj[inter.name] = tx; |             prevTxObj[inter.name] = tx; | ||||||
|           prevRxObj[inter.name] = rx; |             prevRxObj[inter.name] = rx; | ||||||
|         } else { |           } else { | ||||||
|           interfaceList[interfaceTypes[inter.name]][0].data.push( |             interfaceList[interfaceTypes[inter.name]][0].data.push( | ||||||
|             inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0, |               inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0, | ||||||
|           ); |             ); | ||||||
|           interfaceList[interfaceTypes[inter.name]][1].data.push( |             interfaceList[interfaceTypes[inter.name]][1].data.push( | ||||||
|             inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0, |               inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0, | ||||||
|           ); |             ); | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     for (let y = 0; y < interfaceList.length; y += 1) { |       for (let y = 0; y < interfaceList.length; y += 1) { | ||||||
|       for (let z = 0; z < interfaceList[y].length; z += 1) { |         for (let z = 0; z < interfaceList[y].length; z += 1) { | ||||||
|         const newArray = interfaceList[y][z].data; |           const newArray = interfaceList[y][z].data; | ||||||
|         if (newArray.length > 0) newArray.shift(); |           if (newArray.length > 0) newArray.shift(); | ||||||
|         interfaceList[y][z].data = newArray; |           interfaceList[y][z].data = newArray; | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const newCategories = categories; |       const newCategories = categories; | ||||||
|     if (newCategories.length > 0) newCategories.shift(); |       if (newCategories.length > 0) newCategories.shift(); | ||||||
|     const interfaceOptions = { |       const interfaceOptions = { | ||||||
|       chart: { |         chart: { | ||||||
|         id: 'chart', |           id: 'chart', | ||||||
|       }, |         }, | ||||||
|       stroke: { |         stroke: { | ||||||
|         curve: 'smooth', |           curve: 'smooth', | ||||||
|       }, |         }, | ||||||
|       xaxis: { |         xaxis: { | ||||||
|         title: { |           title: { | ||||||
|           text: 'Time', |             text: 'Time', | ||||||
|           style: { |             style: { | ||||||
|             fontSize: '15px', |               fontSize: '15px', | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |           categories: newCategories, | ||||||
|  |           tickAmount: areSameDay ? 15 : 10, | ||||||
|  |         }, | ||||||
|  |         yaxis: { | ||||||
|  |           labels: { | ||||||
|  |             minWidth: 40, | ||||||
|  |           }, | ||||||
|  |           title: { | ||||||
|  |             text: t('statistics.data'), | ||||||
|  |             style: { | ||||||
|  |               fontSize: '15px', | ||||||
|  |             }, | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|         categories: newCategories, |         legend: { | ||||||
|         tickAmount: areSameDay ? 15 : 10, |           position: 'top', | ||||||
|       }, |           horizontalAlign: 'right', | ||||||
|       yaxis: { |           float: true, | ||||||
|         labels: { |  | ||||||
|           minWidth: 40, |  | ||||||
|         }, |         }, | ||||||
|         title: { |       }; | ||||||
|           text: t('statistics.data'), |  | ||||||
|           style: { |       const memoryOptions = { | ||||||
|             fontSize: '15px', |         chart: { | ||||||
|  |           id: 'chart', | ||||||
|  |         }, | ||||||
|  |         stroke: { | ||||||
|  |           curve: 'smooth', | ||||||
|  |         }, | ||||||
|  |         xaxis: { | ||||||
|  |           tickAmount: areSameDay ? 15 : 10, | ||||||
|  |           title: { | ||||||
|  |             text: 'Time', | ||||||
|  |             style: { | ||||||
|  |               fontSize: '15px', | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |           categories, | ||||||
|  |         }, | ||||||
|  |         yaxis: { | ||||||
|  |           tickAmount: 5, | ||||||
|  |           title: { | ||||||
|  |             text: t('statistics.data_mb'), | ||||||
|  |             style: { | ||||||
|  |               fontSize: '15px', | ||||||
|  |             }, | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|       }, |         legend: { | ||||||
|       legend: { |           position: 'top', | ||||||
|         position: 'top', |           horizontalAlign: 'right', | ||||||
|         horizontalAlign: 'right', |           float: true, | ||||||
|         float: true, |  | ||||||
|       }, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     const memoryOptions = { |  | ||||||
|       chart: { |  | ||||||
|         id: 'chart', |  | ||||||
|       }, |  | ||||||
|       stroke: { |  | ||||||
|         curve: 'smooth', |  | ||||||
|       }, |  | ||||||
|       xaxis: { |  | ||||||
|         tickAmount: areSameDay ? 15 : 10, |  | ||||||
|         title: { |  | ||||||
|           text: 'Time', |  | ||||||
|           style: { |  | ||||||
|             fontSize: '15px', |  | ||||||
|           }, |  | ||||||
|         }, |         }, | ||||||
|         categories, |       }; | ||||||
|       }, |  | ||||||
|       yaxis: { |  | ||||||
|         tickAmount: 5, |  | ||||||
|         title: { |  | ||||||
|           text: t('statistics.data_mb'), |  | ||||||
|           style: { |  | ||||||
|             fontSize: '15px', |  | ||||||
|           }, |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       legend: { |  | ||||||
|         position: 'top', |  | ||||||
|         horizontalAlign: 'right', |  | ||||||
|         float: true, |  | ||||||
|       }, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     const newOptions = { |       const newOptions = { | ||||||
|       interfaceList, |         interfaceList, | ||||||
|       memory: [memoryUsed], |         memory: [memoryUsed], | ||||||
|       interfaceOptions, |         interfaceOptions, | ||||||
|       memoryOptions, |         memoryOptions, | ||||||
|       start: new Date(sortedData[0].recorded * 1000).toISOString(), |         start: new Date(sortedData[0].recorded * 1000).toISOString(), | ||||||
|       end: new Date(sortedData[sortedData.length - 1].recorded * 1000).toISOString(), |         end: new Date(sortedData[sortedData.length - 1].recorded * 1000).toISOString(), | ||||||
|     }; |       }; | ||||||
|  |  | ||||||
|     if (statOptions !== newOptions) { |       if (statOptions !== newOptions) { | ||||||
|       const sectionOptions = newOptions.interfaceList.map((opt) => ({ |         const sectionOptions = newOptions.interfaceList.map((opt) => ({ | ||||||
|         value: opt[0].titleName, |           value: opt[0].titleName, | ||||||
|         label: opt[0].titleName, |           label: opt[0].titleName, | ||||||
|       })); |         })); | ||||||
|       setOptions([...sectionOptions, { value: 'memory', label: t('statistics.memory') }]); |         setOptions([...sectionOptions, { value: 'memory', label: t('statistics.memory') }]); | ||||||
|       setStatOptions({ ...newOptions }); |         setStatOptions({ ...newOptions }); | ||||||
|  |       } | ||||||
|  |       setError(undefined); | ||||||
|  |     } catch (e) { | ||||||
|  |       if (data?.length === 0) { | ||||||
|  |         setError('nodata'); | ||||||
|  |       } else { | ||||||
|  |         setError('error'); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const getInterface = useCallback(() => { |   const getInterface = useCallback(() => { | ||||||
|  |     if (error === 'error') { | ||||||
|  |       return ( | ||||||
|  |         <div style={{ textAlign: 'center' }}> | ||||||
|  |           <CAlert color="danger" style={{ width: '240px', margin: 'auto' }}> | ||||||
|  |             Error while parsing statistics | ||||||
|  |           </CAlert> | ||||||
|  |         </div> | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     if (error === 'nodata') { | ||||||
|  |       return ( | ||||||
|  |         <div style={{ textAlign: 'center' }}> | ||||||
|  |           <CAlert color="danger" style={{ width: '340px', margin: 'auto' }}> | ||||||
|  |             No available statistics during this time period | ||||||
|  |           </CAlert> | ||||||
|  |         </div> | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|     if (statOptions.interfaceList.length === 0) return <p>N/A</p>; |     if (statOptions.interfaceList.length === 0) return <p>N/A</p>; | ||||||
|  |  | ||||||
|     const interfaceToShow = statOptions.interfaceList.find( |     const interfaceToShow = statOptions.interfaceList.find( | ||||||
| @@ -273,8 +301,9 @@ const StatisticsChartList = ({ deviceSerialNumber, setOptions, section, time }) | |||||||
|         </div> |         </div> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return <p>N/A</p>; |     return <p>N/A</p>; | ||||||
|   }, [statOptions, section]); |   }, [statOptions, section, error]); | ||||||
|  |  | ||||||
|   const getStatistics = () => { |   const getStatistics = () => { | ||||||
|     setLoading(true); |     setLoading(true); | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ const NetworkDiagram = ({ show, elements, setElements }) => { | |||||||
|         onElementsRemove={onElementsRemove} |         onElementsRemove={onElementsRemove} | ||||||
|         onLoad={onLoad} |         onLoad={onLoad} | ||||||
|         snapToGrid |         snapToGrid | ||||||
|  |         minZoom={0.1} | ||||||
|         snapGrid={[20, 20]} |         snapGrid={[20, 20]} | ||||||
|       > |       > | ||||||
|         <MiniMap |         <MiniMap | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ const associationNode = (associationInfo) => ( | |||||||
|   <div> |   <div> | ||||||
|     <CRow> |     <CRow> | ||||||
|       <CCol className="text-center"> |       <CCol className="text-center"> | ||||||
|         <h6>{associationInfo.bssid}</h6> |         <h6>{associationInfo.station}</h6> | ||||||
|       </CCol> |       </CCol> | ||||||
|     </CRow> |     </CRow> | ||||||
|     <CRow> |     <CRow> | ||||||
| @@ -92,7 +92,6 @@ const NetworkDiagram = ({ show, radios, associations }) => { | |||||||
|     // Creating the association nodes and their edges |     // Creating the association nodes and their edges | ||||||
|     for (let i = 0; i < associations.length; i += 1) { |     for (let i = 0; i < associations.length; i += 1) { | ||||||
|       const assoc = associations[i]; |       const assoc = associations[i]; | ||||||
|  |  | ||||||
|       // If the radio has not been added, we create a new unknown radio based on its index |       // If the radio has not been added, we create a new unknown radio based on its index | ||||||
|       if (radiosAdded[assoc.radio.radioIndex] === undefined) { |       if (radiosAdded[assoc.radio.radioIndex] === undefined) { | ||||||
|         newElements.push({ |         newElements.push({ | ||||||
| @@ -107,7 +106,7 @@ const NetworkDiagram = ({ show, radios, associations }) => { | |||||||
|  |  | ||||||
|       // Adding the association |       // Adding the association | ||||||
|       newElements.push({ |       newElements.push({ | ||||||
|         id: `a-${assoc.bssid}`, |         id: `a-${assoc.station}`, | ||||||
|         data: { label: associationNode(assoc) }, |         data: { label: associationNode(assoc) }, | ||||||
|         position: { |         position: { | ||||||
|           x: getX(radiosAdded[assoc.radio.radioIndex]), |           x: getX(radiosAdded[assoc.radio.radioIndex]), | ||||||
| @@ -120,9 +119,9 @@ const NetworkDiagram = ({ show, radios, associations }) => { | |||||||
|  |  | ||||||
|       // Creating the edge |       // Creating the edge | ||||||
|       newElements.push({ |       newElements.push({ | ||||||
|         id: `e-${assoc.radio.radioIndex}-${assoc.bssid}`, |         id: `e-${assoc.radio.radioIndex}-${assoc.station}`, | ||||||
|         source: `r-${assoc.radio.radioIndex}`, |         source: `r-${assoc.radio.radioIndex}`, | ||||||
|         target: `a-${assoc.bssid}`, |         target: `a-${assoc.station}`, | ||||||
|         arrowHeadType: 'arrowclosed', |         arrowHeadType: 'arrowclosed', | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -89,7 +89,18 @@ const ActionModal = ({ show, toggleModal }) => { | |||||||
|         }); |         }); | ||||||
|         toggleModal(); |         toggleModal(); | ||||||
|       }) |       }) | ||||||
|       .catch(() => { |       .catch((e) => { | ||||||
|  |         if (e.response?.data?.ErrorDescription !== undefined) { | ||||||
|  |           const split = e.response?.data?.ErrorDescription.split(':'); | ||||||
|  |           if (split !== undefined && split.length >= 2) { | ||||||
|  |             addToast({ | ||||||
|  |               title: t('common.error'), | ||||||
|  |               body: split[1], | ||||||
|  |               color: 'danger', | ||||||
|  |               autohide: true, | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|         setResult('error'); |         setResult('error'); | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
|   | |||||||
| @@ -105,12 +105,17 @@ const TelemetryModal = ({ show, toggle }) => { | |||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|       .catch((e) => { |       .catch((e) => { | ||||||
|         addToast({ |         if (e.response?.data?.ErrorDescription !== undefined) { | ||||||
|           title: t('common.error'), |           const split = e.response?.data?.ErrorDescription.split(':'); | ||||||
|           body: t('telemetry.connection_failed', { error: e.response?.data?.ErrorDescription }), |           if (split !== undefined && split.length >= 2) { | ||||||
|           color: 'danger', |             addToast({ | ||||||
|           autohide: true, |               title: t('common.error'), | ||||||
|         }); |               body: split[1], | ||||||
|  |               color: 'danger', | ||||||
|  |               autohide: true, | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|       }) |       }) | ||||||
|       .finally(() => setLoading(false)); |       .finally(() => setLoading(false)); | ||||||
|   }; |   }; | ||||||
|   | |||||||
| @@ -23,13 +23,14 @@ import PropTypes from 'prop-types'; | |||||||
| import 'react-widgets/styles.css'; | import 'react-widgets/styles.css'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
| import eventBus from 'utils/eventBus'; | import eventBus from 'utils/eventBus'; | ||||||
| import { LoadingButton, useAuth, useDevice } from 'ucentral-libs'; | import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs'; | ||||||
| import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | ||||||
| import WaitingForTraceBody from './WaitingForTraceBody'; | import WaitingForTraceBody from './WaitingForTraceBody'; | ||||||
|  |  | ||||||
| const TraceModal = ({ show, toggleModal }) => { | const TraceModal = ({ show, toggleModal }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const { currentToken, endpoints } = useAuth(); |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { addToast } = useToast(); | ||||||
|   const { deviceSerialNumber, getDeviceConnection } = useDevice(); |   const { deviceSerialNumber, getDeviceConnection } = useDevice(); | ||||||
|   const [hadSuccess, setHadSuccess] = useState(false); |   const [hadSuccess, setHadSuccess] = useState(false); | ||||||
|   const [hadFailure, setHadFailure] = useState(false); |   const [hadFailure, setHadFailure] = useState(false); | ||||||
| @@ -94,7 +95,18 @@ const TraceModal = ({ show, toggleModal }) => { | |||||||
|           setWaitingForTrace(true); |           setWaitingForTrace(true); | ||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|       .catch(() => { |       .catch((e) => { | ||||||
|  |         if (e.response?.data?.ErrorDescription !== undefined) { | ||||||
|  |           const split = e.response?.data?.ErrorDescription.split(':'); | ||||||
|  |           if (split !== undefined && split.length >= 2) { | ||||||
|  |             addToast({ | ||||||
|  |               title: t('common.error'), | ||||||
|  |               body: split[1], | ||||||
|  |               color: 'danger', | ||||||
|  |               autohide: true, | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|         setResponseBody(t('commands.error')); |         setResponseBody(t('commands.error')); | ||||||
|         setHadFailure(true); |         setHadFailure(true); | ||||||
|       }) |       }) | ||||||
|   | |||||||
| @@ -20,31 +20,22 @@ import PropTypes from 'prop-types'; | |||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
| import eventBus from 'utils/eventBus'; | import eventBus from 'utils/eventBus'; | ||||||
| import { prettyDateForFile } from 'utils/helper'; | import { prettyDateForFile } from 'utils/helper'; | ||||||
| import { useAuth, useDevice } from 'ucentral-libs'; | import { useAuth, useDevice, useToast } from 'ucentral-libs'; | ||||||
| import WifiChannelTable from 'components/WifiScanResultModal/WifiChannelTable'; | import WifiChannelTable from 'components/WifiScanResultModal/WifiChannelTable'; | ||||||
| import 'react-widgets/styles.css'; | import 'react-widgets/styles.css'; | ||||||
| import { CSVLink } from 'react-csv'; | import { CSVLink } from 'react-csv'; | ||||||
| import Select from 'react-select'; |  | ||||||
| import IeDisplay from 'components/WifiScanResultModal/IeDisplay'; | import IeDisplay from 'components/WifiScanResultModal/IeDisplay'; | ||||||
| import IE_OPTIONS from './IE_OPTIONS.json'; | import IE_OPTIONS from './IE_OPTIONS.json'; | ||||||
|  |  | ||||||
| const getIeOptions = () => { | const allIes = Object.entries(IE_OPTIONS).map(([, value]) => value); | ||||||
|   const arr = []; |  | ||||||
|   for (const [key, value] of Object.entries(IE_OPTIONS)) { |  | ||||||
|     arr.push({ |  | ||||||
|       label: `${key} (${value})`, |  | ||||||
|       value, |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|   return arr; |  | ||||||
| }; |  | ||||||
| const WifiScanModal = ({ show, toggleModal }) => { | const WifiScanModal = ({ show, toggleModal }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const { currentToken, endpoints } = useAuth(); |   const { currentToken, endpoints } = useAuth(); | ||||||
|   const { deviceSerialNumber } = useDevice(); |   const { deviceSerialNumber } = useDevice(); | ||||||
|  |   const { addToast } = useToast(); | ||||||
|   const [hadSuccess, setHadSuccess] = useState(false); |   const [hadSuccess, setHadSuccess] = useState(false); | ||||||
|   const [selectedIes, setSelectedIes] = useState(undefined); |   const [selectedIes, setSelectedIes] = useState(undefined); | ||||||
|   const [ies, setIes] = useState([]); |  | ||||||
|   const [hadFailure, setHadFailure] = useState(false); |   const [hadFailure, setHadFailure] = useState(false); | ||||||
|   const [errorCode, setErrorCode] = useState(0); |   const [errorCode, setErrorCode] = useState(0); | ||||||
|   const [waiting, setWaiting] = useState(false); |   const [waiting, setWaiting] = useState(false); | ||||||
| @@ -63,14 +54,6 @@ const WifiScanModal = ({ show, toggleModal }) => { | |||||||
|     setActiveScan(!activeScan); |     setActiveScan(!activeScan); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const onIesChange = (v) => { |  | ||||||
|     if (v.find(({ value }) => value === '*')) { |  | ||||||
|       setIes(getIeOptions()); |  | ||||||
|     } else { |  | ||||||
|       setIes(v); |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setHadSuccess(false); |     setHadSuccess(false); | ||||||
|     setHadFailure(false); |     setHadFailure(false); | ||||||
| @@ -82,7 +65,7 @@ const WifiScanModal = ({ show, toggleModal }) => { | |||||||
|     setActiveScan(false); |     setActiveScan(false); | ||||||
|     setHideOptions(false); |     setHideOptions(false); | ||||||
|     setErrorCode(0); |     setErrorCode(0); | ||||||
|     setIes([]); |     setSelectedIes(undefined); | ||||||
|   }, [show]); |   }, [show]); | ||||||
|  |  | ||||||
|   const parseThroughList = (scanList) => { |   const parseThroughList = (scanList) => { | ||||||
| @@ -164,7 +147,7 @@ const WifiScanModal = ({ show, toggleModal }) => { | |||||||
|       override_dfs: dfs, |       override_dfs: dfs, | ||||||
|       bandwidth: bandwidth !== '' ? bandwidth : undefined, |       bandwidth: bandwidth !== '' ? bandwidth : undefined, | ||||||
|       activeScan, |       activeScan, | ||||||
|       ies: ies?.length > 0 ? ies.map(({ value }) => value) : undefined, |       ies: allIes, | ||||||
|     }; |     }; | ||||||
|     const headers = { |     const headers = { | ||||||
|       Accept: 'application/json', |       Accept: 'application/json', | ||||||
| @@ -190,7 +173,18 @@ const WifiScanModal = ({ show, toggleModal }) => { | |||||||
|           setHadFailure(true); |           setHadFailure(true); | ||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|       .catch(() => { |       .catch((e) => { | ||||||
|  |         if (e.response?.data?.ErrorDescription !== undefined) { | ||||||
|  |           const split = e.response?.data?.ErrorDescription.split(':'); | ||||||
|  |           if (split !== undefined && split.length >= 2) { | ||||||
|  |             addToast({ | ||||||
|  |               title: t('common.error'), | ||||||
|  |               body: split[1], | ||||||
|  |               color: 'danger', | ||||||
|  |               autohide: true, | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|         setHadFailure(true); |         setHadFailure(true); | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
| @@ -289,23 +283,6 @@ const WifiScanModal = ({ show, toggleModal }) => { | |||||||
|               </CSelect> |               </CSelect> | ||||||
|             </CCol> |             </CCol> | ||||||
|           </CRow> |           </CRow> | ||||||
|           <CRow className="mt-3"> |  | ||||||
|             <CCol md="3"> |  | ||||||
|               <p className="pl-2">{t('actions.request_ie')}:</p> |  | ||||||
|             </CCol> |  | ||||||
|             <CCol> |  | ||||||
|               <Select |  | ||||||
|                 isMulti |  | ||||||
|                 closeMenuOnSelect={false} |  | ||||||
|                 name="request_ie" |  | ||||||
|                 options={[{ label: 'All', value: '*' }, ...getIeOptions()]} |  | ||||||
|                 onChange={onIesChange} |  | ||||||
|                 value={ies} |  | ||||||
|                 className="basic-multi-select" |  | ||||||
|                 classNamePrefix="select" |  | ||||||
|               /> |  | ||||||
|             </CCol> |  | ||||||
|           </CRow> |  | ||||||
|         </div> |         </div> | ||||||
|         <div hidden={!waiting}> |         <div hidden={!waiting}> | ||||||
|           <CRow> |           <CRow> | ||||||
| @@ -332,10 +309,10 @@ const WifiScanModal = ({ show, toggleModal }) => { | |||||||
|               </CCol> |               </CCol> | ||||||
|             </CRow> |             </CRow> | ||||||
|           )} |           )} | ||||||
|           {selectedIes && <IeDisplay ies={selectedIes} setIes={setSelectedIes} />} |  | ||||||
|           {selectedIes || channelList === null ? null : ( |           {selectedIes || channelList === null ? null : ( | ||||||
|             <WifiChannelTable channels={channelList} setIes={setSelectedIes} /> |             <WifiChannelTable channels={channelList} setIes={setSelectedIes} /> | ||||||
|           )} |           )} | ||||||
|  |           {selectedIes && <IeDisplay ies={selectedIes} setIes={setSelectedIes} />} | ||||||
|         </div> |         </div> | ||||||
|       </CModalBody> |       </CModalBody> | ||||||
|     </CModal> |     </CModal> | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								src/constants.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/constants.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | export const AUTH_EXPIRED_TOKEN_CODE = 9; | ||||||
|  | export const AUTH_INVALID_TOKEN_CODE = 8; | ||||||
|  |  | ||||||
|  | export const LOGOUT_ON_SEC_ERROR_CODES = [AUTH_EXPIRED_TOKEN_CODE, AUTH_INVALID_TOKEN_CODE]; | ||||||
| @@ -11,7 +11,7 @@ const WebSocketContext = React.createContext({ | |||||||
|   addDeviceListener: () => {}, |   addDeviceListener: () => {}, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| export const WebSocketProvider = ({ children }) => { | export const WebSocketProvider = ({ children, setNewConnectionData }) => { | ||||||
|   const { currentToken, endpoints } = useAuth(); |   const { currentToken, endpoints } = useAuth(); | ||||||
|   const [isOpen, setIsOpen] = useState(false); |   const [isOpen, setIsOpen] = useState(false); | ||||||
|   const ws = useRef(undefined); |   const ws = useRef(undefined); | ||||||
| @@ -20,6 +20,9 @@ export const WebSocketProvider = ({ children }) => { | |||||||
|  |  | ||||||
|   const onMessage = useCallback((message) => { |   const onMessage = useCallback((message) => { | ||||||
|     const result = extractWebSocketResponse(message); |     const result = extractWebSocketResponse(message); | ||||||
|  |     if (result?.type === 'device_connections_statistics') { | ||||||
|  |       setNewConnectionData(result.content); | ||||||
|  |     } | ||||||
|     if (result?.type === 'NOTIFICATION') { |     if (result?.type === 'NOTIFICATION') { | ||||||
|       dispatch({ type: 'NEW_NOTIFICATION', notification: result.notification }); |       dispatch({ type: 'NEW_NOTIFICATION', notification: result.notification }); | ||||||
|       pushNotification(result.notification); |       pushNotification(result.notification); | ||||||
| @@ -36,23 +39,29 @@ export const WebSocketProvider = ({ children }) => { | |||||||
|     } |     } | ||||||
|   }, []); |   }, []); | ||||||
|  |  | ||||||
|   // useEffect for created the WebSocket and 'storing' it in useRef |   const onStartWebSocket = () => { | ||||||
|   useEffect(() => { |     ws.current = new WebSocket(`${endpoints.owgw?.replace('https', 'wss')}/api/v1/ws`); | ||||||
|     ws.current = new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`); |  | ||||||
|     ws.current.onopen = () => { |     ws.current.onopen = () => { | ||||||
|       setIsOpen(true); |       setIsOpen(true); | ||||||
|       ws.current?.send(`token:${currentToken}`); |       ws.current?.send(`token:${currentToken}`); | ||||||
|     }; |     }; | ||||||
|     ws.current.onclose = () => { |     ws.current.onclose = () => { | ||||||
|       setIsOpen(false); |       setIsOpen(false); | ||||||
|  |       setTimeout(onStartWebSocket, 3000); | ||||||
|     }; |     }; | ||||||
|     ws.current.onerror = () => { |     ws.current.onerror = () => { | ||||||
|       setIsOpen(false); |       setIsOpen(false); | ||||||
|     }; |     }; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   // useEffect for created the WebSocket and 'storing' it in useRef | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (endpoints?.owgw !== undefined) { | ||||||
|  |       onStartWebSocket(); | ||||||
|  |     } | ||||||
|     const wsCurrent = ws?.current; |     const wsCurrent = ws?.current; | ||||||
|     return () => wsCurrent?.close(); |     return () => wsCurrent?.close(); | ||||||
|   }, []); |   }, [endpoints]); | ||||||
|  |  | ||||||
|   // useEffect for generating global notifications |   // useEffect for generating global notifications | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
| @@ -65,6 +74,7 @@ export const WebSocketProvider = ({ children }) => { | |||||||
|       if (wsCurrent) wsCurrent.removeEventListener('message', onMessage); |       if (wsCurrent) wsCurrent.removeEventListener('message', onMessage); | ||||||
|     }; |     }; | ||||||
|   }, [ws?.current]); |   }, [ws?.current]); | ||||||
|  |  | ||||||
|   const values = useMemo( |   const values = useMemo( | ||||||
|     () => ({ |     () => ({ | ||||||
|       lastMessage, |       lastMessage, | ||||||
| @@ -83,6 +93,7 @@ export const WebSocketProvider = ({ children }) => { | |||||||
|  |  | ||||||
| WebSocketProvider.propTypes = { | WebSocketProvider.propTypes = { | ||||||
|   children: PropTypes.node.isRequired, |   children: PropTypes.node.isRequired, | ||||||
|  |   setNewConnectionData: PropTypes.func.isRequired, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const useGlobalWebSocket = () => React.useContext(WebSocketContext); | export const useGlobalWebSocket = () => React.useContext(WebSocketContext); | ||||||
|   | |||||||
| @@ -26,30 +26,11 @@ export const extractWebSocketResponse = (message) => { | |||||||
|     if (data.command_response_id) { |     if (data.command_response_id) { | ||||||
|       return { data, type: 'COMMAND' }; |       return { data, type: 'COMMAND' }; | ||||||
|     } |     } | ||||||
|  |     if (data.notification.type === 'device_connections_statistics') { | ||||||
|  |       return { content: data.notification.content, type: 'device_connections_statistics' }; | ||||||
|  |     } | ||||||
|   } catch { |   } catch { | ||||||
|     return undefined; |     return undefined; | ||||||
|   } |   } | ||||||
|   return undefined; |   return undefined; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const getStatusFromNotification = (notification) => { |  | ||||||
|   let status = 'success'; |  | ||||||
|   if (notification.content.warning?.length > 0) status = 'warning'; |  | ||||||
|   if (notification.content.error?.length > 0) status = 'error'; |  | ||||||
|  |  | ||||||
|   return status; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const getNotificationDescription = (t, notification) => { |  | ||||||
|   if ( |  | ||||||
|     notification.content.type === 'venue_configuration_update' || |  | ||||||
|     notification.content.type === 'entity_configuration_update' |  | ||||||
|   ) { |  | ||||||
|     return t('configurations.notification_details', { |  | ||||||
|       success: notification.content.success.length, |  | ||||||
|       warning: notification.content.warning.length, |  | ||||||
|       error: notification.content.error.length, |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|   return notification.content.details; |  | ||||||
| }; |  | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								src/hooks/useToggle/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/hooks/useToggle/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | import { useState } from 'react'; | ||||||
|  |  | ||||||
|  | const useToggle = (initialState) => { | ||||||
|  |   const [value, setValue] = useState(initialState); | ||||||
|  |  | ||||||
|  |   return [ | ||||||
|  |     value, | ||||||
|  |     () => { | ||||||
|  |       setValue(!value); | ||||||
|  |     }, | ||||||
|  |     (newValue) => { | ||||||
|  |       setValue(newValue); | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default useToggle; | ||||||
							
								
								
									
										120
									
								
								src/layout/Devices.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/layout/Devices.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | |||||||
|  | import React, { useState, useEffect } from 'react'; | ||||||
|  | import axiosInstance from 'utils/axiosInstance'; | ||||||
|  | import { useAuth } from 'ucentral-libs'; | ||||||
|  | import { CPopover } from '@coreui/react'; | ||||||
|  | import { extraCompactSecondsToDetailed, secondsToDetailed } from 'utils/helper'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  |  | ||||||
|  | const propTypes = { | ||||||
|  |   newData: PropTypes.instanceOf(Object), | ||||||
|  | }; | ||||||
|  | const defaultProps = { | ||||||
|  |   newData: undefined, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const SidebarDevices = ({ newData }) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const [stats, setStats] = useState(); | ||||||
|  |   const [lastUpdate, setLastUpdate] = useState(); | ||||||
|  |   const [lastTime, setLastTime] = useState(); | ||||||
|  |  | ||||||
|  |   const getInitialStats = async () => { | ||||||
|  |     const options = { | ||||||
|  |       headers: { | ||||||
|  |         Accept: 'application/json', | ||||||
|  |         Authorization: `Bearer ${currentToken}`, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     axiosInstance | ||||||
|  |       .get(`${endpoints.owgw}/api/v1/devices?connectionStatistics=true`, options) | ||||||
|  |       .then(({ data }) => { | ||||||
|  |         setStats(data); | ||||||
|  |         setLastUpdate(new Date()); | ||||||
|  |       }) | ||||||
|  |       .catch(() => {}); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const getTime = () => { | ||||||
|  |     if (lastTime === undefined || lastUpdate === undefined) return null; | ||||||
|  |  | ||||||
|  |     const seconds = lastTime.getTime() - lastUpdate.getTime(); | ||||||
|  |  | ||||||
|  |     return Math.max(0, Math.floor(seconds / 1000)); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (newData !== undefined && Object.keys(newData).length > 0) { | ||||||
|  |       setStats({ ...newData }); | ||||||
|  |       setLastUpdate(new Date()); | ||||||
|  |     } | ||||||
|  |   }, [newData]); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     getInitialStats(); | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     const interval = setInterval(() => { | ||||||
|  |       setLastTime(new Date()); | ||||||
|  |     }, 1000); | ||||||
|  |     return () => { | ||||||
|  |       clearInterval(interval); | ||||||
|  |     }; | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|  |   if (!stats) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <div | ||||||
|  |       style={{ | ||||||
|  |         position: 'absolute', | ||||||
|  |         bottom: '0px', | ||||||
|  |         width: '100%', | ||||||
|  |         background: '#2f3d54 !important', | ||||||
|  |         backgroundColor: '#2f3d54 !important', | ||||||
|  |         borderTop: '3px solid #d8dbe0', | ||||||
|  |         color: 'white', | ||||||
|  |         textAlign: 'center', | ||||||
|  |         paddingTop: '15px', | ||||||
|  |         paddingBottom: '25px', | ||||||
|  |       }} | ||||||
|  |     > | ||||||
|  |       <h3 style={{ marginBottom: '0px' }}>{stats?.connectedDevices ?? stats?.numberOfDevices}</h3> | ||||||
|  |       <h6>Connected Devices</h6> | ||||||
|  |       <CPopover | ||||||
|  |         content={secondsToDetailed( | ||||||
|  |           stats?.averageConnectionTime ?? stats?.averageConnectedTime, | ||||||
|  |           t('common.day'), | ||||||
|  |           t('common.days'), | ||||||
|  |           t('common.hour'), | ||||||
|  |           t('common.hours'), | ||||||
|  |           t('common.minute'), | ||||||
|  |           t('common.minutes'), | ||||||
|  |           t('common.second'), | ||||||
|  |           t('common.seconds'), | ||||||
|  |         )} | ||||||
|  |       > | ||||||
|  |         <h3 style={{ marginBottom: '0px' }}> | ||||||
|  |           {extraCompactSecondsToDetailed( | ||||||
|  |             stats?.averageConnectionTime ?? stats?.averageConnectedTime, | ||||||
|  |             t('common.day'), | ||||||
|  |             t('common.days'), | ||||||
|  |             t('common.seconds'), | ||||||
|  |           )} | ||||||
|  |         </h3> | ||||||
|  |       </CPopover> | ||||||
|  |       <h6>Avg. Connection Time</h6> | ||||||
|  |       <h7 style={{ color: '#ebedef', fontStyle: 'italic' }}>{getTime()} seconds ago</h7> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | SidebarDevices.propTypes = propTypes; | ||||||
|  | SidebarDevices.defaultProps = defaultProps; | ||||||
|  |  | ||||||
|  | export default React.memo(SidebarDevices); | ||||||
							
								
								
									
										49
									
								
								src/layout/Sidebar/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/layout/Sidebar/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { CSidebar, CSidebarBrand, CSidebarNav } from '@coreui/react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
|  | const Sidebar = ({ | ||||||
|  |   showSidebar, | ||||||
|  |   setShowSidebar, | ||||||
|  |   logo, | ||||||
|  |   options, | ||||||
|  |   redirectTo, | ||||||
|  |   logoHeight, | ||||||
|  |   logoWidth, | ||||||
|  | }) => ( | ||||||
|  |   <CSidebar show={showSidebar} onShowChange={(val) => setShowSidebar(val)}> | ||||||
|  |     <CSidebarBrand className="d-md-down-none" to={redirectTo}> | ||||||
|  |       <img | ||||||
|  |         className={[styles.sidebarImgFull, 'c-sidebar-brand-full'].join(' ')} | ||||||
|  |         style={{ height: logoHeight ?? undefined, width: logoWidth ?? undefined }} | ||||||
|  |         src={logo} | ||||||
|  |         alt="OpenWifi" | ||||||
|  |       /> | ||||||
|  |       <img | ||||||
|  |         className={[styles.sidebarImgMinimized, 'c-sidebar-brand-minimized'].join(' ')} | ||||||
|  |         style={{ height: logoHeight ?? undefined, width: logoWidth ?? undefined }} | ||||||
|  |         src={logo} | ||||||
|  |         alt="OpenWifi" | ||||||
|  |       /> | ||||||
|  |     </CSidebarBrand> | ||||||
|  |     <CSidebarNav>{options}</CSidebarNav> | ||||||
|  |   </CSidebar> | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Sidebar.propTypes = { | ||||||
|  |   showSidebar: PropTypes.string.isRequired, | ||||||
|  |   setShowSidebar: PropTypes.func.isRequired, | ||||||
|  |   logo: PropTypes.string.isRequired, | ||||||
|  |   options: PropTypes.node.isRequired, | ||||||
|  |   redirectTo: PropTypes.string.isRequired, | ||||||
|  |   logoHeight: PropTypes.string, | ||||||
|  |   logoWidth: PropTypes.string, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Sidebar.defaultProps = { | ||||||
|  |   logoHeight: null, | ||||||
|  |   logoWidth: null, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default React.memo(Sidebar); | ||||||
							
								
								
									
										9
									
								
								src/layout/Sidebar/index.module.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/layout/Sidebar/index.module.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | .sidebarImgFull { | ||||||
|  |   height: 75px; | ||||||
|  |   width: 175px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .sidebarImgMinimized { | ||||||
|  |   height: 75px; | ||||||
|  |   width: 75px; | ||||||
|  | } | ||||||
| @@ -4,13 +4,20 @@ import routes from 'routes'; | |||||||
| import { CSidebarNavItem } from '@coreui/react'; | import { CSidebarNavItem } from '@coreui/react'; | ||||||
| import { cilBarcode, cilRouter, cilSave, cilSettings, cilPeople } from '@coreui/icons'; | import { cilBarcode, cilRouter, cilSave, cilSettings, cilPeople } from '@coreui/icons'; | ||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import { Header, Sidebar, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs'; | import { Header, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs'; | ||||||
| import { WebSocketProvider } from 'contexts/WebSocketProvider'; | import { WebSocketProvider } from 'contexts/WebSocketProvider'; | ||||||
|  | import Sidebar from './Sidebar'; | ||||||
|  | import SidebarDevices from './Devices'; | ||||||
|  |  | ||||||
| const TheLayout = () => { | const TheLayout = () => { | ||||||
|   const [showSidebar, setShowSidebar] = useState('responsive'); |   const [showSidebar, setShowSidebar] = useState('responsive'); | ||||||
|   const { endpoints, currentToken, user, avatar, logout } = useAuth(); |   const { endpoints, currentToken, user, avatar, logout } = useAuth(); | ||||||
|   const { t, i18n } = useTranslation(); |   const { t, i18n } = useTranslation(); | ||||||
|  |   const [newConnectionData, setNewConnectionData] = useState(); | ||||||
|  |  | ||||||
|  |   const onConnectionDataChange = React.useCallback((newData) => { | ||||||
|  |     setNewConnectionData({ ...newData }); | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <div className="c-app c-default-layout"> |     <div className="c-app c-default-layout"> | ||||||
| @@ -50,6 +57,7 @@ const TheLayout = () => { | |||||||
|               to="/system" |               to="/system" | ||||||
|               icon={<CIcon content={cilSettings} size="xl" className="mr-3" />} |               icon={<CIcon content={cilSettings} size="xl" className="mr-3" />} | ||||||
|             /> |             /> | ||||||
|  |             <SidebarDevices newData={newConnectionData} /> | ||||||
|           </> |           </> | ||||||
|         } |         } | ||||||
|         redirectTo="/devices" |         redirectTo="/devices" | ||||||
| @@ -71,7 +79,7 @@ const TheLayout = () => { | |||||||
|         /> |         /> | ||||||
|         <div className="c-body"> |         <div className="c-body"> | ||||||
|           <ToastProvider> |           <ToastProvider> | ||||||
|             <WebSocketProvider> |             <WebSocketProvider setNewConnectionData={onConnectionDataChange}> | ||||||
|               <PageContainer t={t} routes={routes} redirectTo="/devices" /> |               <PageContainer t={t} routes={routes} redirectTo="/devices" /> | ||||||
|             </WebSocketProvider> |             </WebSocketProvider> | ||||||
|           </ToastProvider> |           </ToastProvider> | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import React, { useEffect, useState } from 'react'; | import React, { useEffect, useState } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useHistory, useParams } from 'react-router-dom'; | ||||||
| import { CRow, CCol, CCard, CCardBody, CNav, CNavLink, CTabPane, CTabContent } from '@coreui/react'; | import { CRow, CCol, CCard, CCardBody, CNav, CNavLink, CTabPane, CTabContent } from '@coreui/react'; | ||||||
| import DeviceHealth from 'components/DeviceHealth'; | import DeviceHealth from 'components/DeviceHealth'; | ||||||
| import CommandHistory from 'components/CommandHistory'; | import CommandHistory from 'components/CommandHistory'; | ||||||
| @@ -7,7 +7,7 @@ import DeviceLogs from 'components/DeviceLogs'; | |||||||
| import DeviceStatisticsCard from 'components/InterfaceStatistics'; | import DeviceStatisticsCard from 'components/InterfaceStatistics'; | ||||||
| import DeviceActionCard from 'components/DeviceActionCard'; | import DeviceActionCard from 'components/DeviceActionCard'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
| import { DeviceProvider, useAuth } from 'ucentral-libs'; | import { DeviceProvider, useAuth, useToast } from 'ucentral-libs'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import ConfigurationDisplay from 'components/ConfigurationDisplay'; | import ConfigurationDisplay from 'components/ConfigurationDisplay'; | ||||||
| import WifiAnalysis from 'components/WifiAnalysis'; | import WifiAnalysis from 'components/WifiAnalysis'; | ||||||
| @@ -23,7 +23,9 @@ const DevicePage = () => { | |||||||
|   const [index, setIndex] = useState(0); |   const [index, setIndex] = useState(0); | ||||||
|   const { currentToken, endpoints } = useAuth(); |   const { currentToken, endpoints } = useAuth(); | ||||||
|   const [lastStats, setLastStats] = useState(null); |   const [lastStats, setLastStats] = useState(null); | ||||||
|  |   const { addToast } = useToast(); | ||||||
|   const [status, setStatus] = useState(null); |   const [status, setStatus] = useState(null); | ||||||
|  |   const history = useHistory(); | ||||||
|   const [deviceConfig, setDeviceConfig] = useState(null); |   const [deviceConfig, setDeviceConfig] = useState(null); | ||||||
|   const [error, setError] = useState(false); |   const [error, setError] = useState(false); | ||||||
|   const [loading, setLoading] = useState(false); |   const [loading, setLoading] = useState(false); | ||||||
| @@ -64,7 +66,16 @@ const DevicePage = () => { | |||||||
|       .then((response) => { |       .then((response) => { | ||||||
|         if (response) setDeviceConfig({ ...deviceInfo, extendedInfo: response.data.extendedInfo }); |         if (response) setDeviceConfig({ ...deviceInfo, extendedInfo: response.data.extendedInfo }); | ||||||
|       }) |       }) | ||||||
|       .catch(() => { |       .catch((e) => { | ||||||
|  |         if (e.response?.status === 404 || e.response?.status === 400) { | ||||||
|  |           addToast({ | ||||||
|  |             title: t('common.error'), | ||||||
|  |             body: t('device.mac_not_found'), | ||||||
|  |             color: 'danger', | ||||||
|  |             autohide: true, | ||||||
|  |           }); | ||||||
|  |           history.push('/devices'); | ||||||
|  |         } | ||||||
|         setDeviceConfig(deviceInfo); |         setDeviceConfig(deviceInfo); | ||||||
|       }); |       }); | ||||||
|   }; |   }; | ||||||
|   | |||||||
							
								
								
									
										189
									
								
								src/pages/SystemPage/ApiStatusCard/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								src/pages/SystemPage/ApiStatusCard/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { | ||||||
|  |   CCard, | ||||||
|  |   CCardBody, | ||||||
|  |   CCardHeader, | ||||||
|  |   CRow, | ||||||
|  |   CCol, | ||||||
|  |   CButton, | ||||||
|  |   CPopover, | ||||||
|  |   CModal, | ||||||
|  |   CModalBody, | ||||||
|  |   CModalHeader, | ||||||
|  |   CModalTitle, | ||||||
|  |   CDataTable, | ||||||
|  | } from '@coreui/react'; | ||||||
|  | import Select from 'react-select'; | ||||||
|  | import CIcon from '@coreui/icons-react'; | ||||||
|  | import { cilSync, cilX } from '@coreui/icons'; | ||||||
|  | import { prettyDate } from 'utils/helper'; | ||||||
|  | import useToggle from 'hooks/useToggle'; | ||||||
|  | import FormattedDate from 'components/FormattedDate'; | ||||||
|  |  | ||||||
|  | const ApiStatusCard = ({ t, info, reload }) => { | ||||||
|  |   const [types, setTypes] = useState([]); | ||||||
|  |   const [showCerts, toggleCerts] = useToggle(); | ||||||
|  |  | ||||||
|  |   const submit = () => { | ||||||
|  |     reload( | ||||||
|  |       types.map((v) => v.value), | ||||||
|  |       info.endpoint, | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CCard> | ||||||
|  |       <CCardHeader className="dark-header"> | ||||||
|  |         <div style={{ fontWeight: '600' }} className=" text-value-lg float-left"> | ||||||
|  |           {info.title} | ||||||
|  |         </div> | ||||||
|  |       </CCardHeader> | ||||||
|  |       <CCardBody> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol sm="4"> | ||||||
|  |             <div block="true">{t('common.endpoint')}:</div> | ||||||
|  |           </CCol> | ||||||
|  |           <CCol> | ||||||
|  |             <div block="true">{info.endpoint}</div> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol sm="4"> | ||||||
|  |             <div block="true">{t('system.hostname')}:</div> | ||||||
|  |           </CCol> | ||||||
|  |           <CCol> | ||||||
|  |             <div block="true">{info.hostname}</div> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol sm="4"> | ||||||
|  |             <div block="true">{t('system.os')}:</div> | ||||||
|  |           </CCol> | ||||||
|  |           <CCol> | ||||||
|  |             <div block="true">{info.os}</div> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol sm="4"> | ||||||
|  |             <div block="true">{t('system.processors')}:</div> | ||||||
|  |           </CCol> | ||||||
|  |           <CCol> | ||||||
|  |             <div block="true">{info.processors}</div> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol sm="4"> | ||||||
|  |             <div block="true">{t('common.start')}:</div> | ||||||
|  |           </CCol> | ||||||
|  |           <CCol> | ||||||
|  |             <div block="true"> | ||||||
|  |               {info.start ? <FormattedDate date={info.start} /> : t('common.unknown')} | ||||||
|  |             </div> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol sm="4"> | ||||||
|  |             <div block="true">{t('status.uptime')}:</div> | ||||||
|  |           </CCol> | ||||||
|  |           <CCol> | ||||||
|  |             <div block="true">{info.uptime}</div> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol sm="4"> | ||||||
|  |             <div block="true">{t('footer.version')}:</div> | ||||||
|  |           </CCol> | ||||||
|  |           <CCol> | ||||||
|  |             <div block="true">{info.version}</div> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol sm="4"> | ||||||
|  |             <div block="true">{t('common.certificates')}:</div> | ||||||
|  |           </CCol> | ||||||
|  |           <CCol> | ||||||
|  |             <div block="true"> | ||||||
|  |               {info.certificates?.length > 0 ? ( | ||||||
|  |                 <CButton className="ml-0 pl-0 py-0" color="link" onClick={toggleCerts}> | ||||||
|  |                   {t('common.details')} ({info.certificates.length}) | ||||||
|  |                 </CButton> | ||||||
|  |               ) : ( | ||||||
|  |                 <div>{t('common.unknown')}</div> | ||||||
|  |               )} | ||||||
|  |             </div> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol sm="4" className="pt-1"> | ||||||
|  |             <div block="true">{t('system.reload_subsystems')}:</div> | ||||||
|  |           </CCol> | ||||||
|  |           <CCol> | ||||||
|  |             <div block="true"> | ||||||
|  |               {info.subsystems.length === 0 ? ( | ||||||
|  |                 t('common.unknown') | ||||||
|  |               ) : ( | ||||||
|  |                 <div> | ||||||
|  |                   <div className="float-left" style={{ width: '85%' }}> | ||||||
|  |                     <Select | ||||||
|  |                       isMulti | ||||||
|  |                       closeMenuOnSelect={false} | ||||||
|  |                       name="Subsystems" | ||||||
|  |                       options={info.subsystems.map((sys) => ({ value: sys, label: sys }))} | ||||||
|  |                       onChange={setTypes} | ||||||
|  |                       value={types} | ||||||
|  |                       className="basic-multi-select" | ||||||
|  |                       classNamePrefix="select" | ||||||
|  |                     /> | ||||||
|  |                   </div> | ||||||
|  |                   <div className="float-left text-right" style={{ width: '15%' }}> | ||||||
|  |                     <CPopover content={t('system.reload')}> | ||||||
|  |                       <CButton | ||||||
|  |                         color="primary" | ||||||
|  |                         variant="outline" | ||||||
|  |                         onClick={submit} | ||||||
|  |                         disabled={types.length === 0} | ||||||
|  |                       > | ||||||
|  |                         <CIcon content={cilSync} /> | ||||||
|  |                       </CButton> | ||||||
|  |                     </CPopover> | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               )} | ||||||
|  |             </div> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |       </CCardBody> | ||||||
|  |       <CModal size="lg" show={showCerts} onClose={toggleCerts}> | ||||||
|  |         <CModalHeader className="p-1"> | ||||||
|  |           <CModalTitle className="pl-1 pt-1">{t('common.certificates')}</CModalTitle> | ||||||
|  |           <div className="text-right"> | ||||||
|  |             <CPopover content={t('common.close')}> | ||||||
|  |               <CButton color="primary" variant="outline" className="ml-2" onClick={toggleCerts}> | ||||||
|  |                 <CIcon content={cilX} /> | ||||||
|  |               </CButton> | ||||||
|  |             </CPopover> | ||||||
|  |           </div> | ||||||
|  |         </CModalHeader> | ||||||
|  |         <CModalBody> | ||||||
|  |           <CDataTable | ||||||
|  |             addTableClasses="table-sm" | ||||||
|  |             border | ||||||
|  |             items={info?.certificates.map((cert) => ({ | ||||||
|  |               ...cert, | ||||||
|  |               expiresOn: prettyDate(cert.expiresOn), | ||||||
|  |             }))} | ||||||
|  |           /> | ||||||
|  |         </CModalBody> | ||||||
|  |       </CModal> | ||||||
|  |     </CCard> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | ApiStatusCard.propTypes = { | ||||||
|  |   t: PropTypes.func.isRequired, | ||||||
|  |   info: PropTypes.instanceOf(Object).isRequired, | ||||||
|  |   reload: PropTypes.func.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default React.memo(ApiStatusCard); | ||||||
| @@ -1,22 +1,135 @@ | |||||||
| import React from 'react'; | import React, { useState, useEffect } from 'react'; | ||||||
|  | import { v4 as createUuid } from 'uuid'; | ||||||
|  | import { CRow, CCol } from '@coreui/react'; | ||||||
|  | import { useAuth, useToast } from 'ucentral-libs'; | ||||||
|  | import { secondsToDetailed } from 'utils/helper'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import { SystemPage as Page, useToast, useAuth } from 'ucentral-libs'; |  | ||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
|  | import ApiStatusCard from './ApiStatusCard'; | ||||||
|  |  | ||||||
| const SystemPage = () => { | const SystemPage = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const { currentToken, endpoints } = useAuth(); |   const { currentToken, endpoints } = useAuth(); | ||||||
|   const { addToast } = useToast(); |   const { addToast } = useToast(); | ||||||
|  |   const [endpointsInfo, setEndpointsInfo] = useState([]); | ||||||
|  |  | ||||||
|  |   const getSystemInfo = async (key, endpoint) => { | ||||||
|  |     let systemInfo = { | ||||||
|  |       title: key, | ||||||
|  |       endpoint, | ||||||
|  |       hostname: t('common.unknown'), | ||||||
|  |       os: t('common.unknown'), | ||||||
|  |       processors: t('common.unknown'), | ||||||
|  |       uptime: t('common.unknown'), | ||||||
|  |       version: t('common.unknown'), | ||||||
|  |       certificates: [], | ||||||
|  |       subsystems: [], | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const options = { | ||||||
|  |       headers: { | ||||||
|  |         Accept: 'application/json', | ||||||
|  |         Authorization: `Bearer ${currentToken}`, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     await axiosInstance | ||||||
|  |       .get(`${endpoint}/api/v1/system?command=info`, options) | ||||||
|  |       .then((newInfo) => { | ||||||
|  |         systemInfo = { ...systemInfo, ...newInfo.data }; | ||||||
|  |         systemInfo.uptime = secondsToDetailed( | ||||||
|  |           newInfo.data.uptime, | ||||||
|  |           t('common.day'), | ||||||
|  |           t('common.days'), | ||||||
|  |           t('common.hour'), | ||||||
|  |           t('common.hours'), | ||||||
|  |           t('common.minute'), | ||||||
|  |           t('common.minutes'), | ||||||
|  |           t('common.second'), | ||||||
|  |           t('common.seconds'), | ||||||
|  |         ); | ||||||
|  |         systemInfo.start = newInfo.data.start; | ||||||
|  |       }) | ||||||
|  |       .catch(() => {}); | ||||||
|  |     await axiosInstance | ||||||
|  |       .post(`${endpoint}/api/v1/system`, { command: 'getsubsystemnames' }, options) | ||||||
|  |       .then((newSubs) => { | ||||||
|  |         systemInfo.subsystems = newSubs.data.list.sort((a, b) => { | ||||||
|  |           if (a < b) return -1; | ||||||
|  |           if (a > b) return 1; | ||||||
|  |           return 0; | ||||||
|  |         }); | ||||||
|  |       }) | ||||||
|  |       .catch(() => {}); | ||||||
|  |  | ||||||
|  |     return systemInfo; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const reload = (subsystems, endpoint) => { | ||||||
|  |     const options = { | ||||||
|  |       headers: { | ||||||
|  |         Accept: 'application/json', | ||||||
|  |         Authorization: `Bearer ${currentToken}`, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const parameters = { | ||||||
|  |       command: 'reload', | ||||||
|  |       subsystems, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     axiosInstance | ||||||
|  |       .post(`${endpoint}/api/v1/system?command=info`, parameters, options) | ||||||
|  |       .then(() => { | ||||||
|  |         addToast({ | ||||||
|  |           title: t('common.success'), | ||||||
|  |           body: t('system.success_reload'), | ||||||
|  |           color: 'success', | ||||||
|  |           autohide: true, | ||||||
|  |         }); | ||||||
|  |       }) | ||||||
|  |       .catch((e) => { | ||||||
|  |         addToast({ | ||||||
|  |           title: t('common.error'), | ||||||
|  |           body: t('system.error_reloading', { error: e.response?.data?.ErrorDescription }), | ||||||
|  |           color: 'danger', | ||||||
|  |           autohide: true, | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const getAllInfo = async () => { | ||||||
|  |     const promises = []; | ||||||
|  |  | ||||||
|  |     for (const [key, value] of Object.entries(endpoints)) { | ||||||
|  |       promises.push(getSystemInfo(key, value)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       const results = await Promise.all(promises); | ||||||
|  |       setEndpointsInfo(results); | ||||||
|  |     } catch (e) { | ||||||
|  |       addToast({ | ||||||
|  |         title: t('common.error'), | ||||||
|  |         body: t('system.error_fetching'), | ||||||
|  |         color: 'danger', | ||||||
|  |         autohide: true, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     getAllInfo(); | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Page |     <CRow> | ||||||
|       t={t} |       {endpointsInfo.map((info) => ( | ||||||
|       currentToken={currentToken} |         <CCol sm="12" lg="6" xxl="4" key={createUuid()}> | ||||||
|       endpoints={endpoints} |           <ApiStatusCard t={t} info={info} reload={reload} /> | ||||||
|       addToast={addToast} |         </CCol> | ||||||
|       axiosInstance={axiosInstance} |       ))} | ||||||
|     /> |     </CRow> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default SystemPage; | export default SystemPage; | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ const Routes = () => { | |||||||
|       path="/" |       path="/" | ||||||
|       name="Devices" |       name="Devices" | ||||||
|       render={(props) => |       render={(props) => | ||||||
|         currentToken !== '' && Object.keys(endpoints).length !== 0 ? ( |         currentToken !== '' && endpoints && Object.keys(endpoints).length !== 0 ? ( | ||||||
|           <TheLayout {...props} /> |           <TheLayout {...props} /> | ||||||
|         ) : ( |         ) : ( | ||||||
|           <ToastProvider> |           <ToastProvider> | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import * as axios from 'axios'; | import * as axios from 'axios'; | ||||||
| import axiosRetry from 'axios-retry'; | import axiosRetry from 'axios-retry'; | ||||||
|  | import { LOGOUT_ON_SEC_ERROR_CODES } from 'constants'; | ||||||
|  |  | ||||||
| const axiosInstance = axios.create(); | const axiosInstance = axios.create(); | ||||||
|  |  | ||||||
| @@ -27,7 +28,7 @@ axiosInstance.interceptors.response.use( | |||||||
|           retries += 1; |           retries += 1; | ||||||
|           localStorage.setItem('sec_retries', retries); |           localStorage.setItem('sec_retries', retries); | ||||||
|         } |         } | ||||||
|         if (error.response.data?.ErrorCode === 9) { |         if (LOGOUT_ON_SEC_ERROR_CODES.includes(error.response.data?.ErrorCode)) { | ||||||
|           localStorage.removeItem('access_token'); |           localStorage.removeItem('access_token'); | ||||||
|           localStorage.removeItem('gateway_endpoints'); |           localStorage.removeItem('gateway_endpoints'); | ||||||
|           sessionStorage.clear(); |           sessionStorage.clear(); | ||||||
|   | |||||||
| @@ -1,5 +1,3 @@ | |||||||
| export const cleanTimestamp = (timestamp) => timestamp.replace('T', ' ').replace('Z', ' '); |  | ||||||
|  |  | ||||||
| export const cleanBytesString = (bytes, decimals = 2) => { | export const cleanBytesString = (bytes, decimals = 2) => { | ||||||
|   const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; |   const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; | ||||||
|   if (!bytes || bytes === 0) { |   if (!bytes || bytes === 0) { | ||||||
| @@ -132,6 +130,25 @@ export const compactSecondsToDetailed = (seconds, dayLabel, daysLabel, secondsLa | |||||||
|   return finalString; |   return finalString; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | export const extraCompactSecondsToDetailed = (seconds) => { | ||||||
|  |   let secondsLeft = seconds; | ||||||
|  |   const days = Math.floor(secondsLeft / (3600 * 24)); | ||||||
|  |   secondsLeft -= days * (3600 * 24); | ||||||
|  |   const hours = Math.floor(secondsLeft / 3600); | ||||||
|  |   secondsLeft -= hours * 3600; | ||||||
|  |   const minutes = Math.floor(secondsLeft / 60); | ||||||
|  |   secondsLeft -= minutes * 60; | ||||||
|  |  | ||||||
|  |   let finalString = ''; | ||||||
|  |  | ||||||
|  |   finalString = `${finalString}${prettyNumber(days)}:`; | ||||||
|  |   finalString = `${finalString}${prettyNumber(hours)}:`; | ||||||
|  |   finalString = `${finalString}${prettyNumber(minutes)}:`; | ||||||
|  |   finalString = `${finalString}${prettyNumber(secondsLeft)}`; | ||||||
|  |  | ||||||
|  |   return finalString; | ||||||
|  | }; | ||||||
|  |  | ||||||
| export const validateEmail = (email) => { | export const validateEmail = (email) => { | ||||||
|   const regex = /\S+@\S+\.\S+/; |   const regex = /\S+@\S+\.\S+/; | ||||||
|   return regex.test(email); |   return regex.test(email); | ||||||
| @@ -143,3 +160,25 @@ export const testRegex = (value, regexString) => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| export const datesSameDay = (first, second) => first.getDate() === second.getDate(); | export const datesSameDay = (first, second) => first.getDate() === second.getDate(); | ||||||
|  |  | ||||||
|  | const units = { | ||||||
|  |   year: 24 * 60 * 60 * 1000 * 365, | ||||||
|  |   month: (24 * 60 * 60 * 1000 * 365) / 12, | ||||||
|  |   day: 24 * 60 * 60 * 1000, | ||||||
|  |   hour: 60 * 60 * 1000, | ||||||
|  |   minute: 60 * 1000, | ||||||
|  |   second: 1000, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const rtf = new Intl.RelativeTimeFormat('en', { localeMatcher: 'best fit', style: 'long' }); | ||||||
|  | export const formatDaysAgo = (d1, d2 = new Date()) => { | ||||||
|  |   const convertedTimestamp = unixToDateString(d1); | ||||||
|  |   const date = new Date(convertedTimestamp); | ||||||
|  |   const elapsed = date - d2; | ||||||
|  |  | ||||||
|  |   for (const [key] of Object.entries(units)) | ||||||
|  |     if (Math.abs(elapsed) > units[key] || key === 'second') | ||||||
|  |       return rtf.format(Math.round(elapsed / units[key]), key); | ||||||
|  |  | ||||||
|  |   return prettyDate(date); | ||||||
|  | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user