mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
				synced 2025-10-30 02:12:33 +00:00 
			
		
		
		
	Compare commits
	
		
			43 Commits
		
	
	
		
			v2.5.0-RC1
			...
			v2.6.0-RC1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 5101e41565 | ||
|   | ebd2419634 | ||
|   | 133c256543 | ||
|   | 98a2a72f33 | ||
|   | f008fd082e | ||
|   | d2fd895582 | ||
|   | 746a812ae8 | ||
|   | b67c69b88b | ||
|   | f6ee20730a | ||
|   | 2829a96c84 | ||
|   | 37e1a92a89 | ||
|   | 81c4717472 | ||
|   | 94aac686c9 | ||
|   | b75848515b | ||
|   | a26cf9a3ff | ||
|   | a7e4f728d2 | ||
|   | 921d234972 | ||
|   | 6bec9f977f | ||
|   | 6eaa9f8af1 | ||
|   | 5ef189b445 | ||
|   | 9f8283892e | ||
|   | 6ba2dc9601 | ||
|   | e23512c860 | ||
|   | 32b6fe1625 | ||
|   | 8663b6d108 | ||
|   | 7794aa4c99 | ||
|   | ba90ea59f4 | ||
|   | aadb4c44a1 | ||
|   | 467ad39873 | ||
|   | 0a92b2db48 | ||
|   | 60a8f1ea61 | ||
|   | 1063061b47 | ||
|   | 54186575e0 | ||
|   | 114005d572 | ||
|   | cde59a5ab1 | ||
|   | e6bb26ce12 | ||
|   | 0cde953d58 | ||
|   | ce7a804a70 | ||
|   | 67716aedde | ||
|   | 54a98cd6e5 | ||
|   | 31a901bea9 | ||
|   | f0fdc90226 | ||
|   | e14f892bc6 | 
							
								
								
									
										33
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -12,6 +12,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - 'release/*' | ||||
|  | ||||
| defaults: | ||||
|   run: | ||||
| @@ -37,3 +38,35 @@ jobs: | ||||
|         registry: tip-tip-wlan-cloud-ucentral.jfrog.io | ||||
|         registry_user: ucentral | ||||
|         registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} | ||||
|  | ||||
|     - name: Notify on failure via Slack | ||||
|       if: failure() && github.ref == 'refs/heads/main' | ||||
|       uses: rtCamp/action-slack-notify@v2 | ||||
|       env: | ||||
|         SLACK_USERNAME: GitHub Actions failure notifier | ||||
|         SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} | ||||
|         SLACK_COLOR: "${{ job.status }}" | ||||
|         SLACK_ICON: https://raw.githubusercontent.com/quintessence/slack-icons/master/images/github-logo-slack-icon.png | ||||
|         SLACK_TITLE: Docker build failed for OWGW-UI service | ||||
|  | ||||
|   trigger-deploy-to-dev: | ||||
|     runs-on: ubuntu-latest | ||||
|     if: github.ref == 'refs/heads/main' | ||||
|     needs: | ||||
|       - docker | ||||
|     steps: | ||||
|     - name: Checkout actions repo | ||||
|       uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: Telecominfraproject/.github | ||||
|         path: github | ||||
|  | ||||
|     - name: Trigger deployment of the latest version to dev instance and wait for result | ||||
|       uses: ./github/composite-actions/trigger-workflow-and-wait | ||||
|       with: | ||||
|         owner: Telecominfraproject | ||||
|         repo: wlan-testing | ||||
|         workflow: ucentralgw-dev-deployment.yaml | ||||
|         token: ${{ secrets.WLAN_TESTING_PAT }} | ||||
|         ref: master | ||||
|         inputs: '{"force_latest": "true"}' | ||||
|   | ||||
							
								
								
									
										1
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							| @@ -4,6 +4,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - 'release/*' | ||||
|     types: [ closed ] | ||||
|  | ||||
| defaults: | ||||
|   | ||||
							
								
								
									
										46
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| name: Release chart package | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     tags: | ||||
|       - 'v*' | ||||
|  | ||||
| defaults: | ||||
|   run: | ||||
|     shell: bash | ||||
|  | ||||
| jobs: | ||||
|   helm-package: | ||||
|     runs-on: ubuntu-20.04 | ||||
|     env: | ||||
|       HELM_REPO_URL: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/ | ||||
|       HELM_REPO_USERNAME: ucentral | ||||
|     steps: | ||||
|       - name: Checkout uCentral assembly chart repo | ||||
|         uses: actions/checkout@v2 | ||||
|         with: | ||||
|           path: wlan-cloud-ucentralgw-ui | ||||
|  | ||||
|       - name: Build package | ||||
|         working-directory: wlan-cloud-ucentralgw-ui/helm | ||||
|         run: | | ||||
|           helm plugin install https://github.com/aslafy-z/helm-git --version 0.10.0 | ||||
|           helm repo add bitnami https://charts.bitnami.com/bitnami | ||||
|           helm repo update | ||||
|           helm dependency update | ||||
|           mkdir dist | ||||
|           helm package . -d dist | ||||
|  | ||||
|       - name: Generate GitHub release body | ||||
|         working-directory: wlan-cloud-ucentralgw-ui/helm | ||||
|         run: | | ||||
|           pip3 install yq -q | ||||
|           echo "Docker image - tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui:$GITHUB_REF_NAME" > release.txt | ||||
|           echo "Helm charted may be attached to this release" >> release.txt | ||||
|           echo "Deployment artifacts may be found in https://github.com/Telecominfraproject/wlan-cloud-ucentral-deploy/tree/$GITHUB_REF_NAME" >> release.txt | ||||
|  | ||||
|       - name: Create GitHub release | ||||
|         uses: softprops/action-gh-release@v1 | ||||
|         with: | ||||
|           body_path: wlan-cloud-ucentralgw-ui/helm/release.txt | ||||
|           files: wlan-cloud-ucentralgw-ui/helm/dist/* | ||||
| @@ -30,3 +30,13 @@ Create chart name and version as used by the chart label. | ||||
| {{- define "owgwui.chart" -}} | ||||
| {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} | ||||
| {{- end -}} | ||||
|  | ||||
| {{- define "owgwui.ingress.apiVersion" -}} | ||||
|   {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" -}} | ||||
|       {{- print "networking.k8s.io/v1" -}} | ||||
|   {{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" -}} | ||||
|     {{- print "networking.k8s.io/v1beta1" -}} | ||||
|   {{- else -}} | ||||
|     {{- print "extensions/v1beta1" -}} | ||||
|   {{- end -}} | ||||
| {{- end -}} | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| {{- range $ingress, $ingressValue := .Values.ingresses }} | ||||
| {{- if $ingressValue.enabled }} | ||||
| --- | ||||
| apiVersion: extensions/v1beta1 | ||||
| apiVersion: {{ include "owgwui.ingress.apiVersion" $root }} | ||||
| kind: Ingress | ||||
| metadata: | ||||
|   name: {{ include "owgwui.fullname" $root }}-{{ $ingress }} | ||||
| @@ -36,9 +36,23 @@ spec: | ||||
|       paths: | ||||
|       {{- range $ingressValue.paths }} | ||||
|         - path: {{ .path }} | ||||
|           {{- if $root.Capabilities.APIVersions.Has "networking.k8s.io/v1" }} | ||||
|           pathType: {{ .pathType | default "ImplementationSpecific" }} | ||||
|           {{- end }} | ||||
|           backend: | ||||
|             {{- if $root.Capabilities.APIVersions.Has "networking.k8s.io/v1" }} | ||||
|             service: | ||||
|               name: {{ include "owgwui.fullname" $root }}-{{ .serviceName }} | ||||
|               port: | ||||
|               {{- if kindIs "string" .servicePort }} | ||||
|                 name: {{ .servicePort }} | ||||
|               {{- else }} | ||||
|                 number: {{ .servicePort }} | ||||
|               {{- end }} | ||||
|             {{- else }} | ||||
|             serviceName: {{ include "owgwui.fullname" $root }}-{{ .serviceName }} | ||||
|             servicePort: {{ .servicePort }} | ||||
|             {{- end }} | ||||
|       {{- end }} | ||||
|   {{- end }} | ||||
|  | ||||
|   | ||||
| @@ -8,12 +8,12 @@ fullnameOverride: "" | ||||
| images: | ||||
|   owgwui: | ||||
|     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui | ||||
|     tag: main | ||||
|     tag: v2.6.0-RC1 | ||||
|     pullPolicy: Always | ||||
|  | ||||
| services: | ||||
|   owgwui: | ||||
|     type: NodePort | ||||
|     type: ClusterIP | ||||
|     ports: | ||||
|       http: | ||||
|         servicePort: 80 | ||||
| @@ -49,6 +49,7 @@ ingresses: | ||||
|     - chart-example.local | ||||
|     paths: | ||||
|     - path: / | ||||
|       pathType: ImplementationSpecific | ||||
|       serviceName: owgwui | ||||
|       servicePort: http | ||||
|  | ||||
|   | ||||
							
								
								
									
										144
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										144
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | ||||
| { | ||||
|   "name": "ucentral-client", | ||||
|   "version": "2.5.44", | ||||
|   "version": "2.6.25", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "ucentral-client", | ||||
|       "version": "2.5.44", | ||||
|       "version": "2.6.25", | ||||
|       "dependencies": { | ||||
|         "@coreui/coreui": "^3.4.0", | ||||
|         "@coreui/icons": "^2.0.1", | ||||
| @@ -23,6 +23,7 @@ | ||||
|         "prop-types": "^15.7.2", | ||||
|         "react": "^17.0.2", | ||||
|         "react-apexcharts": "^1.3.9", | ||||
|         "react-country-flag": "^3.0.2", | ||||
|         "react-csv": "^2.2.2", | ||||
|         "react-dom": "^17.0.2", | ||||
|         "react-flow-renderer": "^9.6.6", | ||||
| @@ -2944,6 +2945,18 @@ | ||||
|         "url": "https://github.com/sponsors/sindresorhus" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ansi-escapes/node_modules/type-fest": { | ||||
|       "version": "0.21.3", | ||||
|       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", | ||||
|       "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", | ||||
|       "dev": true, | ||||
|       "engines": { | ||||
|         "node": ">=10" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/sindresorhus" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ansi-html": { | ||||
|       "version": "0.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", | ||||
| @@ -3176,9 +3189,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/async": { | ||||
|       "version": "2.6.3", | ||||
|       "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", | ||||
|       "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", | ||||
|       "version": "2.6.4", | ||||
|       "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", | ||||
|       "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", | ||||
|       "dev": true, | ||||
|       "dependencies": { | ||||
|         "lodash": "^4.17.14" | ||||
| @@ -3943,9 +3956,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cliui/node_modules/ansi-regex": { | ||||
|       "version": "4.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", | ||||
|       "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", | ||||
|       "version": "4.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", | ||||
|       "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", | ||||
|       "dev": true, | ||||
|       "engines": { | ||||
|         "node": ">=6" | ||||
| @@ -6740,9 +6753,9 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "node_modules/follow-redirects": { | ||||
|       "version": "1.14.7", | ||||
|       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", | ||||
|       "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", | ||||
|       "version": "1.14.9", | ||||
|       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", | ||||
|       "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", | ||||
|       "funding": [ | ||||
|         { | ||||
|           "type": "individual", | ||||
| @@ -9101,9 +9114,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/minimist": { | ||||
|       "version": "1.2.5", | ||||
|       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", | ||||
|       "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", | ||||
|       "version": "1.2.6", | ||||
|       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
|       "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", | ||||
|       "devOptional": true | ||||
|     }, | ||||
|     "node_modules/mixin-deep": { | ||||
| @@ -9132,9 +9145,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/moment": { | ||||
|       "version": "2.29.1", | ||||
|       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", | ||||
|       "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", | ||||
|       "version": "2.29.3", | ||||
|       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", | ||||
|       "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==", | ||||
|       "engines": { | ||||
|         "node": "*" | ||||
|       } | ||||
| @@ -10915,6 +10928,17 @@ | ||||
|         "react": ">=0.13" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-country-flag": { | ||||
|       "version": "3.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/react-country-flag/-/react-country-flag-3.0.2.tgz", | ||||
|       "integrity": "sha512-JPaz+q3QD0/nZtHBKj5x3O7r/SgSG9kxbymdaIU0RqlDAcorJIe4KV0DFhWIdKh69q5cPVkIVERcMYGZdvXgAA==", | ||||
|       "engines": { | ||||
|         "node": ">=12" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "react": ">=16" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-csv": { | ||||
|       "version": "2.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz", | ||||
| @@ -13308,10 +13332,12 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/type-fest": { | ||||
|       "version": "0.21.3", | ||||
|       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", | ||||
|       "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", | ||||
|       "version": "0.13.1", | ||||
|       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", | ||||
|       "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "peer": true, | ||||
|       "engines": { | ||||
|         "node": ">=10" | ||||
|       }, | ||||
| @@ -13570,9 +13596,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/url-parse": { | ||||
|       "version": "1.5.4", | ||||
|       "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.4.tgz", | ||||
|       "integrity": "sha512-ITeAByWWoqutFClc/lRZnFplgXgEZr3WJ6XngMM/N9DMIm4K8zXPCZ1Jdu0rERwO84w1WC5wkle2ubwTA4NTBg==", | ||||
|       "version": "1.5.10", | ||||
|       "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", | ||||
|       "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", | ||||
|       "dev": true, | ||||
|       "dependencies": { | ||||
|         "querystringify": "^2.1.1", | ||||
| @@ -14804,9 +14830,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/yargs/node_modules/ansi-regex": { | ||||
|       "version": "4.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", | ||||
|       "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", | ||||
|       "version": "4.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", | ||||
|       "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", | ||||
|       "dev": true, | ||||
|       "engines": { | ||||
|         "node": ">=6" | ||||
| @@ -17000,6 +17026,14 @@ | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "type-fest": "^0.21.3" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "type-fest": { | ||||
|           "version": "0.21.3", | ||||
|           "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", | ||||
|           "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "ansi-html": { | ||||
| @@ -17168,9 +17202,9 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "async": { | ||||
|       "version": "2.6.3", | ||||
|       "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", | ||||
|       "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", | ||||
|       "version": "2.6.4", | ||||
|       "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", | ||||
|       "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "lodash": "^4.17.14" | ||||
| @@ -17780,9 +17814,9 @@ | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "ansi-regex": { | ||||
|           "version": "4.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", | ||||
|           "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", | ||||
|           "version": "4.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", | ||||
|           "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "emoji-regex": { | ||||
| @@ -19924,9 +19958,9 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "follow-redirects": { | ||||
|       "version": "1.14.7", | ||||
|       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", | ||||
|       "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" | ||||
|       "version": "1.14.9", | ||||
|       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", | ||||
|       "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" | ||||
|     }, | ||||
|     "for-in": { | ||||
|       "version": "1.0.2", | ||||
| @@ -21682,9 +21716,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "minimist": { | ||||
|       "version": "1.2.5", | ||||
|       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", | ||||
|       "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", | ||||
|       "version": "1.2.6", | ||||
|       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
|       "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", | ||||
|       "devOptional": true | ||||
|     }, | ||||
|     "mixin-deep": { | ||||
| @@ -21707,9 +21741,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "moment": { | ||||
|       "version": "2.29.1", | ||||
|       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", | ||||
|       "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" | ||||
|       "version": "2.29.3", | ||||
|       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", | ||||
|       "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==" | ||||
|     }, | ||||
|     "mrmime": { | ||||
|       "version": "1.0.0", | ||||
| @@ -22983,6 +23017,12 @@ | ||||
|         "prop-types": "^15.5.7" | ||||
|       } | ||||
|     }, | ||||
|     "react-country-flag": { | ||||
|       "version": "3.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/react-country-flag/-/react-country-flag-3.0.2.tgz", | ||||
|       "integrity": "sha512-JPaz+q3QD0/nZtHBKj5x3O7r/SgSG9kxbymdaIU0RqlDAcorJIe4KV0DFhWIdKh69q5cPVkIVERcMYGZdvXgAA==", | ||||
|       "requires": {} | ||||
|     }, | ||||
|     "react-csv": { | ||||
|       "version": "2.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz", | ||||
| @@ -24844,10 +24884,12 @@ | ||||
|       } | ||||
|     }, | ||||
|     "type-fest": { | ||||
|       "version": "0.21.3", | ||||
|       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", | ||||
|       "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", | ||||
|       "dev": true | ||||
|       "version": "0.13.1", | ||||
|       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", | ||||
|       "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "peer": true | ||||
|     }, | ||||
|     "type-is": { | ||||
|       "version": "1.6.18", | ||||
| @@ -25062,9 +25104,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "url-parse": { | ||||
|       "version": "1.5.4", | ||||
|       "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.4.tgz", | ||||
|       "integrity": "sha512-ITeAByWWoqutFClc/lRZnFplgXgEZr3WJ6XngMM/N9DMIm4K8zXPCZ1Jdu0rERwO84w1WC5wkle2ubwTA4NTBg==", | ||||
|       "version": "1.5.10", | ||||
|       "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", | ||||
|       "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "querystringify": "^2.1.1", | ||||
| @@ -25983,9 +26025,9 @@ | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "ansi-regex": { | ||||
|           "version": "4.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", | ||||
|           "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", | ||||
|           "version": "4.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", | ||||
|           "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "emoji-regex": { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "ucentral-client", | ||||
|   "version": "2.5.44", | ||||
|   "version": "2.6.25", | ||||
|   "dependencies": { | ||||
|     "@coreui/coreui": "^3.4.0", | ||||
|     "@coreui/icons": "^2.0.1", | ||||
| @@ -17,6 +17,7 @@ | ||||
|     "prop-types": "^15.7.2", | ||||
|     "react": "^17.0.2", | ||||
|     "react-apexcharts": "^1.3.9", | ||||
|     "react-country-flag": "^3.0.2", | ||||
|     "react-csv": "^2.2.2", | ||||
|     "react-dom": "^17.0.2", | ||||
|     "react-flow-renderer": "^9.6.6", | ||||
|   | ||||
| @@ -62,12 +62,14 @@ const BlinkModal = ({ show, toggleModal }) => { | ||||
|         { headers }, | ||||
|       ) | ||||
|       .then(() => { | ||||
|         addToast({ | ||||
|           title: t('common.success'), | ||||
|           body: t('commands.command_success'), | ||||
|           color: 'success', | ||||
|           autohide: true, | ||||
|         }); | ||||
|         if (chosenPattern !== 'blink') { | ||||
|           addToast({ | ||||
|             title: t('common.success'), | ||||
|             body: t('commands.command_success'), | ||||
|             color: 'success', | ||||
|             autohide: true, | ||||
|           }); | ||||
|         } | ||||
|         toggleModal(); | ||||
|       }) | ||||
|       .catch(() => { | ||||
| @@ -145,8 +147,10 @@ const BlinkModal = ({ show, toggleModal }) => { | ||||
|           </CModalBody> | ||||
|           <CModalFooter> | ||||
|             <LoadingButton | ||||
|               label={t('blink.set_leds')} | ||||
|               isLoadingLabel={t('common.loading_ellipsis')} | ||||
|               label={t('common.submit')} | ||||
|               isLoadingLabel={ | ||||
|                 chosenPattern === 'blink' ? 'LEDs are blinking...  ' : t('common.loading_ellipsis') | ||||
|               } | ||||
|               isLoading={waiting} | ||||
|               action={doAction} | ||||
|               block={false} | ||||
|   | ||||
| @@ -37,7 +37,7 @@ const ConfigurationDisplay = ({ getData, deviceConfig }) => { | ||||
|           <CopyToClipboardButton | ||||
|             t={t} | ||||
|             size="sm" | ||||
|             content={JSON.stringify(deviceConfig?.configuration ?? {})} | ||||
|             content={JSON.stringify(deviceConfig?.configuration ?? {}, null, 4)} | ||||
|           /> | ||||
|         </h5> | ||||
|         <CRow> | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { | ||||
|   CAlert, | ||||
|   CButton, | ||||
|   CModal, | ||||
|   CModalHeader, | ||||
| @@ -100,8 +101,14 @@ const ConfigureModal = ({ show, toggleModal }) => { | ||||
|         }); | ||||
|         toggleModal(); | ||||
|       }) | ||||
|       .catch(() => { | ||||
|       .catch((e) => { | ||||
|         setResponseBody('Error while submitting command!'); | ||||
|         addToast({ | ||||
|           title: t('common.error'), | ||||
|           body: `${t('common.general_error')}: ${e.response?.data?.ErrorDescription}`, | ||||
|           color: 'danger', | ||||
|           autohide: true, | ||||
|         }); | ||||
|         setHadFailure(true); | ||||
|       }) | ||||
|       .finally(() => { | ||||
| @@ -196,11 +203,9 @@ const ConfigureModal = ({ show, toggleModal }) => { | ||||
|                 /> | ||||
|               </CCol> | ||||
|             </CRow> | ||||
|             <div hidden={!hadSuccess && !hadFailure}> | ||||
|               <div> | ||||
|                 <pre className="ignore">{responseBody}</pre> | ||||
|               </div> | ||||
|             </div> | ||||
|             <CAlert color="danger" hidden={!hadSuccess && !hadFailure}> | ||||
|               {responseBody} | ||||
|             </CAlert> | ||||
|           </CModalBody> | ||||
|           <CModalFooter> | ||||
|             <div hidden={!checkingIfSure}>Are you sure?</div> | ||||
|   | ||||
| @@ -66,7 +66,16 @@ const DeviceDashboard = ({ t, data, loading }) => ( | ||||
|         </CCol> | ||||
|         <CCol> | ||||
|           <CWidgetIcon | ||||
|             text={t('common.devices')} | ||||
|             text={ | ||||
|               <div> | ||||
|                 <div className="float-left">{t('common.devices')}</div> | ||||
|                 <div className="float-left ml-2"> | ||||
|                   <CPopover content={t('device.count_explanation')}> | ||||
|                     <CIcon content={cilInfo} /> | ||||
|                   </CPopover> | ||||
|                 </div> | ||||
|               </div> | ||||
|             } | ||||
|             header={<h2>{data.numberOfDevices}</h2>} | ||||
|             color="primary" | ||||
|             iconPadding={false} | ||||
| @@ -87,7 +96,10 @@ const DeviceDashboard = ({ t, data, loading }) => ( | ||||
|                   tooltips: { | ||||
|                     callbacks: { | ||||
|                       title: (item, ds) => ds.labels[item[0].index], | ||||
|                       label: (item, ds) => `${ds.datasets[0].data[item.index]}%`, | ||||
|                       label: (item, ds) => | ||||
|                         `${ds.datasets[0].data[item.index]} devices, (${ | ||||
|                           data.statusDevices[ds.datasets[0].data[item.index]] | ||||
|                         }%)`, | ||||
|                     }, | ||||
|                   }, | ||||
|                   legend: { | ||||
| @@ -120,7 +132,9 @@ const DeviceDashboard = ({ t, data, loading }) => ( | ||||
|                     callbacks: { | ||||
|                       title: (item, ds) => ds.labels[item[0].index], | ||||
|                       label: (item, ds) => | ||||
|                         `${ds.datasets[0].data[item.index]}${t('common.of_connected')}`, | ||||
|                         `${ds.datasets[0].data[item.index]} connected devices (${ | ||||
|                           data.healthDevices[ds.datasets[0].data[item.index]] | ||||
|                         }%)`, | ||||
|                     }, | ||||
|                   }, | ||||
|                   legend: { | ||||
| @@ -149,9 +163,9 @@ const DeviceDashboard = ({ t, data, loading }) => ( | ||||
|                     callbacks: { | ||||
|                       title: (item, ds) => ds.labels[item[0].index], | ||||
|                       label: (item, ds) => | ||||
|                         `${ds.datasets[0].data[item.index]}% of ${ | ||||
|                           data.totalAssociations | ||||
|                         } associations`, | ||||
|                         `${ds.datasets[0].data[item.index]} associations (${ | ||||
|                           data.associationData[ds.datasets[0].data[item.index]] | ||||
|                         }%)`, | ||||
|                     }, | ||||
|                   }, | ||||
|                   legend: { | ||||
| @@ -306,7 +320,9 @@ const DeviceDashboard = ({ t, data, loading }) => ( | ||||
|                     callbacks: { | ||||
|                       title: (item, ds) => ds.labels[item[0].index], | ||||
|                       label: (item, ds) => | ||||
|                         `${ds.datasets[0].data[item.index]}${t('common.of_connected')}`, | ||||
|                         `${ds.datasets[0].data[item.index]} connected devices (${ | ||||
|                           data.certificateData[ds.datasets[0].data[item.index]] | ||||
|                         }%)`, | ||||
|                     }, | ||||
|                   }, | ||||
|                   legend: { | ||||
|   | ||||
| @@ -55,9 +55,12 @@ const DeviceDashboard = () => { | ||||
|     const statusColors = []; | ||||
|     const statusLabels = []; | ||||
|     let totalDevices = parsedData.status.reduce((acc, point) => acc + point.value, 0); | ||||
|     parsedData.numberOfDevices = totalDevices; | ||||
|     parsedData.statusDevices = {}; | ||||
|     for (const point of parsedData.status) { | ||||
|       statusDs.push(Math.round((point.value / totalDevices) * 100)); | ||||
|       statusDs.push(point.value); | ||||
|       statusLabels.push(point.tag); | ||||
|       parsedData.statusDevices[point.value] = Math.round((point.value / totalDevices) * 100); | ||||
|       let color = ''; | ||||
|       switch (point.tag) { | ||||
|         case 'connected': | ||||
| @@ -96,7 +99,7 @@ const DeviceDashboard = () => { | ||||
|     const healthLabels = []; | ||||
|     totalDevices = parsedData.healths.reduce((acc, point) => acc + point.value, 0); | ||||
|     for (const point of parsedData.healths) { | ||||
|       healthDs.push(Math.round((point.value / totalDevices) * 100)); | ||||
|       healthDs.push(point.value); | ||||
|       healthLabels.push(point.tag); | ||||
|       let color = ''; | ||||
|       switch (point.tag) { | ||||
| @@ -122,6 +125,12 @@ const DeviceDashboard = () => { | ||||
|       } | ||||
|       healthColors.push(color); | ||||
|     } | ||||
|     parsedData.healthDevices = { | ||||
|       [devicesAt100]: Math.round((devicesAt100 / totalDevices) * 100), | ||||
|       [devicesUp90]: Math.round((devicesUp90 / totalDevices) * 100), | ||||
|       [devicesUp60]: Math.round((devicesUp60 / totalDevices) * 100), | ||||
|       [devicesDown60]: Math.round((devicesDown60 / totalDevices) * 100), | ||||
|     }; | ||||
|     parsedData.healths = { | ||||
|       datasets: [ | ||||
|         { | ||||
| @@ -144,10 +153,12 @@ const DeviceDashboard = () => { | ||||
|     const associationsColors = []; | ||||
|     const associationsLabels = []; | ||||
|     const totalAssociations = parsedData.associations.reduce((acc, point) => acc + point.value, 0); | ||||
|     parsedData.associationData = {}; | ||||
|     for (let i = 0; i < parsedData.associations.length; i += 1) { | ||||
|       const point = parsedData.associations[i]; | ||||
|       associationsDs.push(Math.round((point.value / totalAssociations) * 100)); | ||||
|       associationsDs.push(point.value); | ||||
|       associationsLabels.push(point.tag); | ||||
|       parsedData.associationData[point.value] = Math.round((point.value / totalAssociations) * 100); | ||||
|  | ||||
|       switch (parsedData.associations[i].tag) { | ||||
|         case '2G': | ||||
| @@ -258,8 +269,10 @@ const DeviceDashboard = () => { | ||||
|     const certificatesColors = []; | ||||
|     const certificatesLabels = []; | ||||
|     const totalCerts = parsedData.certificates.reduce((acc, point) => acc + point.value, 0); | ||||
|     parsedData.certificateData = {}; | ||||
|     for (const point of parsedData.certificates) { | ||||
|       certificatesDs.push(Math.round((point.value / totalCerts) * 100)); | ||||
|       certificatesDs.push(point.value); | ||||
|       parsedData.certificateData[point.value] = Math.round((point.value / totalCerts) * 100); | ||||
|       certificatesLabels.push(point.tag); | ||||
|       let color = ''; | ||||
|       switch (point.tag) { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; | ||||
| import { useAuth, useToast, useToggle } from 'ucentral-libs'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { useGlobalWebSocket } from 'contexts/WebSocketProvider'; | ||||
| import Modal from './Modal'; | ||||
|  | ||||
| const DeviceFirmwareModal = ({ | ||||
| @@ -19,6 +20,7 @@ const DeviceFirmwareModal = ({ | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [firmwareVersions, setFirmwareVersions] = useState([]); | ||||
|   const [keepRedirector, toggleKeepRedirector, setKeepRedirector] = useToggle(true); | ||||
|   const { addDeviceListener } = useGlobalWebSocket(); | ||||
|  | ||||
|   const getPartialFirmware = async (offset) => { | ||||
|     const headers = { | ||||
| @@ -90,6 +92,17 @@ const DeviceFirmwareModal = ({ | ||||
|         headers, | ||||
|       }) | ||||
|       .then((response) => { | ||||
|         addDeviceListener({ | ||||
|           serialNumber: device.serialNumber, | ||||
|           types: ['device_firmware_upgrade'], | ||||
|           addToast: (title, body) => | ||||
|             addToast({ | ||||
|               title, | ||||
|               body, | ||||
|               color: 'info', | ||||
|               autohide: true, | ||||
|             }), | ||||
|         }); | ||||
|         setUpgradeStatus({ | ||||
|           loading: false, | ||||
|           result: { | ||||
|   | ||||
| @@ -27,6 +27,7 @@ import ReactTooltip from 'react-tooltip'; | ||||
| import { v4 as createUuid } from 'uuid'; | ||||
| import { cleanBytesString } from 'utils/helper'; | ||||
| import { DeviceBadge, LoadingButton } from 'ucentral-libs'; | ||||
| import ReactCountryFlag from 'react-country-flag'; | ||||
| import styles from './index.module.scss'; | ||||
|  | ||||
| const DeviceListTable = ({ | ||||
| @@ -324,11 +325,18 @@ const DeviceListTable = ({ | ||||
|               ipAddress: (item) => ( | ||||
|                 <td className="align-middle"> | ||||
|                   <CPopover | ||||
|                     content={item.ipAddress ? item.ipAddress : t('common.na')} | ||||
|                     content={`${item.locale !== '' ? `${item.locale} - ` : ''}${item.ipAddress}`} | ||||
|                     placement="top" | ||||
|                   > | ||||
|                     <div style={{ width: 'calc(8vw)' }} className="text-truncate align-middle"> | ||||
|                       {item.ipAddress} | ||||
|                       {item.locale !== '' && item.ipAddress !== '' && ( | ||||
|                         <ReactCountryFlag | ||||
|                           style={{ width: '24px', height: '24px' }} | ||||
|                           countryCode={item?.locale} | ||||
|                           svg | ||||
|                         /> | ||||
|                       )} | ||||
|                       {`  ${item.ipAddress}`} | ||||
|                     </div> | ||||
|                   </CPopover> | ||||
|                 </td> | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import { getItem, setItem } from 'utils/localStorageHelper'; | ||||
| import DeviceSearchBar from 'components/DeviceSearchBar'; | ||||
| import DeviceFirmwareModal from 'components/DeviceFirmwareModal'; | ||||
| import FirmwareHistoryModal from 'components/FirmwareHistoryModal'; | ||||
| import { useGlobalWebSocket } from 'contexts/WebSocketProvider'; | ||||
| import { useAuth, useToast } from 'ucentral-libs'; | ||||
| import Table from './Table'; | ||||
| import meshIcon from '../../assets/icons/Mesh.png'; | ||||
| @@ -17,6 +18,7 @@ const DeviceList = () => { | ||||
|   const { t } = useTranslation(); | ||||
|   const { addToast } = useToast(); | ||||
|   const history = useHistory(); | ||||
|   const [overrides, setOverrides] = useState({}); | ||||
|   const [page, setPage] = useState(parseInt(sessionStorage.getItem('deviceTable') ?? 0, 10)); | ||||
|   const { currentToken, endpoints } = useAuth(); | ||||
|   const [upgradeStatus, setUpgradeStatus] = useState({ | ||||
| @@ -36,6 +38,7 @@ const DeviceList = () => { | ||||
|     deviceType: '', | ||||
|     serialNumber: '', | ||||
|   }); | ||||
|   const { lastMessage } = useGlobalWebSocket(); | ||||
|  | ||||
|   const deviceIcons = { | ||||
|     meshIcon, | ||||
| @@ -56,6 +59,7 @@ const DeviceList = () => { | ||||
|  | ||||
|   const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => { | ||||
|     setLoading(true); | ||||
|     setOverrides({}); | ||||
|  | ||||
|     const options = { | ||||
|       headers: { | ||||
| @@ -355,10 +359,28 @@ const DeviceList = () => { | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   const displayDevices = () => | ||||
|     devices.map((device) => ({ | ||||
|       ...device, | ||||
|       connected: | ||||
|         overrides[device.serialNumber] !== undefined | ||||
|           ? overrides[device.serialNumber] | ||||
|           : device.connected, | ||||
|     })); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     getCount(); | ||||
|   }, []); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (lastMessage && lastMessage.type === 'DEVICE') { | ||||
|       const { serialNumber: msgSerial, isConnected } = lastMessage; | ||||
|       if (overrides[msgSerial] === undefined || overrides[msgSerial] !== isConnected) { | ||||
|         setOverrides({ ...overrides, [msgSerial]: isConnected }); | ||||
|       } | ||||
|     } | ||||
|   }, [lastMessage, overrides]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (upgradeStatus.result !== undefined) { | ||||
|       addToast({ | ||||
| @@ -382,7 +404,7 @@ const DeviceList = () => { | ||||
|         currentPage={page} | ||||
|         t={t} | ||||
|         searchBar={<DeviceSearchBar />} | ||||
|         devices={devices} | ||||
|         devices={displayDevices()} | ||||
|         loading={loading} | ||||
|         updateDevicesPerPage={updateDevicesPerPage} | ||||
|         devicesPerPage={devicesPerPage} | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import { useAuth, DeviceSearchBar as SearchBar } from 'ucentral-libs'; | ||||
| import { checkIfJson } from 'utils/helper'; | ||||
| import { toJson } from 'utils/helper'; | ||||
|  | ||||
| const DeviceSearchBar = ({ action }) => { | ||||
|   const { t } = useTranslation(); | ||||
| @@ -44,11 +44,9 @@ const DeviceSearchBar = ({ action }) => { | ||||
|       }; | ||||
|  | ||||
|       socket.onmessage = (event) => { | ||||
|         if (checkIfJson(event.data)) { | ||||
|           const result = JSON.parse(event.data); | ||||
|           if (result.command === 'serial_number_search' && result.serialNumbers) { | ||||
|             setResults(result.serialNumbers); | ||||
|           } | ||||
|         const result = toJson(event.data); | ||||
|         if (result && result.serialNumbers) { | ||||
|           setResults(result.serialNumbers); | ||||
|         } | ||||
|       }; | ||||
|  | ||||
|   | ||||
| @@ -65,7 +65,16 @@ const FirmwareDashboard = ({ t, data, loading }) => { | ||||
|           </CCol> | ||||
|           <CCol> | ||||
|             <CWidgetIcon | ||||
|               text={t('common.devices')} | ||||
|               text={ | ||||
|                 <div> | ||||
|                   <div className="float-left">{t('common.devices')}</div> | ||||
|                   <div className="float-left ml-2"> | ||||
|                     <CPopover content={t('device.firmware_count_explanation')}> | ||||
|                       <CIcon content={cilInfo} /> | ||||
|                     </CPopover> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               } | ||||
|               header={<h2>{data.numberOfDevices}</h2>} | ||||
|               color="primary" | ||||
|               iconPadding={false} | ||||
| @@ -212,7 +221,7 @@ const FirmwareDashboard = ({ t, data, loading }) => { | ||||
|                     tooltips: { | ||||
|                       callbacks: { | ||||
|                         title: (item, ds) => ds.labels[item[0].index], | ||||
|                         label: (item, ds) => `${ds.datasets[0].data[item.index]}%`, | ||||
|                         label: (item, ds) => `${ds.datasets[0].data[item.index]} devices`, | ||||
|                       }, | ||||
|                     }, | ||||
|                     legend: { | ||||
|   | ||||
| @@ -65,9 +65,12 @@ const FirmwareDashboard = () => { | ||||
|     const statusColors = []; | ||||
|     const statusLabels = []; | ||||
|     const totalDevices = parsedData.status.reduce((acc, point) => acc + point.value, 0); | ||||
|     parsedData.statusDevices = {}; | ||||
|     parsedData.numberOfDevices = totalDevices; | ||||
|     for (const point of parsedData.status) { | ||||
|       statusDs.push(Math.round((point.value / totalDevices) * 100)); | ||||
|       statusDs.push(point.value); | ||||
|       statusLabels.push(point.tag); | ||||
|       parsedData[point.value] = point.value; | ||||
|       let color = ''; | ||||
|       switch (point.tag) { | ||||
|         case 'connected': | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import { cilX } from '@coreui/icons'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
| import { useAuth, useDevice } from 'ucentral-libs'; | ||||
| import { useAuth, useDevice, CopyToClipboardButton } from 'ucentral-libs'; | ||||
|  | ||||
| const LatestStatisticsModal = ({ show, toggle }) => { | ||||
|   const { t } = useTranslation(); | ||||
| @@ -51,6 +51,13 @@ const LatestStatisticsModal = ({ show, toggle }) => { | ||||
|         </div> | ||||
|       </CModalHeader> | ||||
|       <CModalBody> | ||||
|         <div style={{ textAlign: 'right' }}> | ||||
|           <CopyToClipboardButton | ||||
|             t={t} | ||||
|             size="lg" | ||||
|             content={JSON.stringify(latestStats ?? {}, null, 4)} | ||||
|           /> | ||||
|         </div> | ||||
|         <pre className="ignore">{JSON.stringify(latestStats, null, 2)}</pre> | ||||
|       </CModalBody> | ||||
|     </CModal> | ||||
|   | ||||
| @@ -124,50 +124,27 @@ const StatisticsChartList = ({ setOptions, section, setStart, setEnd, time }) => | ||||
|     } | ||||
|  | ||||
|     // Looping through all the data | ||||
|     let prevTx = 0; | ||||
|     let prevRx = 0; | ||||
|     const prevTxObj = {}; | ||||
|     const prevRxObj = {}; | ||||
|     for (const log of sortedData) { | ||||
|       // Looping through the interfaces of the log | ||||
|       const version = log.data.version ?? 0; | ||||
|       for (const inter of log.data.interfaces) { | ||||
|         if (inter.ssids?.length > 0) { | ||||
|           let totalTx = 0; | ||||
|           let totalRx = 0; | ||||
|           for (const ssid of inter.ssids) { | ||||
|             if (ssid.associations) { | ||||
|               for (const assoc of ssid.associations) { | ||||
|                 if (version === 0) { | ||||
|                   if (assoc.deltas) { | ||||
|                     totalTx += assoc.deltas?.tx_bytes ?? 0; | ||||
|                     totalRx += assoc.deltas?.rx_bytes ?? 0; | ||||
|                   } else { | ||||
|                     totalTx += assoc.tx_bytes ?? 0; | ||||
|                     totalRx += assoc.rx_bytes ?? 0; | ||||
|                   } | ||||
|                 } else { | ||||
|                   totalTx += assoc.tx_bytes ?? 0; | ||||
|                   totalRx += assoc.rx_bytes ?? 0; | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|           if (version > 0) { | ||||
|             const tx = Math.floor(totalTx / 1024); | ||||
|             const rx = Math.floor(totalRx / 1024); | ||||
|             interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(tx - prevTx, 0)); | ||||
|             interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(rx - prevRx, 0)); | ||||
|             prevTx = tx; | ||||
|             prevRx = rx; | ||||
|           } else { | ||||
|             interfaceList[interfaceTypes[inter.name]][0].data.push(Math.floor(totalTx / 1024)); | ||||
|             interfaceList[interfaceTypes[inter.name]][1].data.push(Math.floor(totalRx / 1024)); | ||||
|           } | ||||
|         if (version > 0) { | ||||
|           const prevTx = prevTxObj[inter.name] !== undefined ? prevTxObj[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 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]][1].data.push(Math.max(0, rx - prevRx)); | ||||
|           prevTxObj[inter.name] = tx; | ||||
|           prevRxObj[inter.name] = rx; | ||||
|         } else { | ||||
|           interfaceList[interfaceTypes[inter.name]][0].data.push( | ||||
|             inter.counters ? Math.floor(inter.counters.tx_bytes) : 0, | ||||
|             inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0, | ||||
|           ); | ||||
|           interfaceList[interfaceTypes[inter.name]][1].data.push( | ||||
|             inter.counters ? Math.floor(inter.counters.rx_bytes) : 0, | ||||
|             inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0, | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|   | ||||
| @@ -22,11 +22,13 @@ import axiosInstance from 'utils/axiosInstance'; | ||||
| import eventBus from 'utils/eventBus'; | ||||
| import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs'; | ||||
| import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | ||||
| import { useGlobalWebSocket } from 'contexts/WebSocketProvider'; | ||||
|  | ||||
| const ActionModal = ({ show, toggleModal }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const { currentToken, endpoints } = useAuth(); | ||||
|   const { deviceSerialNumber } = useDevice(); | ||||
|   const { addDeviceListener } = useGlobalWebSocket(); | ||||
|   const { addToast } = useToast(); | ||||
|   const [waiting, setWaiting] = useState(false); | ||||
|   const [result, setResult] = useState(null); | ||||
| @@ -74,11 +76,16 @@ const ActionModal = ({ show, toggleModal }) => { | ||||
|         { headers }, | ||||
|       ) | ||||
|       .then(() => { | ||||
|         addToast({ | ||||
|           title: t('common.success'), | ||||
|           body: t('commands.command_success'), | ||||
|           color: 'success', | ||||
|           autohide: true, | ||||
|         addDeviceListener({ | ||||
|           serialNumber: deviceSerialNumber, | ||||
|           types: ['device_connection', 'device_disconnection'], | ||||
|           addToast: (title, body) => | ||||
|             addToast({ | ||||
|               title, | ||||
|               body, | ||||
|               color: 'info', | ||||
|               autohide: true, | ||||
|             }), | ||||
|         }); | ||||
|         toggleModal(); | ||||
|       }) | ||||
|   | ||||
| @@ -40,7 +40,7 @@ const TraceModal = ({ show, toggleModal }) => { | ||||
|   const [responseBody, setResponseBody] = useState(''); | ||||
|   const [chosenInterface, setChosenInterface] = useState('up'); | ||||
|   const [isDeviceConnected, setIsDeviceConnected] = useState(false); | ||||
|   const [waitForTrace, setWaitForTrace] = useState(false); | ||||
|   const [waitForTrace, setWaitForTrace] = useState(true); | ||||
|   const [waitingForTrace, setWaitingForTrace] = useState(false); | ||||
|   const [commandUuid, setCommandUuid] = useState(null); | ||||
|  | ||||
| @@ -49,7 +49,7 @@ const TraceModal = ({ show, toggleModal }) => { | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setWaitForTrace(false); | ||||
|     setWaitForTrace(true); | ||||
|     setHadSuccess(false); | ||||
|     setHadFailure(false); | ||||
|     setResponseBody(''); | ||||
| @@ -137,25 +137,19 @@ const TraceModal = ({ show, toggleModal }) => { | ||||
|         <CModalBody> | ||||
|           <h6>{t('trace.directions')}</h6> | ||||
|           <CRow className="mt-3"> | ||||
|             <CCol> | ||||
|               <CButton | ||||
|                 disabled={blockFields} | ||||
|                 block | ||||
|                 color="primary" | ||||
|                 onClick={() => setUsingDuration(true)} | ||||
|               > | ||||
|                 {t('common.duration')} | ||||
|               </CButton> | ||||
|             <CCol md="4" className="pt-2"> | ||||
|               {t('contact.type')} | ||||
|             </CCol> | ||||
|             <CCol> | ||||
|               <CButton | ||||
|             <CCol xs="12" md="8"> | ||||
|               <CSelect | ||||
|                 custom | ||||
|                 value={usingDuration ? 'duration' : 'packets'} | ||||
|                 disabled={blockFields} | ||||
|                 block | ||||
|                 color="primary" | ||||
|                 onClick={() => setUsingDuration(false)} | ||||
|                 onChange={(e) => setUsingDuration(e.target.value === 'duration')} | ||||
|               > | ||||
|                 {t('trace.packets')} | ||||
|               </CButton> | ||||
|                 <option value="duration">{t('common.duration')}</option> | ||||
|                 <option value="packets">{t('trace.packets')}</option> | ||||
|               </CSelect> | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|           <CRow className="mt-3"> | ||||
| @@ -220,7 +214,7 @@ const TraceModal = ({ show, toggleModal }) => { | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|           <CRow className="mt-3" hidden={!isDeviceConnected}> | ||||
|             <CCol md="8"> | ||||
|             <CCol md="7"> | ||||
|               <p>{t('trace.wait_for_file')}</p> | ||||
|             </CCol> | ||||
|             <CCol> | ||||
|   | ||||
| @@ -23,7 +23,7 @@ const WifiAnalysisTable = ({ t, data, loading }) => { | ||||
|   }; | ||||
|   const columns = [ | ||||
|     { key: 'radio', label: '#', _style: { width: '1%' } }, | ||||
|     { key: 'bssid', label: 'BSSID', _style: { width: '1%' } }, | ||||
|     { key: 'station', label: 'Station ID', _style: { width: '1%' } }, | ||||
|     { key: 'vendor', label: t('wifi_analysis.vendor'), _style: { width: '15%' }, sorter: false }, | ||||
|     { key: 'mode', label: t('wifi_analysis.mode'), _style: { width: '1%' }, sorter: false }, | ||||
|     { key: 'ssid', label: 'SSID', _style: { width: '15%' } }, | ||||
| @@ -84,12 +84,12 @@ const WifiAnalysisTable = ({ t, data, loading }) => { | ||||
|         sorter | ||||
|         sorterValue={{ column: 'radio', asc: true }} | ||||
|         scopedSlots={{ | ||||
|           bssid: (item) => ( | ||||
|           station: (item) => ( | ||||
|             <td | ||||
|               className="text-center align-middle" | ||||
|               style={{ fontFamily: 'monospace', fontSize: '0.96rem' }} | ||||
|             > | ||||
|               {item.bssid} | ||||
|               {item.station} | ||||
|             </td> | ||||
|           ), | ||||
|           radio: (item) => <td className="text-center align-middle">{item.radio.radio}</td>, | ||||
|   | ||||
| @@ -147,12 +147,12 @@ const WifiAnalysis = () => { | ||||
|  | ||||
|             if ('associations' in ssid) { | ||||
|               for (const association of ssid.associations) { | ||||
|                 bssidObj[association.bssid] = 0; | ||||
|                 bssidObj[association.station] = 0; | ||||
|  | ||||
|                 const data = { | ||||
|                   radio: radioInfo, | ||||
|                   ...extractIp(stat.data, association.bssid), | ||||
|                   bssid: association.bssid, | ||||
|                   station: association.station, | ||||
|                   ssid: ssid.ssid, | ||||
|                   rssi: association.rssi ? parseDbm(association.rssi) : '-', | ||||
|                   mode: ssid.mode, | ||||
| @@ -181,7 +181,7 @@ const WifiAnalysis = () => { | ||||
|     for (let i = 0; i < newParsedAssociationStats.length; i += 1) { | ||||
|       for (let y = 0; y < newParsedAssociationStats[i].length; y += 1) { | ||||
|         newParsedAssociationStats[i][y].vendor = | ||||
|           vendors[newParsedAssociationStats[i][y].bssid] ?? '-'; | ||||
|           vendors[newParsedAssociationStats[i][y].station] ?? '-'; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import { | ||||
|   CCol, | ||||
|   CSpinner, | ||||
|   CPopover, | ||||
|   CSelect, | ||||
| } from '@coreui/react'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import { cilCloudDownload, cilGauge, cilX } from '@coreui/icons'; | ||||
| @@ -34,6 +35,7 @@ const WifiScanModal = ({ show, toggleModal }) => { | ||||
|   const [errorCode, setErrorCode] = useState(0); | ||||
|   const [waiting, setWaiting] = useState(false); | ||||
|   const [dfs, setDfs] = useState(true); | ||||
|   const [bandwidth, setBandwidth] = useState(''); | ||||
|   const [activeScan, setActiveScan] = useState(false); | ||||
|   const [hideOptions, setHideOptions] = useState(false); | ||||
|   const [channelList, setChannelList] = useState([]); | ||||
| @@ -53,6 +55,7 @@ const WifiScanModal = ({ show, toggleModal }) => { | ||||
|     setWaiting(false); | ||||
|     setChannelList([]); | ||||
|     setCsvData(null); | ||||
|     setBandwidth(''); | ||||
|     setDfs(true); | ||||
|     setActiveScan(false); | ||||
|     setHideOptions(false); | ||||
| @@ -135,6 +138,7 @@ const WifiScanModal = ({ show, toggleModal }) => { | ||||
|     const parameters = { | ||||
|       serialNumber: deviceSerialNumber, | ||||
|       override_dfs: dfs, | ||||
|       bandwidth: bandwidth !== '' ? bandwidth : undefined, | ||||
|       activeScan, | ||||
|     }; | ||||
|     const headers = { | ||||
| @@ -246,6 +250,26 @@ const WifiScanModal = ({ show, toggleModal }) => { | ||||
|               </CForm> | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|           <CRow className="mt-3"> | ||||
|             <CCol md="3"> | ||||
|               <p className="pl-2">Bandwidth:</p> | ||||
|             </CCol> | ||||
|             <CCol> | ||||
|               <CForm className="pl-4"> | ||||
|                 <CSelect | ||||
|                   custom | ||||
|                   value={bandwidth} | ||||
|                   onChange={(e) => setBandwidth(e.target.value)} | ||||
|                   style={{ width: '100px' }} | ||||
|                 > | ||||
|                   <option value="">Default</option> | ||||
|                   <option value="20">20 MHz</option> | ||||
|                   <option value="40">40 MHz</option> | ||||
|                   <option value="80">80 MHz</option> | ||||
|                 </CSelect> | ||||
|               </CForm> | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|         </div> | ||||
|         <div hidden={!waiting}> | ||||
|           <CRow> | ||||
|   | ||||
| @@ -0,0 +1,26 @@ | ||||
| import { useCallback, useMemo } from 'react'; | ||||
| import { useToast } from 'ucentral-libs'; | ||||
|  | ||||
| const useWebSocketNotification = () => { | ||||
|   const { addToast } = useToast(); | ||||
|  | ||||
|   const pushNotification = useCallback((notification) => { | ||||
|     addToast({ | ||||
|       title: notification.content.title, | ||||
|       body: notification.content.details, | ||||
|       color: 'info', | ||||
|       autohide: true, | ||||
|     }); | ||||
|   }, []); | ||||
|  | ||||
|   const toReturn = useMemo( | ||||
|     () => ({ | ||||
|       pushNotification, | ||||
|     }), | ||||
|     [], | ||||
|   ); | ||||
|  | ||||
|   return toReturn; | ||||
| }; | ||||
|  | ||||
| export default useWebSocketNotification; | ||||
							
								
								
									
										88
									
								
								src/contexts/WebSocketProvider/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/contexts/WebSocketProvider/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; | ||||
| import { useAuth } from 'ucentral-libs'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import useWebSocketNotification from './hooks/NotificationContent/useWebSocketNotification'; | ||||
| import useSocketReducer from './useSocketReducer'; | ||||
| import { extractWebSocketResponse } from './utils'; | ||||
|  | ||||
| const WebSocketContext = React.createContext({ | ||||
|   webSocket: undefined, | ||||
|   isOpen: false, | ||||
|   addDeviceListener: () => {}, | ||||
| }); | ||||
|  | ||||
| export const WebSocketProvider = ({ children }) => { | ||||
|   const { currentToken, endpoints } = useAuth(); | ||||
|   const [isOpen, setIsOpen] = useState(false); | ||||
|   const ws = useRef(undefined); | ||||
|   const { lastMessage, dispatch } = useSocketReducer(); | ||||
|   const { pushNotification } = useWebSocketNotification(); | ||||
|  | ||||
|   const onMessage = useCallback((message) => { | ||||
|     const result = extractWebSocketResponse(message); | ||||
|     if (result?.type === 'NOTIFICATION') { | ||||
|       dispatch({ type: 'NEW_NOTIFICATION', notification: result.notification }); | ||||
|       pushNotification(result.notification); | ||||
|     } | ||||
|     if (result?.type === 'DEVICE_NOTIFICATION') { | ||||
|       dispatch({ | ||||
|         type: 'NEW_DEVICE_NOTIFICATION', | ||||
|         serialNumber: result.serialNumber, | ||||
|         subType: result.subType, | ||||
|       }); | ||||
|     } | ||||
|     if (result?.type === 'COMMAND') { | ||||
|       dispatch({ type: 'NEW_COMMAND', data: result.data }); | ||||
|     } | ||||
|   }, []); | ||||
|  | ||||
|   // useEffect for created the WebSocket and 'storing' it in useRef | ||||
|   useEffect(() => { | ||||
|     ws.current = new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`); | ||||
|     ws.current.onopen = () => { | ||||
|       setIsOpen(true); | ||||
|       ws.current?.send(`token:${currentToken}`); | ||||
|     }; | ||||
|     ws.current.onclose = () => { | ||||
|       setIsOpen(false); | ||||
|     }; | ||||
|     ws.current.onerror = () => { | ||||
|       setIsOpen(false); | ||||
|     }; | ||||
|  | ||||
|     const wsCurrent = ws?.current; | ||||
|     return () => wsCurrent?.close(); | ||||
|   }, []); | ||||
|  | ||||
|   // useEffect for generating global notifications | ||||
|   useEffect(() => { | ||||
|     if (ws?.current) { | ||||
|       ws.current.addEventListener('message', onMessage); | ||||
|     } | ||||
|  | ||||
|     const wsCurrent = ws?.current; | ||||
|     return () => { | ||||
|       if (wsCurrent) wsCurrent.removeEventListener('message', onMessage); | ||||
|     }; | ||||
|   }, [ws?.current]); | ||||
|   const values = useMemo( | ||||
|     () => ({ | ||||
|       lastMessage, | ||||
|       webSocket: ws.current, | ||||
|       addDeviceListener: ({ serialNumber, types, addToast, onTrigger }) => | ||||
|         dispatch({ type: 'ADD_DEVICE_LISTENER', serialNumber, types, addToast, onTrigger }), | ||||
|       removeDeviceListener: ({ serialNumber }) => | ||||
|         dispatch({ type: 'REMOVE_DEVICE_LISTENER', serialNumber }), | ||||
|       isOpen, | ||||
|     }), | ||||
|     [ws, isOpen, lastMessage], | ||||
|   ); | ||||
|  | ||||
|   return <WebSocketContext.Provider value={values}>{children}</WebSocketContext.Provider>; | ||||
| }; | ||||
|  | ||||
| WebSocketProvider.propTypes = { | ||||
|   children: PropTypes.node.isRequired, | ||||
| }; | ||||
|  | ||||
| export const useGlobalWebSocket = () => React.useContext(WebSocketContext); | ||||
							
								
								
									
										98
									
								
								src/contexts/WebSocketProvider/useSocketReducer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/contexts/WebSocketProvider/useSocketReducer.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| import { useReducer } from 'react'; | ||||
|  | ||||
| const titles = { | ||||
|   device_connection: 'Connected', | ||||
|   device_disconnection: 'Disconnected', | ||||
|   device_firmware_upgrade: 'Firmware Upgraded', | ||||
| }; | ||||
| const bodies = { | ||||
|   device_connection: 'This device has rebooted and is now connected!', | ||||
|   device_disconnection: 'This device has started rebooting and is now disconnected!', | ||||
|   device_firmware_upgrade: 'This device has updated to new firmware!', | ||||
| }; | ||||
|  | ||||
| const reducer = (state, action) => { | ||||
|   switch (action.type) { | ||||
|     case 'NEW_NOTIFICATION': { | ||||
|       const obj = { type: 'NOTIFICATION', data: action.notification, timestamp: new Date() }; | ||||
|       return { ...state, lastMessage: obj }; | ||||
|     } | ||||
|     case 'NEW_COMMAND': { | ||||
|       const obj = { | ||||
|         type: 'COMMAND', | ||||
|         response: action.data.response, | ||||
|         timestamp: new Date(), | ||||
|         id: action.data.command_response_id, | ||||
|       }; | ||||
|       return { ...state, lastMessage: obj }; | ||||
|     } | ||||
|     case 'NEW_DEVICE_NOTIFICATION': { | ||||
|       const newListeners = state.deviceListeners; | ||||
|       let obj; | ||||
|  | ||||
|       if (action.subType === 'device_connection' || action.subType === 'device_disconnection') { | ||||
|         obj = { | ||||
|           type: 'DEVICE', | ||||
|           isConnected: action.subType === 'device_connection', | ||||
|           serialNumber: action.serialNumber, | ||||
|           timestamp: new Date(), | ||||
|         }; | ||||
|       } | ||||
|       for (let i = 0; i < state.deviceListeners.length; i += 1) { | ||||
|         if ( | ||||
|           state.deviceListeners[i].serialNumber === action.serialNumber && | ||||
|           state.deviceListeners[i].type === action.subType | ||||
|         ) { | ||||
|           if (state.deviceListeners[i].onTrigger) { | ||||
|             setTimeout(() => state.deviceListeners[i].onTrigger(action.subType), 1000); | ||||
|           } else if (state.deviceListeners[i].addToast) { | ||||
|             state.deviceListeners[i].addToast( | ||||
|               `${action.serialNumber} ${titles[state.deviceListeners[i].type]}`, | ||||
|               bodies[state.deviceListeners[i].type], | ||||
|             ); | ||||
|             const found = newListeners.findIndex( | ||||
|               (listener) => | ||||
|                 listener.serialNumber === action.serialNumber && listener.type === action.subType, | ||||
|             ); | ||||
|             if (found >= 0) newListeners.splice(found, 1); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       return { ...state, lastMessage: obj ?? state.lastMessage, deviceListeners: newListeners }; | ||||
|     } | ||||
|     case 'ADD_DEVICE_LISTENER': { | ||||
|       let newListeners = action.types.map((actionType) => ({ | ||||
|         type: actionType, | ||||
|         serialNumber: action.serialNumber, | ||||
|         addToast: action.addToast, | ||||
|         onTrigger: action.onTrigger, | ||||
|       })); | ||||
|       newListeners = newListeners.concat(state.deviceListeners); | ||||
|       return { ...state, lastMessage: state.lastMessage, deviceListeners: newListeners }; | ||||
|     } | ||||
|     case 'REMOVE_DEVICE_LISTENER': { | ||||
|       const newListeners = state.deviceListeners.filter( | ||||
|         (listener) => | ||||
|           listener.serialNumber !== action.serialNumber || listener.onTrigger === undefined, | ||||
|       ); | ||||
|       return { ...state, lastMessage: state.lastMessage, deviceListeners: newListeners }; | ||||
|     } | ||||
|     case 'UNKNOWN': { | ||||
|       const obj = { type: 'UNKNOWN', data: action.newMessage, timestamp: new Date() }; | ||||
|       return { ...state, lastMessage: obj }; | ||||
|     } | ||||
|     default: | ||||
|       throw new Error(); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const useSocketReducer = () => { | ||||
|   const [{ lastMessage, deviceListeners }, dispatch] = useReducer(reducer, { | ||||
|     deviceListeners: [], | ||||
|   }); | ||||
|  | ||||
|   return { lastMessage, deviceListeners, dispatch }; | ||||
| }; | ||||
|  | ||||
| export default useSocketReducer; | ||||
							
								
								
									
										55
									
								
								src/contexts/WebSocketProvider/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/contexts/WebSocketProvider/utils.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| export const acceptedNotificationTypes = [ | ||||
|   'venue_configuration_update', | ||||
|   'entity_configuration_update', | ||||
| ]; | ||||
| export const deviceNotificationTypes = [ | ||||
|   'device_connection', | ||||
|   'device_disconnection', | ||||
|   'device_firmware_upgrade', | ||||
|   'device_statistics', | ||||
| ]; | ||||
|  | ||||
| export const extractWebSocketResponse = (message) => { | ||||
|   try { | ||||
|     const data = JSON.parse(message.data); | ||||
|     if (data.notification && acceptedNotificationTypes.includes(data.notification.type)) { | ||||
|       const { notification } = data; | ||||
|       return { notification, type: 'NOTIFICATION' }; | ||||
|     } | ||||
|     if (data.notification && deviceNotificationTypes.includes(data.notification.type)) { | ||||
|       return { | ||||
|         serialNumber: data.notification.content.serialNumber, | ||||
|         type: 'DEVICE_NOTIFICATION', | ||||
|         subType: data.notification.type, | ||||
|       }; | ||||
|     } | ||||
|     if (data.command_response_id) { | ||||
|       return { data, type: 'COMMAND' }; | ||||
|     } | ||||
|   } catch { | ||||
|     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; | ||||
| }; | ||||
| @@ -5,6 +5,7 @@ import { CSidebarNavItem } from '@coreui/react'; | ||||
| import { cilBarcode, cilRouter, cilSave, cilSettings, cilPeople } from '@coreui/icons'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import { Header, Sidebar, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs'; | ||||
| import { WebSocketProvider } from 'contexts/WebSocketProvider'; | ||||
|  | ||||
| const TheLayout = () => { | ||||
|   const [showSidebar, setShowSidebar] = useState('responsive'); | ||||
| @@ -70,7 +71,9 @@ const TheLayout = () => { | ||||
|         /> | ||||
|         <div className="c-body"> | ||||
|           <ToastProvider> | ||||
|             <PageContainer t={t} routes={routes} redirectTo="/devices" /> | ||||
|             <WebSocketProvider> | ||||
|               <PageContainer t={t} routes={routes} redirectTo="/devices" /> | ||||
|             </WebSocketProvider> | ||||
|           </ToastProvider> | ||||
|         </div> | ||||
|         <Footer t={t} version={process.env.VERSION} /> | ||||
|   | ||||
| @@ -15,12 +15,17 @@ import { | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import { cilSync } from '@coreui/icons'; | ||||
| import { prettyDate } from 'utils/helper'; | ||||
| import { CopyToClipboardButton, HideTextButton } from 'ucentral-libs'; | ||||
| import { CopyToClipboardButton, HideTextButton, useAuth } from 'ucentral-libs'; | ||||
| import { getCountryFromLocale } from 'utils/countries'; | ||||
| import ReactCountryFlag from 'react-country-flag'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
|  | ||||
| import styles from './index.module.scss'; | ||||
|  | ||||
| const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats }) => { | ||||
|   const [showPassword, setShowPassword] = useState(false); | ||||
|   const [subName, setSubName] = useState(''); | ||||
|   const { currentToken, endpoints } = useAuth(); | ||||
|  | ||||
|   const toggleShowPassword = () => { | ||||
|     setShowPassword(!showPassword); | ||||
| @@ -32,21 +37,51 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats }) | ||||
|     return showPassword ? password : '******'; | ||||
|   }; | ||||
|  | ||||
|   const displayExtra = (key, value, extraData) => { | ||||
|     if (!extraData || !extraData[key]) return value; | ||||
|   const getSubData = async (subId) => { | ||||
|     const options = { | ||||
|       headers: { | ||||
|         Accept: 'application/json', | ||||
|         Authorization: `Bearer ${currentToken}`, | ||||
|       }, | ||||
|     }; | ||||
|  | ||||
|     if (!localStorage.getItem('owprov-ui') || key === 'owner') return extraData[key].name; | ||||
|     axiosInstance | ||||
|       .get(`${endpoints.owsec}/api/v1/subuser/${subId}`, options) | ||||
|       .then((response) => setSubName(response.data.name ?? '')) | ||||
|       .catch(() => setSubName('')); | ||||
|   }; | ||||
|  | ||||
|   const getSubscriber = () => { | ||||
|     if (!deviceConfig?.subscriber || deviceConfig.subscriber === '') return ''; | ||||
|     getSubData(deviceConfig.subscriber); | ||||
|  | ||||
|     return ( | ||||
|       <CLink | ||||
|         className="c-subheader-nav-link align-self-center" | ||||
|         aria-current="page" | ||||
|         href={`${localStorage.getItem('owprov-ui')}/#/${key === 'entity' ? 'entity' : 'venue'}/${ | ||||
|           extraData[key].id | ||||
|         }`} | ||||
|         href={`${localStorage.getItem('owprov-ui')}/#/subscriber/${deviceConfig.subscriber}`} | ||||
|         target="_blank" | ||||
|       > | ||||
|         {extraData[key].name} | ||||
|         {subName !== '' ? subName : deviceConfig.subscriber} | ||||
|       </CLink> | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   const displayExtra = (key, value, extraData) => { | ||||
|     if (!extraData || !extraData[key]) return value; | ||||
|  | ||||
|     if (!localStorage.getItem('owprov-ui')) return extraData[key].name; | ||||
|  | ||||
|     return ( | ||||
|       <CLink | ||||
|         className="c-subheader-nav-link align-self-center" | ||||
|         aria-current="page" | ||||
|         href={`${localStorage.getItem('owprov-ui')}/#/${ | ||||
|           key === 'entity' ? 'entity' : 'venue' | ||||
|         }/${value}`} | ||||
|         target="_blank" | ||||
|       > | ||||
|         {!extraData || !extraData[key] ? value : extraData[key].name} | ||||
|       </CLink> | ||||
|     ); | ||||
|   }; | ||||
| @@ -99,13 +134,13 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats }) | ||||
|                 /> | ||||
|               </CCol> | ||||
|               <CCol className="border-left" lg="2" xl="1" xxl="1"> | ||||
|                 <CLabel>{t('configuration.owner')}:</CLabel> | ||||
|                 <CLabel>{t('inventory.subscriber')}:</CLabel> | ||||
|               </CCol> | ||||
|               <CCol lg="2" xl="3" xxl="3"> | ||||
|                 {deviceConfig?.owner} | ||||
|                 {getSubscriber()} | ||||
|               </CCol> | ||||
|               <CCol lg="2" xl="1" xxl="1"> | ||||
|                 <CLabel>{t('common.mac')}:</CLabel> | ||||
|                 <CLabel>MAC:</CLabel> | ||||
|               </CCol> | ||||
|               <CCol className="border-right" lg="2" xl="3" xxl="3"> | ||||
|                 {deviceConfig?.macAddress} | ||||
| @@ -117,25 +152,16 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats }) | ||||
|                 {deviceConfig?.deviceType} | ||||
|               </CCol> | ||||
|               <CCol className="border-left" lg="2" xl="1" xxl="1"> | ||||
|                 <CLabel> | ||||
|                   {deviceConfig?.venue?.substring(0, 3) === 'ent' | ||||
|                     ? t('entity.entity') | ||||
|                     : t('inventory.venue')} | ||||
|                   : | ||||
|                 </CLabel> | ||||
|                 <CLabel>{t('entity.entity')}:</CLabel> | ||||
|               </CCol> | ||||
|               <CCol lg="2" xl="3" xxl="3"> | ||||
|                 {deviceConfig?.venue?.substring(0, 3) === 'ent' | ||||
|                 {deviceConfig?.entity !== '' | ||||
|                   ? displayExtra( | ||||
|                       'entity', | ||||
|                       deviceConfig?.venue?.slice(4), | ||||
|                       deviceConfig?.extendedInfo, | ||||
|                     ) | ||||
|                   : displayExtra( | ||||
|                       'venue', | ||||
|                       deviceConfig?.venue?.slice(4), | ||||
|                       deviceConfig?.extendedInfo, | ||||
|                     )} | ||||
|                   : ''} | ||||
|               </CCol> | ||||
|               <CCol lg="2" xl="1" xxl="1"> | ||||
|                 <CLabel>{t('common.manufacturer')}:</CLabel> | ||||
| @@ -149,6 +175,37 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats }) | ||||
|               <CCol lg="2" xl="3" xxl="3"> | ||||
|                 {prettyDate(deviceConfig?.createdTimestamp)} | ||||
|               </CCol> | ||||
|               <CCol className="border-left" lg="2" xl="1" xxl="1"> | ||||
|                 <CLabel>{t('inventory.venue')}:</CLabel> | ||||
|               </CCol> | ||||
|               <CCol lg="2" xl="3" xxl="3"> | ||||
|                 {deviceConfig?.venue !== '' | ||||
|                   ? displayExtra('venue', deviceConfig?.venue?.slice(4), deviceConfig?.extendedInfo) | ||||
|                   : ''} | ||||
|               </CCol> | ||||
|               <CCol lg="2" xl="1" xxl="1"> | ||||
|                 <CLabel>Locale:</CLabel> | ||||
|               </CCol> | ||||
|               <CCol className="border-right" lg="2" xl="3" xxl="3"> | ||||
|                 {deviceConfig?.locale !== '' && ( | ||||
|                   <ReactCountryFlag | ||||
|                     style={{ width: '24px', height: '24px' }} | ||||
|                     countryCode={deviceConfig?.locale} | ||||
|                     svg | ||||
|                   /> | ||||
|                 )} | ||||
|                 {'  '} | ||||
|                 {deviceConfig?.locale && deviceConfig?.locale !== '' | ||||
|                   ? `${deviceConfig.locale} - ` | ||||
|                   : 'Unknown'} | ||||
|                 {getCountryFromLocale(deviceConfig?.locale ?? '')} | ||||
|               </CCol> | ||||
|               <CCol lg="2" xl="1" xxl="1"> | ||||
|                 <CLabel>{t('common.modified')}: </CLabel> | ||||
|               </CCol> | ||||
|               <CCol lg="2" xl="3" xxl="3"> | ||||
|                 {prettyDate(deviceConfig?.modified)} | ||||
|               </CCol> | ||||
|               <CCol className="border-left" lg="2" xl="1" xxl="1"> | ||||
|                 <CLabel>{t('configuration.location')}:</CLabel> | ||||
|               </CCol> | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next'; | ||||
| import ConfigurationDisplay from 'components/ConfigurationDisplay'; | ||||
| import WifiAnalysis from 'components/WifiAnalysis'; | ||||
| import CapabilitiesDisplay from 'components/CapabilitiesDisplay'; | ||||
| import { useGlobalWebSocket } from 'contexts/WebSocketProvider'; | ||||
| import NotesTab from './NotesTab'; | ||||
| import DeviceDetails from './Details'; | ||||
| import DeviceStatusCard from './DeviceStatusCard'; | ||||
| @@ -26,6 +27,7 @@ const DevicePage = () => { | ||||
|   const [deviceConfig, setDeviceConfig] = useState(null); | ||||
|   const [error, setError] = useState(false); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const { addDeviceListener, removeDeviceListener } = useGlobalWebSocket(); | ||||
|  | ||||
|   const updateNav = (target) => { | ||||
|     sessionStorage.setItem('devicePageIndex', target); | ||||
| @@ -115,9 +117,26 @@ const DevicePage = () => { | ||||
|   useEffect(() => { | ||||
|     setError(false); | ||||
|     if (deviceId) { | ||||
|       getDevice(); | ||||
|       getData(); | ||||
|       addDeviceListener({ | ||||
|         serialNumber: deviceId, | ||||
|         types: [ | ||||
|           'device_connection', | ||||
|           'device_disconnection', | ||||
|           'device_firmware_upgrade', | ||||
|           'device_statistics', | ||||
|         ], | ||||
|         onTrigger: () => refresh(), | ||||
|       }); | ||||
|       refresh(); | ||||
|     } | ||||
|  | ||||
|     return () => { | ||||
|       if (deviceId) { | ||||
|         removeDeviceListener({ | ||||
|           serialNumber: deviceId, | ||||
|         }); | ||||
|       } | ||||
|     }; | ||||
|   }, [deviceId]); | ||||
|  | ||||
|   return ( | ||||
|   | ||||
| @@ -8,7 +8,7 @@ axiosRetry(axiosInstance, { | ||||
|   retryDelay: () => axiosRetry.exponentialDelay, | ||||
| }); | ||||
|  | ||||
| axiosInstance.defaults.timeout = 60000; | ||||
| axiosInstance.defaults.timeout = 160000; | ||||
| axiosInstance.defaults.headers.get.Accept = 'application/json'; | ||||
| axiosInstance.defaults.headers.post.Accept = 'application/json'; | ||||
|  | ||||
|   | ||||
							
								
								
									
										253
									
								
								src/utils/countries.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								src/utils/countries.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,253 @@ | ||||
| export const COUNTRY_LIST = [ | ||||
|   { value: 'US', label: 'United States' }, | ||||
|   { value: 'CA', label: 'Canada' }, | ||||
|   { value: 'AF', label: 'Afghanistan' }, | ||||
|   { value: 'AX', label: 'Aland Islands' }, | ||||
|   { value: 'AL', label: 'Albania' }, | ||||
|   { value: 'DZ', label: 'Algeria' }, | ||||
|   { value: 'AS', label: 'American Samoa' }, | ||||
|   { value: 'AD', label: 'Andorra' }, | ||||
|   { value: 'AO', label: 'Angola' }, | ||||
|   { value: 'AI', label: 'Anguilla' }, | ||||
|   { value: 'AQ', label: 'Antarctica' }, | ||||
|   { value: 'AG', label: 'Antigua And Barbuda' }, | ||||
|   { value: 'AR', label: 'Argentina' }, | ||||
|   { value: 'AM', label: 'Armenia' }, | ||||
|   { value: 'AN', label: 'Netherlands Antilles' }, | ||||
|   { value: 'AW', label: 'Aruba' }, | ||||
|   { value: 'AU', label: 'Australia' }, | ||||
|   { value: 'AT', label: 'Austria' }, | ||||
|   { value: 'AZ', label: 'Azerbaijan' }, | ||||
|   { value: 'BS', label: 'Bahamas' }, | ||||
|   { value: 'BH', label: 'Bahrain' }, | ||||
|   { value: 'BD', label: 'Bangladesh' }, | ||||
|   { value: 'BB', label: 'Barbados' }, | ||||
|   { value: 'BY', label: 'Belarus' }, | ||||
|   { value: 'BE', label: 'Belgium' }, | ||||
|   { value: 'BZ', label: 'Belize' }, | ||||
|   { value: 'BJ', label: 'Benin' }, | ||||
|   { value: 'BM', label: 'Bermuda' }, | ||||
|   { value: 'BT', label: 'Bhutan' }, | ||||
|   { value: 'BO', label: 'Bolivia' }, | ||||
|   { value: 'BA', label: 'Bosnia And Herzegovina' }, | ||||
|   { value: 'BW', label: 'Botswana' }, | ||||
|   { value: 'BV', label: 'Bouvet Island' }, | ||||
|   { value: 'BR', label: 'Brazil' }, | ||||
|   { value: 'IO', label: 'British Indian Ocean Territory' }, | ||||
|   { value: 'BN', label: 'Brunei Darussalam' }, | ||||
|   { value: 'BG', label: 'Bulgaria' }, | ||||
|   { value: 'BF', label: 'Burkina Faso' }, | ||||
|   { value: 'BI', label: 'Burundi' }, | ||||
|   { value: 'KH', label: 'Cambodia' }, | ||||
|   { value: 'CM', label: 'Cameroon' }, | ||||
|   { value: 'CA', label: 'Canada' }, | ||||
|   { value: 'CV', label: 'Cape Verde' }, | ||||
|   { value: 'KY', label: 'Cayman Islands' }, | ||||
|   { value: 'CF', label: 'Central African Republic' }, | ||||
|   { value: 'TD', label: 'Chad' }, | ||||
|   { value: 'CL', label: 'Chile' }, | ||||
|   { value: 'CN', label: 'China' }, | ||||
|   { value: 'CX', label: 'Christmas Island' }, | ||||
|   { value: 'CC', label: 'Cocos (Keeling) Islands' }, | ||||
|   { value: 'CO', label: 'Colombia' }, | ||||
|   { value: 'KM', label: 'Comoros' }, | ||||
|   { value: 'CG', label: 'Congo' }, | ||||
|   { value: 'CD', label: 'Congo, Democratic Republic' }, | ||||
|   { value: 'CK', label: 'Cook Islands' }, | ||||
|   { value: 'CR', label: 'Costa Rica' }, | ||||
|   { value: 'CI', label: "Cote D'Ivoire" }, | ||||
|   { value: 'HR', label: 'Croatia' }, | ||||
|   { value: 'CU', label: 'Cuba' }, | ||||
|   { value: 'CY', label: 'Cyprus' }, | ||||
|   { value: 'CZ', label: 'Czech Republic' }, | ||||
|   { value: 'DK', label: 'Denmark' }, | ||||
|   { value: 'DJ', label: 'Djibouti' }, | ||||
|   { value: 'DM', label: 'Dominica' }, | ||||
|   { value: 'DO', label: 'Dominican Republic' }, | ||||
|   { value: 'EC', label: 'Ecuador' }, | ||||
|   { value: 'EG', label: 'Egypt' }, | ||||
|   { value: 'SV', label: 'El Salvador' }, | ||||
|   { value: 'GQ', label: 'Equatorial Guinea' }, | ||||
|   { value: 'ER', label: 'Eritrea' }, | ||||
|   { value: 'EE', label: 'Estonia' }, | ||||
|   { value: 'ET', label: 'Ethiopia' }, | ||||
|   { value: 'FK', label: 'Falkland Islands (Malvinas)' }, | ||||
|   { value: 'FO', label: 'Faroe Islands' }, | ||||
|   { value: 'FJ', label: 'Fiji' }, | ||||
|   { value: 'FI', label: 'Finland' }, | ||||
|   { value: 'FR', label: 'France' }, | ||||
|   { value: 'GF', label: 'French Guiana' }, | ||||
|   { value: 'PF', label: 'French Polynesia' }, | ||||
|   { value: 'TF', label: 'French Southern Territories' }, | ||||
|   { value: 'GA', label: 'Gabon' }, | ||||
|   { value: 'GM', label: 'Gambia' }, | ||||
|   { value: 'GE', label: 'Georgia' }, | ||||
|   { value: 'DE', label: 'Germany' }, | ||||
|   { value: 'GH', label: 'Ghana' }, | ||||
|   { value: 'GI', label: 'Gibraltar' }, | ||||
|   { value: 'GR', label: 'Greece' }, | ||||
|   { value: 'GL', label: 'Greenland' }, | ||||
|   { value: 'GD', label: 'Grenada' }, | ||||
|   { value: 'GP', label: 'Guadeloupe' }, | ||||
|   { value: 'GU', label: 'Guam' }, | ||||
|   { value: 'GT', label: 'Guatemala' }, | ||||
|   { value: 'GG', label: 'Guernsey' }, | ||||
|   { value: 'GN', label: 'Guinea' }, | ||||
|   { value: 'GW', label: 'Guinea-Bissau' }, | ||||
|   { value: 'GY', label: 'Guyana' }, | ||||
|   { value: 'HT', label: 'Haiti' }, | ||||
|   { value: 'HM', label: 'Heard Island & Mcdonald Islands' }, | ||||
|   { value: 'VA', label: 'Holy See (Vatican City State)' }, | ||||
|   { value: 'HN', label: 'Honduras' }, | ||||
|   { value: 'HK', label: 'Hong Kong' }, | ||||
|   { value: 'HU', label: 'Hungary' }, | ||||
|   { value: 'IS', label: 'Iceland' }, | ||||
|   { value: 'IN', label: 'India' }, | ||||
|   { value: 'ID', label: 'Indonesia' }, | ||||
|   { value: 'IR', label: 'Iran, Islamic Republic Of' }, | ||||
|   { value: 'IQ', label: 'Iraq' }, | ||||
|   { value: 'IE', label: 'Ireland' }, | ||||
|   { value: 'IM', label: 'Isle Of Man' }, | ||||
|   { value: 'IL', label: 'Israel' }, | ||||
|   { value: 'IT', label: 'Italy' }, | ||||
|   { value: 'JM', label: 'Jamaica' }, | ||||
|   { value: 'JP', label: 'Japan' }, | ||||
|   { value: 'JE', label: 'Jersey' }, | ||||
|   { value: 'JO', label: 'Jordan' }, | ||||
|   { value: 'KZ', label: 'Kazakhstan' }, | ||||
|   { value: 'KE', label: 'Kenya' }, | ||||
|   { value: 'KI', label: 'Kiribati' }, | ||||
|   { value: 'KR', label: 'Korea' }, | ||||
|   { value: 'KW', label: 'Kuwait' }, | ||||
|   { value: 'KG', label: 'Kyrgyzstan' }, | ||||
|   { value: 'LA', label: "Lao People's Democratic Republic" }, | ||||
|   { value: 'LV', label: 'Latvia' }, | ||||
|   { value: 'LB', label: 'Lebanon' }, | ||||
|   { value: 'LS', label: 'Lesotho' }, | ||||
|   { value: 'LR', label: 'Liberia' }, | ||||
|   { value: 'LY', label: 'Libyan Arab Jamahiriya' }, | ||||
|   { value: 'LI', label: 'Liechtenstein' }, | ||||
|   { value: 'LT', label: 'Lithuania' }, | ||||
|   { value: 'LU', label: 'Luxembourg' }, | ||||
|   { value: 'MO', label: 'Macao' }, | ||||
|   { value: 'MK', label: 'Macedonia' }, | ||||
|   { value: 'MG', label: 'Madagascar' }, | ||||
|   { value: 'MW', label: 'Malawi' }, | ||||
|   { value: 'MY', label: 'Malaysia' }, | ||||
|   { value: 'MV', label: 'Maldives' }, | ||||
|   { value: 'ML', label: 'Mali' }, | ||||
|   { value: 'MT', label: 'Malta' }, | ||||
|   { value: 'MH', label: 'Marshall Islands' }, | ||||
|   { value: 'MQ', label: 'Martinique' }, | ||||
|   { value: 'MR', label: 'Mauritania' }, | ||||
|   { value: 'MU', label: 'Mauritius' }, | ||||
|   { value: 'YT', label: 'Mayotte' }, | ||||
|   { value: 'MX', label: 'Mexico' }, | ||||
|   { value: 'FM', label: 'Micronesia, Federated States Of' }, | ||||
|   { value: 'MD', label: 'Moldova' }, | ||||
|   { value: 'MC', label: 'Monaco' }, | ||||
|   { value: 'MN', label: 'Mongolia' }, | ||||
|   { value: 'ME', label: 'Montenegro' }, | ||||
|   { value: 'MS', label: 'Montserrat' }, | ||||
|   { value: 'MA', label: 'Morocco' }, | ||||
|   { value: 'MZ', label: 'Mozambique' }, | ||||
|   { value: 'MM', label: 'Myanmar' }, | ||||
|   { value: 'NA', label: 'Namibia' }, | ||||
|   { value: 'NR', label: 'Nauru' }, | ||||
|   { value: 'NP', label: 'Nepal' }, | ||||
|   { value: 'NL', label: 'Netherlands' }, | ||||
|   { value: 'AN', label: 'Netherlands Antilles' }, | ||||
|   { value: 'NC', label: 'New Caledonia' }, | ||||
|   { value: 'NZ', label: 'New Zealand' }, | ||||
|   { value: 'NI', label: 'Nicaragua' }, | ||||
|   { value: 'NE', label: 'Niger' }, | ||||
|   { value: 'NG', label: 'Nigeria' }, | ||||
|   { value: 'NU', label: 'Niue' }, | ||||
|   { value: 'NF', label: 'Norfolk Island' }, | ||||
|   { value: 'MP', label: 'Northern Mariana Islands' }, | ||||
|   { value: 'NO', label: 'Norway' }, | ||||
|   { value: 'OM', label: 'Oman' }, | ||||
|   { value: 'PK', label: 'Pakistan' }, | ||||
|   { value: 'PW', label: 'Palau' }, | ||||
|   { value: 'PS', label: 'Palestinian Territory, Occupied' }, | ||||
|   { value: 'PA', label: 'Panama' }, | ||||
|   { value: 'PG', label: 'Papua New Guinea' }, | ||||
|   { value: 'PY', label: 'Paraguay' }, | ||||
|   { value: 'PE', label: 'Peru' }, | ||||
|   { value: 'PH', label: 'Philippines' }, | ||||
|   { value: 'PN', label: 'Pitcairn' }, | ||||
|   { value: 'PL', label: 'Poland' }, | ||||
|   { value: 'PT', label: 'Portugal' }, | ||||
|   { value: 'PR', label: 'Puerto Rico' }, | ||||
|   { value: 'QA', label: 'Qatar' }, | ||||
|   { value: 'RE', label: 'Reunion' }, | ||||
|   { value: 'RO', label: 'Romania' }, | ||||
|   { value: 'RU', label: 'Russian Federation' }, | ||||
|   { value: 'RW', label: 'Rwanda' }, | ||||
|   { value: 'BL', label: 'Saint Barthelemy' }, | ||||
|   { value: 'SH', label: 'Saint Helena' }, | ||||
|   { value: 'KN', label: 'Saint Kitts And Nevis' }, | ||||
|   { value: 'LC', label: 'Saint Lucia' }, | ||||
|   { value: 'MF', label: 'Saint Martin' }, | ||||
|   { value: 'PM', label: 'Saint Pierre And Miquelon' }, | ||||
|   { value: 'VC', label: 'Saint Vincent And Grenadines' }, | ||||
|   { value: 'WS', label: 'Samoa' }, | ||||
|   { value: 'SM', label: 'San Marino' }, | ||||
|   { value: 'ST', label: 'Sao Tome And Principe' }, | ||||
|   { value: 'SA', label: 'Saudi Arabia' }, | ||||
|   { value: 'SN', label: 'Senegal' }, | ||||
|   { value: 'RS', label: 'Serbia' }, | ||||
|   { value: 'SC', label: 'Seychelles' }, | ||||
|   { value: 'SL', label: 'Sierra Leone' }, | ||||
|   { value: 'SG', label: 'Singapore' }, | ||||
|   { value: 'SK', label: 'Slovakia' }, | ||||
|   { value: 'SI', label: 'Slovenia' }, | ||||
|   { value: 'SB', label: 'Solomon Islands' }, | ||||
|   { value: 'SO', label: 'Somalia' }, | ||||
|   { value: 'ZA', label: 'South Africa' }, | ||||
|   { value: 'GS', label: 'South Georgia And Sandwich Isl.' }, | ||||
|   { value: 'ES', label: 'Spain' }, | ||||
|   { value: 'LK', label: 'Sri Lanka' }, | ||||
|   { value: 'SD', label: 'Sudan' }, | ||||
|   { value: 'SR', label: 'Surilabel' }, | ||||
|   { value: 'SJ', label: 'Svalbard And Jan Mayen' }, | ||||
|   { value: 'SZ', label: 'Swaziland' }, | ||||
|   { value: 'SE', label: 'Sweden' }, | ||||
|   { value: 'CH', label: 'Switzerland' }, | ||||
|   { value: 'SY', label: 'Syrian Arab Republic' }, | ||||
|   { value: 'TW', label: 'Taiwan' }, | ||||
|   { value: 'TJ', label: 'Tajikistan' }, | ||||
|   { value: 'TZ', label: 'Tanzania' }, | ||||
|   { value: 'TH', label: 'Thailand' }, | ||||
|   { value: 'TL', label: 'Timor-Leste' }, | ||||
|   { value: 'TG', label: 'Togo' }, | ||||
|   { value: 'TK', label: 'Tokelau' }, | ||||
|   { value: 'TO', label: 'Tonga' }, | ||||
|   { value: 'TT', label: 'Trinidad And Tobago' }, | ||||
|   { value: 'TN', label: 'Tunisia' }, | ||||
|   { value: 'TR', label: 'Turkey' }, | ||||
|   { value: 'TM', label: 'Turkmenistan' }, | ||||
|   { value: 'TC', label: 'Turks And Caicos Islands' }, | ||||
|   { value: 'TV', label: 'Tuvalu' }, | ||||
|   { value: 'UG', label: 'Uganda' }, | ||||
|   { value: 'UA', label: 'Ukraine' }, | ||||
|   { value: 'AE', label: 'United Arab Emirates' }, | ||||
|   { value: 'GB', label: 'United Kingdom' }, | ||||
|   { value: 'US', label: 'United States' }, | ||||
|   { value: 'UM', label: 'United States Outlying Islands' }, | ||||
|   { value: 'UY', label: 'Uruguay' }, | ||||
|   { value: 'UZ', label: 'Uzbekistan' }, | ||||
|   { value: 'VU', label: 'Vanuatu' }, | ||||
|   { value: 'VE', label: 'Venezuela' }, | ||||
|   { value: 'VN', label: 'Viet Nam' }, | ||||
|   { value: 'VG', label: 'Virgin Islands, British' }, | ||||
|   { value: 'VI', label: 'Virgin Islands, U.S.' }, | ||||
|   { value: 'WF', label: 'Wallis And Futuna' }, | ||||
|   { value: 'EH', label: 'Western Sahara' }, | ||||
|   { value: 'YE', label: 'Yemen' }, | ||||
|   { value: 'ZM', label: 'Zambia' }, | ||||
|   { value: 'ZW', label: 'Zimbabwe' }, | ||||
| ]; | ||||
|  | ||||
| export const getCountryFromLocale = (locale) => | ||||
|   COUNTRY_LIST.find((country) => country.value === locale)?.label ?? ''; | ||||
| @@ -59,6 +59,14 @@ export const checkIfJson = (string) => { | ||||
|   return true; | ||||
| }; | ||||
|  | ||||
| export const toJson = (string) => { | ||||
|   try { | ||||
|     return JSON.parse(string); | ||||
|   } catch (e) { | ||||
|     return undefined; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export const secondsToDetailed = ( | ||||
|   seconds, | ||||
|   dayLabel, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user