mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
				synced 2025-11-04 04:37:45 +00:00 
			
		
		
		
	Compare commits
	
		
			91 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					4b79a0b74c | ||
| 
						 | 
					c158f0aef8 | ||
| 
						 | 
					4e5c6a9426 | ||
| 
						 | 
					7ad184cb48 | ||
| 
						 | 
					41a7d5d0a8 | ||
| 
						 | 
					78c48e004c | ||
| 
						 | 
					7106d61881 | ||
| 
						 | 
					8ead4c4708 | ||
| 
						 | 
					52ca7d3503 | ||
| 
						 | 
					7d504da0a8 | ||
| 
						 | 
					c6dee2252b | ||
| 
						 | 
					680c4a9ec4 | ||
| 
						 | 
					3887d57fa4 | ||
| 
						 | 
					d733daed9d | ||
| 
						 | 
					de8651ab52 | ||
| 
						 | 
					0ce641d10b | ||
| 
						 | 
					316224b424 | ||
| 
						 | 
					cf9bbce284 | ||
| 
						 | 
					6eae6c046e | ||
| 
						 | 
					837a430228 | ||
| 
						 | 
					71431f8fb5 | ||
| 
						 | 
					0c7cd1f299 | ||
| 
						 | 
					674682e919 | ||
| 
						 | 
					a5ca8115af | ||
| 
						 | 
					d4338fce42 | ||
| 
						 | 
					14e8135f81 | ||
| 
						 | 
					e925f07505 | ||
| 
						 | 
					b792b51bd0 | ||
| 
						 | 
					fb64813b2a | ||
| 
						 | 
					b16e0e33ab | ||
| 
						 | 
					818921e4a2 | ||
| 
						 | 
					6c437459ca | ||
| 
						 | 
					b276901874 | ||
| 
						 | 
					85b92f46f5 | ||
| 
						 | 
					237b8b5ede | ||
| 
						 | 
					438d008c34 | ||
| 
						 | 
					53a3de1ebc | ||
| 
						 | 
					2d35747e75 | ||
| 
						 | 
					71feebea6d | ||
| 
						 | 
					c8c75e7a70 | ||
| 
						 | 
					7b2263e9a5 | ||
| 
						 | 
					9cd216bbba | ||
| 
						 | 
					e032ff4485 | ||
| 
						 | 
					fbe9ca5dd9 | ||
| 
						 | 
					4533bb6dd7 | ||
| 
						 | 
					3320c03603 | ||
| 
						 | 
					c3574d96d7 | ||
| 
						 | 
					ebd2419634 | ||
| 
						 | 
					133c256543 | ||
| 
						 | 
					98a2a72f33 | ||
| 
						 | 
					bc12b598ce | ||
| 
						 | 
					a34f679c43 | ||
| 
						 | 
					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:
 | 
					  pull_request:
 | 
				
			||||||
    branches:
 | 
					    branches:
 | 
				
			||||||
      - main
 | 
					      - main
 | 
				
			||||||
 | 
					      - 'release/*'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defaults:
 | 
					defaults:
 | 
				
			||||||
  run:
 | 
					  run:
 | 
				
			||||||
@@ -37,3 +38,35 @@ jobs:
 | 
				
			|||||||
        registry: tip-tip-wlan-cloud-ucentral.jfrog.io
 | 
					        registry: tip-tip-wlan-cloud-ucentral.jfrog.io
 | 
				
			||||||
        registry_user: ucentral
 | 
					        registry_user: ucentral
 | 
				
			||||||
        registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
 | 
					        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"}'
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,6 +4,7 @@ on:
 | 
				
			|||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    branches:
 | 
					    branches:
 | 
				
			||||||
      - main
 | 
					      - main
 | 
				
			||||||
 | 
					      - 'release/*'
 | 
				
			||||||
    types: [ closed ]
 | 
					    types: [ closed ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defaults:
 | 
					defaults:
 | 
				
			||||||
@@ -16,4 +17,10 @@ jobs:
 | 
				
			|||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - run: |
 | 
					      - run: |
 | 
				
			||||||
          export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-')
 | 
					          export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-')
 | 
				
			||||||
          curl -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owgw-ui/$PR_BRANCH_TAG"
 | 
					
 | 
				
			||||||
 | 
					          if [[ ! $PR_BRANCH_TAG =~ (main|master|release-*) ]]; then
 | 
				
			||||||
 | 
					            echo "PR branch is $PR_BRANCH_TAG, deleting Docker image"
 | 
				
			||||||
 | 
					            curl -s -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owgw-ui/$PR_BRANCH_TAG"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            echo "PR branch is $PR_BRANCH_TAG, not deleting Docker image"
 | 
				
			||||||
 | 
					          fi
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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/*
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
FROM node:14-alpine3.11 AS build
 | 
					FROM node:18.7.0-alpine3.15 AS build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY package.json package-lock.json /
 | 
					COPY package.json package-lock.json /
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,7 +8,7 @@ COPY . .
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
RUN npm run build
 | 
					RUN npm run build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM nginx:1.20.1-alpine AS runtime
 | 
					FROM nginx:1.22.0-alpine AS runtime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --from=build /build/ /usr/share/nginx/html/
 | 
					COPY --from=build /build/ /usr/share/nginx/html/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,3 +30,13 @@ Create chart name and version as used by the chart label.
 | 
				
			|||||||
{{- define "owgwui.chart" -}}
 | 
					{{- define "owgwui.chart" -}}
 | 
				
			||||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
 | 
					{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
 | 
				
			||||||
{{- end -}}
 | 
					{{- 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 }}
 | 
					{{- range $ingress, $ingressValue := .Values.ingresses }}
 | 
				
			||||||
{{- if $ingressValue.enabled }}
 | 
					{{- if $ingressValue.enabled }}
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
apiVersion: extensions/v1beta1
 | 
					apiVersion: {{ include "owgwui.ingress.apiVersion" $root }}
 | 
				
			||||||
kind: Ingress
 | 
					kind: Ingress
 | 
				
			||||||
metadata:
 | 
					metadata:
 | 
				
			||||||
  name: {{ include "owgwui.fullname" $root }}-{{ $ingress }}
 | 
					  name: {{ include "owgwui.fullname" $root }}-{{ $ingress }}
 | 
				
			||||||
@@ -36,9 +36,23 @@ spec:
 | 
				
			|||||||
      paths:
 | 
					      paths:
 | 
				
			||||||
      {{- range $ingressValue.paths }}
 | 
					      {{- range $ingressValue.paths }}
 | 
				
			||||||
        - path: {{ .path }}
 | 
					        - path: {{ .path }}
 | 
				
			||||||
 | 
					          {{- if $root.Capabilities.APIVersions.Has "networking.k8s.io/v1" }}
 | 
				
			||||||
 | 
					          pathType: {{ .pathType | default "ImplementationSpecific" }}
 | 
				
			||||||
 | 
					          {{- end }}
 | 
				
			||||||
          backend:
 | 
					          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 }}
 | 
					            serviceName: {{ include "owgwui.fullname" $root }}-{{ .serviceName }}
 | 
				
			||||||
            servicePort: {{ .servicePort }}
 | 
					            servicePort: {{ .servicePort }}
 | 
				
			||||||
 | 
					            {{- end }}
 | 
				
			||||||
      {{- end }}
 | 
					      {{- end }}
 | 
				
			||||||
  {{- end }}
 | 
					  {{- end }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,12 +8,12 @@ fullnameOverride: ""
 | 
				
			|||||||
images:
 | 
					images:
 | 
				
			||||||
  owgwui:
 | 
					  owgwui:
 | 
				
			||||||
    repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
 | 
					    repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
 | 
				
			||||||
    tag: v2.5.1
 | 
					    tag: v2.7.0
 | 
				
			||||||
    pullPolicy: Always
 | 
					    pullPolicy: Always
 | 
				
			||||||
 | 
					
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  owgwui:
 | 
					  owgwui:
 | 
				
			||||||
    type: NodePort
 | 
					    type: ClusterIP
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      http:
 | 
					      http:
 | 
				
			||||||
        servicePort: 80
 | 
					        servicePort: 80
 | 
				
			||||||
@@ -49,6 +49,7 @@ ingresses:
 | 
				
			|||||||
    - chart-example.local
 | 
					    - chart-example.local
 | 
				
			||||||
    paths:
 | 
					    paths:
 | 
				
			||||||
    - path: /
 | 
					    - path: /
 | 
				
			||||||
 | 
					      pathType: ImplementationSpecific
 | 
				
			||||||
      serviceName: owgwui
 | 
					      serviceName: owgwui
 | 
				
			||||||
      servicePort: http
 | 
					      servicePort: http
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										650
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										650
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,12 +1,12 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "ucentral-client",
 | 
					  "name": "ucentral-client",
 | 
				
			||||||
  "version": "2.5.44",
 | 
					  "version": "2.7.0(8)",
 | 
				
			||||||
  "lockfileVersion": 2,
 | 
					  "lockfileVersion": 2,
 | 
				
			||||||
  "requires": true,
 | 
					  "requires": true,
 | 
				
			||||||
  "packages": {
 | 
					  "packages": {
 | 
				
			||||||
    "": {
 | 
					    "": {
 | 
				
			||||||
      "name": "ucentral-client",
 | 
					      "name": "ucentral-client",
 | 
				
			||||||
      "version": "2.5.44",
 | 
					      "version": "2.7.0(8)",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@coreui/coreui": "^3.4.0",
 | 
					        "@coreui/coreui": "^3.4.0",
 | 
				
			||||||
        "@coreui/icons": "^2.0.1",
 | 
					        "@coreui/icons": "^2.0.1",
 | 
				
			||||||
@@ -16,6 +16,7 @@
 | 
				
			|||||||
        "apexcharts": "^3.27.1",
 | 
					        "apexcharts": "^3.27.1",
 | 
				
			||||||
        "axios": "^0.21.1",
 | 
					        "axios": "^0.21.1",
 | 
				
			||||||
        "axios-retry": "^3.1.9",
 | 
					        "axios-retry": "^3.1.9",
 | 
				
			||||||
 | 
					        "buffer": "^6.0.3",
 | 
				
			||||||
        "dagre": "^0.8.5",
 | 
					        "dagre": "^0.8.5",
 | 
				
			||||||
        "i18next": "^20.3.1",
 | 
					        "i18next": "^20.3.1",
 | 
				
			||||||
        "i18next-browser-languagedetector": "^6.1.2",
 | 
					        "i18next-browser-languagedetector": "^6.1.2",
 | 
				
			||||||
@@ -23,6 +24,7 @@
 | 
				
			|||||||
        "prop-types": "^15.7.2",
 | 
					        "prop-types": "^15.7.2",
 | 
				
			||||||
        "react": "^17.0.2",
 | 
					        "react": "^17.0.2",
 | 
				
			||||||
        "react-apexcharts": "^1.3.9",
 | 
					        "react-apexcharts": "^1.3.9",
 | 
				
			||||||
 | 
					        "react-country-flag": "^3.0.2",
 | 
				
			||||||
        "react-csv": "^2.2.2",
 | 
					        "react-csv": "^2.2.2",
 | 
				
			||||||
        "react-dom": "^17.0.2",
 | 
					        "react-dom": "^17.0.2",
 | 
				
			||||||
        "react-flow-renderer": "^9.6.6",
 | 
					        "react-flow-renderer": "^9.6.6",
 | 
				
			||||||
@@ -33,7 +35,7 @@
 | 
				
			|||||||
        "react-tooltip": "^4.2.21",
 | 
					        "react-tooltip": "^4.2.21",
 | 
				
			||||||
        "react-widgets": "^5.1.1",
 | 
					        "react-widgets": "^5.1.1",
 | 
				
			||||||
        "sass": "^1.35.1",
 | 
					        "sass": "^1.35.1",
 | 
				
			||||||
        "ucentral-libs": "^1.0.60",
 | 
					        "ucentral-libs": "^1.0.61",
 | 
				
			||||||
        "uuid": "^8.3.2"
 | 
					        "uuid": "^8.3.2"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "devDependencies": {
 | 
					      "devDependencies": {
 | 
				
			||||||
@@ -2067,6 +2069,64 @@
 | 
				
			|||||||
      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
 | 
					      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@jridgewell/gen-mapping": {
 | 
				
			||||||
 | 
					      "version": "0.3.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@jridgewell/set-array": "^1.0.1",
 | 
				
			||||||
 | 
					        "@jridgewell/sourcemap-codec": "^1.4.10",
 | 
				
			||||||
 | 
					        "@jridgewell/trace-mapping": "^0.3.9"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=6.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@jridgewell/resolve-uri": {
 | 
				
			||||||
 | 
					      "version": "3.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=6.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@jridgewell/set-array": {
 | 
				
			||||||
 | 
					      "version": "1.1.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=6.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@jridgewell/source-map": {
 | 
				
			||||||
 | 
					      "version": "0.3.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@jridgewell/gen-mapping": "^0.3.0",
 | 
				
			||||||
 | 
					        "@jridgewell/trace-mapping": "^0.3.9"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@jridgewell/sourcemap-codec": {
 | 
				
			||||||
 | 
					      "version": "1.4.14",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
 | 
				
			||||||
 | 
					      "dev": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@jridgewell/trace-mapping": {
 | 
				
			||||||
 | 
					      "version": "0.3.14",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@jridgewell/resolve-uri": "^3.0.3",
 | 
				
			||||||
 | 
					        "@jridgewell/sourcemap-codec": "^1.4.10"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@nodelib/fs.scandir": {
 | 
					    "node_modules/@nodelib/fs.scandir": {
 | 
				
			||||||
      "version": "2.1.5",
 | 
					      "version": "2.1.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
 | 
				
			||||||
@@ -2944,10 +3004,22 @@
 | 
				
			|||||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
					        "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": {
 | 
					    "node_modules/ansi-html": {
 | 
				
			||||||
      "version": "0.0.7",
 | 
					      "version": "0.0.7",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
 | 
				
			||||||
      "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=",
 | 
					      "integrity": "sha512-JoAxEa1DfP9m2xfB/y2r/aKcwXNlltr4+0QSBC4TrLfcxyvepX2Pv0t/xpgGV5bGsDzCYV8SzjWgyCW0T9yYbA==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "engines": [
 | 
					      "engines": [
 | 
				
			||||||
        "node >= 0.8.0"
 | 
					        "node >= 0.8.0"
 | 
				
			||||||
@@ -3176,9 +3248,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/async": {
 | 
					    "node_modules/async": {
 | 
				
			||||||
      "version": "2.6.3",
 | 
					      "version": "2.6.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
 | 
					      "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "lodash": "^4.17.14"
 | 
					        "lodash": "^4.17.14"
 | 
				
			||||||
@@ -3419,6 +3491,25 @@
 | 
				
			|||||||
        "node": ">=0.10.0"
 | 
					        "node": ">=0.10.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/base64-js": {
 | 
				
			||||||
 | 
					      "version": "1.5.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
 | 
				
			||||||
 | 
					      "funding": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "type": "github",
 | 
				
			||||||
 | 
					          "url": "https://github.com/sponsors/feross"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "type": "patreon",
 | 
				
			||||||
 | 
					          "url": "https://www.patreon.com/feross"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "type": "consulting",
 | 
				
			||||||
 | 
					          "url": "https://feross.org/support"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/batch": {
 | 
					    "node_modules/batch": {
 | 
				
			||||||
      "version": "0.6.1",
 | 
					      "version": "0.6.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
 | 
				
			||||||
@@ -3561,6 +3652,29 @@
 | 
				
			|||||||
        "url": "https://opencollective.com/browserslist"
 | 
					        "url": "https://opencollective.com/browserslist"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/buffer": {
 | 
				
			||||||
 | 
					      "version": "6.0.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
 | 
				
			||||||
 | 
					      "funding": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "type": "github",
 | 
				
			||||||
 | 
					          "url": "https://github.com/sponsors/feross"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "type": "patreon",
 | 
				
			||||||
 | 
					          "url": "https://www.patreon.com/feross"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "type": "consulting",
 | 
				
			||||||
 | 
					          "url": "https://feross.org/support"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "base64-js": "^1.3.1",
 | 
				
			||||||
 | 
					        "ieee754": "^1.2.1"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/buffer-from": {
 | 
					    "node_modules/buffer-from": {
 | 
				
			||||||
      "version": "1.1.2",
 | 
					      "version": "1.1.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
 | 
				
			||||||
@@ -3943,9 +4057,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/cliui/node_modules/ansi-regex": {
 | 
					    "node_modules/cliui/node_modules/ansi-regex": {
 | 
				
			||||||
      "version": "4.1.0",
 | 
					      "version": "4.1.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
 | 
					      "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=6"
 | 
					        "node": ">=6"
 | 
				
			||||||
@@ -6208,9 +6322,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/eventsource": {
 | 
					    "node_modules/eventsource": {
 | 
				
			||||||
      "version": "1.1.0",
 | 
					      "version": "1.1.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
 | 
					      "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "original": "^1.0.0"
 | 
					        "original": "^1.0.0"
 | 
				
			||||||
@@ -6740,9 +6854,9 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/follow-redirects": {
 | 
					    "node_modules/follow-redirects": {
 | 
				
			||||||
      "version": "1.14.7",
 | 
					      "version": "1.14.9",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
 | 
				
			||||||
      "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
 | 
					      "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
 | 
				
			||||||
      "funding": [
 | 
					      "funding": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "type": "individual",
 | 
					          "type": "individual",
 | 
				
			||||||
@@ -7264,60 +7378,6 @@
 | 
				
			|||||||
        "node": ">=12"
 | 
					        "node": ">=12"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/html-minifier-terser/node_modules/acorn": {
 | 
					 | 
				
			||||||
      "version": "8.7.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "peer": true,
 | 
					 | 
				
			||||||
      "bin": {
 | 
					 | 
				
			||||||
        "acorn": "bin/acorn"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=0.4.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/html-minifier-terser/node_modules/source-map": {
 | 
					 | 
				
			||||||
      "version": "0.7.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">= 8"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/html-minifier-terser/node_modules/terser": {
 | 
					 | 
				
			||||||
      "version": "5.10.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "commander": "^2.20.0",
 | 
					 | 
				
			||||||
        "source-map": "~0.7.2",
 | 
					 | 
				
			||||||
        "source-map-support": "~0.5.20"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "bin": {
 | 
					 | 
				
			||||||
        "terser": "bin/terser"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=10"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "acorn": "^8.5.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependenciesMeta": {
 | 
					 | 
				
			||||||
        "acorn": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": {
 | 
					 | 
				
			||||||
      "version": "2.20.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
 | 
					 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/html-parse-stringify": {
 | 
					    "node_modules/html-parse-stringify": {
 | 
				
			||||||
      "version": "3.0.1",
 | 
					      "version": "3.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
 | 
				
			||||||
@@ -7773,6 +7833,25 @@
 | 
				
			|||||||
        "postcss": "^8.1.0"
 | 
					        "postcss": "^8.1.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/ieee754": {
 | 
				
			||||||
 | 
					      "version": "1.2.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
 | 
				
			||||||
 | 
					      "funding": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "type": "github",
 | 
				
			||||||
 | 
					          "url": "https://github.com/sponsors/feross"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "type": "patreon",
 | 
				
			||||||
 | 
					          "url": "https://www.patreon.com/feross"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "type": "consulting",
 | 
				
			||||||
 | 
					          "url": "https://feross.org/support"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/ignore": {
 | 
					    "node_modules/ignore": {
 | 
				
			||||||
      "version": "4.0.6",
 | 
					      "version": "4.0.6",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
 | 
				
			||||||
@@ -8432,12 +8511,6 @@
 | 
				
			|||||||
      "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
 | 
					      "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/json3": {
 | 
					 | 
				
			||||||
      "version": "3.3.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==",
 | 
					 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/json5": {
 | 
					    "node_modules/json5": {
 | 
				
			||||||
      "version": "2.2.0",
 | 
					      "version": "2.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
 | 
				
			||||||
@@ -9101,9 +9174,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/minimist": {
 | 
					    "node_modules/minimist": {
 | 
				
			||||||
      "version": "1.2.5",
 | 
					      "version": "1.2.6",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
 | 
				
			||||||
      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
 | 
					      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
 | 
				
			||||||
      "devOptional": true
 | 
					      "devOptional": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/mixin-deep": {
 | 
					    "node_modules/mixin-deep": {
 | 
				
			||||||
@@ -9132,9 +9205,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/moment": {
 | 
					    "node_modules/moment": {
 | 
				
			||||||
      "version": "2.29.1",
 | 
					      "version": "2.29.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
 | 
					      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": "*"
 | 
					        "node": "*"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -10915,6 +10988,17 @@
 | 
				
			|||||||
        "react": ">=0.13"
 | 
					        "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": {
 | 
					    "node_modules/react-csv": {
 | 
				
			||||||
      "version": "2.2.2",
 | 
					      "version": "2.2.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz",
 | 
				
			||||||
@@ -11715,9 +11799,9 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/semver-regex": {
 | 
					    "node_modules/semver-regex": {
 | 
				
			||||||
      "version": "3.1.3",
 | 
					      "version": "3.1.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ==",
 | 
					      "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=8"
 | 
					        "node": ">=8"
 | 
				
			||||||
@@ -12220,17 +12304,22 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/sockjs-client": {
 | 
					    "node_modules/sockjs-client": {
 | 
				
			||||||
      "version": "1.5.2",
 | 
					      "version": "1.6.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==",
 | 
					      "integrity": "sha512-qVHJlyfdHFht3eBFZdKEXKTlb7I4IV41xnVNo8yUKA1UHcPJwgW2SvTq9LhnjjCywSkSK7c/e4nghU0GOoMCRQ==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "debug": "^3.2.6",
 | 
					        "debug": "^3.2.7",
 | 
				
			||||||
        "eventsource": "^1.0.7",
 | 
					        "eventsource": "^1.1.0",
 | 
				
			||||||
        "faye-websocket": "^0.11.3",
 | 
					        "faye-websocket": "^0.11.4",
 | 
				
			||||||
        "inherits": "^2.0.4",
 | 
					        "inherits": "^2.0.4",
 | 
				
			||||||
        "json3": "^3.3.3",
 | 
					        "url-parse": "^1.5.10"
 | 
				
			||||||
        "url-parse": "^1.5.3"
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=12"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "url": "https://tidelift.com/funding/github/npm/sockjs-client"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/sockjs-client/node_modules/debug": {
 | 
					    "node_modules/sockjs-client/node_modules/debug": {
 | 
				
			||||||
@@ -12988,6 +13077,24 @@
 | 
				
			|||||||
        "node": ">=6"
 | 
					        "node": ">=6"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/terser": {
 | 
				
			||||||
 | 
					      "version": "5.14.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@jridgewell/source-map": "^0.3.2",
 | 
				
			||||||
 | 
					        "acorn": "^8.5.0",
 | 
				
			||||||
 | 
					        "commander": "^2.20.0",
 | 
				
			||||||
 | 
					        "source-map-support": "~0.5.20"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "bin": {
 | 
				
			||||||
 | 
					        "terser": "bin/terser"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=10"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/terser-webpack-plugin": {
 | 
					    "node_modules/terser-webpack-plugin": {
 | 
				
			||||||
      "version": "5.3.0",
 | 
					      "version": "5.3.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
 | 
				
			||||||
@@ -13022,26 +13129,6 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/terser-webpack-plugin/node_modules/acorn": {
 | 
					 | 
				
			||||||
      "version": "8.7.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "peer": true,
 | 
					 | 
				
			||||||
      "bin": {
 | 
					 | 
				
			||||||
        "acorn": "bin/acorn"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=0.4.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/terser-webpack-plugin/node_modules/commander": {
 | 
					 | 
				
			||||||
      "version": "2.20.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
 | 
					 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/terser-webpack-plugin/node_modules/has-flag": {
 | 
					    "node_modules/terser-webpack-plugin/node_modules/has-flag": {
 | 
				
			||||||
      "version": "4.0.0",
 | 
					      "version": "4.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
 | 
				
			||||||
@@ -13107,39 +13194,23 @@
 | 
				
			|||||||
        "url": "https://github.com/chalk/supports-color?sponsor=1"
 | 
					        "url": "https://github.com/chalk/supports-color?sponsor=1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/terser-webpack-plugin/node_modules/terser": {
 | 
					    "node_modules/terser/node_modules/acorn": {
 | 
				
			||||||
      "version": "5.10.0",
 | 
					      "version": "8.8.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
 | 
					      "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "commander": "^2.20.0",
 | 
					 | 
				
			||||||
        "source-map": "~0.7.2",
 | 
					 | 
				
			||||||
        "source-map-support": "~0.5.20"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "bin": {
 | 
					      "bin": {
 | 
				
			||||||
        "terser": "bin/terser"
 | 
					        "acorn": "bin/acorn"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=10"
 | 
					        "node": ">=0.4.0"
 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "acorn": "^8.5.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependenciesMeta": {
 | 
					 | 
				
			||||||
        "acorn": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/terser-webpack-plugin/node_modules/terser/node_modules/source-map": {
 | 
					    "node_modules/terser/node_modules/commander": {
 | 
				
			||||||
      "version": "0.7.3",
 | 
					      "version": "2.20.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
 | 
					      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true
 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">= 8"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/text-table": {
 | 
					    "node_modules/text-table": {
 | 
				
			||||||
      "version": "0.2.0",
 | 
					      "version": "0.2.0",
 | 
				
			||||||
@@ -13308,10 +13379,12 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/type-fest": {
 | 
					    "node_modules/type-fest": {
 | 
				
			||||||
      "version": "0.21.3",
 | 
					      "version": "0.13.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
 | 
					      "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "peer": true,
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=10"
 | 
					        "node": ">=10"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -13333,9 +13406,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/ucentral-libs": {
 | 
					    "node_modules/ucentral-libs": {
 | 
				
			||||||
      "version": "1.0.60",
 | 
					      "version": "1.0.61",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-1.0.60.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-1.0.61.tgz",
 | 
				
			||||||
      "integrity": "sha512-PRw2QTcbnHdrA8rPQhREI1FOKyuZUt48H3KcSGQgHpik2Ni+0una7jRfMFXwOU9yHzxQAlYG0EWLrzBnrKRvGA==",
 | 
					      "integrity": "sha512-RMUFLC6PMeh4S1MSkDXYjpQfh4yWeZX5Rm5FTRNbfYfaLKuL8CbRZjnuGPFrgABGQRWk5TITxXQASYBpmOq1dQ==",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@coreui/coreui": "^3.4.0",
 | 
					        "@coreui/coreui": "^3.4.0",
 | 
				
			||||||
        "@coreui/icons": "^2.0.1",
 | 
					        "@coreui/icons": "^2.0.1",
 | 
				
			||||||
@@ -13570,9 +13643,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/url-parse": {
 | 
					    "node_modules/url-parse": {
 | 
				
			||||||
      "version": "1.5.4",
 | 
					      "version": "1.5.10",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
 | 
				
			||||||
      "integrity": "sha512-ITeAByWWoqutFClc/lRZnFplgXgEZr3WJ6XngMM/N9DMIm4K8zXPCZ1Jdu0rERwO84w1WC5wkle2ubwTA4NTBg==",
 | 
					      "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "querystringify": "^2.1.1",
 | 
					        "querystringify": "^2.1.1",
 | 
				
			||||||
@@ -14182,7 +14255,7 @@
 | 
				
			|||||||
    "node_modules/webpack-dev-server/node_modules/glob-parent": {
 | 
					    "node_modules/webpack-dev-server/node_modules/glob-parent": {
 | 
				
			||||||
      "version": "3.1.0",
 | 
					      "version": "3.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
 | 
				
			||||||
      "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
 | 
					      "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "is-glob": "^3.1.0",
 | 
					        "is-glob": "^3.1.0",
 | 
				
			||||||
@@ -14804,9 +14877,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/yargs/node_modules/ansi-regex": {
 | 
					    "node_modules/yargs/node_modules/ansi-regex": {
 | 
				
			||||||
      "version": "4.1.0",
 | 
					      "version": "4.1.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
 | 
					      "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=6"
 | 
					        "node": ">=6"
 | 
				
			||||||
@@ -16327,6 +16400,55 @@
 | 
				
			|||||||
      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
 | 
					      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "@jridgewell/gen-mapping": {
 | 
				
			||||||
 | 
					      "version": "0.3.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@jridgewell/set-array": "^1.0.1",
 | 
				
			||||||
 | 
					        "@jridgewell/sourcemap-codec": "^1.4.10",
 | 
				
			||||||
 | 
					        "@jridgewell/trace-mapping": "^0.3.9"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "@jridgewell/resolve-uri": {
 | 
				
			||||||
 | 
					      "version": "3.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
 | 
				
			||||||
 | 
					      "dev": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "@jridgewell/set-array": {
 | 
				
			||||||
 | 
					      "version": "1.1.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
 | 
				
			||||||
 | 
					      "dev": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "@jridgewell/source-map": {
 | 
				
			||||||
 | 
					      "version": "0.3.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@jridgewell/gen-mapping": "^0.3.0",
 | 
				
			||||||
 | 
					        "@jridgewell/trace-mapping": "^0.3.9"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "@jridgewell/sourcemap-codec": {
 | 
				
			||||||
 | 
					      "version": "1.4.14",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
 | 
				
			||||||
 | 
					      "dev": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "@jridgewell/trace-mapping": {
 | 
				
			||||||
 | 
					      "version": "0.3.14",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@jridgewell/resolve-uri": "^3.0.3",
 | 
				
			||||||
 | 
					        "@jridgewell/sourcemap-codec": "^1.4.10"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "@nodelib/fs.scandir": {
 | 
					    "@nodelib/fs.scandir": {
 | 
				
			||||||
      "version": "2.1.5",
 | 
					      "version": "2.1.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
 | 
				
			||||||
@@ -17000,12 +17122,20 @@
 | 
				
			|||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "type-fest": "^0.21.3"
 | 
					        "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": {
 | 
					    "ansi-html": {
 | 
				
			||||||
      "version": "0.0.7",
 | 
					      "version": "0.0.7",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
 | 
				
			||||||
      "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=",
 | 
					      "integrity": "sha512-JoAxEa1DfP9m2xfB/y2r/aKcwXNlltr4+0QSBC4TrLfcxyvepX2Pv0t/xpgGV5bGsDzCYV8SzjWgyCW0T9yYbA==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "ansi-html-community": {
 | 
					    "ansi-html-community": {
 | 
				
			||||||
@@ -17168,9 +17298,9 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "async": {
 | 
					    "async": {
 | 
				
			||||||
      "version": "2.6.3",
 | 
					      "version": "2.6.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
 | 
					      "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "lodash": "^4.17.14"
 | 
					        "lodash": "^4.17.14"
 | 
				
			||||||
@@ -17358,6 +17488,11 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "base64-js": {
 | 
				
			||||||
 | 
					      "version": "1.5.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "batch": {
 | 
					    "batch": {
 | 
				
			||||||
      "version": "0.6.1",
 | 
					      "version": "0.6.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
 | 
				
			||||||
@@ -17477,6 +17612,15 @@
 | 
				
			|||||||
        "picocolors": "^1.0.0"
 | 
					        "picocolors": "^1.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "buffer": {
 | 
				
			||||||
 | 
					      "version": "6.0.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "base64-js": "^1.3.1",
 | 
				
			||||||
 | 
					        "ieee754": "^1.2.1"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "buffer-from": {
 | 
					    "buffer-from": {
 | 
				
			||||||
      "version": "1.1.2",
 | 
					      "version": "1.1.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
 | 
				
			||||||
@@ -17780,9 +17924,9 @@
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "ansi-regex": {
 | 
					        "ansi-regex": {
 | 
				
			||||||
          "version": "4.1.0",
 | 
					          "version": "4.1.1",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
 | 
					          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
 | 
				
			||||||
          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
 | 
					          "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
 | 
				
			||||||
          "dev": true
 | 
					          "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "emoji-regex": {
 | 
					        "emoji-regex": {
 | 
				
			||||||
@@ -19488,9 +19632,9 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "eventsource": {
 | 
					    "eventsource": {
 | 
				
			||||||
      "version": "1.1.0",
 | 
					      "version": "1.1.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
 | 
					      "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "original": "^1.0.0"
 | 
					        "original": "^1.0.0"
 | 
				
			||||||
@@ -19924,9 +20068,9 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "follow-redirects": {
 | 
					    "follow-redirects": {
 | 
				
			||||||
      "version": "1.14.7",
 | 
					      "version": "1.14.9",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
 | 
				
			||||||
      "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ=="
 | 
					      "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "for-in": {
 | 
					    "for-in": {
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
@@ -20315,41 +20459,6 @@
 | 
				
			|||||||
        "param-case": "^3.0.4",
 | 
					        "param-case": "^3.0.4",
 | 
				
			||||||
        "relateurl": "^0.2.7",
 | 
					        "relateurl": "^0.2.7",
 | 
				
			||||||
        "terser": "^5.10.0"
 | 
					        "terser": "^5.10.0"
 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "acorn": {
 | 
					 | 
				
			||||||
          "version": "8.7.0",
 | 
					 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
 | 
					 | 
				
			||||||
          "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
 | 
					 | 
				
			||||||
          "dev": true,
 | 
					 | 
				
			||||||
          "optional": true,
 | 
					 | 
				
			||||||
          "peer": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "source-map": {
 | 
					 | 
				
			||||||
          "version": "0.7.3",
 | 
					 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
 | 
					 | 
				
			||||||
          "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
 | 
					 | 
				
			||||||
          "dev": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "terser": {
 | 
					 | 
				
			||||||
          "version": "5.10.0",
 | 
					 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
 | 
					 | 
				
			||||||
          "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
 | 
					 | 
				
			||||||
          "dev": true,
 | 
					 | 
				
			||||||
          "requires": {
 | 
					 | 
				
			||||||
            "commander": "^2.20.0",
 | 
					 | 
				
			||||||
            "source-map": "~0.7.2",
 | 
					 | 
				
			||||||
            "source-map-support": "~0.5.20"
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          "dependencies": {
 | 
					 | 
				
			||||||
            "commander": {
 | 
					 | 
				
			||||||
              "version": "2.20.3",
 | 
					 | 
				
			||||||
              "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
 | 
					 | 
				
			||||||
              "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
 | 
					 | 
				
			||||||
              "dev": true
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "html-parse-stringify": {
 | 
					    "html-parse-stringify": {
 | 
				
			||||||
@@ -20696,6 +20805,11 @@
 | 
				
			|||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {}
 | 
					      "requires": {}
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "ieee754": {
 | 
				
			||||||
 | 
					      "version": "1.2.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "ignore": {
 | 
					    "ignore": {
 | 
				
			||||||
      "version": "4.0.6",
 | 
					      "version": "4.0.6",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
 | 
				
			||||||
@@ -21165,12 +21279,6 @@
 | 
				
			|||||||
      "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
 | 
					      "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "json3": {
 | 
					 | 
				
			||||||
      "version": "3.3.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==",
 | 
					 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "json5": {
 | 
					    "json5": {
 | 
				
			||||||
      "version": "2.2.0",
 | 
					      "version": "2.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
 | 
				
			||||||
@@ -21682,9 +21790,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "minimist": {
 | 
					    "minimist": {
 | 
				
			||||||
      "version": "1.2.5",
 | 
					      "version": "1.2.6",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
 | 
				
			||||||
      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
 | 
					      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
 | 
				
			||||||
      "devOptional": true
 | 
					      "devOptional": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "mixin-deep": {
 | 
					    "mixin-deep": {
 | 
				
			||||||
@@ -21707,9 +21815,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "moment": {
 | 
					    "moment": {
 | 
				
			||||||
      "version": "2.29.1",
 | 
					      "version": "2.29.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
 | 
					      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "mrmime": {
 | 
					    "mrmime": {
 | 
				
			||||||
      "version": "1.0.0",
 | 
					      "version": "1.0.0",
 | 
				
			||||||
@@ -22983,6 +23091,12 @@
 | 
				
			|||||||
        "prop-types": "^15.5.7"
 | 
					        "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": {
 | 
					    "react-csv": {
 | 
				
			||||||
      "version": "2.2.2",
 | 
					      "version": "2.2.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz",
 | 
				
			||||||
@@ -23576,9 +23690,9 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "semver-regex": {
 | 
					    "semver-regex": {
 | 
				
			||||||
      "version": "3.1.3",
 | 
					      "version": "3.1.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ==",
 | 
					      "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "send": {
 | 
					    "send": {
 | 
				
			||||||
@@ -24002,17 +24116,16 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "sockjs-client": {
 | 
					    "sockjs-client": {
 | 
				
			||||||
      "version": "1.5.2",
 | 
					      "version": "1.6.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==",
 | 
					      "integrity": "sha512-qVHJlyfdHFht3eBFZdKEXKTlb7I4IV41xnVNo8yUKA1UHcPJwgW2SvTq9LhnjjCywSkSK7c/e4nghU0GOoMCRQ==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "debug": "^3.2.6",
 | 
					        "debug": "^3.2.7",
 | 
				
			||||||
        "eventsource": "^1.0.7",
 | 
					        "eventsource": "^1.1.0",
 | 
				
			||||||
        "faye-websocket": "^0.11.3",
 | 
					        "faye-websocket": "^0.11.4",
 | 
				
			||||||
        "inherits": "^2.0.4",
 | 
					        "inherits": "^2.0.4",
 | 
				
			||||||
        "json3": "^3.3.3",
 | 
					        "url-parse": "^1.5.10"
 | 
				
			||||||
        "url-parse": "^1.5.3"
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "debug": {
 | 
					        "debug": {
 | 
				
			||||||
@@ -24609,6 +24722,32 @@
 | 
				
			|||||||
      "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
 | 
					      "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "terser": {
 | 
				
			||||||
 | 
					      "version": "5.14.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@jridgewell/source-map": "^0.3.2",
 | 
				
			||||||
 | 
					        "acorn": "^8.5.0",
 | 
				
			||||||
 | 
					        "commander": "^2.20.0",
 | 
				
			||||||
 | 
					        "source-map-support": "~0.5.20"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "acorn": {
 | 
				
			||||||
 | 
					          "version": "8.8.0",
 | 
				
			||||||
 | 
					          "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
 | 
				
			||||||
 | 
					          "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
 | 
				
			||||||
 | 
					          "dev": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "commander": {
 | 
				
			||||||
 | 
					          "version": "2.20.3",
 | 
				
			||||||
 | 
					          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
 | 
				
			||||||
 | 
					          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
 | 
				
			||||||
 | 
					          "dev": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "terser-webpack-plugin": {
 | 
					    "terser-webpack-plugin": {
 | 
				
			||||||
      "version": "5.3.0",
 | 
					      "version": "5.3.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
 | 
				
			||||||
@@ -24622,20 +24761,6 @@
 | 
				
			|||||||
        "terser": "^5.7.2"
 | 
					        "terser": "^5.7.2"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "acorn": {
 | 
					 | 
				
			||||||
          "version": "8.7.0",
 | 
					 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
 | 
					 | 
				
			||||||
          "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
 | 
					 | 
				
			||||||
          "dev": true,
 | 
					 | 
				
			||||||
          "optional": true,
 | 
					 | 
				
			||||||
          "peer": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "commander": {
 | 
					 | 
				
			||||||
          "version": "2.20.3",
 | 
					 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
 | 
					 | 
				
			||||||
          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
 | 
					 | 
				
			||||||
          "dev": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "has-flag": {
 | 
					        "has-flag": {
 | 
				
			||||||
          "version": "4.0.0",
 | 
					          "version": "4.0.0",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
 | 
					          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
 | 
				
			||||||
@@ -24678,25 +24803,6 @@
 | 
				
			|||||||
          "requires": {
 | 
					          "requires": {
 | 
				
			||||||
            "has-flag": "^4.0.0"
 | 
					            "has-flag": "^4.0.0"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "terser": {
 | 
					 | 
				
			||||||
          "version": "5.10.0",
 | 
					 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
 | 
					 | 
				
			||||||
          "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
 | 
					 | 
				
			||||||
          "dev": true,
 | 
					 | 
				
			||||||
          "requires": {
 | 
					 | 
				
			||||||
            "commander": "^2.20.0",
 | 
					 | 
				
			||||||
            "source-map": "~0.7.2",
 | 
					 | 
				
			||||||
            "source-map-support": "~0.5.20"
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          "dependencies": {
 | 
					 | 
				
			||||||
            "source-map": {
 | 
					 | 
				
			||||||
              "version": "0.7.3",
 | 
					 | 
				
			||||||
              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
 | 
					 | 
				
			||||||
              "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
 | 
					 | 
				
			||||||
              "dev": true
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -24844,10 +24950,12 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "type-fest": {
 | 
					    "type-fest": {
 | 
				
			||||||
      "version": "0.21.3",
 | 
					      "version": "0.13.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
 | 
					      "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "peer": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "type-is": {
 | 
					    "type-is": {
 | 
				
			||||||
      "version": "1.6.18",
 | 
					      "version": "1.6.18",
 | 
				
			||||||
@@ -24860,9 +24968,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "ucentral-libs": {
 | 
					    "ucentral-libs": {
 | 
				
			||||||
      "version": "1.0.60",
 | 
					      "version": "1.0.61",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-1.0.60.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-1.0.61.tgz",
 | 
				
			||||||
      "integrity": "sha512-PRw2QTcbnHdrA8rPQhREI1FOKyuZUt48H3KcSGQgHpik2Ni+0una7jRfMFXwOU9yHzxQAlYG0EWLrzBnrKRvGA==",
 | 
					      "integrity": "sha512-RMUFLC6PMeh4S1MSkDXYjpQfh4yWeZX5Rm5FTRNbfYfaLKuL8CbRZjnuGPFrgABGQRWk5TITxXQASYBpmOq1dQ==",
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "@coreui/coreui": "^3.4.0",
 | 
					        "@coreui/coreui": "^3.4.0",
 | 
				
			||||||
        "@coreui/icons": "^2.0.1",
 | 
					        "@coreui/icons": "^2.0.1",
 | 
				
			||||||
@@ -25062,9 +25170,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "url-parse": {
 | 
					    "url-parse": {
 | 
				
			||||||
      "version": "1.5.4",
 | 
					      "version": "1.5.10",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
 | 
				
			||||||
      "integrity": "sha512-ITeAByWWoqutFClc/lRZnFplgXgEZr3WJ6XngMM/N9DMIm4K8zXPCZ1Jdu0rERwO84w1WC5wkle2ubwTA4NTBg==",
 | 
					      "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "querystringify": "^2.1.1",
 | 
					        "querystringify": "^2.1.1",
 | 
				
			||||||
@@ -25545,7 +25653,7 @@
 | 
				
			|||||||
        "glob-parent": {
 | 
					        "glob-parent": {
 | 
				
			||||||
          "version": "3.1.0",
 | 
					          "version": "3.1.0",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
 | 
					          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
 | 
				
			||||||
          "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
 | 
					          "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
 | 
				
			||||||
          "dev": true,
 | 
					          "dev": true,
 | 
				
			||||||
          "requires": {
 | 
					          "requires": {
 | 
				
			||||||
            "is-glob": "^3.1.0",
 | 
					            "is-glob": "^3.1.0",
 | 
				
			||||||
@@ -25983,9 +26091,9 @@
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "ansi-regex": {
 | 
					        "ansi-regex": {
 | 
				
			||||||
          "version": "4.1.0",
 | 
					          "version": "4.1.1",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
 | 
					          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
 | 
				
			||||||
          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
 | 
					          "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
 | 
				
			||||||
          "dev": true
 | 
					          "dev": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "emoji-regex": {
 | 
					        "emoji-regex": {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "ucentral-client",
 | 
					  "name": "ucentral-client",
 | 
				
			||||||
  "version": "2.5.44",
 | 
					  "version": "2.7.0(8)",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@coreui/coreui": "^3.4.0",
 | 
					    "@coreui/coreui": "^3.4.0",
 | 
				
			||||||
    "@coreui/icons": "^2.0.1",
 | 
					    "@coreui/icons": "^2.0.1",
 | 
				
			||||||
@@ -10,6 +10,7 @@
 | 
				
			|||||||
    "apexcharts": "^3.27.1",
 | 
					    "apexcharts": "^3.27.1",
 | 
				
			||||||
    "axios": "^0.21.1",
 | 
					    "axios": "^0.21.1",
 | 
				
			||||||
    "axios-retry": "^3.1.9",
 | 
					    "axios-retry": "^3.1.9",
 | 
				
			||||||
 | 
					    "buffer": "^6.0.3",
 | 
				
			||||||
    "dagre": "^0.8.5",
 | 
					    "dagre": "^0.8.5",
 | 
				
			||||||
    "i18next": "^20.3.1",
 | 
					    "i18next": "^20.3.1",
 | 
				
			||||||
    "i18next-browser-languagedetector": "^6.1.2",
 | 
					    "i18next-browser-languagedetector": "^6.1.2",
 | 
				
			||||||
@@ -17,6 +18,7 @@
 | 
				
			|||||||
    "prop-types": "^15.7.2",
 | 
					    "prop-types": "^15.7.2",
 | 
				
			||||||
    "react": "^17.0.2",
 | 
					    "react": "^17.0.2",
 | 
				
			||||||
    "react-apexcharts": "^1.3.9",
 | 
					    "react-apexcharts": "^1.3.9",
 | 
				
			||||||
 | 
					    "react-country-flag": "^3.0.2",
 | 
				
			||||||
    "react-csv": "^2.2.2",
 | 
					    "react-csv": "^2.2.2",
 | 
				
			||||||
    "react-dom": "^17.0.2",
 | 
					    "react-dom": "^17.0.2",
 | 
				
			||||||
    "react-flow-renderer": "^9.6.6",
 | 
					    "react-flow-renderer": "^9.6.6",
 | 
				
			||||||
@@ -27,7 +29,7 @@
 | 
				
			|||||||
    "react-tooltip": "^4.2.21",
 | 
					    "react-tooltip": "^4.2.21",
 | 
				
			||||||
    "react-widgets": "^5.1.1",
 | 
					    "react-widgets": "^5.1.1",
 | 
				
			||||||
    "sass": "^1.35.1",
 | 
					    "sass": "^1.35.1",
 | 
				
			||||||
    "ucentral-libs": "^1.0.60",
 | 
					    "ucentral-libs": "^1.0.61",
 | 
				
			||||||
    "uuid": "^8.3.2"
 | 
					    "uuid": "^8.3.2"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "main": "index.js",
 | 
					  "main": "index.js",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@
 | 
				
			|||||||
		"factory_reset": "Auf Werkseinstellungen zurückgesetzt",
 | 
							"factory_reset": "Auf Werkseinstellungen zurückgesetzt",
 | 
				
			||||||
		"firmware_upgrade": "Firmware Aktualisierung",
 | 
							"firmware_upgrade": "Firmware Aktualisierung",
 | 
				
			||||||
		"reboot": "Gerät neustarten",
 | 
							"reboot": "Gerät neustarten",
 | 
				
			||||||
 | 
							"request_ie": "Fordern Sie IEs an",
 | 
				
			||||||
		"telemetry": "Telemetrie",
 | 
							"telemetry": "Telemetrie",
 | 
				
			||||||
		"title": "Geräte Administrations",
 | 
							"title": "Geräte Administrations",
 | 
				
			||||||
		"trace": "Tcpdump starten",
 | 
							"trace": "Tcpdump starten",
 | 
				
			||||||
@@ -27,6 +28,7 @@
 | 
				
			|||||||
		"error": "Fehler beim Senden des Befehls!",
 | 
							"error": "Fehler beim Senden des Befehls!",
 | 
				
			||||||
		"error_delete_log": "Fehler beim Versuch zu löschen: {{error}}",
 | 
							"error_delete_log": "Fehler beim Versuch zu löschen: {{error}}",
 | 
				
			||||||
		"event_queue": "Ereigniswarteschlange",
 | 
							"event_queue": "Ereigniswarteschlange",
 | 
				
			||||||
 | 
							"reboot_start": "Der Neustartvorgang hat begonnen!",
 | 
				
			||||||
		"success": "Befehl wurde erfolgreich übermittelt",
 | 
							"success": "Befehl wurde erfolgreich übermittelt",
 | 
				
			||||||
		"title": "Gerätebefehle",
 | 
							"title": "Gerätebefehle",
 | 
				
			||||||
		"unable_queue": "Anfrage für Ereigniswarteschlange kann nicht abgeschlossen werden: {{error}}"
 | 
							"unable_queue": "Anfrage für Ereigniswarteschlange kann nicht abgeschlossen werden: {{error}}"
 | 
				
			||||||
@@ -148,6 +150,7 @@
 | 
				
			|||||||
		"need_date": "Du brauchst ein Datum...",
 | 
							"need_date": "Du brauchst ein Datum...",
 | 
				
			||||||
		"no": "Nein",
 | 
							"no": "Nein",
 | 
				
			||||||
		"no_addresses_found": "Keine Adressen gefunden",
 | 
							"no_addresses_found": "Keine Adressen gefunden",
 | 
				
			||||||
 | 
							"no_clients_found": "Keine Kunden gefunden",
 | 
				
			||||||
		"no_devices_found": "Keine Geräte gefunden",
 | 
							"no_devices_found": "Keine Geräte gefunden",
 | 
				
			||||||
		"no_items": "Keine Gegenstände",
 | 
							"no_items": "Keine Gegenstände",
 | 
				
			||||||
		"none": "Keiner",
 | 
							"none": "Keiner",
 | 
				
			||||||
@@ -323,15 +326,19 @@
 | 
				
			|||||||
	"device": {
 | 
						"device": {
 | 
				
			||||||
		"add_to_blacklist": "Gerät zur Blacklist hinzufügen",
 | 
							"add_to_blacklist": "Gerät zur Blacklist hinzufügen",
 | 
				
			||||||
		"all_devices": "Alle Geräte",
 | 
							"all_devices": "Alle Geräte",
 | 
				
			||||||
 | 
							"already_running_command": "Gerät führt bereits einen Befehl aus, bitte versuchen Sie es später erneut",
 | 
				
			||||||
		"blacklisted_on": "Datum",
 | 
							"blacklisted_on": "Datum",
 | 
				
			||||||
		"capabilities": "Fähigkeiten",
 | 
							"capabilities": "Fähigkeiten",
 | 
				
			||||||
		"certificate_explanation": "Zertifikate der angeschlossenen Geräte",
 | 
							"certificate_explanation": "Zertifikate der angeschlossenen Geräte",
 | 
				
			||||||
 | 
							"count_explanation": "Geräte, die auf diese Gateway-Instanz verweisen",
 | 
				
			||||||
		"edit_blacklist": "Gerät auf der schwarzen Liste bearbeiten",
 | 
							"edit_blacklist": "Gerät auf der schwarzen Liste bearbeiten",
 | 
				
			||||||
		"error_adding_blacklist": "Fehler beim Hinzufügen des Geräts zur Blacklist: {{error}}",
 | 
							"error_adding_blacklist": "Fehler beim Hinzufügen des Geräts zur Blacklist: {{error}}",
 | 
				
			||||||
		"error_edit_blacklist": "Fehler beim Bearbeiten der schwarzen Liste: {{error}}",
 | 
							"error_edit_blacklist": "Fehler beim Bearbeiten der schwarzen Liste: {{error}}",
 | 
				
			||||||
		"error_fetching_device": "Fehler beim Abrufen der Geräteinformationen: {{error}}",
 | 
							"error_fetching_device": "Fehler beim Abrufen der Geräteinformationen: {{error}}",
 | 
				
			||||||
		"error_fetching_devices": "Fehler beim Abrufen von Geräten: {{error}}",
 | 
							"error_fetching_devices": "Fehler beim Abrufen von Geräten: {{error}}",
 | 
				
			||||||
 | 
							"firmware_count_explanation": "Dies ist die Gesamtzahl der Geräte, die diesem Firmware-Server hinzugefügt wurden, einschließlich der Geräte, die derzeit nicht auf den zugehörigen Gateway-Server verweisen.",
 | 
				
			||||||
		"health_explanation": "Zustand der verbundenen Geräte ((Geräte = 100 % * 100 + Geräte > 90 % * 95 + Geräte > 60 % * 75 + Geräte < 60 % * 35) / Verbundene Geräte)",
 | 
							"health_explanation": "Zustand der verbundenen Geräte ((Geräte = 100 % * 100 + Geräte > 90 % * 95 + Geräte > 60 % * 75 + Geräte < 60 % * 35) / Verbundene Geräte)",
 | 
				
			||||||
 | 
							"mac_not_found": "Seriennummer nicht gefunden, Sie werden zur Seite „Geräte“ weitergeleitet",
 | 
				
			||||||
		"memory_explanation": "Anzahl verbundener Geräte mit entsprechendem belegtem Speicher %",
 | 
							"memory_explanation": "Anzahl verbundener Geräte mit entsprechendem belegtem Speicher %",
 | 
				
			||||||
		"remove_from_blacklist": "Von der schwarzen Liste entfernen",
 | 
							"remove_from_blacklist": "Von der schwarzen Liste entfernen",
 | 
				
			||||||
		"success_added_blacklist": "Gerät erfolgreich zur Blacklist hinzugefügt!",
 | 
							"success_added_blacklist": "Gerät erfolgreich zur Blacklist hinzugefügt!",
 | 
				
			||||||
@@ -424,7 +431,7 @@
 | 
				
			|||||||
		"to_release": "Zu",
 | 
							"to_release": "Zu",
 | 
				
			||||||
		"unknown_firmware_status": "Unbekannter Firmware-Status",
 | 
							"unknown_firmware_status": "Unbekannter Firmware-Status",
 | 
				
			||||||
		"upgrade": "Aktualisierung",
 | 
							"upgrade": "Aktualisierung",
 | 
				
			||||||
		"upgrade_command_submitted": "Upgrade-Befehl erfolgreich gesendet",
 | 
							"upgrade_command_submitted": "Aktualisierung läuft...",
 | 
				
			||||||
		"upgrade_to_latest": "Neueste",
 | 
							"upgrade_to_latest": "Neueste",
 | 
				
			||||||
		"upgrade_to_version": "Upgrade auf diese Revision",
 | 
							"upgrade_to_version": "Upgrade auf diese Revision",
 | 
				
			||||||
		"upgrading": "Upgrade durchführen..."
 | 
							"upgrading": "Upgrade durchführen..."
 | 
				
			||||||
@@ -715,6 +722,8 @@
 | 
				
			|||||||
		"connection_failed": "Verbindung konnte nicht hergestellt werden. Fehler: {{error}}",
 | 
							"connection_failed": "Verbindung konnte nicht hergestellt werden. Fehler: {{error}}",
 | 
				
			||||||
		"interval": "Intervall",
 | 
							"interval": "Intervall",
 | 
				
			||||||
		"last_update": "Letztes Update",
 | 
							"last_update": "Letztes Update",
 | 
				
			||||||
 | 
							"lifetime": "Dauer",
 | 
				
			||||||
 | 
							"outputmode": "Ausgabemodus",
 | 
				
			||||||
		"types": "Typen"
 | 
							"types": "Typen"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"trace": {
 | 
						"trace": {
 | 
				
			||||||
@@ -725,7 +734,7 @@
 | 
				
			|||||||
		"title": "Tcpdump",
 | 
							"title": "Tcpdump",
 | 
				
			||||||
		"trace": "Spur",
 | 
							"trace": "Spur",
 | 
				
			||||||
		"trace_not_successful": "Trace nicht erfolgreich: Gateway hat folgenden Fehler gemeldet: {{error}}",
 | 
							"trace_not_successful": "Trace nicht erfolgreich: Gateway hat folgenden Fehler gemeldet: {{error}}",
 | 
				
			||||||
		"wait_for_file": "Möchten Sie warten, bis die Trace-Datei fertig ist?",
 | 
							"wait_for_file": "Warten, bis die Trace-Datei fertig ist?",
 | 
				
			||||||
		"waiting_directions": "Bitte warten Sie auf die Trace-Datendatei. Dies könnte eine Weile dauern. Sie können das Warten beenden und die Ablaufverfolgungsdatei später aus der Befehlstabelle abrufen.",
 | 
							"waiting_directions": "Bitte warten Sie auf die Trace-Datendatei. Dies könnte eine Weile dauern. Sie können das Warten beenden und die Ablaufverfolgungsdatei später aus der Befehlstabelle abrufen.",
 | 
				
			||||||
		"waiting_seconds": "Verstrichene Zeit: {{seconds}} Sekunden"
 | 
							"waiting_seconds": "Verstrichene Zeit: {{seconds}} Sekunden"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
@@ -809,6 +818,7 @@
 | 
				
			|||||||
		"radios": "Radios",
 | 
							"radios": "Radios",
 | 
				
			||||||
		"scan_warning": "Ihr 5G-Funkgerät befindet sich auf einem Radarkanal, Sie müssen „Override DFS“ aktivieren, um das Scannen aller 5G-Kanäle zu ermöglichen",
 | 
							"scan_warning": "Ihr 5G-Funkgerät befindet sich auf einem Radarkanal, Sie müssen „Override DFS“ aktivieren, um das Scannen aller 5G-Kanäle zu ermöglichen",
 | 
				
			||||||
		"title": "WLAN-Analyse",
 | 
							"title": "WLAN-Analyse",
 | 
				
			||||||
		"vendor": "Verkäufer"
 | 
							"vendor": "Verkäufer",
 | 
				
			||||||
 | 
							"waiting_for_data": "Warten auf Empfang von Gerätedaten. Bitte schauen Sie später noch einmal nach"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@
 | 
				
			|||||||
		"factory_reset": "Factory Reset",
 | 
							"factory_reset": "Factory Reset",
 | 
				
			||||||
		"firmware_upgrade": "Firmware Upgrade",
 | 
							"firmware_upgrade": "Firmware Upgrade",
 | 
				
			||||||
		"reboot": "Reboot",
 | 
							"reboot": "Reboot",
 | 
				
			||||||
 | 
							"request_ie": "Request IEs",
 | 
				
			||||||
		"telemetry": "Telemetry",
 | 
							"telemetry": "Telemetry",
 | 
				
			||||||
		"title": "Commands",
 | 
							"title": "Commands",
 | 
				
			||||||
		"trace": "Trace",
 | 
							"trace": "Trace",
 | 
				
			||||||
@@ -27,6 +28,7 @@
 | 
				
			|||||||
		"error": "Error while submitting command!",
 | 
							"error": "Error while submitting command!",
 | 
				
			||||||
		"error_delete_log": "Error while trying to delete: {{error}}",
 | 
							"error_delete_log": "Error while trying to delete: {{error}}",
 | 
				
			||||||
		"event_queue": "Event Queue",
 | 
							"event_queue": "Event Queue",
 | 
				
			||||||
 | 
							"reboot_start": "Reboot process has started!",
 | 
				
			||||||
		"success": "Command submitted successfully, you can look at the Commands log for the result",
 | 
							"success": "Command submitted successfully, you can look at the Commands log for the result",
 | 
				
			||||||
		"title": "Command History",
 | 
							"title": "Command History",
 | 
				
			||||||
		"unable_queue": "Unable to complete event queue request: {{error}}"
 | 
							"unable_queue": "Unable to complete event queue request: {{error}}"
 | 
				
			||||||
@@ -148,6 +150,7 @@
 | 
				
			|||||||
		"need_date": "You need a date...",
 | 
							"need_date": "You need a date...",
 | 
				
			||||||
		"no": "No",
 | 
							"no": "No",
 | 
				
			||||||
		"no_addresses_found": "No Addresses Found",
 | 
							"no_addresses_found": "No Addresses Found",
 | 
				
			||||||
 | 
							"no_clients_found": "No Clients Found",
 | 
				
			||||||
		"no_devices_found": "No Devices Found",
 | 
							"no_devices_found": "No Devices Found",
 | 
				
			||||||
		"no_items": "No Items",
 | 
							"no_items": "No Items",
 | 
				
			||||||
		"none": "None",
 | 
							"none": "None",
 | 
				
			||||||
@@ -323,15 +326,19 @@
 | 
				
			|||||||
	"device": {
 | 
						"device": {
 | 
				
			||||||
		"add_to_blacklist": "Add Device To Blacklist",
 | 
							"add_to_blacklist": "Add Device To Blacklist",
 | 
				
			||||||
		"all_devices": "All Devices",
 | 
							"all_devices": "All Devices",
 | 
				
			||||||
 | 
							"already_running_command": "Device is already executing a command, please try later",
 | 
				
			||||||
		"blacklisted_on": "Date",
 | 
							"blacklisted_on": "Date",
 | 
				
			||||||
		"capabilities": "Capabilities",
 | 
							"capabilities": "Capabilities",
 | 
				
			||||||
		"certificate_explanation": "Certificates of connected devices",
 | 
							"certificate_explanation": "Certificates of connected devices",
 | 
				
			||||||
 | 
							"count_explanation": "Devices pointing at this gateway instance",
 | 
				
			||||||
		"edit_blacklist": "Edit Blacklisted Device",
 | 
							"edit_blacklist": "Edit Blacklisted Device",
 | 
				
			||||||
		"error_adding_blacklist": "Error adding device to blacklist: {{error}}",
 | 
							"error_adding_blacklist": "Error adding device to blacklist: {{error}}",
 | 
				
			||||||
		"error_edit_blacklist": "Error editing blacklist: {{error}}",
 | 
							"error_edit_blacklist": "Error editing blacklist: {{error}}",
 | 
				
			||||||
		"error_fetching_device": "Error fetching device information: {{error}}",
 | 
							"error_fetching_device": "Error fetching device information: {{error}}",
 | 
				
			||||||
		"error_fetching_devices": "Error while fetching devices: {{error}}",
 | 
							"error_fetching_devices": "Error while fetching devices: {{error}}",
 | 
				
			||||||
 | 
							"firmware_count_explanation": "This is the total amount of devices that were added to this firmware server, including devices not currently pointing at the related gateway server.",
 | 
				
			||||||
		"health_explanation": "Health of connected devices ((Devices=100% * 100 + Devices>90% * 95 + Devices>60% * 75 + Devices<60% * 35) / ConnectedDevices)",
 | 
							"health_explanation": "Health of connected devices ((Devices=100% * 100 + Devices>90% * 95 + Devices>60% * 75 + Devices<60% * 35) / ConnectedDevices)",
 | 
				
			||||||
 | 
							"mac_not_found": "Serial number not found, redirecting you to the Devices page",
 | 
				
			||||||
		"memory_explanation": "Amount of connected devices with corresponding memory used percentage",
 | 
							"memory_explanation": "Amount of connected devices with corresponding memory used percentage",
 | 
				
			||||||
		"remove_from_blacklist": "Remove from blacklist",
 | 
							"remove_from_blacklist": "Remove from blacklist",
 | 
				
			||||||
		"success_added_blacklist": "Device successfully added to blacklist!",
 | 
							"success_added_blacklist": "Device successfully added to blacklist!",
 | 
				
			||||||
@@ -424,7 +431,7 @@
 | 
				
			|||||||
		"to_release": "To",
 | 
							"to_release": "To",
 | 
				
			||||||
		"unknown_firmware_status": "Unknown Firmware Status",
 | 
							"unknown_firmware_status": "Unknown Firmware Status",
 | 
				
			||||||
		"upgrade": "Upgrade",
 | 
							"upgrade": "Upgrade",
 | 
				
			||||||
		"upgrade_command_submitted": "Upgrade Command Submitted Successfully",
 | 
							"upgrade_command_submitted": "Upgrade in progress...",
 | 
				
			||||||
		"upgrade_to_latest": "Latest",
 | 
							"upgrade_to_latest": "Latest",
 | 
				
			||||||
		"upgrade_to_version": "Upgrade to this Revision",
 | 
							"upgrade_to_version": "Upgrade to this Revision",
 | 
				
			||||||
		"upgrading": "Upgrading..."
 | 
							"upgrading": "Upgrading..."
 | 
				
			||||||
@@ -715,6 +722,8 @@
 | 
				
			|||||||
		"connection_failed": "Failed to create connection. Error: {{error}}",
 | 
							"connection_failed": "Failed to create connection. Error: {{error}}",
 | 
				
			||||||
		"interval": "Interval",
 | 
							"interval": "Interval",
 | 
				
			||||||
		"last_update": "Last Update",
 | 
							"last_update": "Last Update",
 | 
				
			||||||
 | 
							"lifetime": "Duration",
 | 
				
			||||||
 | 
							"outputmode": "Output Mode",
 | 
				
			||||||
		"types": "Types"
 | 
							"types": "Types"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"trace": {
 | 
						"trace": {
 | 
				
			||||||
@@ -725,7 +734,7 @@
 | 
				
			|||||||
		"title": "Trace",
 | 
							"title": "Trace",
 | 
				
			||||||
		"trace": "Trace",
 | 
							"trace": "Trace",
 | 
				
			||||||
		"trace_not_successful": "Trace not successful: gateway reported the following error : {{error}}",
 | 
							"trace_not_successful": "Trace not successful: gateway reported the following error : {{error}}",
 | 
				
			||||||
		"wait_for_file": "Would you like to wait until the trace file is ready?",
 | 
							"wait_for_file": "Wait until the trace file is ready?",
 | 
				
			||||||
		"waiting_directions": "Please wait for the trace data file. This may take some time. You can exit the wait and retrieve the trace file from the commands table later.",
 | 
							"waiting_directions": "Please wait for the trace data file. This may take some time. You can exit the wait and retrieve the trace file from the commands table later.",
 | 
				
			||||||
		"waiting_seconds": "Time Elapsed: {{seconds}} seconds"
 | 
							"waiting_seconds": "Time Elapsed: {{seconds}} seconds"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
@@ -809,6 +818,7 @@
 | 
				
			|||||||
		"radios": "Radios",
 | 
							"radios": "Radios",
 | 
				
			||||||
		"scan_warning": "Your 5G radio is on a radar channel, you must enable “Override DFS” to allow scanning of all 5G channels",
 | 
							"scan_warning": "Your 5G radio is on a radar channel, you must enable “Override DFS” to allow scanning of all 5G channels",
 | 
				
			||||||
		"title": "Wi-Fi Analysis",
 | 
							"title": "Wi-Fi Analysis",
 | 
				
			||||||
		"vendor": "Vendor"
 | 
							"vendor": "Vendor",
 | 
				
			||||||
 | 
							"waiting_for_data": "Waiting to receive device data. Please check again later"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@
 | 
				
			|||||||
		"factory_reset": "Restablecimiento De Fábrica",
 | 
							"factory_reset": "Restablecimiento De Fábrica",
 | 
				
			||||||
		"firmware_upgrade": "Actualización de firmware",
 | 
							"firmware_upgrade": "Actualización de firmware",
 | 
				
			||||||
		"reboot": "Reiniciar",
 | 
							"reboot": "Reiniciar",
 | 
				
			||||||
 | 
							"request_ie": "Solicitar IE",
 | 
				
			||||||
		"telemetry": "Telemetria",
 | 
							"telemetry": "Telemetria",
 | 
				
			||||||
		"title": "Comandos",
 | 
							"title": "Comandos",
 | 
				
			||||||
		"trace": "Rastro",
 | 
							"trace": "Rastro",
 | 
				
			||||||
@@ -27,6 +28,7 @@
 | 
				
			|||||||
		"error": "¡Error al enviar el comando!",
 | 
							"error": "¡Error al enviar el comando!",
 | 
				
			||||||
		"error_delete_log": "Error al intentar eliminar: {{error}}",
 | 
							"error_delete_log": "Error al intentar eliminar: {{error}}",
 | 
				
			||||||
		"event_queue": "Cola de eventos",
 | 
							"event_queue": "Cola de eventos",
 | 
				
			||||||
 | 
							"reboot_start": "¡El proceso de reinicio ha comenzado!",
 | 
				
			||||||
		"success": "Comando enviado con éxito, puede consultar el registro de Comandos para ver el resultado",
 | 
							"success": "Comando enviado con éxito, puede consultar el registro de Comandos para ver el resultado",
 | 
				
			||||||
		"title": "Historial de Comandos",
 | 
							"title": "Historial de Comandos",
 | 
				
			||||||
		"unable_queue": "No se pudo completar la solicitud de cola de eventos: {{error}}"
 | 
							"unable_queue": "No se pudo completar la solicitud de cola de eventos: {{error}}"
 | 
				
			||||||
@@ -148,6 +150,7 @@
 | 
				
			|||||||
		"need_date": "Necesitas una cita ...",
 | 
							"need_date": "Necesitas una cita ...",
 | 
				
			||||||
		"no": "No",
 | 
							"no": "No",
 | 
				
			||||||
		"no_addresses_found": "No se encontraron direcciones",
 | 
							"no_addresses_found": "No se encontraron direcciones",
 | 
				
			||||||
 | 
							"no_clients_found": "No se encontraron clientes",
 | 
				
			||||||
		"no_devices_found": "No se encontraron dispositivos",
 | 
							"no_devices_found": "No se encontraron dispositivos",
 | 
				
			||||||
		"no_items": "No hay articulos",
 | 
							"no_items": "No hay articulos",
 | 
				
			||||||
		"none": "Ninguna",
 | 
							"none": "Ninguna",
 | 
				
			||||||
@@ -323,15 +326,19 @@
 | 
				
			|||||||
	"device": {
 | 
						"device": {
 | 
				
			||||||
		"add_to_blacklist": "Agregar dispositivo a la lista negra",
 | 
							"add_to_blacklist": "Agregar dispositivo a la lista negra",
 | 
				
			||||||
		"all_devices": "Todos los dispositivos",
 | 
							"all_devices": "Todos los dispositivos",
 | 
				
			||||||
 | 
							"already_running_command": "El dispositivo ya está ejecutando un comando, intente más tarde",
 | 
				
			||||||
		"blacklisted_on": "Fecha",
 | 
							"blacklisted_on": "Fecha",
 | 
				
			||||||
		"capabilities": "capacidades",
 | 
							"capabilities": "capacidades",
 | 
				
			||||||
		"certificate_explanation": "Certificados de dispositivos conectados",
 | 
							"certificate_explanation": "Certificados de dispositivos conectados",
 | 
				
			||||||
 | 
							"count_explanation": "Dispositivos que apuntan a esta instancia de puerta de enlace",
 | 
				
			||||||
		"edit_blacklist": "Editar dispositivo incluido en la lista negra",
 | 
							"edit_blacklist": "Editar dispositivo incluido en la lista negra",
 | 
				
			||||||
		"error_adding_blacklist": "Error al agregar el dispositivo a la lista negra: {{error}}",
 | 
							"error_adding_blacklist": "Error al agregar el dispositivo a la lista negra: {{error}}",
 | 
				
			||||||
		"error_edit_blacklist": "Error al editar la lista negra: {{error}}",
 | 
							"error_edit_blacklist": "Error al editar la lista negra: {{error}}",
 | 
				
			||||||
		"error_fetching_device": "Error al obtener la información del dispositivo: {{error}}",
 | 
							"error_fetching_device": "Error al obtener la información del dispositivo: {{error}}",
 | 
				
			||||||
		"error_fetching_devices": "Error al recuperar dispositivos: {{error}}",
 | 
							"error_fetching_devices": "Error al recuperar dispositivos: {{error}}",
 | 
				
			||||||
 | 
							"firmware_count_explanation": "Esta es la cantidad total de dispositivos que se agregaron a este servidor de firmware, incluidos los dispositivos que actualmente no apuntan al servidor de puerta de enlace relacionado.",
 | 
				
			||||||
		"health_explanation": "Estado de los dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos conectados)",
 | 
							"health_explanation": "Estado de los dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos conectados)",
 | 
				
			||||||
 | 
							"mac_not_found": "Número de serie no encontrado, lo redirige a la página Dispositivos",
 | 
				
			||||||
		"memory_explanation": "Cantidad de dispositivos conectados con la memoria correspondiente utilizada%",
 | 
							"memory_explanation": "Cantidad de dispositivos conectados con la memoria correspondiente utilizada%",
 | 
				
			||||||
		"remove_from_blacklist": "ELIMINAR DE LA LISTA NEGRA",
 | 
							"remove_from_blacklist": "ELIMINAR DE LA LISTA NEGRA",
 | 
				
			||||||
		"success_added_blacklist": "¡Dispositivo agregado exitosamente a la lista negra!",
 | 
							"success_added_blacklist": "¡Dispositivo agregado exitosamente a la lista negra!",
 | 
				
			||||||
@@ -424,7 +431,7 @@
 | 
				
			|||||||
		"to_release": "A",
 | 
							"to_release": "A",
 | 
				
			||||||
		"unknown_firmware_status": "Estado de firmware desconocido",
 | 
							"unknown_firmware_status": "Estado de firmware desconocido",
 | 
				
			||||||
		"upgrade": "Mejorar",
 | 
							"upgrade": "Mejorar",
 | 
				
			||||||
		"upgrade_command_submitted": "El comando de actualización se envió correctamente",
 | 
							"upgrade_command_submitted": "Actualización en curso...",
 | 
				
			||||||
		"upgrade_to_latest": "último",
 | 
							"upgrade_to_latest": "último",
 | 
				
			||||||
		"upgrade_to_version": "Actualizar a esta revisión",
 | 
							"upgrade_to_version": "Actualizar a esta revisión",
 | 
				
			||||||
		"upgrading": "Actualizando ..."
 | 
							"upgrading": "Actualizando ..."
 | 
				
			||||||
@@ -715,6 +722,8 @@
 | 
				
			|||||||
		"connection_failed": "No se pudo crear la conexión. Error: {{error}}",
 | 
							"connection_failed": "No se pudo crear la conexión. Error: {{error}}",
 | 
				
			||||||
		"interval": "intervalo",
 | 
							"interval": "intervalo",
 | 
				
			||||||
		"last_update": "Última actualización",
 | 
							"last_update": "Última actualización",
 | 
				
			||||||
 | 
							"lifetime": "Duración",
 | 
				
			||||||
 | 
							"outputmode": "Modo salida",
 | 
				
			||||||
		"types": "Los tipos"
 | 
							"types": "Los tipos"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"trace": {
 | 
						"trace": {
 | 
				
			||||||
@@ -725,7 +734,7 @@
 | 
				
			|||||||
		"title": "Rastro",
 | 
							"title": "Rastro",
 | 
				
			||||||
		"trace": "Rastro",
 | 
							"trace": "Rastro",
 | 
				
			||||||
		"trace_not_successful": "Seguimiento fallido: la puerta de enlace informó el siguiente error: {{error}}",
 | 
							"trace_not_successful": "Seguimiento fallido: la puerta de enlace informó el siguiente error: {{error}}",
 | 
				
			||||||
		"wait_for_file": "¿Le gustaría esperar hasta que el archivo de seguimiento esté listo?",
 | 
							"wait_for_file": "¿Esperar hasta que el archivo de rastreo esté listo?",
 | 
				
			||||||
		"waiting_directions": "Espere el archivo de datos de seguimiento. Esto puede tomar algo de tiempo. Puede salir de la espera y recuperar el archivo de seguimiento de la tabla de comandos más tarde.",
 | 
							"waiting_directions": "Espere el archivo de datos de seguimiento. Esto puede tomar algo de tiempo. Puede salir de la espera y recuperar el archivo de seguimiento de la tabla de comandos más tarde.",
 | 
				
			||||||
		"waiting_seconds": "Tiempo transcurrido: {{seconds}} segundos"
 | 
							"waiting_seconds": "Tiempo transcurrido: {{seconds}} segundos"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
@@ -809,6 +818,7 @@
 | 
				
			|||||||
		"radios": "Radios",
 | 
							"radios": "Radios",
 | 
				
			||||||
		"scan_warning": "Su radio 5G está en un canal de radar, debe habilitar \"Anular DFS\" para permitir el escaneo de todos los canales 5G",
 | 
							"scan_warning": "Su radio 5G está en un canal de radar, debe habilitar \"Anular DFS\" para permitir el escaneo de todos los canales 5G",
 | 
				
			||||||
		"title": "Análisis de Wi-Fi",
 | 
							"title": "Análisis de Wi-Fi",
 | 
				
			||||||
		"vendor": "Vendedor"
 | 
							"vendor": "Vendedor",
 | 
				
			||||||
 | 
							"waiting_for_data": "Esperando recibir datos del dispositivo. Vuelva a consultar más tarde"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@
 | 
				
			|||||||
		"factory_reset": "Retour aux paramètres d'usine",
 | 
							"factory_reset": "Retour aux paramètres d'usine",
 | 
				
			||||||
		"firmware_upgrade": "Mise à jour du firmware",
 | 
							"firmware_upgrade": "Mise à jour du firmware",
 | 
				
			||||||
		"reboot": "Redémarrer",
 | 
							"reboot": "Redémarrer",
 | 
				
			||||||
 | 
							"request_ie": "Demander des IE",
 | 
				
			||||||
		"telemetry": "Télémétrie",
 | 
							"telemetry": "Télémétrie",
 | 
				
			||||||
		"title": "Les commandes",
 | 
							"title": "Les commandes",
 | 
				
			||||||
		"trace": "Trace",
 | 
							"trace": "Trace",
 | 
				
			||||||
@@ -27,6 +28,7 @@
 | 
				
			|||||||
		"error": "Erreur lors de la soumission de la commande !",
 | 
							"error": "Erreur lors de la soumission de la commande !",
 | 
				
			||||||
		"error_delete_log": "Erreur lors de la tentative de suppression : {{error}}",
 | 
							"error_delete_log": "Erreur lors de la tentative de suppression : {{error}}",
 | 
				
			||||||
		"event_queue": "File d'attente d'événements",
 | 
							"event_queue": "File d'attente d'événements",
 | 
				
			||||||
 | 
							"reboot_start": "Le processus de redémarrage a commencé !",
 | 
				
			||||||
		"success": "Commande soumise avec succès, vous pouvez consulter le journal des commandes pour le résultat",
 | 
							"success": "Commande soumise avec succès, vous pouvez consulter le journal des commandes pour le résultat",
 | 
				
			||||||
		"title": "Historique des commandes",
 | 
							"title": "Historique des commandes",
 | 
				
			||||||
		"unable_queue": "Impossible de terminer la demande de file d'attente d'événements: {{error}}"
 | 
							"unable_queue": "Impossible de terminer la demande de file d'attente d'événements: {{error}}"
 | 
				
			||||||
@@ -148,6 +150,7 @@
 | 
				
			|||||||
		"need_date": "Vous avez besoin d'un rendez-vous...",
 | 
							"need_date": "Vous avez besoin d'un rendez-vous...",
 | 
				
			||||||
		"no": "Non",
 | 
							"no": "Non",
 | 
				
			||||||
		"no_addresses_found": "Aucune adresse trouvée",
 | 
							"no_addresses_found": "Aucune adresse trouvée",
 | 
				
			||||||
 | 
							"no_clients_found": "Aucun client trouvé",
 | 
				
			||||||
		"no_devices_found": "Aucun périphérique trouvé",
 | 
							"no_devices_found": "Aucun périphérique trouvé",
 | 
				
			||||||
		"no_items": "Pas d'objet",
 | 
							"no_items": "Pas d'objet",
 | 
				
			||||||
		"none": "Aucun",
 | 
							"none": "Aucun",
 | 
				
			||||||
@@ -323,15 +326,19 @@
 | 
				
			|||||||
	"device": {
 | 
						"device": {
 | 
				
			||||||
		"add_to_blacklist": "Ajouter un appareil à la liste noire",
 | 
							"add_to_blacklist": "Ajouter un appareil à la liste noire",
 | 
				
			||||||
		"all_devices": "Tous les dispositifs",
 | 
							"all_devices": "Tous les dispositifs",
 | 
				
			||||||
 | 
							"already_running_command": "L'appareil exécute déjà une commande, veuillez réessayer plus tard",
 | 
				
			||||||
		"blacklisted_on": "Rendez-vous amoureux",
 | 
							"blacklisted_on": "Rendez-vous amoureux",
 | 
				
			||||||
		"capabilities": "Capacités",
 | 
							"capabilities": "Capacités",
 | 
				
			||||||
		"certificate_explanation": "Certificats des appareils connectés",
 | 
							"certificate_explanation": "Certificats des appareils connectés",
 | 
				
			||||||
 | 
							"count_explanation": "Périphériques pointant vers cette instance de passerelle",
 | 
				
			||||||
		"edit_blacklist": "Modifier l'appareil sur liste noire",
 | 
							"edit_blacklist": "Modifier l'appareil sur liste noire",
 | 
				
			||||||
		"error_adding_blacklist": "Erreur lors de l'ajout de l'appareil à la liste noire : {{error}}",
 | 
							"error_adding_blacklist": "Erreur lors de l'ajout de l'appareil à la liste noire : {{error}}",
 | 
				
			||||||
		"error_edit_blacklist": "Erreur lors de la modification de la liste noire : {{error}}",
 | 
							"error_edit_blacklist": "Erreur lors de la modification de la liste noire : {{error}}",
 | 
				
			||||||
		"error_fetching_device": "Erreur lors de la récupération des informations sur l'appareil : {{error}}",
 | 
							"error_fetching_device": "Erreur lors de la récupération des informations sur l'appareil : {{error}}",
 | 
				
			||||||
		"error_fetching_devices": "Erreur lors de la récupération des appareils : {{error}}",
 | 
							"error_fetching_devices": "Erreur lors de la récupération des appareils : {{error}}",
 | 
				
			||||||
 | 
							"firmware_count_explanation": "Il s'agit du nombre total d'appareils qui ont été ajoutés à ce serveur de micrologiciel, y compris les appareils qui ne pointent pas actuellement vers le serveur de passerelle associé.",
 | 
				
			||||||
		"health_explanation": "Santé des appareils connectés ((Appareils = 100 % * 100 + Appareils> 90 % * 95 + Appareils> 60 % * 75 + Appareils < 60 % * 35) / Appareils connectés)",
 | 
							"health_explanation": "Santé des appareils connectés ((Appareils = 100 % * 100 + Appareils> 90 % * 95 + Appareils> 60 % * 75 + Appareils < 60 % * 35) / Appareils connectés)",
 | 
				
			||||||
 | 
							"mac_not_found": "Numéro de série introuvable, vous redirigeant vers la page Appareils",
 | 
				
			||||||
		"memory_explanation": "Nombre d'appareils connectés avec la mémoire correspondante utilisée %",
 | 
							"memory_explanation": "Nombre d'appareils connectés avec la mémoire correspondante utilisée %",
 | 
				
			||||||
		"remove_from_blacklist": "Supprimer de la liste noire",
 | 
							"remove_from_blacklist": "Supprimer de la liste noire",
 | 
				
			||||||
		"success_added_blacklist": "Appareil ajouté avec succès à la liste noire !",
 | 
							"success_added_blacklist": "Appareil ajouté avec succès à la liste noire !",
 | 
				
			||||||
@@ -424,7 +431,7 @@
 | 
				
			|||||||
		"to_release": "à",
 | 
							"to_release": "à",
 | 
				
			||||||
		"unknown_firmware_status": "État du micrologiciel inconnu",
 | 
							"unknown_firmware_status": "État du micrologiciel inconnu",
 | 
				
			||||||
		"upgrade": "Améliorer",
 | 
							"upgrade": "Améliorer",
 | 
				
			||||||
		"upgrade_command_submitted": "Commande de mise à niveau soumise avec succès",
 | 
							"upgrade_command_submitted": "Mise à jour en cours...",
 | 
				
			||||||
		"upgrade_to_latest": "Dernier",
 | 
							"upgrade_to_latest": "Dernier",
 | 
				
			||||||
		"upgrade_to_version": "Mettre à niveau vers cette révision",
 | 
							"upgrade_to_version": "Mettre à niveau vers cette révision",
 | 
				
			||||||
		"upgrading": "Mise à niveau..."
 | 
							"upgrading": "Mise à niveau..."
 | 
				
			||||||
@@ -715,6 +722,8 @@
 | 
				
			|||||||
		"connection_failed": "Échec de la création de la connexion. Erreur : {{error}}",
 | 
							"connection_failed": "Échec de la création de la connexion. Erreur : {{error}}",
 | 
				
			||||||
		"interval": "Intervalle",
 | 
							"interval": "Intervalle",
 | 
				
			||||||
		"last_update": "Dernière mise à jour",
 | 
							"last_update": "Dernière mise à jour",
 | 
				
			||||||
 | 
							"lifetime": "Durée",
 | 
				
			||||||
 | 
							"outputmode": "Mode de sortie",
 | 
				
			||||||
		"types": "Les types"
 | 
							"types": "Les types"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"trace": {
 | 
						"trace": {
 | 
				
			||||||
@@ -725,7 +734,7 @@
 | 
				
			|||||||
		"title": "Trace",
 | 
							"title": "Trace",
 | 
				
			||||||
		"trace": "Trace",
 | 
							"trace": "Trace",
 | 
				
			||||||
		"trace_not_successful": "Trace non réussie : la passerelle a signalé l'erreur suivante : {{error}}",
 | 
							"trace_not_successful": "Trace non réussie : la passerelle a signalé l'erreur suivante : {{error}}",
 | 
				
			||||||
		"wait_for_file": "Souhaitez-vous attendre que le fichier de trace soit prêt ?",
 | 
							"wait_for_file": "Attendre que le fichier de trace soit prêt ?",
 | 
				
			||||||
		"waiting_directions": "Veuillez attendre le fichier de données de trace. Cela peut prendre un certain temps. Vous pouvez quitter l'attente et récupérer le fichier de trace de la table des commandes plus tard.",
 | 
							"waiting_directions": "Veuillez attendre le fichier de données de trace. Cela peut prendre un certain temps. Vous pouvez quitter l'attente et récupérer le fichier de trace de la table des commandes plus tard.",
 | 
				
			||||||
		"waiting_seconds": "Temps écoulé : {{seconds}} secondes"
 | 
							"waiting_seconds": "Temps écoulé : {{seconds}} secondes"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
@@ -809,6 +818,7 @@
 | 
				
			|||||||
		"radios": "Radios",
 | 
							"radios": "Radios",
 | 
				
			||||||
		"scan_warning": "Votre radio 5G est sur un canal radar, vous devez activer \"Override DFS\" pour permettre le balayage de tous les canaux 5G",
 | 
							"scan_warning": "Votre radio 5G est sur un canal radar, vous devez activer \"Override DFS\" pour permettre le balayage de tous les canaux 5G",
 | 
				
			||||||
		"title": "Analyse Wi-Fi",
 | 
							"title": "Analyse Wi-Fi",
 | 
				
			||||||
		"vendor": "vendeur"
 | 
							"vendor": "vendeur",
 | 
				
			||||||
 | 
							"waiting_for_data": "En attente de réception des données de l'appareil. Veuillez revérifier plus tard"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@
 | 
				
			|||||||
		"factory_reset": "Restauração de fábrica",
 | 
							"factory_reset": "Restauração de fábrica",
 | 
				
			||||||
		"firmware_upgrade": "Atualização de firmware",
 | 
							"firmware_upgrade": "Atualização de firmware",
 | 
				
			||||||
		"reboot": "Reiniciar",
 | 
							"reboot": "Reiniciar",
 | 
				
			||||||
 | 
							"request_ie": "Solicitar IEs",
 | 
				
			||||||
		"telemetry": "Telemetria",
 | 
							"telemetry": "Telemetria",
 | 
				
			||||||
		"title": "Comandos",
 | 
							"title": "Comandos",
 | 
				
			||||||
		"trace": "Vestígio",
 | 
							"trace": "Vestígio",
 | 
				
			||||||
@@ -27,6 +28,7 @@
 | 
				
			|||||||
		"error": "Erro ao enviar comando!",
 | 
							"error": "Erro ao enviar comando!",
 | 
				
			||||||
		"error_delete_log": "Erro ao tentar excluir: {{error}}",
 | 
							"error_delete_log": "Erro ao tentar excluir: {{error}}",
 | 
				
			||||||
		"event_queue": "Fila de Eventos",
 | 
							"event_queue": "Fila de Eventos",
 | 
				
			||||||
 | 
							"reboot_start": "O processo de reinicialização foi iniciado!",
 | 
				
			||||||
		"success": "Comando enviado com sucesso, você pode consultar o log de Comandos para ver o resultado",
 | 
							"success": "Comando enviado com sucesso, você pode consultar o log de Comandos para ver o resultado",
 | 
				
			||||||
		"title": "Histórico de Comandos",
 | 
							"title": "Histórico de Comandos",
 | 
				
			||||||
		"unable_queue": "Incapaz de completar o pedido de fila de eventos: {{error}}"
 | 
							"unable_queue": "Incapaz de completar o pedido de fila de eventos: {{error}}"
 | 
				
			||||||
@@ -148,6 +150,7 @@
 | 
				
			|||||||
		"need_date": "Você precisa de um encontro ...",
 | 
							"need_date": "Você precisa de um encontro ...",
 | 
				
			||||||
		"no": "Não",
 | 
							"no": "Não",
 | 
				
			||||||
		"no_addresses_found": "Nenhum endereço encontrado",
 | 
							"no_addresses_found": "Nenhum endereço encontrado",
 | 
				
			||||||
 | 
							"no_clients_found": "Nenhum cliente encontrado",
 | 
				
			||||||
		"no_devices_found": "Nenhum dispositivo encontrado",
 | 
							"no_devices_found": "Nenhum dispositivo encontrado",
 | 
				
			||||||
		"no_items": "Nenhum item",
 | 
							"no_items": "Nenhum item",
 | 
				
			||||||
		"none": "Nenhum",
 | 
							"none": "Nenhum",
 | 
				
			||||||
@@ -323,15 +326,19 @@
 | 
				
			|||||||
	"device": {
 | 
						"device": {
 | 
				
			||||||
		"add_to_blacklist": "Adicionar dispositivo à lista negra",
 | 
							"add_to_blacklist": "Adicionar dispositivo à lista negra",
 | 
				
			||||||
		"all_devices": "Todos os dispositivos",
 | 
							"all_devices": "Todos os dispositivos",
 | 
				
			||||||
 | 
							"already_running_command": "O dispositivo já está executando um comando, tente mais tarde",
 | 
				
			||||||
		"blacklisted_on": "Encontro",
 | 
							"blacklisted_on": "Encontro",
 | 
				
			||||||
		"capabilities": "Recursos",
 | 
							"capabilities": "Recursos",
 | 
				
			||||||
		"certificate_explanation": "Certificados de dispositivos conectados",
 | 
							"certificate_explanation": "Certificados de dispositivos conectados",
 | 
				
			||||||
 | 
							"count_explanation": "Dispositivos apontando para esta instância de gateway",
 | 
				
			||||||
		"edit_blacklist": "Editar dispositivo na lista negra",
 | 
							"edit_blacklist": "Editar dispositivo na lista negra",
 | 
				
			||||||
		"error_adding_blacklist": "Erro ao adicionar dispositivo à lista negra: {{error}}",
 | 
							"error_adding_blacklist": "Erro ao adicionar dispositivo à lista negra: {{error}}",
 | 
				
			||||||
		"error_edit_blacklist": "Erro ao editar a lista negra: {{error}}",
 | 
							"error_edit_blacklist": "Erro ao editar a lista negra: {{error}}",
 | 
				
			||||||
		"error_fetching_device": "Erro ao buscar informações do dispositivo: {{error}}",
 | 
							"error_fetching_device": "Erro ao buscar informações do dispositivo: {{error}}",
 | 
				
			||||||
		"error_fetching_devices": "Erro ao buscar dispositivos: {{error}}",
 | 
							"error_fetching_devices": "Erro ao buscar dispositivos: {{error}}",
 | 
				
			||||||
 | 
							"firmware_count_explanation": "Esta é a quantidade total de dispositivos que foram adicionados a este servidor de firmware, incluindo dispositivos que não estão apontando para o servidor de gateway relacionado.",
 | 
				
			||||||
		"health_explanation": "Integridade dos dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos Conectados)",
 | 
							"health_explanation": "Integridade dos dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos Conectados)",
 | 
				
			||||||
 | 
							"mac_not_found": "Número de série não encontrado, redirecionando você para a página Dispositivos",
 | 
				
			||||||
		"memory_explanation": "Quantidade de dispositivos conectados com a memória correspondente usada%",
 | 
							"memory_explanation": "Quantidade de dispositivos conectados com a memória correspondente usada%",
 | 
				
			||||||
		"remove_from_blacklist": "Remover da lista negra",
 | 
							"remove_from_blacklist": "Remover da lista negra",
 | 
				
			||||||
		"success_added_blacklist": "Dispositivo adicionado à lista negra com sucesso!",
 | 
							"success_added_blacklist": "Dispositivo adicionado à lista negra com sucesso!",
 | 
				
			||||||
@@ -424,7 +431,7 @@
 | 
				
			|||||||
		"to_release": "Para",
 | 
							"to_release": "Para",
 | 
				
			||||||
		"unknown_firmware_status": "Status de firmware desconhecido",
 | 
							"unknown_firmware_status": "Status de firmware desconhecido",
 | 
				
			||||||
		"upgrade": "Melhorar",
 | 
							"upgrade": "Melhorar",
 | 
				
			||||||
		"upgrade_command_submitted": "Comando de atualização enviado com sucesso",
 | 
							"upgrade_command_submitted": "Atualização em andamento...",
 | 
				
			||||||
		"upgrade_to_latest": "Mais recentes",
 | 
							"upgrade_to_latest": "Mais recentes",
 | 
				
			||||||
		"upgrade_to_version": "Atualize para esta revisão",
 | 
							"upgrade_to_version": "Atualize para esta revisão",
 | 
				
			||||||
		"upgrading": "Atualizando ..."
 | 
							"upgrading": "Atualizando ..."
 | 
				
			||||||
@@ -715,6 +722,8 @@
 | 
				
			|||||||
		"connection_failed": "Falha ao criar conexão. Erro: {{error}}",
 | 
							"connection_failed": "Falha ao criar conexão. Erro: {{error}}",
 | 
				
			||||||
		"interval": "intervalo",
 | 
							"interval": "intervalo",
 | 
				
			||||||
		"last_update": "Última atualização",
 | 
							"last_update": "Última atualização",
 | 
				
			||||||
 | 
							"lifetime": "Duração",
 | 
				
			||||||
 | 
							"outputmode": "Modo saída",
 | 
				
			||||||
		"types": "Tipos"
 | 
							"types": "Tipos"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"trace": {
 | 
						"trace": {
 | 
				
			||||||
@@ -725,7 +734,7 @@
 | 
				
			|||||||
		"title": "Vestígio",
 | 
							"title": "Vestígio",
 | 
				
			||||||
		"trace": "Vestígio",
 | 
							"trace": "Vestígio",
 | 
				
			||||||
		"trace_not_successful": "O rastreamento não foi bem-sucedido: o gateway relatou o seguinte erro: {{error}}",
 | 
							"trace_not_successful": "O rastreamento não foi bem-sucedido: o gateway relatou o seguinte erro: {{error}}",
 | 
				
			||||||
		"wait_for_file": "Você gostaria de esperar até que o arquivo de rastreamento esteja pronto?",
 | 
							"wait_for_file": "Esperar até que o arquivo de rastreamento esteja pronto?",
 | 
				
			||||||
		"waiting_directions": "Aguarde o arquivo de dados de rastreamento. Isto pode tomar algum tempo. Você pode sair da espera e recuperar o arquivo de rastreamento da tabela de comandos mais tarde.",
 | 
							"waiting_directions": "Aguarde o arquivo de dados de rastreamento. Isto pode tomar algum tempo. Você pode sair da espera e recuperar o arquivo de rastreamento da tabela de comandos mais tarde.",
 | 
				
			||||||
		"waiting_seconds": "Tempo decorrido: {{seconds}} segundos"
 | 
							"waiting_seconds": "Tempo decorrido: {{seconds}} segundos"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
@@ -809,6 +818,7 @@
 | 
				
			|||||||
		"radios": "Rádios",
 | 
							"radios": "Rádios",
 | 
				
			||||||
		"scan_warning": "Seu rádio 5G está em um canal de radar, você deve habilitar “Override DFS” para permitir a varredura de todos os canais 5G",
 | 
							"scan_warning": "Seu rádio 5G está em um canal de radar, você deve habilitar “Override DFS” para permitir a varredura de todos os canais 5G",
 | 
				
			||||||
		"title": "Análise de Wi-Fi",
 | 
							"title": "Análise de Wi-Fi",
 | 
				
			||||||
		"vendor": "fornecedor"
 | 
							"vendor": "fornecedor",
 | 
				
			||||||
 | 
							"waiting_for_data": "Aguardando para receber dados do dispositivo. Verifique novamente mais tarde"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,15 +62,28 @@ const BlinkModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
        { headers },
 | 
					        { headers },
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      .then(() => {
 | 
					      .then(() => {
 | 
				
			||||||
        addToast({
 | 
					        if (chosenPattern !== 'blink') {
 | 
				
			||||||
          title: t('common.success'),
 | 
					          addToast({
 | 
				
			||||||
          body: t('commands.command_success'),
 | 
					            title: t('common.success'),
 | 
				
			||||||
          color: 'success',
 | 
					            body: t('commands.command_success'),
 | 
				
			||||||
          autohide: true,
 | 
					            color: 'success',
 | 
				
			||||||
        });
 | 
					            autohide: true,
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        toggleModal();
 | 
					        toggleModal();
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        if (e.response?.data?.ErrorDescription !== undefined) {
 | 
				
			||||||
 | 
					          const split = e.response?.data?.ErrorDescription.split(':');
 | 
				
			||||||
 | 
					          if (split !== undefined && split.length >= 2) {
 | 
				
			||||||
 | 
					            addToast({
 | 
				
			||||||
 | 
					              title: t('common.error'),
 | 
				
			||||||
 | 
					              body: split[1],
 | 
				
			||||||
 | 
					              color: 'danger',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        setResult('error');
 | 
					        setResult('error');
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .finally(() => {
 | 
					      .finally(() => {
 | 
				
			||||||
@@ -145,8 +158,10 @@ const BlinkModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
          </CModalBody>
 | 
					          </CModalBody>
 | 
				
			||||||
          <CModalFooter>
 | 
					          <CModalFooter>
 | 
				
			||||||
            <LoadingButton
 | 
					            <LoadingButton
 | 
				
			||||||
              label={t('blink.set_leds')}
 | 
					              label={t('common.submit')}
 | 
				
			||||||
              isLoadingLabel={t('common.loading_ellipsis')}
 | 
					              isLoadingLabel={
 | 
				
			||||||
 | 
					                chosenPattern === 'blink' ? 'LEDs are blinking...  ' : t('common.loading_ellipsis')
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
              isLoading={waiting}
 | 
					              isLoading={waiting}
 | 
				
			||||||
              action={doAction}
 | 
					              action={doAction}
 | 
				
			||||||
              block={false}
 | 
					              block={false}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -205,10 +205,11 @@ const DeviceCommands = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const columns = [
 | 
					  const columns = [
 | 
				
			||||||
    { key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } },
 | 
					    { key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } },
 | 
				
			||||||
    { key: 'command', label: t('common.command'), _style: { width: '15%' } },
 | 
					    { key: 'command', label: t('common.command'), _style: { width: '0%' } },
 | 
				
			||||||
 | 
					    { key: 'status', label: t('common.status'), _style: { width: '0%' } },
 | 
				
			||||||
    { key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } },
 | 
					    { key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } },
 | 
				
			||||||
    { key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } },
 | 
					    { key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } },
 | 
				
			||||||
    { key: 'errorCode', label: t('common.error_code'), filter: false, _style: { width: '8%' } },
 | 
					    { key: 'errorCode', label: t('common.error_code'), filter: false },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      key: 'show_buttons',
 | 
					      key: 'show_buttons',
 | 
				
			||||||
      label: '',
 | 
					      label: '',
 | 
				
			||||||
@@ -317,16 +318,17 @@ const DeviceCommands = () => {
 | 
				
			|||||||
                    {item.completed && item.completed !== 0 ? (
 | 
					                    {item.completed && item.completed !== 0 ? (
 | 
				
			||||||
                      <FormattedDate date={item.completed} />
 | 
					                      <FormattedDate date={item.completed} />
 | 
				
			||||||
                    ) : (
 | 
					                    ) : (
 | 
				
			||||||
                      'Pending'
 | 
					                      '-'
 | 
				
			||||||
                    )}
 | 
					                    )}
 | 
				
			||||||
                  </td>
 | 
					                  </td>
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
 | 
					                status: (item) => <td className="align-middle">{item.status}</td>,
 | 
				
			||||||
                executed: (item) => (
 | 
					                executed: (item) => (
 | 
				
			||||||
                  <td className="align-middle">
 | 
					                  <td className="align-middle">
 | 
				
			||||||
                    {item.executed && item.executed !== 0 ? (
 | 
					                    {item.executed && item.executed !== 0 ? (
 | 
				
			||||||
                      <FormattedDate date={item.executed} />
 | 
					                      <FormattedDate date={item.executed} />
 | 
				
			||||||
                    ) : (
 | 
					                    ) : (
 | 
				
			||||||
                      'Pending'
 | 
					                      '-'
 | 
				
			||||||
                    )}
 | 
					                    )}
 | 
				
			||||||
                  </td>
 | 
					                  </td>
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
@@ -335,7 +337,7 @@ const DeviceCommands = () => {
 | 
				
			|||||||
                    {item.submitted && item.submitted !== '' ? (
 | 
					                    {item.submitted && item.submitted !== '' ? (
 | 
				
			||||||
                      <FormattedDate date={item.submitted} />
 | 
					                      <FormattedDate date={item.submitted} />
 | 
				
			||||||
                    ) : (
 | 
					                    ) : (
 | 
				
			||||||
                      'Pending'
 | 
					                      '-'
 | 
				
			||||||
                    )}
 | 
					                    )}
 | 
				
			||||||
                  </td>
 | 
					                  </td>
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
@@ -358,6 +360,7 @@ const DeviceCommands = () => {
 | 
				
			|||||||
                          shape="square"
 | 
					                          shape="square"
 | 
				
			||||||
                          size="sm"
 | 
					                          size="sm"
 | 
				
			||||||
                          className="mx-2"
 | 
					                          className="mx-2"
 | 
				
			||||||
 | 
					                          disabled={item.completed === 0}
 | 
				
			||||||
                          onClick={() => {
 | 
					                          onClick={() => {
 | 
				
			||||||
                            toggleDetails(item);
 | 
					                            toggleDetails(item);
 | 
				
			||||||
                          }}
 | 
					                          }}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@ const ConfigurationDisplay = ({ getData, deviceConfig }) => {
 | 
				
			|||||||
          <CopyToClipboardButton
 | 
					          <CopyToClipboardButton
 | 
				
			||||||
            t={t}
 | 
					            t={t}
 | 
				
			||||||
            size="sm"
 | 
					            size="sm"
 | 
				
			||||||
            content={JSON.stringify(deviceConfig?.configuration ?? {})}
 | 
					            content={JSON.stringify(deviceConfig?.configuration ?? {}, null, 4)}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </h5>
 | 
					        </h5>
 | 
				
			||||||
        <CRow>
 | 
					        <CRow>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  CAlert,
 | 
				
			||||||
  CButton,
 | 
					  CButton,
 | 
				
			||||||
  CModal,
 | 
					  CModal,
 | 
				
			||||||
  CModalHeader,
 | 
					  CModalHeader,
 | 
				
			||||||
@@ -100,8 +101,19 @@ const ConfigureModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
        toggleModal();
 | 
					        toggleModal();
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
        setResponseBody('Error while submitting command!');
 | 
					        setResponseBody('Error while submitting command!');
 | 
				
			||||||
 | 
					        if (e.response?.data?.ErrorDescription !== undefined) {
 | 
				
			||||||
 | 
					          const split = e.response?.data?.ErrorDescription.split(':');
 | 
				
			||||||
 | 
					          if (split !== undefined && split.length >= 2) {
 | 
				
			||||||
 | 
					            addToast({
 | 
				
			||||||
 | 
					              title: t('common.error'),
 | 
				
			||||||
 | 
					              body: split[1],
 | 
				
			||||||
 | 
					              color: 'danger',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        setHadFailure(true);
 | 
					        setHadFailure(true);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .finally(() => {
 | 
					      .finally(() => {
 | 
				
			||||||
@@ -196,11 +208,9 @@ const ConfigureModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
                />
 | 
					                />
 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
            </CRow>
 | 
					            </CRow>
 | 
				
			||||||
            <div hidden={!hadSuccess && !hadFailure}>
 | 
					            <CAlert color="danger" hidden={!hadSuccess && !hadFailure}>
 | 
				
			||||||
              <div>
 | 
					              {responseBody}
 | 
				
			||||||
                <pre className="ignore">{responseBody}</pre>
 | 
					            </CAlert>
 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </CModalBody>
 | 
					          </CModalBody>
 | 
				
			||||||
          <CModalFooter>
 | 
					          <CModalFooter>
 | 
				
			||||||
            <div hidden={!checkingIfSure}>Are you sure?</div>
 | 
					            <div hidden={!checkingIfSure}>Are you sure?</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,12 +54,17 @@ const DeviceActions = ({ device }) => {
 | 
				
			|||||||
        if (newWindow) newWindow.opener = null;
 | 
					        if (newWindow) newWindow.opener = null;
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch((e) => {
 | 
					      .catch((e) => {
 | 
				
			||||||
        addToast({
 | 
					        if (e.response?.data?.ErrorDescription !== undefined) {
 | 
				
			||||||
          title: t('common.error'),
 | 
					          const split = e.response?.data?.ErrorDescription.split(':');
 | 
				
			||||||
          body: t('connect.error_trying_to_connect', { error: e.response?.data?.ErrorDescription }),
 | 
					          if (split !== undefined && split.length >= 2) {
 | 
				
			||||||
          color: 'danger',
 | 
					            addToast({
 | 
				
			||||||
          autohide: true,
 | 
					              title: t('common.error'),
 | 
				
			||||||
        });
 | 
					              body: split[1],
 | 
				
			||||||
 | 
					              color: 'danger',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .finally(() => {
 | 
					      .finally(() => {
 | 
				
			||||||
        setConnectLoading(false);
 | 
					        setConnectLoading(false);
 | 
				
			||||||
@@ -68,18 +73,20 @@ const DeviceActions = ({ device }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (upgradeStatus.result !== undefined) {
 | 
					    if (upgradeStatus.result !== undefined) {
 | 
				
			||||||
      addToast({
 | 
					      if (upgradeStatus.result.success) {
 | 
				
			||||||
        title: upgradeStatus.result.success ? t('common.success') : t('common.error'),
 | 
					        addToast({
 | 
				
			||||||
        body: upgradeStatus.result.success
 | 
					          title: upgradeStatus.result.success ? t('common.success') : t('common.error'),
 | 
				
			||||||
          ? t('firmware.upgrade_command_submitted')
 | 
					          body: upgradeStatus.result.success
 | 
				
			||||||
          : upgradeStatus.result.error,
 | 
					            ? t('firmware.upgrade_command_submitted')
 | 
				
			||||||
        color: upgradeStatus.result.success ? 'success' : 'danger',
 | 
					            : upgradeStatus.result.error,
 | 
				
			||||||
        autohide: true,
 | 
					          color: upgradeStatus.result.success ? 'success' : 'danger',
 | 
				
			||||||
      });
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        setShowUpgradeModal(false);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      setUpgradeStatus({
 | 
					      setUpgradeStatus({
 | 
				
			||||||
        loading: false,
 | 
					        loading: false,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      setShowUpgradeModal(false);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }, [upgradeStatus]);
 | 
					  }, [upgradeStatus]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,7 +66,16 @@ const DeviceDashboard = ({ t, data, loading }) => (
 | 
				
			|||||||
        </CCol>
 | 
					        </CCol>
 | 
				
			||||||
        <CCol>
 | 
					        <CCol>
 | 
				
			||||||
          <CWidgetIcon
 | 
					          <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>}
 | 
					            header={<h2>{data.numberOfDevices}</h2>}
 | 
				
			||||||
            color="primary"
 | 
					            color="primary"
 | 
				
			||||||
            iconPadding={false}
 | 
					            iconPadding={false}
 | 
				
			||||||
@@ -87,7 +96,10 @@ const DeviceDashboard = ({ t, data, loading }) => (
 | 
				
			|||||||
                  tooltips: {
 | 
					                  tooltips: {
 | 
				
			||||||
                    callbacks: {
 | 
					                    callbacks: {
 | 
				
			||||||
                      title: (item, ds) => ds.labels[item[0].index],
 | 
					                      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: {
 | 
					                  legend: {
 | 
				
			||||||
@@ -120,7 +132,9 @@ const DeviceDashboard = ({ t, data, loading }) => (
 | 
				
			|||||||
                    callbacks: {
 | 
					                    callbacks: {
 | 
				
			||||||
                      title: (item, ds) => ds.labels[item[0].index],
 | 
					                      title: (item, ds) => ds.labels[item[0].index],
 | 
				
			||||||
                      label: (item, ds) =>
 | 
					                      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: {
 | 
					                  legend: {
 | 
				
			||||||
@@ -149,9 +163,9 @@ const DeviceDashboard = ({ t, data, loading }) => (
 | 
				
			|||||||
                    callbacks: {
 | 
					                    callbacks: {
 | 
				
			||||||
                      title: (item, ds) => ds.labels[item[0].index],
 | 
					                      title: (item, ds) => ds.labels[item[0].index],
 | 
				
			||||||
                      label: (item, ds) =>
 | 
					                      label: (item, ds) =>
 | 
				
			||||||
                        `${ds.datasets[0].data[item.index]}% of ${
 | 
					                        `${ds.datasets[0].data[item.index]} associations (${
 | 
				
			||||||
                          data.totalAssociations
 | 
					                          data.associationData[ds.datasets[0].data[item.index]]
 | 
				
			||||||
                        } associations`,
 | 
					                        }%)`,
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                  legend: {
 | 
					                  legend: {
 | 
				
			||||||
@@ -306,7 +320,9 @@ const DeviceDashboard = ({ t, data, loading }) => (
 | 
				
			|||||||
                    callbacks: {
 | 
					                    callbacks: {
 | 
				
			||||||
                      title: (item, ds) => ds.labels[item[0].index],
 | 
					                      title: (item, ds) => ds.labels[item[0].index],
 | 
				
			||||||
                      label: (item, ds) =>
 | 
					                      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: {
 | 
					                  legend: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,9 +55,12 @@ const DeviceDashboard = () => {
 | 
				
			|||||||
    const statusColors = [];
 | 
					    const statusColors = [];
 | 
				
			||||||
    const statusLabels = [];
 | 
					    const statusLabels = [];
 | 
				
			||||||
    let totalDevices = parsedData.status.reduce((acc, point) => acc + point.value, 0);
 | 
					    let totalDevices = parsedData.status.reduce((acc, point) => acc + point.value, 0);
 | 
				
			||||||
 | 
					    parsedData.numberOfDevices = totalDevices;
 | 
				
			||||||
 | 
					    parsedData.statusDevices = {};
 | 
				
			||||||
    for (const point of parsedData.status) {
 | 
					    for (const point of parsedData.status) {
 | 
				
			||||||
      statusDs.push(Math.round((point.value / totalDevices) * 100));
 | 
					      statusDs.push(point.value);
 | 
				
			||||||
      statusLabels.push(point.tag);
 | 
					      statusLabels.push(point.tag);
 | 
				
			||||||
 | 
					      parsedData.statusDevices[point.value] = Math.round((point.value / totalDevices) * 100);
 | 
				
			||||||
      let color = '';
 | 
					      let color = '';
 | 
				
			||||||
      switch (point.tag) {
 | 
					      switch (point.tag) {
 | 
				
			||||||
        case 'connected':
 | 
					        case 'connected':
 | 
				
			||||||
@@ -96,7 +99,7 @@ const DeviceDashboard = () => {
 | 
				
			|||||||
    const healthLabels = [];
 | 
					    const healthLabels = [];
 | 
				
			||||||
    totalDevices = parsedData.healths.reduce((acc, point) => acc + point.value, 0);
 | 
					    totalDevices = parsedData.healths.reduce((acc, point) => acc + point.value, 0);
 | 
				
			||||||
    for (const point of parsedData.healths) {
 | 
					    for (const point of parsedData.healths) {
 | 
				
			||||||
      healthDs.push(Math.round((point.value / totalDevices) * 100));
 | 
					      healthDs.push(point.value);
 | 
				
			||||||
      healthLabels.push(point.tag);
 | 
					      healthLabels.push(point.tag);
 | 
				
			||||||
      let color = '';
 | 
					      let color = '';
 | 
				
			||||||
      switch (point.tag) {
 | 
					      switch (point.tag) {
 | 
				
			||||||
@@ -122,6 +125,12 @@ const DeviceDashboard = () => {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      healthColors.push(color);
 | 
					      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 = {
 | 
					    parsedData.healths = {
 | 
				
			||||||
      datasets: [
 | 
					      datasets: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -144,10 +153,12 @@ const DeviceDashboard = () => {
 | 
				
			|||||||
    const associationsColors = [];
 | 
					    const associationsColors = [];
 | 
				
			||||||
    const associationsLabels = [];
 | 
					    const associationsLabels = [];
 | 
				
			||||||
    const totalAssociations = parsedData.associations.reduce((acc, point) => acc + point.value, 0);
 | 
					    const totalAssociations = parsedData.associations.reduce((acc, point) => acc + point.value, 0);
 | 
				
			||||||
 | 
					    parsedData.associationData = {};
 | 
				
			||||||
    for (let i = 0; i < parsedData.associations.length; i += 1) {
 | 
					    for (let i = 0; i < parsedData.associations.length; i += 1) {
 | 
				
			||||||
      const point = parsedData.associations[i];
 | 
					      const point = parsedData.associations[i];
 | 
				
			||||||
      associationsDs.push(Math.round((point.value / totalAssociations) * 100));
 | 
					      associationsDs.push(point.value);
 | 
				
			||||||
      associationsLabels.push(point.tag);
 | 
					      associationsLabels.push(point.tag);
 | 
				
			||||||
 | 
					      parsedData.associationData[point.value] = Math.round((point.value / totalAssociations) * 100);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      switch (parsedData.associations[i].tag) {
 | 
					      switch (parsedData.associations[i].tag) {
 | 
				
			||||||
        case '2G':
 | 
					        case '2G':
 | 
				
			||||||
@@ -258,8 +269,10 @@ const DeviceDashboard = () => {
 | 
				
			|||||||
    const certificatesColors = [];
 | 
					    const certificatesColors = [];
 | 
				
			||||||
    const certificatesLabels = [];
 | 
					    const certificatesLabels = [];
 | 
				
			||||||
    const totalCerts = parsedData.certificates.reduce((acc, point) => acc + point.value, 0);
 | 
					    const totalCerts = parsedData.certificates.reduce((acc, point) => acc + point.value, 0);
 | 
				
			||||||
 | 
					    parsedData.certificateData = {};
 | 
				
			||||||
    for (const point of parsedData.certificates) {
 | 
					    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);
 | 
					      certificatesLabels.push(point.tag);
 | 
				
			||||||
      let color = '';
 | 
					      let color = '';
 | 
				
			||||||
      switch (point.tag) {
 | 
					      switch (point.tag) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
 | 
				
			|||||||
import { useAuth, useToast, useToggle } from 'ucentral-libs';
 | 
					import { useAuth, useToast, useToggle } from 'ucentral-libs';
 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
 | 
					import { useGlobalWebSocket } from 'contexts/WebSocketProvider';
 | 
				
			||||||
import Modal from './Modal';
 | 
					import Modal from './Modal';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DeviceFirmwareModal = ({
 | 
					const DeviceFirmwareModal = ({
 | 
				
			||||||
@@ -19,6 +20,7 @@ const DeviceFirmwareModal = ({
 | 
				
			|||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
  const [firmwareVersions, setFirmwareVersions] = useState([]);
 | 
					  const [firmwareVersions, setFirmwareVersions] = useState([]);
 | 
				
			||||||
  const [keepRedirector, toggleKeepRedirector, setKeepRedirector] = useToggle(true);
 | 
					  const [keepRedirector, toggleKeepRedirector, setKeepRedirector] = useToggle(true);
 | 
				
			||||||
 | 
					  const { addDeviceListener } = useGlobalWebSocket();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getPartialFirmware = async (offset) => {
 | 
					  const getPartialFirmware = async (offset) => {
 | 
				
			||||||
    const headers = {
 | 
					    const headers = {
 | 
				
			||||||
@@ -90,6 +92,17 @@ const DeviceFirmwareModal = ({
 | 
				
			|||||||
        headers,
 | 
					        headers,
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .then((response) => {
 | 
					      .then((response) => {
 | 
				
			||||||
 | 
					        addDeviceListener({
 | 
				
			||||||
 | 
					          serialNumber: device.serialNumber,
 | 
				
			||||||
 | 
					          types: ['device_firmware_upgrade'],
 | 
				
			||||||
 | 
					          addToast: (title, body) =>
 | 
				
			||||||
 | 
					            addToast({
 | 
				
			||||||
 | 
					              title,
 | 
				
			||||||
 | 
					              body,
 | 
				
			||||||
 | 
					              color: 'info',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        setUpgradeStatus({
 | 
					        setUpgradeStatus({
 | 
				
			||||||
          loading: false,
 | 
					          loading: false,
 | 
				
			||||||
          result: {
 | 
					          result: {
 | 
				
			||||||
@@ -98,7 +111,18 @@ const DeviceFirmwareModal = ({
 | 
				
			|||||||
          },
 | 
					          },
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        if (e.response?.data?.ErrorDescription !== undefined) {
 | 
				
			||||||
 | 
					          const split = e.response?.data?.ErrorDescription.split(':');
 | 
				
			||||||
 | 
					          if (split !== undefined && split.length >= 2) {
 | 
				
			||||||
 | 
					            addToast({
 | 
				
			||||||
 | 
					              title: t('common.error'),
 | 
				
			||||||
 | 
					              body: split[1],
 | 
				
			||||||
 | 
					              color: 'danger',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        setUpgradeStatus({
 | 
					        setUpgradeStatus({
 | 
				
			||||||
          loading: false,
 | 
					          loading: false,
 | 
				
			||||||
          result: {
 | 
					          result: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,6 +27,7 @@ import ReactTooltip from 'react-tooltip';
 | 
				
			|||||||
import { v4 as createUuid } from 'uuid';
 | 
					import { v4 as createUuid } from 'uuid';
 | 
				
			||||||
import { cleanBytesString } from 'utils/helper';
 | 
					import { cleanBytesString } from 'utils/helper';
 | 
				
			||||||
import { DeviceBadge, LoadingButton } from 'ucentral-libs';
 | 
					import { DeviceBadge, LoadingButton } from 'ucentral-libs';
 | 
				
			||||||
 | 
					import ReactCountryFlag from 'react-country-flag';
 | 
				
			||||||
import styles from './index.module.scss';
 | 
					import styles from './index.module.scss';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DeviceListTable = ({
 | 
					const DeviceListTable = ({
 | 
				
			||||||
@@ -324,11 +325,18 @@ const DeviceListTable = ({
 | 
				
			|||||||
              ipAddress: (item) => (
 | 
					              ipAddress: (item) => (
 | 
				
			||||||
                <td className="align-middle">
 | 
					                <td className="align-middle">
 | 
				
			||||||
                  <CPopover
 | 
					                  <CPopover
 | 
				
			||||||
                    content={item.ipAddress ? item.ipAddress : t('common.na')}
 | 
					                    content={`${item.locale !== '' ? `${item.locale} - ` : ''}${item.ipAddress}`}
 | 
				
			||||||
                    placement="top"
 | 
					                    placement="top"
 | 
				
			||||||
                  >
 | 
					                  >
 | 
				
			||||||
                    <div style={{ width: 'calc(8vw)' }} className="text-truncate align-middle">
 | 
					                    <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>
 | 
					                    </div>
 | 
				
			||||||
                  </CPopover>
 | 
					                  </CPopover>
 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
@@ -406,6 +414,8 @@ const DeviceListTable = ({
 | 
				
			|||||||
                nextClassName="page-item"
 | 
					                nextClassName="page-item"
 | 
				
			||||||
                nextLinkClassName="page-link"
 | 
					                nextLinkClassName="page-link"
 | 
				
			||||||
                activeClassName="active"
 | 
					                activeClassName="active"
 | 
				
			||||||
 | 
					                pageRangeDisplayed={5}
 | 
				
			||||||
 | 
					                marginPagesDisplayed={1}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <p className="pr-2 mt-1">{t('common.items_per_page')}</p>
 | 
					            <p className="pr-2 mt-1">{t('common.items_per_page')}</p>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ import { getItem, setItem } from 'utils/localStorageHelper';
 | 
				
			|||||||
import DeviceSearchBar from 'components/DeviceSearchBar';
 | 
					import DeviceSearchBar from 'components/DeviceSearchBar';
 | 
				
			||||||
import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
 | 
					import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
 | 
				
			||||||
import FirmwareHistoryModal from 'components/FirmwareHistoryModal';
 | 
					import FirmwareHistoryModal from 'components/FirmwareHistoryModal';
 | 
				
			||||||
 | 
					import { useGlobalWebSocket } from 'contexts/WebSocketProvider';
 | 
				
			||||||
import { useAuth, useToast } from 'ucentral-libs';
 | 
					import { useAuth, useToast } from 'ucentral-libs';
 | 
				
			||||||
import Table from './Table';
 | 
					import Table from './Table';
 | 
				
			||||||
import meshIcon from '../../assets/icons/Mesh.png';
 | 
					import meshIcon from '../../assets/icons/Mesh.png';
 | 
				
			||||||
@@ -17,8 +18,10 @@ const DeviceList = () => {
 | 
				
			|||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { addToast } = useToast();
 | 
					  const { addToast } = useToast();
 | 
				
			||||||
  const history = useHistory();
 | 
					  const history = useHistory();
 | 
				
			||||||
 | 
					  const [overrides, setOverrides] = useState({});
 | 
				
			||||||
  const [page, setPage] = useState(parseInt(sessionStorage.getItem('deviceTable') ?? 0, 10));
 | 
					  const [page, setPage] = useState(parseInt(sessionStorage.getItem('deviceTable') ?? 0, 10));
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
 | 
					  const [deviceToRefresh, setDeviceToRefresh] = useState(undefined);
 | 
				
			||||||
  const [upgradeStatus, setUpgradeStatus] = useState({
 | 
					  const [upgradeStatus, setUpgradeStatus] = useState({
 | 
				
			||||||
    loading: false,
 | 
					    loading: false,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@@ -36,6 +39,7 @@ const DeviceList = () => {
 | 
				
			|||||||
    deviceType: '',
 | 
					    deviceType: '',
 | 
				
			||||||
    serialNumber: '',
 | 
					    serialNumber: '',
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					  const { lastMessage } = useGlobalWebSocket();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const deviceIcons = {
 | 
					  const deviceIcons = {
 | 
				
			||||||
    meshIcon,
 | 
					    meshIcon,
 | 
				
			||||||
@@ -56,6 +60,7 @@ const DeviceList = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => {
 | 
					  const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => {
 | 
				
			||||||
    setLoading(true);
 | 
					    setLoading(true);
 | 
				
			||||||
 | 
					    setOverrides({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const options = {
 | 
					    const options = {
 | 
				
			||||||
      headers: {
 | 
					      headers: {
 | 
				
			||||||
@@ -355,10 +360,31 @@ const DeviceList = () => {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const displayDevices = () =>
 | 
				
			||||||
 | 
					    devices.map((device) => ({
 | 
				
			||||||
 | 
					      ...device,
 | 
				
			||||||
 | 
					      connected:
 | 
				
			||||||
 | 
					        overrides[device.serialNumber] !== undefined
 | 
				
			||||||
 | 
					          ? overrides[device.serialNumber]
 | 
				
			||||||
 | 
					          : device.connected,
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    getCount();
 | 
					    getCount();
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (deviceToRefresh) refreshDevice(deviceToRefresh.serial);
 | 
				
			||||||
 | 
					  }, [deviceToRefresh?.timestamp]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (lastMessage && lastMessage.type === 'DEVICE') {
 | 
				
			||||||
 | 
					      const { serialNumber: msgSerial, timestamp } = lastMessage;
 | 
				
			||||||
 | 
					      if (timestamp !== deviceToRefresh?.timestamp)
 | 
				
			||||||
 | 
					        setDeviceToRefresh({ serial: msgSerial, timestamp });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [lastMessage, deviceToRefresh]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (upgradeStatus.result !== undefined) {
 | 
					    if (upgradeStatus.result !== undefined) {
 | 
				
			||||||
      addToast({
 | 
					      addToast({
 | 
				
			||||||
@@ -382,7 +408,7 @@ const DeviceList = () => {
 | 
				
			|||||||
        currentPage={page}
 | 
					        currentPage={page}
 | 
				
			||||||
        t={t}
 | 
					        t={t}
 | 
				
			||||||
        searchBar={<DeviceSearchBar />}
 | 
					        searchBar={<DeviceSearchBar />}
 | 
				
			||||||
        devices={devices}
 | 
					        devices={displayDevices()}
 | 
				
			||||||
        loading={loading}
 | 
					        loading={loading}
 | 
				
			||||||
        updateDevicesPerPage={updateDevicesPerPage}
 | 
					        updateDevicesPerPage={updateDevicesPerPage}
 | 
				
			||||||
        devicesPerPage={devicesPerPage}
 | 
					        devicesPerPage={devicesPerPage}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										75
									
								
								src/components/DeviceSearchBar/Input.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/components/DeviceSearchBar/Input.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					import React, { useState } from 'react';
 | 
				
			||||||
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					import Select, { components } from 'react-select';
 | 
				
			||||||
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DeviceSearchBarInput = ({ search, results, history, action, isDisabled }) => {
 | 
				
			||||||
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
 | 
					  const [selected, setSelected] = useState('');
 | 
				
			||||||
 | 
					  const NoOptionsMessage = (props) => (
 | 
				
			||||||
 | 
					    <components.NoOptionsMessage {...props}>
 | 
				
			||||||
 | 
					      <span>{t('common.no_devices_found')}</span>
 | 
				
			||||||
 | 
					    </components.NoOptionsMessage>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onInputChange = (value) => {
 | 
				
			||||||
 | 
					    if (value === '' || value.match('^[a-fA-F0-9-*]+$')) {
 | 
				
			||||||
 | 
					      setSelected(value);
 | 
				
			||||||
 | 
					      search(value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Select
 | 
				
			||||||
 | 
					      components={{ NoOptionsMessage }}
 | 
				
			||||||
 | 
					      options={results.map((serial) => ({ label: serial, value: serial }))}
 | 
				
			||||||
 | 
					      filterOption={() => true}
 | 
				
			||||||
 | 
					      inputValue={selected}
 | 
				
			||||||
 | 
					      placeholder={t('common.search')}
 | 
				
			||||||
 | 
					      isDisabled={isDisabled}
 | 
				
			||||||
 | 
					      styles={{
 | 
				
			||||||
 | 
					        placeholder: (provided) => ({
 | 
				
			||||||
 | 
					          ...provided,
 | 
				
			||||||
 | 
					          // disable placeholder mouse events
 | 
				
			||||||
 | 
					          pointerEvents: 'none',
 | 
				
			||||||
 | 
					          userSelect: 'none',
 | 
				
			||||||
 | 
					          MozUserSelect: 'none',
 | 
				
			||||||
 | 
					          WebkitUserSelect: 'none',
 | 
				
			||||||
 | 
					          msUserSelect: 'none',
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        input: (css) => ({
 | 
				
			||||||
 | 
					          ...css,
 | 
				
			||||||
 | 
					          /* expand the Input Component div */
 | 
				
			||||||
 | 
					          flex: '1 1 auto',
 | 
				
			||||||
 | 
					          /* expand the Input Component child div */
 | 
				
			||||||
 | 
					          '> div': {
 | 
				
			||||||
 | 
					            width: '100%',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          /* expand the Input Component input */
 | 
				
			||||||
 | 
					          input: {
 | 
				
			||||||
 | 
					            width: '100% !important',
 | 
				
			||||||
 | 
					            textAlign: 'left',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      onInputChange={onInputChange}
 | 
				
			||||||
 | 
					      onChange={(property) =>
 | 
				
			||||||
 | 
					        action === null ? history.push(`/devices/${property.value}`) : action(property.value)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DeviceSearchBarInput.propTypes = {
 | 
				
			||||||
 | 
					  search: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					  results: PropTypes.instanceOf(Array).isRequired,
 | 
				
			||||||
 | 
					  history: PropTypes.instanceOf(Object).isRequired,
 | 
				
			||||||
 | 
					  isDisabled: PropTypes.bool.isRequired,
 | 
				
			||||||
 | 
					  action: PropTypes.func,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DeviceSearchBarInput.defaultProps = {
 | 
				
			||||||
 | 
					  action: null,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default DeviceSearchBarInput;
 | 
				
			||||||
@@ -1,12 +1,11 @@
 | 
				
			|||||||
import React, { useEffect, useState } from 'react';
 | 
					import React, { useEffect, useState } from 'react';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					 | 
				
			||||||
import { useHistory } from 'react-router-dom';
 | 
					import { useHistory } from 'react-router-dom';
 | 
				
			||||||
import { useAuth, DeviceSearchBar as SearchBar } from 'ucentral-libs';
 | 
					import { useAuth } from 'ucentral-libs';
 | 
				
			||||||
import { checkIfJson } from 'utils/helper';
 | 
					import { toJson } from 'utils/helper';
 | 
				
			||||||
 | 
					import DeviceSearchBarInput from './Input';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DeviceSearchBar = ({ action }) => {
 | 
					const DeviceSearchBar = ({ action }) => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					 | 
				
			||||||
  const history = useHistory();
 | 
					  const history = useHistory();
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
  const [socket, setSocket] = useState(null);
 | 
					  const [socket, setSocket] = useState(null);
 | 
				
			||||||
@@ -14,20 +13,22 @@ const DeviceSearchBar = ({ action }) => {
 | 
				
			|||||||
  const [waitingSearch, setWaitingSearch] = useState('');
 | 
					  const [waitingSearch, setWaitingSearch] = useState('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const search = (value) => {
 | 
					  const search = (value) => {
 | 
				
			||||||
    if (socket.readyState === WebSocket.OPEN) {
 | 
					    if (socket) {
 | 
				
			||||||
      if (value.length > 1 && value.match('^[a-fA-F0-9-*]+$')) {
 | 
					      if (socket.readyState === WebSocket.OPEN) {
 | 
				
			||||||
        setWaitingSearch('');
 | 
					        if (value.length > 1 && value.match('^[a-fA-F0-9-*]+$')) {
 | 
				
			||||||
        socket.send(
 | 
					          setWaitingSearch('');
 | 
				
			||||||
          JSON.stringify({ command: 'serial_number_search', serial_prefix: value.toLowerCase() }),
 | 
					          socket.send(
 | 
				
			||||||
        );
 | 
					            JSON.stringify({ command: 'serial_number_search', serial_prefix: value.toLowerCase() }),
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          setResults([]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else if (socket.readyState !== WebSocket.CONNECTING && endpoints?.owgw !== undefined) {
 | 
				
			||||||
 | 
					        setWaitingSearch(value);
 | 
				
			||||||
 | 
					        setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`));
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        setResults([]);
 | 
					        setWaitingSearch(value);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else if (socket.readyState !== WebSocket.CONNECTING) {
 | 
					 | 
				
			||||||
      setWaitingSearch(value);
 | 
					 | 
				
			||||||
      setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`));
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      setWaitingSearch(value);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,11 +45,9 @@ const DeviceSearchBar = ({ action }) => {
 | 
				
			|||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      socket.onmessage = (event) => {
 | 
					      socket.onmessage = (event) => {
 | 
				
			||||||
        if (checkIfJson(event.data)) {
 | 
					        const result = toJson(event.data);
 | 
				
			||||||
          const result = JSON.parse(event.data);
 | 
					        if (result && result.serialNumbers) {
 | 
				
			||||||
          if (result.command === 'serial_number_search' && result.serialNumbers) {
 | 
					          setResults(result.serialNumbers);
 | 
				
			||||||
            setResults(result.serialNumbers);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -61,12 +60,20 @@ const DeviceSearchBar = ({ action }) => {
 | 
				
			|||||||
  }, [socket]);
 | 
					  }, [socket]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (socket === null && endpoints?.owgw) {
 | 
					    if (socket === null && endpoints?.owgw !== undefined) {
 | 
				
			||||||
      setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`));
 | 
					      setSocket(new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return <SearchBar t={t} search={search} results={results} history={history} action={action} />;
 | 
					  return (
 | 
				
			||||||
 | 
					    <DeviceSearchBarInput
 | 
				
			||||||
 | 
					      search={search}
 | 
				
			||||||
 | 
					      results={results}
 | 
				
			||||||
 | 
					      history={history}
 | 
				
			||||||
 | 
					      action={action}
 | 
				
			||||||
 | 
					      isDisabled={endpoints.owgw === undefined}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DeviceSearchBar.propTypes = {
 | 
					DeviceSearchBar.propTypes = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,12 +34,17 @@ const EventQueueModal = ({ show, toggle }) => {
 | 
				
			|||||||
        setResult(response.data);
 | 
					        setResult(response.data);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch((e) => {
 | 
					      .catch((e) => {
 | 
				
			||||||
        addToast({
 | 
					        if (e.response?.data?.ErrorDescription !== undefined) {
 | 
				
			||||||
          title: t('common.error'),
 | 
					          const split = e.response?.data?.ErrorDescription.split(':');
 | 
				
			||||||
          body: t('commands.unable_queue', { error: e.response?.data?.ErrorDescription }),
 | 
					          if (split !== undefined && split.length >= 2) {
 | 
				
			||||||
          color: 'danger',
 | 
					            addToast({
 | 
				
			||||||
          autohide: true,
 | 
					              title: t('common.error'),
 | 
				
			||||||
        });
 | 
					              body: split[1],
 | 
				
			||||||
 | 
					              color: 'danger',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .finally(() => {
 | 
					      .finally(() => {
 | 
				
			||||||
        setLoading(false);
 | 
					        setLoading(false);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ import React, { useState, useEffect } from 'react';
 | 
				
			|||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import 'react-widgets/styles.css';
 | 
					import 'react-widgets/styles.css';
 | 
				
			||||||
import { useAuth, useDevice } from 'ucentral-libs';
 | 
					import { useAuth, useDevice, useToast } from 'ucentral-libs';
 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
 | 
					import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,6 +26,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
  const { deviceSerialNumber } = useDevice();
 | 
					  const { deviceSerialNumber } = useDevice();
 | 
				
			||||||
 | 
					  const { addToast } = useToast();
 | 
				
			||||||
  const [hadSuccess, setHadSuccess] = useState(false);
 | 
					  const [hadSuccess, setHadSuccess] = useState(false);
 | 
				
			||||||
  const [hadFailure, setHadFailure] = useState(false);
 | 
					  const [hadFailure, setHadFailure] = useState(false);
 | 
				
			||||||
  const [doingNow, setDoingNow] = useState(false);
 | 
					  const [doingNow, setDoingNow] = useState(false);
 | 
				
			||||||
@@ -74,7 +75,18 @@ const ConfigureModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
      .then(() => {
 | 
					      .then(() => {
 | 
				
			||||||
        setHadSuccess(true);
 | 
					        setHadSuccess(true);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        if (e.response?.data?.ErrorDescription !== undefined) {
 | 
				
			||||||
 | 
					          const split = e.response?.data?.ErrorDescription.split(':');
 | 
				
			||||||
 | 
					          if (split !== undefined && split.length >= 2) {
 | 
				
			||||||
 | 
					            addToast({
 | 
				
			||||||
 | 
					              title: t('common.error'),
 | 
				
			||||||
 | 
					              body: split[1],
 | 
				
			||||||
 | 
					              color: 'danger',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        setResponseBody(t('commands.error'));
 | 
					        setResponseBody(t('commands.error'));
 | 
				
			||||||
        setHadFailure(true);
 | 
					        setHadFailure(true);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,7 +65,16 @@ const FirmwareDashboard = ({ t, data, loading }) => {
 | 
				
			|||||||
          </CCol>
 | 
					          </CCol>
 | 
				
			||||||
          <CCol>
 | 
					          <CCol>
 | 
				
			||||||
            <CWidgetIcon
 | 
					            <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>}
 | 
					              header={<h2>{data.numberOfDevices}</h2>}
 | 
				
			||||||
              color="primary"
 | 
					              color="primary"
 | 
				
			||||||
              iconPadding={false}
 | 
					              iconPadding={false}
 | 
				
			||||||
@@ -212,7 +221,7 @@ const FirmwareDashboard = ({ t, data, loading }) => {
 | 
				
			|||||||
                    tooltips: {
 | 
					                    tooltips: {
 | 
				
			||||||
                      callbacks: {
 | 
					                      callbacks: {
 | 
				
			||||||
                        title: (item, ds) => ds.labels[item[0].index],
 | 
					                        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: {
 | 
					                    legend: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,9 +65,12 @@ const FirmwareDashboard = () => {
 | 
				
			|||||||
    const statusColors = [];
 | 
					    const statusColors = [];
 | 
				
			||||||
    const statusLabels = [];
 | 
					    const statusLabels = [];
 | 
				
			||||||
    const totalDevices = parsedData.status.reduce((acc, point) => acc + point.value, 0);
 | 
					    const totalDevices = parsedData.status.reduce((acc, point) => acc + point.value, 0);
 | 
				
			||||||
 | 
					    parsedData.statusDevices = {};
 | 
				
			||||||
 | 
					    parsedData.numberOfDevices = totalDevices;
 | 
				
			||||||
    for (const point of parsedData.status) {
 | 
					    for (const point of parsedData.status) {
 | 
				
			||||||
      statusDs.push(Math.round((point.value / totalDevices) * 100));
 | 
					      statusDs.push(point.value);
 | 
				
			||||||
      statusLabels.push(point.tag);
 | 
					      statusLabels.push(point.tag);
 | 
				
			||||||
 | 
					      parsedData[point.value] = point.value;
 | 
				
			||||||
      let color = '';
 | 
					      let color = '';
 | 
				
			||||||
      switch (point.tag) {
 | 
					      switch (point.tag) {
 | 
				
			||||||
        case 'connected':
 | 
					        case 'connected':
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										32
									
								
								src/components/FormattedDate/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/components/FormattedDate/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					import { CPopover } from '@coreui/react';
 | 
				
			||||||
 | 
					import { formatDaysAgo, prettyDate } from 'utils/helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FormattedDate = ({ date, size }) => {
 | 
				
			||||||
 | 
					  if (size === 'lg') {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <CPopover content={prettyDate(date)} advancedOptions={{ animation: false }}>
 | 
				
			||||||
 | 
					        <h2 className="d-inline-block">{date === 0 ? '-' : formatDaysAgo(date)}</h2>
 | 
				
			||||||
 | 
					      </CPopover>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <CPopover content={prettyDate(date)} advancedOptions={{ animation: false }}>
 | 
				
			||||||
 | 
					      <span className="d-inline-block">{date === 0 ? '-' : formatDaysAgo(date)}</span>
 | 
				
			||||||
 | 
					    </CPopover>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FormattedDate.propTypes = {
 | 
				
			||||||
 | 
					  date: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
 | 
				
			||||||
 | 
					  size: PropTypes.string,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FormattedDate.defaultProps = {
 | 
				
			||||||
 | 
					  date: 0,
 | 
				
			||||||
 | 
					  size: 'md',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default FormattedDate;
 | 
				
			||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect, useMemo } from 'react';
 | 
				
			||||||
import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
 | 
					import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
import { cilX } from '@coreui/icons';
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
import { useAuth, useDevice } from 'ucentral-libs';
 | 
					import { useAuth, useDevice, CopyToClipboardButton } from 'ucentral-libs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const LatestStatisticsModal = ({ show, toggle }) => {
 | 
					const LatestStatisticsModal = ({ show, toggle }) => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
@@ -32,6 +32,17 @@ const LatestStatisticsModal = ({ show, toggle }) => {
 | 
				
			|||||||
      .catch(() => {});
 | 
					      .catch(() => {});
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const latestStatsString = useMemo(() => {
 | 
				
			||||||
 | 
					    if (latestStats) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        return JSON.stringify(latestStats, null, 2);
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return '';
 | 
				
			||||||
 | 
					  }, [latestStats]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (show) {
 | 
					    if (show) {
 | 
				
			||||||
      getLatestStats();
 | 
					      getLatestStats();
 | 
				
			||||||
@@ -51,7 +62,10 @@ const LatestStatisticsModal = ({ show, toggle }) => {
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      <CModalBody>
 | 
					      <CModalBody>
 | 
				
			||||||
        <pre className="ignore">{JSON.stringify(latestStats, null, 2)}</pre>
 | 
					        <div style={{ textAlign: 'right' }}>
 | 
				
			||||||
 | 
					          <CopyToClipboardButton t={t} size="lg" content={latestStatsString} />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <pre className="ignore">{latestStatsString}</pre>
 | 
				
			||||||
      </CModalBody>
 | 
					      </CModalBody>
 | 
				
			||||||
    </CModal>
 | 
					    </CModal>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
import React, { useState, useEffect, useCallback } from 'react';
 | 
					import React, { useState, useEffect, useCallback } from 'react';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { CSpinner } from '@coreui/react';
 | 
					import { CSpinner, CAlert } from '@coreui/react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import { v4 as createUuid } from 'uuid';
 | 
					import { v4 as createUuid } from 'uuid';
 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
import { useAuth, useDevice } from 'ucentral-libs';
 | 
					import { useAuth } from 'ucentral-libs';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  capitalizeFirstLetter,
 | 
					  capitalizeFirstLetter,
 | 
				
			||||||
  datesSameDay,
 | 
					  datesSameDay,
 | 
				
			||||||
@@ -14,267 +14,267 @@ import {
 | 
				
			|||||||
} from 'utils/helper';
 | 
					} from 'utils/helper';
 | 
				
			||||||
import DeviceStatisticsChart from './DeviceStatisticsChart';
 | 
					import DeviceStatisticsChart from './DeviceStatisticsChart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const StatisticsChartList = ({ setOptions, section, setStart, setEnd, time }) => {
 | 
					const StatisticsChartList = ({ deviceSerialNumber, setOptions, section, time }) => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
  const { deviceSerialNumber } = useDevice();
 | 
					 | 
				
			||||||
  const [statOptions, setStatOptions] = useState({
 | 
					  const [statOptions, setStatOptions] = useState({
 | 
				
			||||||
    interfaceList: [],
 | 
					    interfaceList: [],
 | 
				
			||||||
    memory: [],
 | 
					    memory: [],
 | 
				
			||||||
    settings: {},
 | 
					    settings: {},
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					  const [error, setError] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const transformIntoDataset = (data) => {
 | 
					  const transformIntoDataset = (data) => {
 | 
				
			||||||
    let sortedData = data.sort((a, b) => {
 | 
					    try {
 | 
				
			||||||
      if (a.recorded > b.recorded) return 1;
 | 
					      let sortedData = data.sort((a, b) => {
 | 
				
			||||||
      if (b.recorded > a.recorded) return -1;
 | 
					        if (a.recorded > b.recorded) return 1;
 | 
				
			||||||
      return 0;
 | 
					        if (b.recorded > a.recorded) return -1;
 | 
				
			||||||
    });
 | 
					        return 0;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dataLength = sortedData.length;
 | 
					      const dataLength = sortedData.length;
 | 
				
			||||||
    if (dataLength > 1000 && dataLength < 3000) {
 | 
					      if (dataLength > 1000 && dataLength < 3000) {
 | 
				
			||||||
      sortedData = sortedData.filter((dat, index) => index % 4 === 0);
 | 
					        sortedData = sortedData.filter((dat, index) => index % 4 === 0);
 | 
				
			||||||
    } else if (dataLength >= 3000 && dataLength < 5000) {
 | 
					      } else if (dataLength >= 3000 && dataLength < 5000) {
 | 
				
			||||||
      sortedData = sortedData.filter((dat, index) => index % 8 === 0);
 | 
					        sortedData = sortedData.filter((dat, index) => index % 8 === 0);
 | 
				
			||||||
    } else if (dataLength >= 5000 && dataLength < 7000) {
 | 
					      } else if (dataLength >= 5000 && dataLength < 7000) {
 | 
				
			||||||
      sortedData = sortedData.filter((dat, index) => index % 12 === 0);
 | 
					        sortedData = sortedData.filter((dat, index) => index % 12 === 0);
 | 
				
			||||||
    } else if (dataLength > 7000) {
 | 
					      } else if (dataLength > 7000) {
 | 
				
			||||||
      sortedData = sortedData.filter((dat, index) => index % 20 === 0);
 | 
					        sortedData = sortedData.filter((dat, index) => index % 20 === 0);
 | 
				
			||||||
    }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Looping through data to build our memory graph data
 | 
					      // Looping through data to build our memory graph data
 | 
				
			||||||
    const memoryUsed = [
 | 
					      const memoryUsed = [
 | 
				
			||||||
      {
 | 
					        {
 | 
				
			||||||
        titleName: t('statistics.memory'),
 | 
					          titleName: t('statistics.memory'),
 | 
				
			||||||
        name: 'Used',
 | 
					          name: 'Used',
 | 
				
			||||||
        backgroundColor: 'rgb(228,102,81,0.9)',
 | 
					          backgroundColor: 'rgb(228,102,81,0.9)',
 | 
				
			||||||
        data: [],
 | 
					          data: [],
 | 
				
			||||||
        fill: true,
 | 
					          fill: true,
 | 
				
			||||||
      },
 | 
					        },
 | 
				
			||||||
      {
 | 
					        {
 | 
				
			||||||
        titleName: t('statistics.memory'),
 | 
					          titleName: t('statistics.memory'),
 | 
				
			||||||
        name: 'Buffered',
 | 
					          name: 'Buffered',
 | 
				
			||||||
        backgroundColor: 'rgb(228,102,81,0.9)',
 | 
					          backgroundColor: 'rgb(228,102,81,0.9)',
 | 
				
			||||||
        data: [],
 | 
					          data: [],
 | 
				
			||||||
        fill: true,
 | 
					          fill: true,
 | 
				
			||||||
      },
 | 
					        },
 | 
				
			||||||
      {
 | 
					        {
 | 
				
			||||||
        titleName: t('statistics.memory'),
 | 
					          titleName: t('statistics.memory'),
 | 
				
			||||||
        name: 'Cached',
 | 
					          name: 'Cached',
 | 
				
			||||||
        backgroundColor: 'rgb(228,102,81,0.9)',
 | 
					          backgroundColor: 'rgb(228,102,81,0.9)',
 | 
				
			||||||
        data: [],
 | 
					          data: [],
 | 
				
			||||||
        fill: true,
 | 
					          fill: true,
 | 
				
			||||||
      },
 | 
					        },
 | 
				
			||||||
    ];
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const log of sortedData) {
 | 
					      for (const log of sortedData) {
 | 
				
			||||||
      memoryUsed[0].data.push(
 | 
					        memoryUsed[0].data.push(
 | 
				
			||||||
        Math.floor((log.data.unit.memory.total - log.data.unit.memory.free) / 1024 / 1024),
 | 
					          Math.floor((log.data.unit.memory.total - log.data.unit.memory.free) / 1024 / 1024),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        memoryUsed[1].data.push(Math.floor(log.data.unit.memory.buffered / 1024 / 1024));
 | 
				
			||||||
 | 
					        memoryUsed[2].data.push(Math.floor(log.data.unit.memory.cached / 1024 / 1024));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const newUsed = memoryUsed[0].data;
 | 
				
			||||||
 | 
					      if (newUsed.length > 0) newUsed.shift();
 | 
				
			||||||
 | 
					      memoryUsed[0].data = newUsed;
 | 
				
			||||||
 | 
					      const newBuff = memoryUsed[1].data;
 | 
				
			||||||
 | 
					      if (newBuff.length > 0) newBuff.shift();
 | 
				
			||||||
 | 
					      memoryUsed[1].data = newBuff;
 | 
				
			||||||
 | 
					      const newCached = memoryUsed[2].data;
 | 
				
			||||||
 | 
					      if (newCached.length > 0) newCached.shift();
 | 
				
			||||||
 | 
					      memoryUsed[2].data = newCached;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // This dictionary will have a key that is the interface name and a value of it's index in the final array
 | 
				
			||||||
 | 
					      const interfaceTypes = {};
 | 
				
			||||||
 | 
					      const interfaceList = [];
 | 
				
			||||||
 | 
					      const categories = [];
 | 
				
			||||||
 | 
					      let i = 0;
 | 
				
			||||||
 | 
					      const areSameDay = datesSameDay(
 | 
				
			||||||
 | 
					        new Date(sortedData[0].recorded * 1000),
 | 
				
			||||||
 | 
					        new Date(sortedData[sortedData.length - 1].recorded * 1000),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      memoryUsed[1].data.push(Math.floor(log.data.unit.memory.buffered / 1024 / 1024));
 | 
					 | 
				
			||||||
      memoryUsed[2].data.push(Math.floor(log.data.unit.memory.cached / 1024 / 1024));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const newUsed = memoryUsed[0].data;
 | 
					      // Just building the array for all the interfaces
 | 
				
			||||||
    if (newUsed.length > 0) newUsed.shift();
 | 
					      for (const log of sortedData) {
 | 
				
			||||||
    memoryUsed[0].data = newUsed;
 | 
					        categories.push(areSameDay ? unixToTime(log.recorded) : prettyDate(log.recorded));
 | 
				
			||||||
    const newBuff = memoryUsed[1].data;
 | 
					        for (const logInterface of log.data.interfaces) {
 | 
				
			||||||
    if (newBuff.length > 0) newBuff.shift();
 | 
					          if (interfaceTypes[logInterface.name] === undefined) {
 | 
				
			||||||
    memoryUsed[1].data = newBuff;
 | 
					            interfaceTypes[logInterface.name] = i;
 | 
				
			||||||
    const newCached = memoryUsed[2].data;
 | 
					            interfaceList.push([
 | 
				
			||||||
    if (newCached.length > 0) newCached.shift();
 | 
					              {
 | 
				
			||||||
    memoryUsed[2].data = newCached;
 | 
					                titleName: logInterface.name,
 | 
				
			||||||
 | 
					                name: 'Tx',
 | 
				
			||||||
    // This dictionary will have a key that is the interface name and a value of it's index in the final array
 | 
					                backgroundColor: 'rgb(228,102,81,0.9)',
 | 
				
			||||||
    const interfaceTypes = {};
 | 
					                data: [],
 | 
				
			||||||
    const interfaceList = [];
 | 
					                fill: false,
 | 
				
			||||||
    const categories = [];
 | 
					              },
 | 
				
			||||||
    let i = 0;
 | 
					              {
 | 
				
			||||||
    const areSameDay = datesSameDay(
 | 
					                titleName: logInterface.name,
 | 
				
			||||||
      new Date(sortedData[0].recorded * 1000),
 | 
					                name: 'Rx',
 | 
				
			||||||
      new Date(sortedData[sortedData.length - 1].recorded * 1000),
 | 
					                backgroundColor: 'rgb(0,216,255,0.9)',
 | 
				
			||||||
    );
 | 
					                data: [],
 | 
				
			||||||
 | 
					                fill: false,
 | 
				
			||||||
    // Just building the array for all the interfaces
 | 
					              },
 | 
				
			||||||
    for (const log of sortedData) {
 | 
					            ]);
 | 
				
			||||||
      categories.push(areSameDay ? unixToTime(log.recorded) : prettyDate(log.recorded));
 | 
					            i += 1;
 | 
				
			||||||
      for (const logInterface of log.data.interfaces) {
 | 
					          }
 | 
				
			||||||
        if (interfaceTypes[logInterface.name] === undefined) {
 | 
					 | 
				
			||||||
          interfaceTypes[logInterface.name] = i;
 | 
					 | 
				
			||||||
          interfaceList.push([
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
              titleName: logInterface.name,
 | 
					 | 
				
			||||||
              name: 'Tx',
 | 
					 | 
				
			||||||
              backgroundColor: 'rgb(228,102,81,0.9)',
 | 
					 | 
				
			||||||
              data: [],
 | 
					 | 
				
			||||||
              fill: false,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
              titleName: logInterface.name,
 | 
					 | 
				
			||||||
              name: 'Rx',
 | 
					 | 
				
			||||||
              backgroundColor: 'rgb(0,216,255,0.9)',
 | 
					 | 
				
			||||||
              data: [],
 | 
					 | 
				
			||||||
              fill: false,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          ]);
 | 
					 | 
				
			||||||
          i += 1;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Looping through all the data
 | 
					      // Looping through all the data
 | 
				
			||||||
    let prevTx = 0;
 | 
					      const prevTxObj = {};
 | 
				
			||||||
    let prevRx = 0;
 | 
					      const prevRxObj = {};
 | 
				
			||||||
    for (const log of sortedData) {
 | 
					      for (const log of sortedData) {
 | 
				
			||||||
      // Looping through the interfaces of the log
 | 
					        // Looping through the interfaces of the log
 | 
				
			||||||
      const version = log.data.version ?? 0;
 | 
					        const version = log.data.version ?? 0;
 | 
				
			||||||
      for (const inter of log.data.interfaces) {
 | 
					        for (const inter of log.data.interfaces) {
 | 
				
			||||||
        if (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) {
 | 
					          if (version > 0) {
 | 
				
			||||||
            const tx = Math.floor(totalTx / 1024);
 | 
					            const prevTx = prevTxObj[inter.name] !== undefined ? prevTxObj[inter.name] : 0;
 | 
				
			||||||
            const rx = Math.floor(totalRx / 1024);
 | 
					            const prevRx = prevTxObj[inter.name] !== undefined ? prevRxObj[inter.name] : 0;
 | 
				
			||||||
            interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(tx - prevTx, 0));
 | 
					            const tx = inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0;
 | 
				
			||||||
            interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(rx - prevRx, 0));
 | 
					            const rx = inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0;
 | 
				
			||||||
            prevTx = tx;
 | 
					            interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(0, tx - prevTx));
 | 
				
			||||||
            prevRx = rx;
 | 
					            interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(0, rx - prevRx));
 | 
				
			||||||
 | 
					            prevTxObj[inter.name] = tx;
 | 
				
			||||||
 | 
					            prevRxObj[inter.name] = rx;
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            interfaceList[interfaceTypes[inter.name]][0].data.push(Math.floor(totalTx / 1024));
 | 
					            interfaceList[interfaceTypes[inter.name]][0].data.push(
 | 
				
			||||||
            interfaceList[interfaceTypes[inter.name]][1].data.push(Math.floor(totalRx / 1024));
 | 
					              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 / 1024) : 0,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          interfaceList[interfaceTypes[inter.name]][0].data.push(
 | 
					 | 
				
			||||||
            inter.counters ? Math.floor(inter.counters.tx_bytes) : 0,
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
          interfaceList[interfaceTypes[inter.name]][1].data.push(
 | 
					 | 
				
			||||||
            inter.counters ? Math.floor(inter.counters.rx_bytes) : 0,
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (let y = 0; y < interfaceList.length; y += 1) {
 | 
					      for (let y = 0; y < interfaceList.length; y += 1) {
 | 
				
			||||||
      for (let z = 0; z < interfaceList[y].length; z += 1) {
 | 
					        for (let z = 0; z < interfaceList[y].length; z += 1) {
 | 
				
			||||||
        const newArray = interfaceList[y][z].data;
 | 
					          const newArray = interfaceList[y][z].data;
 | 
				
			||||||
        if (newArray.length > 0) newArray.shift();
 | 
					          if (newArray.length > 0) newArray.shift();
 | 
				
			||||||
        interfaceList[y][z].data = newArray;
 | 
					          interfaceList[y][z].data = newArray;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const newCategories = categories;
 | 
					      const newCategories = categories;
 | 
				
			||||||
    if (newCategories.length > 0) newCategories.shift();
 | 
					      if (newCategories.length > 0) newCategories.shift();
 | 
				
			||||||
    const interfaceOptions = {
 | 
					      const interfaceOptions = {
 | 
				
			||||||
      chart: {
 | 
					        chart: {
 | 
				
			||||||
        id: 'chart',
 | 
					          id: 'chart',
 | 
				
			||||||
      },
 | 
					        },
 | 
				
			||||||
      stroke: {
 | 
					        stroke: {
 | 
				
			||||||
        curve: 'smooth',
 | 
					          curve: 'smooth',
 | 
				
			||||||
      },
 | 
					        },
 | 
				
			||||||
      xaxis: {
 | 
					        xaxis: {
 | 
				
			||||||
        title: {
 | 
					          title: {
 | 
				
			||||||
          text: 'Time',
 | 
					            text: 'Time',
 | 
				
			||||||
          style: {
 | 
					            style: {
 | 
				
			||||||
            fontSize: '15px',
 | 
					              fontSize: '15px',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          categories: newCategories,
 | 
				
			||||||
 | 
					          tickAmount: areSameDay ? 15 : 10,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        yaxis: {
 | 
				
			||||||
 | 
					          labels: {
 | 
				
			||||||
 | 
					            minWidth: 40,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          title: {
 | 
				
			||||||
 | 
					            text: t('statistics.data'),
 | 
				
			||||||
 | 
					            style: {
 | 
				
			||||||
 | 
					              fontSize: '15px',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        categories: newCategories,
 | 
					        legend: {
 | 
				
			||||||
        tickAmount: areSameDay ? 15 : 10,
 | 
					          position: 'top',
 | 
				
			||||||
      },
 | 
					          horizontalAlign: 'right',
 | 
				
			||||||
      yaxis: {
 | 
					          float: true,
 | 
				
			||||||
        labels: {
 | 
					 | 
				
			||||||
          minWidth: 40,
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        title: {
 | 
					      };
 | 
				
			||||||
          text: t('statistics.data'),
 | 
					
 | 
				
			||||||
          style: {
 | 
					      const memoryOptions = {
 | 
				
			||||||
            fontSize: '15px',
 | 
					        chart: {
 | 
				
			||||||
 | 
					          id: 'chart',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        stroke: {
 | 
				
			||||||
 | 
					          curve: 'smooth',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        xaxis: {
 | 
				
			||||||
 | 
					          tickAmount: areSameDay ? 15 : 10,
 | 
				
			||||||
 | 
					          title: {
 | 
				
			||||||
 | 
					            text: 'Time',
 | 
				
			||||||
 | 
					            style: {
 | 
				
			||||||
 | 
					              fontSize: '15px',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          categories,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        yaxis: {
 | 
				
			||||||
 | 
					          tickAmount: 5,
 | 
				
			||||||
 | 
					          title: {
 | 
				
			||||||
 | 
					            text: t('statistics.data_mb'),
 | 
				
			||||||
 | 
					            style: {
 | 
				
			||||||
 | 
					              fontSize: '15px',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					        legend: {
 | 
				
			||||||
      legend: {
 | 
					          position: 'top',
 | 
				
			||||||
        position: 'top',
 | 
					          horizontalAlign: 'right',
 | 
				
			||||||
        horizontalAlign: 'right',
 | 
					          float: true,
 | 
				
			||||||
        float: true,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const memoryOptions = {
 | 
					 | 
				
			||||||
      chart: {
 | 
					 | 
				
			||||||
        id: 'chart',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      stroke: {
 | 
					 | 
				
			||||||
        curve: 'smooth',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      xaxis: {
 | 
					 | 
				
			||||||
        tickAmount: areSameDay ? 15 : 10,
 | 
					 | 
				
			||||||
        title: {
 | 
					 | 
				
			||||||
          text: 'Time',
 | 
					 | 
				
			||||||
          style: {
 | 
					 | 
				
			||||||
            fontSize: '15px',
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        categories,
 | 
					      };
 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      yaxis: {
 | 
					 | 
				
			||||||
        tickAmount: 5,
 | 
					 | 
				
			||||||
        title: {
 | 
					 | 
				
			||||||
          text: t('statistics.data_mb'),
 | 
					 | 
				
			||||||
          style: {
 | 
					 | 
				
			||||||
            fontSize: '15px',
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      legend: {
 | 
					 | 
				
			||||||
        position: 'top',
 | 
					 | 
				
			||||||
        horizontalAlign: 'right',
 | 
					 | 
				
			||||||
        float: true,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const newOptions = {
 | 
					      const newOptions = {
 | 
				
			||||||
      interfaceList,
 | 
					        interfaceList,
 | 
				
			||||||
      memory: [memoryUsed],
 | 
					        memory: [memoryUsed],
 | 
				
			||||||
      interfaceOptions,
 | 
					        interfaceOptions,
 | 
				
			||||||
      memoryOptions,
 | 
					        memoryOptions,
 | 
				
			||||||
      start: new Date(sortedData[0].recorded * 1000).toISOString(),
 | 
					        start: new Date(sortedData[0].recorded * 1000).toISOString(),
 | 
				
			||||||
      end: new Date(sortedData[sortedData.length - 1].recorded * 1000).toISOString(),
 | 
					        end: new Date(sortedData[sortedData.length - 1].recorded * 1000).toISOString(),
 | 
				
			||||||
    };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (statOptions !== newOptions) {
 | 
					      if (statOptions !== newOptions) {
 | 
				
			||||||
      const sectionOptions = newOptions.interfaceList.map((opt) => ({
 | 
					        const sectionOptions = newOptions.interfaceList.map((opt) => ({
 | 
				
			||||||
        value: opt[0].titleName,
 | 
					          value: opt[0].titleName,
 | 
				
			||||||
        label: opt[0].titleName,
 | 
					          label: opt[0].titleName,
 | 
				
			||||||
      }));
 | 
					        }));
 | 
				
			||||||
      setOptions([...sectionOptions, { value: 'memory', label: t('statistics.memory') }]);
 | 
					        setOptions([...sectionOptions, { value: 'memory', label: t('statistics.memory') }]);
 | 
				
			||||||
      setStatOptions({ ...newOptions });
 | 
					        setStatOptions({ ...newOptions });
 | 
				
			||||||
      if (sortedData.length > 0) {
 | 
					      }
 | 
				
			||||||
        setStart(new Date(sortedData[0].recorded * 1000));
 | 
					      setError(undefined);
 | 
				
			||||||
        setEnd(new Date(sortedData[sortedData.length - 1].recorded * 1000));
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      if (data?.length === 0) {
 | 
				
			||||||
 | 
					        setError('nodata');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        setError('error');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getInterface = useCallback(() => {
 | 
					  const getInterface = useCallback(() => {
 | 
				
			||||||
 | 
					    if (error === 'error') {
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <div style={{ textAlign: 'center' }}>
 | 
				
			||||||
 | 
					          <CAlert color="danger" style={{ width: '240px', margin: 'auto' }}>
 | 
				
			||||||
 | 
					            Error while parsing statistics
 | 
				
			||||||
 | 
					          </CAlert>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (error === 'nodata') {
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <div style={{ textAlign: 'center' }}>
 | 
				
			||||||
 | 
					          <CAlert color="danger" style={{ width: '340px', margin: 'auto' }}>
 | 
				
			||||||
 | 
					            No available statistics during this time period
 | 
				
			||||||
 | 
					          </CAlert>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (statOptions.interfaceList.length === 0) return <p>N/A</p>;
 | 
					    if (statOptions.interfaceList.length === 0) return <p>N/A</p>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const interfaceToShow = statOptions.interfaceList.find(
 | 
					    const interfaceToShow = statOptions.interfaceList.find(
 | 
				
			||||||
@@ -301,8 +301,9 @@ const StatisticsChartList = ({ setOptions, section, setStart, setEnd, time }) =>
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return <p>N/A</p>;
 | 
					    return <p>N/A</p>;
 | 
				
			||||||
  }, [statOptions, section]);
 | 
					  }, [statOptions, section, error]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getStatistics = () => {
 | 
					  const getStatistics = () => {
 | 
				
			||||||
    setLoading(true);
 | 
					    setLoading(true);
 | 
				
			||||||
@@ -381,11 +382,10 @@ const StatisticsChartList = ({ setOptions, section, setStart, setEnd, time }) =>
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
StatisticsChartList.propTypes = {
 | 
					StatisticsChartList.propTypes = {
 | 
				
			||||||
 | 
					  deviceSerialNumber: PropTypes.string.isRequired,
 | 
				
			||||||
  setOptions: PropTypes.func.isRequired,
 | 
					  setOptions: PropTypes.func.isRequired,
 | 
				
			||||||
  section: PropTypes.string.isRequired,
 | 
					  section: PropTypes.string.isRequired,
 | 
				
			||||||
  time: PropTypes.instanceOf(Object).isRequired,
 | 
					  time: PropTypes.instanceOf(Object).isRequired,
 | 
				
			||||||
  setStart: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  setEnd: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default React.memo(StatisticsChartList);
 | 
					export default React.memo(StatisticsChartList);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,55 +12,98 @@ import {
 | 
				
			|||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
import DatePicker from 'react-widgets/DatePicker';
 | 
					import DatePicker from 'react-widgets/DatePicker';
 | 
				
			||||||
import { cilSync } from '@coreui/icons';
 | 
					import { cilSync } from '@coreui/icons';
 | 
				
			||||||
 | 
					import { useDevice } from 'ucentral-libs';
 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { useGlobalWebSocket } from 'contexts/WebSocketProvider';
 | 
				
			||||||
import StatisticsChartList from './StatisticsChartList';
 | 
					import StatisticsChartList from './StatisticsChartList';
 | 
				
			||||||
import LatestStatisticsmodal from './LatestStatisticsModal';
 | 
					import LatestStatisticsmodal from './LatestStatisticsModal';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getStart = () => {
 | 
				
			||||||
 | 
					  const date = new Date();
 | 
				
			||||||
 | 
					  date.setHours(date.getHours() - 1);
 | 
				
			||||||
 | 
					  return date;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
const DeviceStatisticsCard = () => {
 | 
					const DeviceStatisticsCard = () => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const [showLatestModal, setShowLatestModal] = useState(false);
 | 
					  const [showLatestModal, setShowLatestModal] = useState(false);
 | 
				
			||||||
  const [options, setOptions] = useState([]);
 | 
					  const [options, setOptions] = useState([]);
 | 
				
			||||||
  const [section, setSection] = useState('');
 | 
					  const [section, setSection] = useState('');
 | 
				
			||||||
  const [start, setStart] = useState(null);
 | 
					 | 
				
			||||||
  const [startError, setStartError] = useState(false);
 | 
					  const [startError, setStartError] = useState(false);
 | 
				
			||||||
  const [end, setEnd] = useState(null);
 | 
					 | 
				
			||||||
  const [endError, setEndError] = useState(false);
 | 
					  const [endError, setEndError] = useState(false);
 | 
				
			||||||
  const [time, setTime] = useState({ refreshId: '0', start: null, end: null });
 | 
					  const { deviceSerialNumber } = useDevice();
 | 
				
			||||||
 | 
					  const [nextUpdate, setNextUpdate] = useState(undefined);
 | 
				
			||||||
 | 
					  const { addDeviceListener, removeDeviceListener } = useGlobalWebSocket();
 | 
				
			||||||
 | 
					  const [time, setTime] = useState({
 | 
				
			||||||
 | 
					    refreshId: '0',
 | 
				
			||||||
 | 
					    start: getStart(),
 | 
				
			||||||
 | 
					    end: new Date().toISOString(),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const toggleLatestModal = () => {
 | 
					  const toggleLatestModal = () => {
 | 
				
			||||||
    setShowLatestModal(!showLatestModal);
 | 
					    setShowLatestModal(!showLatestModal);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const modifyStart = (value) => {
 | 
					  const modifyStart = (value, refresh = true) => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      new Date(value).toISOString();
 | 
					      new Date(value).toISOString();
 | 
				
			||||||
      setStartError(false);
 | 
					      setStartError(false);
 | 
				
			||||||
      setStart(value);
 | 
					      if (refresh) setTime({ ...time, refreshId: createUuid(), start: value, isChosen: true });
 | 
				
			||||||
 | 
					      else setTime({ ...time, start: value, isChosen: true });
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      setStart('');
 | 
					 | 
				
			||||||
      setStartError(true);
 | 
					      setStartError(true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const modifyEnd = (value) => {
 | 
					  const modifyEnd = (value, refresh = true) => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      new Date(value).toISOString();
 | 
					      new Date(value).toISOString();
 | 
				
			||||||
      setEndError(false);
 | 
					      setEndError(false);
 | 
				
			||||||
      setEnd(value);
 | 
					      if (refresh) setTime({ ...time, refreshId: createUuid(), end: value, isChosen: true });
 | 
				
			||||||
 | 
					      else setTime({ ...time, end: value, isChosen: true });
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      setEnd('');
 | 
					 | 
				
			||||||
      setEndError(true);
 | 
					      setEndError(true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const refresh = () => {
 | 
					  const refresh = () => {
 | 
				
			||||||
    setTime({ refreshId: createUuid(), start, end });
 | 
					    setTime({ refreshId: createUuid(), start: getStart(), end: new Date().toISOString() });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleRefreshClick = () => {
 | 
				
			||||||
 | 
					    refresh();
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (section === '' && options.length > 0) setSection(options[0].value);
 | 
					    if (section === '' && options.length > 0) setSection(options[0].value);
 | 
				
			||||||
  }, [options]);
 | 
					  }, [options]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (nextUpdate && !time.isChosen) {
 | 
				
			||||||
 | 
					      setTime({ refreshId: createUuid(), start: getStart(), end: new Date().toISOString() });
 | 
				
			||||||
 | 
					      setNextUpdate(undefined);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [nextUpdate, time]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setNextUpdate(undefined);
 | 
				
			||||||
 | 
					    if (deviceSerialNumber) {
 | 
				
			||||||
 | 
					      addDeviceListener({
 | 
				
			||||||
 | 
					        serialNumber: deviceSerialNumber,
 | 
				
			||||||
 | 
					        types: ['device_statistics'],
 | 
				
			||||||
 | 
					        onTrigger: () => setNextUpdate(1),
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      refresh();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      if (deviceSerialNumber) {
 | 
				
			||||||
 | 
					        removeDeviceListener({
 | 
				
			||||||
 | 
					          serialNumber: deviceSerialNumber,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }, [deviceSerialNumber]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
      <CCard className="m-0">
 | 
					      <CCard className="m-0">
 | 
				
			||||||
@@ -68,7 +111,12 @@ const DeviceStatisticsCard = () => {
 | 
				
			|||||||
          <div className="d-flex flex-row-reverse align-items-center">
 | 
					          <div className="d-flex flex-row-reverse align-items-center">
 | 
				
			||||||
            <div className="pl-2">
 | 
					            <div className="pl-2">
 | 
				
			||||||
              <CPopover content={t('common.refresh')}>
 | 
					              <CPopover content={t('common.refresh')}>
 | 
				
			||||||
                <CButton size="sm" color="info" onClick={refresh} disabled={startError || endError}>
 | 
					                <CButton
 | 
				
			||||||
 | 
					                  size="sm"
 | 
				
			||||||
 | 
					                  color="info"
 | 
				
			||||||
 | 
					                  onClick={handleRefreshClick}
 | 
				
			||||||
 | 
					                  disabled={startError || endError}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                  <CIcon content={cilSync} />
 | 
					                  <CIcon content={cilSync} />
 | 
				
			||||||
                </CButton>
 | 
					                </CButton>
 | 
				
			||||||
              </CPopover>
 | 
					              </CPopover>
 | 
				
			||||||
@@ -77,7 +125,7 @@ const DeviceStatisticsCard = () => {
 | 
				
			|||||||
              <DatePicker
 | 
					              <DatePicker
 | 
				
			||||||
                includeTime
 | 
					                includeTime
 | 
				
			||||||
                onChange={(date) => modifyEnd(date)}
 | 
					                onChange={(date) => modifyEnd(date)}
 | 
				
			||||||
                value={end ? new Date(end) : undefined}
 | 
					                value={time.end ? new Date(time.end) : undefined}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
              <CFormText color="danger" hidden={!endError}>
 | 
					              <CFormText color="danger" hidden={!endError}>
 | 
				
			||||||
                {t('common.invalid_date_explanation')}
 | 
					                {t('common.invalid_date_explanation')}
 | 
				
			||||||
@@ -88,7 +136,7 @@ const DeviceStatisticsCard = () => {
 | 
				
			|||||||
              <DatePicker
 | 
					              <DatePicker
 | 
				
			||||||
                includeTime
 | 
					                includeTime
 | 
				
			||||||
                onChange={(date) => modifyStart(date)}
 | 
					                onChange={(date) => modifyStart(date)}
 | 
				
			||||||
                value={start ? new Date(start) : undefined}
 | 
					                value={time.start ? new Date(time.start) : undefined}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
              <CFormText color="danger" hidden={!startError}>
 | 
					              <CFormText color="danger" hidden={!startError}>
 | 
				
			||||||
                {t('common.invalid_date_explanation')}
 | 
					                {t('common.invalid_date_explanation')}
 | 
				
			||||||
@@ -118,11 +166,10 @@ const DeviceStatisticsCard = () => {
 | 
				
			|||||||
        </CCardHeader>
 | 
					        </CCardHeader>
 | 
				
			||||||
        <CCardBody className="p-1">
 | 
					        <CCardBody className="p-1">
 | 
				
			||||||
          <StatisticsChartList
 | 
					          <StatisticsChartList
 | 
				
			||||||
 | 
					            deviceSerialNumber={deviceSerialNumber}
 | 
				
			||||||
            setOptions={setOptions}
 | 
					            setOptions={setOptions}
 | 
				
			||||||
            section={section}
 | 
					            section={section}
 | 
				
			||||||
            time={time}
 | 
					            time={time}
 | 
				
			||||||
            setStart={setStart}
 | 
					 | 
				
			||||||
            setEnd={setEnd}
 | 
					 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </CCardBody>
 | 
					        </CCardBody>
 | 
				
			||||||
      </CCard>
 | 
					      </CCard>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,7 @@ const NetworkDiagram = ({ show, elements, setElements }) => {
 | 
				
			|||||||
        onElementsRemove={onElementsRemove}
 | 
					        onElementsRemove={onElementsRemove}
 | 
				
			||||||
        onLoad={onLoad}
 | 
					        onLoad={onLoad}
 | 
				
			||||||
        snapToGrid
 | 
					        snapToGrid
 | 
				
			||||||
 | 
					        minZoom={0.1}
 | 
				
			||||||
        snapGrid={[20, 20]}
 | 
					        snapGrid={[20, 20]}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <MiniMap
 | 
					        <MiniMap
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,7 +47,7 @@ const associationNode = (associationInfo) => (
 | 
				
			|||||||
  <div>
 | 
					  <div>
 | 
				
			||||||
    <CRow>
 | 
					    <CRow>
 | 
				
			||||||
      <CCol className="text-center">
 | 
					      <CCol className="text-center">
 | 
				
			||||||
        <h6>{associationInfo.bssid}</h6>
 | 
					        <h6>{associationInfo.station}</h6>
 | 
				
			||||||
      </CCol>
 | 
					      </CCol>
 | 
				
			||||||
    </CRow>
 | 
					    </CRow>
 | 
				
			||||||
    <CRow>
 | 
					    <CRow>
 | 
				
			||||||
@@ -92,7 +92,6 @@ const NetworkDiagram = ({ show, radios, associations }) => {
 | 
				
			|||||||
    // Creating the association nodes and their edges
 | 
					    // Creating the association nodes and their edges
 | 
				
			||||||
    for (let i = 0; i < associations.length; i += 1) {
 | 
					    for (let i = 0; i < associations.length; i += 1) {
 | 
				
			||||||
      const assoc = associations[i];
 | 
					      const assoc = associations[i];
 | 
				
			||||||
 | 
					 | 
				
			||||||
      // If the radio has not been added, we create a new unknown radio based on its index
 | 
					      // If the radio has not been added, we create a new unknown radio based on its index
 | 
				
			||||||
      if (radiosAdded[assoc.radio.radioIndex] === undefined) {
 | 
					      if (radiosAdded[assoc.radio.radioIndex] === undefined) {
 | 
				
			||||||
        newElements.push({
 | 
					        newElements.push({
 | 
				
			||||||
@@ -107,7 +106,7 @@ const NetworkDiagram = ({ show, radios, associations }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      // Adding the association
 | 
					      // Adding the association
 | 
				
			||||||
      newElements.push({
 | 
					      newElements.push({
 | 
				
			||||||
        id: `a-${assoc.bssid}`,
 | 
					        id: `a-${assoc.station}`,
 | 
				
			||||||
        data: { label: associationNode(assoc) },
 | 
					        data: { label: associationNode(assoc) },
 | 
				
			||||||
        position: {
 | 
					        position: {
 | 
				
			||||||
          x: getX(radiosAdded[assoc.radio.radioIndex]),
 | 
					          x: getX(radiosAdded[assoc.radio.radioIndex]),
 | 
				
			||||||
@@ -120,9 +119,9 @@ const NetworkDiagram = ({ show, radios, associations }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      // Creating the edge
 | 
					      // Creating the edge
 | 
				
			||||||
      newElements.push({
 | 
					      newElements.push({
 | 
				
			||||||
        id: `e-${assoc.radio.radioIndex}-${assoc.bssid}`,
 | 
					        id: `e-${assoc.radio.radioIndex}-${assoc.station}`,
 | 
				
			||||||
        source: `r-${assoc.radio.radioIndex}`,
 | 
					        source: `r-${assoc.radio.radioIndex}`,
 | 
				
			||||||
        target: `a-${assoc.bssid}`,
 | 
					        target: `a-${assoc.station}`,
 | 
				
			||||||
        arrowHeadType: 'arrowclosed',
 | 
					        arrowHeadType: 'arrowclosed',
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,11 +22,13 @@ import axiosInstance from 'utils/axiosInstance';
 | 
				
			|||||||
import eventBus from 'utils/eventBus';
 | 
					import eventBus from 'utils/eventBus';
 | 
				
			||||||
import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs';
 | 
					import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs';
 | 
				
			||||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
 | 
					import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
 | 
				
			||||||
 | 
					import { useGlobalWebSocket } from 'contexts/WebSocketProvider';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ActionModal = ({ show, toggleModal }) => {
 | 
					const ActionModal = ({ show, toggleModal }) => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
  const { deviceSerialNumber } = useDevice();
 | 
					  const { deviceSerialNumber } = useDevice();
 | 
				
			||||||
 | 
					  const { addDeviceListener } = useGlobalWebSocket();
 | 
				
			||||||
  const { addToast } = useToast();
 | 
					  const { addToast } = useToast();
 | 
				
			||||||
  const [waiting, setWaiting] = useState(false);
 | 
					  const [waiting, setWaiting] = useState(false);
 | 
				
			||||||
  const [result, setResult] = useState(null);
 | 
					  const [result, setResult] = useState(null);
 | 
				
			||||||
@@ -74,15 +76,31 @@ const ActionModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
        { headers },
 | 
					        { headers },
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      .then(() => {
 | 
					      .then(() => {
 | 
				
			||||||
        addToast({
 | 
					        addDeviceListener({
 | 
				
			||||||
          title: t('common.success'),
 | 
					          serialNumber: deviceSerialNumber,
 | 
				
			||||||
          body: t('commands.command_success'),
 | 
					          types: ['device_connection', 'device_disconnection'],
 | 
				
			||||||
          color: 'success',
 | 
					          addToast: (title, body) =>
 | 
				
			||||||
          autohide: true,
 | 
					            addToast({
 | 
				
			||||||
 | 
					              title,
 | 
				
			||||||
 | 
					              body,
 | 
				
			||||||
 | 
					              color: 'info',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        toggleModal();
 | 
					        toggleModal();
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        if (e.response?.data?.ErrorDescription !== undefined) {
 | 
				
			||||||
 | 
					          const split = e.response?.data?.ErrorDescription.split(':');
 | 
				
			||||||
 | 
					          if (split !== undefined && split.length >= 2) {
 | 
				
			||||||
 | 
					            addToast({
 | 
				
			||||||
 | 
					              title: t('common.error'),
 | 
				
			||||||
 | 
					              body: split[1],
 | 
				
			||||||
 | 
					              color: 'danger',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        setResult('error');
 | 
					        setResult('error');
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .finally(() => {
 | 
					      .finally(() => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import React, { useEffect, useState } from 'react';
 | 
					import React, { useEffect, useMemo, useState } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import Select from 'react-select';
 | 
					import Select from 'react-select';
 | 
				
			||||||
@@ -12,6 +12,9 @@ import {
 | 
				
			|||||||
  CRow,
 | 
					  CRow,
 | 
				
			||||||
  CCol,
 | 
					  CCol,
 | 
				
			||||||
  CInput,
 | 
					  CInput,
 | 
				
			||||||
 | 
					  CFormGroup,
 | 
				
			||||||
 | 
					  CInputRadio,
 | 
				
			||||||
 | 
					  CLabel,
 | 
				
			||||||
  CSpinner,
 | 
					  CSpinner,
 | 
				
			||||||
  CAlert,
 | 
					  CAlert,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
@@ -36,11 +39,14 @@ const TelemetryModal = ({ show, toggle }) => {
 | 
				
			|||||||
  const [lastMessage, setLastMessage] = useState({});
 | 
					  const [lastMessage, setLastMessage] = useState({});
 | 
				
			||||||
  const [receivedMessages, setReceivedMessages] = useState(0);
 | 
					  const [receivedMessages, setReceivedMessages] = useState(0);
 | 
				
			||||||
  const [types, setTypes] = useState([]);
 | 
					  const [types, setTypes] = useState([]);
 | 
				
			||||||
 | 
					  const [chosenMethod, setChosenMethod] = useState('false');
 | 
				
			||||||
 | 
					  const [lifetime, setLifetime] = useState(5);
 | 
				
			||||||
  const [interval, setInterval] = useState(3);
 | 
					  const [interval, setInterval] = useState(3);
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
  const [lastUpdate, setLastUpdate] = useState('');
 | 
					  const [lastUpdate, setLastUpdate] = useState('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onIntervalChange = (e) => setInterval(e.target.value);
 | 
					  const onIntervalChange = (e) => setInterval(e.target.value);
 | 
				
			||||||
 | 
					  const onLifetimeChange = (e) => setLifetime(e.target.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const closeSocket = () => {
 | 
					  const closeSocket = () => {
 | 
				
			||||||
    if (socket !== null) {
 | 
					    if (socket !== null) {
 | 
				
			||||||
@@ -49,6 +55,17 @@ const TelemetryModal = ({ show, toggle }) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const msgToDisplay = useMemo(() => {
 | 
				
			||||||
 | 
					    const display = {};
 | 
				
			||||||
 | 
					    if (lastMessage) {
 | 
				
			||||||
 | 
					      for (const type of types) {
 | 
				
			||||||
 | 
					        display[type.value] = lastMessage[type.value];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return display;
 | 
				
			||||||
 | 
					  }, [lastMessage, types]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getUrl = () => {
 | 
					  const getUrl = () => {
 | 
				
			||||||
    setLastUpdate('');
 | 
					    setLastUpdate('');
 | 
				
			||||||
    setLastMessage({});
 | 
					    setLastMessage({});
 | 
				
			||||||
@@ -57,6 +74,8 @@ const TelemetryModal = ({ show, toggle }) => {
 | 
				
			|||||||
    const parameters = {
 | 
					    const parameters = {
 | 
				
			||||||
      serialNumber: deviceSerialNumber,
 | 
					      serialNumber: deviceSerialNumber,
 | 
				
			||||||
      interval: parseInt(interval, 10),
 | 
					      interval: parseInt(interval, 10),
 | 
				
			||||||
 | 
					      lifetime: parseInt(lifetime * 60, 10),
 | 
				
			||||||
 | 
					      kafka: chosenMethod,
 | 
				
			||||||
      types: types.map((type) => type.value),
 | 
					      types: types.map((type) => type.value),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,18 +91,31 @@ const TelemetryModal = ({ show, toggle }) => {
 | 
				
			|||||||
        { headers },
 | 
					        { headers },
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      .then((response) => {
 | 
					      .then((response) => {
 | 
				
			||||||
        if (response.data.uri && response.data.uri !== '') {
 | 
					        if (chosenMethod === 'true') {
 | 
				
			||||||
 | 
					          addToast({
 | 
				
			||||||
 | 
					            title: t('common.success'),
 | 
				
			||||||
 | 
					            body: t('commands.command_success'),
 | 
				
			||||||
 | 
					            color: 'success',
 | 
				
			||||||
 | 
					            autohide: true,
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          toggle();
 | 
				
			||||||
 | 
					        } else if (response.data.uri && response.data.uri !== '') {
 | 
				
			||||||
          setReceivedMessages(0);
 | 
					          setReceivedMessages(0);
 | 
				
			||||||
          setSocket(new WebSocket(response.data.uri));
 | 
					          setSocket(new WebSocket(response.data.uri));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch((e) => {
 | 
					      .catch((e) => {
 | 
				
			||||||
        addToast({
 | 
					        if (e.response?.data?.ErrorDescription !== undefined) {
 | 
				
			||||||
          title: t('common.error'),
 | 
					          const split = e.response?.data?.ErrorDescription.split(':');
 | 
				
			||||||
          body: t('telemetry.connection_failed', { error: e.response?.data?.ErrorDescription }),
 | 
					          if (split !== undefined && split.length >= 2) {
 | 
				
			||||||
          color: 'danger',
 | 
					            addToast({
 | 
				
			||||||
          autohide: true,
 | 
					              title: t('common.error'),
 | 
				
			||||||
        });
 | 
					              body: split[1],
 | 
				
			||||||
 | 
					              color: 'danger',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .finally(() => setLoading(false));
 | 
					      .finally(() => setLoading(false));
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -146,6 +178,50 @@ const TelemetryModal = ({ show, toggle }) => {
 | 
				
			|||||||
                />
 | 
					                />
 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
            </CRow>
 | 
					            </CRow>
 | 
				
			||||||
 | 
					            <CRow>
 | 
				
			||||||
 | 
					              <CCol>{`${t('telemetry.lifetime')}: ${lifetime} ${t('common.minutes')}`}</CCol>
 | 
				
			||||||
 | 
					            </CRow>
 | 
				
			||||||
 | 
					            <CRow>
 | 
				
			||||||
 | 
					              <CCol>
 | 
				
			||||||
 | 
					                <CInput
 | 
				
			||||||
 | 
					                  type="range"
 | 
				
			||||||
 | 
					                  min="1"
 | 
				
			||||||
 | 
					                  max="120"
 | 
				
			||||||
 | 
					                  step="1"
 | 
				
			||||||
 | 
					                  onChange={onLifetimeChange}
 | 
				
			||||||
 | 
					                  value={lifetime}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </CCol>
 | 
				
			||||||
 | 
					            </CRow>
 | 
				
			||||||
 | 
					            <CFormGroup row className="mb-0">
 | 
				
			||||||
 | 
					              <CCol md="3">
 | 
				
			||||||
 | 
					                <CLabel>{t('telemetry.outputmode')}</CLabel>
 | 
				
			||||||
 | 
					              </CCol>
 | 
				
			||||||
 | 
					              <CCol>
 | 
				
			||||||
 | 
					                <CFormGroup variant="checkbox" onClick={() => setChosenMethod('false')} inline>
 | 
				
			||||||
 | 
					                  <CInputRadio
 | 
				
			||||||
 | 
					                    defaultChecked={chosenMethod === 'false'}
 | 
				
			||||||
 | 
					                    id="traceRadio1"
 | 
				
			||||||
 | 
					                    name="radios"
 | 
				
			||||||
 | 
					                    value="traceOption1"
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                  <CLabel variant="checkbox" htmlFor="traceRadio1">
 | 
				
			||||||
 | 
					                    Websocket
 | 
				
			||||||
 | 
					                  </CLabel>
 | 
				
			||||||
 | 
					                </CFormGroup>
 | 
				
			||||||
 | 
					                <CFormGroup variant="checkbox" onClick={() => setChosenMethod('true')} inline>
 | 
				
			||||||
 | 
					                  <CInputRadio
 | 
				
			||||||
 | 
					                    defaultChecked={chosenMethod === 'true'}
 | 
				
			||||||
 | 
					                    id="traceRadio2"
 | 
				
			||||||
 | 
					                    name="radios"
 | 
				
			||||||
 | 
					                    value="traceOption2"
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                  <CLabel variant="checkbox" htmlFor="traceRadio2">
 | 
				
			||||||
 | 
					                    Kafka
 | 
				
			||||||
 | 
					                  </CLabel>
 | 
				
			||||||
 | 
					                </CFormGroup>
 | 
				
			||||||
 | 
					              </CCol>
 | 
				
			||||||
 | 
					            </CFormGroup>
 | 
				
			||||||
            <CRow>
 | 
					            <CRow>
 | 
				
			||||||
              <CCol sm="2" className="pt-2">
 | 
					              <CCol sm="2" className="pt-2">
 | 
				
			||||||
                {t('telemetry.types')}:
 | 
					                {t('telemetry.types')}:
 | 
				
			||||||
@@ -178,6 +254,11 @@ const TelemetryModal = ({ show, toggle }) => {
 | 
				
			|||||||
                {t('telemetry.interval')}: {interval} {t('common.seconds')}
 | 
					                {t('telemetry.interval')}: {interval} {t('common.seconds')}
 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
            </CRow>
 | 
					            </CRow>
 | 
				
			||||||
 | 
					            <CRow>
 | 
				
			||||||
 | 
					              <CCol>
 | 
				
			||||||
 | 
					                {t('telemetry.lifetime')}: {lifetime} {t('common.minutes')}
 | 
				
			||||||
 | 
					              </CCol>
 | 
				
			||||||
 | 
					            </CRow>
 | 
				
			||||||
            <CRow>
 | 
					            <CRow>
 | 
				
			||||||
              <CCol>
 | 
					              <CCol>
 | 
				
			||||||
                {t('telemetry.types')}: {types.map((type) => type.label).join(', ')}
 | 
					                {t('telemetry.types')}: {types.map((type) => type.label).join(', ')}
 | 
				
			||||||
@@ -193,7 +274,7 @@ const TelemetryModal = ({ show, toggle }) => {
 | 
				
			|||||||
            </CRow>
 | 
					            </CRow>
 | 
				
			||||||
            <CRow>
 | 
					            <CRow>
 | 
				
			||||||
              <CCol>
 | 
					              <CCol>
 | 
				
			||||||
                <pre>{JSON.stringify(lastMessage, null, 2)}</pre>
 | 
					                <pre>{JSON.stringify(msgToDisplay, null, 2)}</pre>
 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
            </CRow>
 | 
					            </CRow>
 | 
				
			||||||
            <CRow>
 | 
					            <CRow>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,13 +23,14 @@ import PropTypes from 'prop-types';
 | 
				
			|||||||
import 'react-widgets/styles.css';
 | 
					import 'react-widgets/styles.css';
 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
import eventBus from 'utils/eventBus';
 | 
					import eventBus from 'utils/eventBus';
 | 
				
			||||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
 | 
					import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs';
 | 
				
			||||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
 | 
					import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
 | 
				
			||||||
import WaitingForTraceBody from './WaitingForTraceBody';
 | 
					import WaitingForTraceBody from './WaitingForTraceBody';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TraceModal = ({ show, toggleModal }) => {
 | 
					const TraceModal = ({ show, toggleModal }) => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
 | 
					  const { addToast } = useToast();
 | 
				
			||||||
  const { deviceSerialNumber, getDeviceConnection } = useDevice();
 | 
					  const { deviceSerialNumber, getDeviceConnection } = useDevice();
 | 
				
			||||||
  const [hadSuccess, setHadSuccess] = useState(false);
 | 
					  const [hadSuccess, setHadSuccess] = useState(false);
 | 
				
			||||||
  const [hadFailure, setHadFailure] = useState(false);
 | 
					  const [hadFailure, setHadFailure] = useState(false);
 | 
				
			||||||
@@ -40,7 +41,7 @@ const TraceModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
  const [responseBody, setResponseBody] = useState('');
 | 
					  const [responseBody, setResponseBody] = useState('');
 | 
				
			||||||
  const [chosenInterface, setChosenInterface] = useState('up');
 | 
					  const [chosenInterface, setChosenInterface] = useState('up');
 | 
				
			||||||
  const [isDeviceConnected, setIsDeviceConnected] = useState(false);
 | 
					  const [isDeviceConnected, setIsDeviceConnected] = useState(false);
 | 
				
			||||||
  const [waitForTrace, setWaitForTrace] = useState(false);
 | 
					  const [waitForTrace, setWaitForTrace] = useState(true);
 | 
				
			||||||
  const [waitingForTrace, setWaitingForTrace] = useState(false);
 | 
					  const [waitingForTrace, setWaitingForTrace] = useState(false);
 | 
				
			||||||
  const [commandUuid, setCommandUuid] = useState(null);
 | 
					  const [commandUuid, setCommandUuid] = useState(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -49,7 +50,7 @@ const TraceModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    setWaitForTrace(false);
 | 
					    setWaitForTrace(true);
 | 
				
			||||||
    setHadSuccess(false);
 | 
					    setHadSuccess(false);
 | 
				
			||||||
    setHadFailure(false);
 | 
					    setHadFailure(false);
 | 
				
			||||||
    setResponseBody('');
 | 
					    setResponseBody('');
 | 
				
			||||||
@@ -94,7 +95,18 @@ const TraceModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
          setWaitingForTrace(true);
 | 
					          setWaitingForTrace(true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        if (e.response?.data?.ErrorDescription !== undefined) {
 | 
				
			||||||
 | 
					          const split = e.response?.data?.ErrorDescription.split(':');
 | 
				
			||||||
 | 
					          if (split !== undefined && split.length >= 2) {
 | 
				
			||||||
 | 
					            addToast({
 | 
				
			||||||
 | 
					              title: t('common.error'),
 | 
				
			||||||
 | 
					              body: split[1],
 | 
				
			||||||
 | 
					              color: 'danger',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        setResponseBody(t('commands.error'));
 | 
					        setResponseBody(t('commands.error'));
 | 
				
			||||||
        setHadFailure(true);
 | 
					        setHadFailure(true);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
@@ -137,25 +149,19 @@ const TraceModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
        <CModalBody>
 | 
					        <CModalBody>
 | 
				
			||||||
          <h6>{t('trace.directions')}</h6>
 | 
					          <h6>{t('trace.directions')}</h6>
 | 
				
			||||||
          <CRow className="mt-3">
 | 
					          <CRow className="mt-3">
 | 
				
			||||||
            <CCol>
 | 
					            <CCol md="4" className="pt-2">
 | 
				
			||||||
              <CButton
 | 
					              {t('contact.type')}
 | 
				
			||||||
                disabled={blockFields}
 | 
					 | 
				
			||||||
                block
 | 
					 | 
				
			||||||
                color="primary"
 | 
					 | 
				
			||||||
                onClick={() => setUsingDuration(true)}
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                {t('common.duration')}
 | 
					 | 
				
			||||||
              </CButton>
 | 
					 | 
				
			||||||
            </CCol>
 | 
					            </CCol>
 | 
				
			||||||
            <CCol>
 | 
					            <CCol xs="12" md="8">
 | 
				
			||||||
              <CButton
 | 
					              <CSelect
 | 
				
			||||||
 | 
					                custom
 | 
				
			||||||
 | 
					                value={usingDuration ? 'duration' : 'packets'}
 | 
				
			||||||
                disabled={blockFields}
 | 
					                disabled={blockFields}
 | 
				
			||||||
                block
 | 
					                onChange={(e) => setUsingDuration(e.target.value === 'duration')}
 | 
				
			||||||
                color="primary"
 | 
					 | 
				
			||||||
                onClick={() => setUsingDuration(false)}
 | 
					 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                {t('trace.packets')}
 | 
					                <option value="duration">{t('common.duration')}</option>
 | 
				
			||||||
              </CButton>
 | 
					                <option value="packets">{t('trace.packets')}</option>
 | 
				
			||||||
 | 
					              </CSelect>
 | 
				
			||||||
            </CCol>
 | 
					            </CCol>
 | 
				
			||||||
          </CRow>
 | 
					          </CRow>
 | 
				
			||||||
          <CRow className="mt-3">
 | 
					          <CRow className="mt-3">
 | 
				
			||||||
@@ -220,7 +226,7 @@ const TraceModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
            </CCol>
 | 
					            </CCol>
 | 
				
			||||||
          </CRow>
 | 
					          </CRow>
 | 
				
			||||||
          <CRow className="mt-3" hidden={!isDeviceConnected}>
 | 
					          <CRow className="mt-3" hidden={!isDeviceConnected}>
 | 
				
			||||||
            <CCol md="8">
 | 
					            <CCol md="7">
 | 
				
			||||||
              <p>{t('trace.wait_for_file')}</p>
 | 
					              <p>{t('trace.wait_for_file')}</p>
 | 
				
			||||||
            </CCol>
 | 
					            </CCol>
 | 
				
			||||||
            <CCol>
 | 
					            <CCol>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,7 @@ const WifiAnalysis = () => {
 | 
				
			|||||||
  const secondsToLabel = (seconds) =>
 | 
					  const secondsToLabel = (seconds) =>
 | 
				
			||||||
    compactSecondsToDetailed(seconds, t('common.day'), t('common.days'), t('common.seconds'));
 | 
					    compactSecondsToDetailed(seconds, t('common.day'), t('common.days'), t('common.seconds'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const extractIp = (json, bssid) => {
 | 
					  const extractIp = (json, station) => {
 | 
				
			||||||
    const ips = {
 | 
					    const ips = {
 | 
				
			||||||
      ipV4: [],
 | 
					      ipV4: [],
 | 
				
			||||||
      ipV6: [],
 | 
					      ipV6: [],
 | 
				
			||||||
@@ -57,7 +57,7 @@ const WifiAnalysis = () => {
 | 
				
			|||||||
    for (const obj of json.interfaces) {
 | 
					    for (const obj of json.interfaces) {
 | 
				
			||||||
      if ('clients' in obj) {
 | 
					      if ('clients' in obj) {
 | 
				
			||||||
        for (const client of obj.clients) {
 | 
					        for (const client of obj.clients) {
 | 
				
			||||||
          if (client.mac === bssid) {
 | 
					          if (client.mac === station) {
 | 
				
			||||||
            ips.ipV4 = ips.ipV4.concat(client.ipv4_addresses ?? []);
 | 
					            ips.ipV4 = ips.ipV4.concat(client.ipv4_addresses ?? []);
 | 
				
			||||||
            ips.ipV6 = ips.ipV6.concat(client.ipv6_addresses ?? []);
 | 
					            ips.ipV6 = ips.ipV6.concat(client.ipv6_addresses ?? []);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@@ -151,7 +151,7 @@ const WifiAnalysis = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                const data = {
 | 
					                const data = {
 | 
				
			||||||
                  radio: radioInfo,
 | 
					                  radio: radioInfo,
 | 
				
			||||||
                  ...extractIp(stat.data, association.bssid),
 | 
					                  ...extractIp(stat.data, association.station),
 | 
				
			||||||
                  station: association.station,
 | 
					                  station: association.station,
 | 
				
			||||||
                  ssid: ssid.ssid,
 | 
					                  ssid: ssid.ssid,
 | 
				
			||||||
                  rssi: association.rssi ? parseDbm(association.rssi) : '-',
 | 
					                  rssi: association.rssi ? parseDbm(association.rssi) : '-',
 | 
				
			||||||
@@ -238,7 +238,7 @@ const WifiAnalysis = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
      <CCard>
 | 
					      <CCard className="mb-0">
 | 
				
			||||||
        <CCardHeader className="dark-header d-flex flex-row-reverse align-items-center">
 | 
					        <CCardHeader className="dark-header d-flex flex-row-reverse align-items-center">
 | 
				
			||||||
          <div className="pl-2">
 | 
					          <div className="pl-2">
 | 
				
			||||||
            <CPopover content={t('common.refresh')}>
 | 
					            <CPopover content={t('common.refresh')}>
 | 
				
			||||||
@@ -254,35 +254,47 @@ const WifiAnalysis = () => {
 | 
				
			|||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </CCardHeader>
 | 
					        </CCardHeader>
 | 
				
			||||||
        <CCardBody>
 | 
					        <CCardBody>
 | 
				
			||||||
          <CRow className="mb-4">
 | 
					          {!loading && parsedAssociationStats.length === 0 ? (
 | 
				
			||||||
            <CCol className="text-center">
 | 
					            <div className="text-center">
 | 
				
			||||||
              <input
 | 
					              <h3>{t('wifi_analysis.waiting_for_data')}</h3>
 | 
				
			||||||
                type="range"
 | 
					            </div>
 | 
				
			||||||
                style={{ width: '80%' }}
 | 
					          ) : (
 | 
				
			||||||
                className="form-range"
 | 
					            <>
 | 
				
			||||||
                min="0"
 | 
					              <CRow className="mb-4">
 | 
				
			||||||
                max={range}
 | 
					                <CCol className="text-center">
 | 
				
			||||||
                step="1"
 | 
					                  <input
 | 
				
			||||||
                onChange={(e) => updateSelectedStats(e.target.value)}
 | 
					                    type="range"
 | 
				
			||||||
                defaultValue={range}
 | 
					                    style={{ width: '80%' }}
 | 
				
			||||||
                disabled={!selectedRadioStats}
 | 
					                    className="form-range"
 | 
				
			||||||
              />
 | 
					                    min="0"
 | 
				
			||||||
              <h5>
 | 
					                    max={range}
 | 
				
			||||||
                {t('common.timestamp')}: {tableTime}
 | 
					                    step="1"
 | 
				
			||||||
              </h5>
 | 
					                    onChange={(e) => updateSelectedStats(e.target.value)}
 | 
				
			||||||
            </CCol>
 | 
					                    defaultValue={range}
 | 
				
			||||||
          </CRow>
 | 
					                    disabled={!selectedRadioStats}
 | 
				
			||||||
          <div className="overflow-auto" style={{ height: 'calc(100vh - 300px)' }}>
 | 
					                  />
 | 
				
			||||||
            <h5 className="pb-3 text-center">{t('wifi_analysis.radios')}</h5>
 | 
					                  <h5>
 | 
				
			||||||
            <RadioAnalysisTable data={selectedRadioStats ?? []} loading={loading} range={range} />
 | 
					                    {t('common.timestamp')}: {tableTime}
 | 
				
			||||||
            <h5 className="pt-5 pb-3 text-center">{t('wifi_analysis.associations')}</h5>
 | 
					                  </h5>
 | 
				
			||||||
            <WifiAnalysisTable
 | 
					                </CCol>
 | 
				
			||||||
              t={t}
 | 
					              </CRow>
 | 
				
			||||||
              data={selectedAssociationStats ?? []}
 | 
					              <div className="overflow-auto" style={{ height: 'calc(100vh - 300px)' }}>
 | 
				
			||||||
              loading={loading}
 | 
					                <h5 className="pb-3 text-center">{t('wifi_analysis.radios')}</h5>
 | 
				
			||||||
              range={range}
 | 
					                <RadioAnalysisTable
 | 
				
			||||||
            />
 | 
					                  data={selectedRadioStats ?? []}
 | 
				
			||||||
          </div>
 | 
					                  loading={loading}
 | 
				
			||||||
 | 
					                  range={range}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <h5 className="pt-5 pb-3 text-center">{t('wifi_analysis.associations')}</h5>
 | 
				
			||||||
 | 
					                <WifiAnalysisTable
 | 
				
			||||||
 | 
					                  t={t}
 | 
				
			||||||
 | 
					                  data={selectedAssociationStats ?? []}
 | 
				
			||||||
 | 
					                  loading={loading}
 | 
				
			||||||
 | 
					                  range={range}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
        </CCardBody>
 | 
					        </CCardBody>
 | 
				
			||||||
      </CCard>
 | 
					      </CCard>
 | 
				
			||||||
      <CModal size="xl" show={showModal} onClose={toggleModal}>
 | 
					      <CModal size="xl" show={showModal} onClose={toggleModal}>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										187
									
								
								src/components/WifiScanModal/IE_OPTIONS.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								src/components/WifiScanModal/IE_OPTIONS.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,187 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "SSID": 0,
 | 
				
			||||||
 | 
					  "SUPP_RATES": 1,
 | 
				
			||||||
 | 
					  "FH_PARAMS": 2,
 | 
				
			||||||
 | 
					  "DS_PARAMS": 3,
 | 
				
			||||||
 | 
					  "CF_PARAMS": 4,
 | 
				
			||||||
 | 
					  "TIM": 5,
 | 
				
			||||||
 | 
					  "IBSS_PARAMS": 6,
 | 
				
			||||||
 | 
					  "COUNTRY": 7,
 | 
				
			||||||
 | 
					  "REQUEST": 10,
 | 
				
			||||||
 | 
					  "QBSS_LOAD": 11,
 | 
				
			||||||
 | 
					  "EDCA_PARAM_SET": 12,
 | 
				
			||||||
 | 
					  "TSPEC": 13,
 | 
				
			||||||
 | 
					  "TCLAS": 14,
 | 
				
			||||||
 | 
					  "SCHEDULE": 15,
 | 
				
			||||||
 | 
					  "CHALLENGE": 16,
 | 
				
			||||||
 | 
					  "PWR_CONSTRAINT": 32,
 | 
				
			||||||
 | 
					  "PWR_CAPABILITY": 33,
 | 
				
			||||||
 | 
					  "TPC_REQUEST": 34,
 | 
				
			||||||
 | 
					  "TPC_REPORT": 35,
 | 
				
			||||||
 | 
					  "SUPPORTED_CHANNELS": 36,
 | 
				
			||||||
 | 
					  "CHANNEL_SWITCH": 37,
 | 
				
			||||||
 | 
					  "MEASURE_REQUEST": 38,
 | 
				
			||||||
 | 
					  "MEASURE_REPORT": 39,
 | 
				
			||||||
 | 
					  "QUIET": 40,
 | 
				
			||||||
 | 
					  "IBSS_DFS": 41,
 | 
				
			||||||
 | 
					  "ERP_INFO": 42,
 | 
				
			||||||
 | 
					  "TS_DELAY": 43,
 | 
				
			||||||
 | 
					  "TCLAS_PROCESSING": 44,
 | 
				
			||||||
 | 
					  "HT_CAPABILITY": 45,
 | 
				
			||||||
 | 
					  "QOS_CAPA": 46,
 | 
				
			||||||
 | 
					  "RSN": 48,
 | 
				
			||||||
 | 
					  "802_15_COEX": 49,
 | 
				
			||||||
 | 
					  "EXT_SUPP_RATES": 50,
 | 
				
			||||||
 | 
					  "AP_CHAN_REPORT": 51,
 | 
				
			||||||
 | 
					  "NEIGHBOR_REPORT": 52,
 | 
				
			||||||
 | 
					  "RCPI": 53,
 | 
				
			||||||
 | 
					  "MOBILITY_DOMAIN": 54,
 | 
				
			||||||
 | 
					  "FAST_BSS_TRANSITION": 55,
 | 
				
			||||||
 | 
					  "TIMEOUT_INTERVAL": 56,
 | 
				
			||||||
 | 
					  "RIC_DATA": 57,
 | 
				
			||||||
 | 
					  "DSE_REGISTERED_LOCATION": 58,
 | 
				
			||||||
 | 
					  "SUPPORTED_REGULATORY_CLASSES": 59,
 | 
				
			||||||
 | 
					  "EXT_CHANSWITCH_ANN": 60,
 | 
				
			||||||
 | 
					  "HT_OPERATION": 61,
 | 
				
			||||||
 | 
					  "SECONDARY_CHANNEL_OFFSET": 62,
 | 
				
			||||||
 | 
					  "BSS_AVG_ACCESS_DELAY": 63,
 | 
				
			||||||
 | 
					  "ANTENNA_INFO": 64,
 | 
				
			||||||
 | 
					  "RSNI": 65,
 | 
				
			||||||
 | 
					  "MEASUREMENT_PILOT_TX_INFO": 66,
 | 
				
			||||||
 | 
					  "BSS_AVAILABLE_CAPACITY": 67,
 | 
				
			||||||
 | 
					  "BSS_AC_ACCESS_DELAY": 68,
 | 
				
			||||||
 | 
					  "TIME_ADVERTISEMENT": 69,
 | 
				
			||||||
 | 
					  "RRM_ENABLED_CAPABILITIES": 70,
 | 
				
			||||||
 | 
					  "MULTIPLE_BSSID": 71,
 | 
				
			||||||
 | 
					  "BSS_COEX_2040": 72,
 | 
				
			||||||
 | 
					  "BSS_INTOLERANT_CHL_REPORT": 73,
 | 
				
			||||||
 | 
					  "OVERLAP_BSS_SCAN_PARAM": 74,
 | 
				
			||||||
 | 
					  "RIC_DESCRIPTOR": 75,
 | 
				
			||||||
 | 
					  "MMIE": 76,
 | 
				
			||||||
 | 
					  "ASSOC_COMEBACK_TIME": 77,
 | 
				
			||||||
 | 
					  "EVENT_REQUEST": 78,
 | 
				
			||||||
 | 
					  "EVENT_REPORT": 79,
 | 
				
			||||||
 | 
					  "DIAGNOSTIC_REQUEST": 80,
 | 
				
			||||||
 | 
					  "DIAGNOSTIC_REPORT": 81,
 | 
				
			||||||
 | 
					  "LOCATION_PARAMS": 82,
 | 
				
			||||||
 | 
					  "NON_TX_BSSID_CAP": 83,
 | 
				
			||||||
 | 
					  "SSID_LIST": 84,
 | 
				
			||||||
 | 
					  "MULTI_BSSID_IDX": 85,
 | 
				
			||||||
 | 
					  "FMS_DESCRIPTOR": 86,
 | 
				
			||||||
 | 
					  "FMS_REQUEST": 87,
 | 
				
			||||||
 | 
					  "FMS_RESPONSE": 88,
 | 
				
			||||||
 | 
					  "QOS_TRAFFIC_CAPA": 89,
 | 
				
			||||||
 | 
					  "BSS_MAX_IDLE_PERIOD": 90,
 | 
				
			||||||
 | 
					  "TSF_REQUEST": 91,
 | 
				
			||||||
 | 
					  "TSF_RESPOSNE": 92,
 | 
				
			||||||
 | 
					  "WNM_SLEEP_MODE": 93,
 | 
				
			||||||
 | 
					  "TIM_BCAST_REQ": 94,
 | 
				
			||||||
 | 
					  "TIM_BCAST_RESP": 95,
 | 
				
			||||||
 | 
					  "COLL_IF_REPORT": 96,
 | 
				
			||||||
 | 
					  "CHANNEL_USAGE": 97,
 | 
				
			||||||
 | 
					  "TIME_ZONE": 98,
 | 
				
			||||||
 | 
					  "DMS_REQUEST": 99,
 | 
				
			||||||
 | 
					  "DMS_RESPONSE": 100,
 | 
				
			||||||
 | 
					  "LINK_ID": 101,
 | 
				
			||||||
 | 
					  "WAKEUP_SCHEDUL": 102,
 | 
				
			||||||
 | 
					  "CHAN_SWITCH_TIMING": 104,
 | 
				
			||||||
 | 
					  "PTI_CONTROL": 105,
 | 
				
			||||||
 | 
					  "PU_BUFFER_STATUS": 106,
 | 
				
			||||||
 | 
					  "INTERWORKING": 107,
 | 
				
			||||||
 | 
					  "ADVERTISEMENT_PROTOCOL": 108,
 | 
				
			||||||
 | 
					  "EXPEDITED_BW_REQ": 109,
 | 
				
			||||||
 | 
					  "QOS_MAP_SET": 110,
 | 
				
			||||||
 | 
					  "ROAMING_CONSORTIUM": 111,
 | 
				
			||||||
 | 
					  "EMERGENCY_ALERT": 112,
 | 
				
			||||||
 | 
					  "MESH_CONFIG": 113,
 | 
				
			||||||
 | 
					  "MESH_ID": 114,
 | 
				
			||||||
 | 
					  "LINK_METRIC_REPORT": 115,
 | 
				
			||||||
 | 
					  "CONGESTION_NOTIFICATION": 116,
 | 
				
			||||||
 | 
					  "PEER_MGMT": 117,
 | 
				
			||||||
 | 
					  "CHAN_SWITCH_PARAM": 118,
 | 
				
			||||||
 | 
					  "MESH_AWAKE_WINDOW": 119,
 | 
				
			||||||
 | 
					  "BEACON_TIMING": 120,
 | 
				
			||||||
 | 
					  "MCCAOP_SETUP_REQ": 121,
 | 
				
			||||||
 | 
					  "MCCAOP_SETUP_RESP": 122,
 | 
				
			||||||
 | 
					  "MCCAOP_ADVERT": 123,
 | 
				
			||||||
 | 
					  "MCCAOP_TEARDOWN": 124,
 | 
				
			||||||
 | 
					  "GANN": 125,
 | 
				
			||||||
 | 
					  "RANN": 126,
 | 
				
			||||||
 | 
					  "EXT_CAPABILITY": 127,
 | 
				
			||||||
 | 
					  "PREQ": 130,
 | 
				
			||||||
 | 
					  "PREP": 131,
 | 
				
			||||||
 | 
					  "PERR": 132,
 | 
				
			||||||
 | 
					  "PXU": 137,
 | 
				
			||||||
 | 
					  "PXUC": 138,
 | 
				
			||||||
 | 
					  "AUTH_MESH_PEER_EXCH": 139,
 | 
				
			||||||
 | 
					  "MIC": 140,
 | 
				
			||||||
 | 
					  "DESTINATION_URI": 141,
 | 
				
			||||||
 | 
					  "UAPSD_COEX": 142,
 | 
				
			||||||
 | 
					  "WAKEUP_SCHEDULE": 143,
 | 
				
			||||||
 | 
					  "EXT_SCHEDULE": 144,
 | 
				
			||||||
 | 
					  "STA_AVAILABILITY": 145,
 | 
				
			||||||
 | 
					  "DMG_TSPEC": 146,
 | 
				
			||||||
 | 
					  "DMG_AT": 147,
 | 
				
			||||||
 | 
					  "DMG_CAP": 148,
 | 
				
			||||||
 | 
					  "CISCO_VENDOR_SPECIFIC": 150,
 | 
				
			||||||
 | 
					  "DMG_OPERATION": 151,
 | 
				
			||||||
 | 
					  "DMG_BSS_PARAM_CHANGE": 152,
 | 
				
			||||||
 | 
					  "DMG_BEAM_REFINEMENT": 153,
 | 
				
			||||||
 | 
					  "CHANNEL_MEASURE_FEEDBACK": 154,
 | 
				
			||||||
 | 
					  "AWAKE_WINDOW": 157,
 | 
				
			||||||
 | 
					  "MULTI_BAND": 158,
 | 
				
			||||||
 | 
					  "ADDBA_EXT": 159,
 | 
				
			||||||
 | 
					  "NEXT_PCP_LIST": 160,
 | 
				
			||||||
 | 
					  "PCP_HANDOVER": 161,
 | 
				
			||||||
 | 
					  "DMG_LINK_MARGIN": 162,
 | 
				
			||||||
 | 
					  "SWITCHING_STREAM": 163,
 | 
				
			||||||
 | 
					  "SESSION_TRANSITION": 164,
 | 
				
			||||||
 | 
					  "DYN_TONE_PAIRING_REPORT": 165,
 | 
				
			||||||
 | 
					  "CLUSTER_REPORT": 166,
 | 
				
			||||||
 | 
					  "RELAY_CAP": 167,
 | 
				
			||||||
 | 
					  "RELAY_XFER_PARAM_SET": 168,
 | 
				
			||||||
 | 
					  "BEAM_LINK_MAINT": 169,
 | 
				
			||||||
 | 
					  "MULTIPLE_MAC_ADDR": 170,
 | 
				
			||||||
 | 
					  "U_PID": 171,
 | 
				
			||||||
 | 
					  "DMG_LINK_ADAPT_ACK": 172,
 | 
				
			||||||
 | 
					  "MCCAOP_ADV_OVERVIEW": 174,
 | 
				
			||||||
 | 
					  "QUIET_PERIOD_REQ": 175,
 | 
				
			||||||
 | 
					  "QUIET_PERIOD_RESP": 177,
 | 
				
			||||||
 | 
					  "EPAC_POLICY": 182,
 | 
				
			||||||
 | 
					  "CLISTER_TIME_OFF": 183,
 | 
				
			||||||
 | 
					  "INTER_AC_PRIO": 184,
 | 
				
			||||||
 | 
					  "SCS_DESCRIPTOR": 185,
 | 
				
			||||||
 | 
					  "QLOAD_REPORT": 186,
 | 
				
			||||||
 | 
					  "HCCA_TXOP_UPDATE_COUNT": 187,
 | 
				
			||||||
 | 
					  "HL_STREAM_ID": 188,
 | 
				
			||||||
 | 
					  "GCR_GROUP_ADDR": 189,
 | 
				
			||||||
 | 
					  "ANTENNA_SECTOR_ID_PATTERN": 190,
 | 
				
			||||||
 | 
					  "VHT_CAPABILITY": 191,
 | 
				
			||||||
 | 
					  "VHT_OPERATION": 192,
 | 
				
			||||||
 | 
					  "EXTENDED_BSS_LOAD": 193,
 | 
				
			||||||
 | 
					  "WIDE_BW_CHANNEL_SWITCH": 194,
 | 
				
			||||||
 | 
					  "TX_POWER_ENVELOPE": 195,
 | 
				
			||||||
 | 
					  "CHANNEL_SWITCH_WRAPPER": 196,
 | 
				
			||||||
 | 
					  "AID": 197,
 | 
				
			||||||
 | 
					  "QUIET_CHANNEL": 198,
 | 
				
			||||||
 | 
					  "OPMODE_NOTIF": 199,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "REDUCED_NEIGHBOR_REPORT": 201,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "AID_REQUEST": 210,
 | 
				
			||||||
 | 
					  "AID_RESPONSE": 211,
 | 
				
			||||||
 | 
					  "S1G_BCN_COMPAT": 213,
 | 
				
			||||||
 | 
					  "S1G_SHORT_BCN_INTERVAL": 214,
 | 
				
			||||||
 | 
					  "S1G_TWT": 216,
 | 
				
			||||||
 | 
					  "S1G_CAPABILITIES": 217,
 | 
				
			||||||
 | 
					  "VENDOR_SPECIFIC": 221,
 | 
				
			||||||
 | 
					  "QOS_PARAMETER": 222,
 | 
				
			||||||
 | 
					  "S1G_OPERATION": 232,
 | 
				
			||||||
 | 
					  "CAG_NUMBER": 237,
 | 
				
			||||||
 | 
					  "AP_CSN": 239,
 | 
				
			||||||
 | 
					  "FILS_INDICATION": 240,
 | 
				
			||||||
 | 
					  "DILS": 241,
 | 
				
			||||||
 | 
					  "FRAGMENT": 242,
 | 
				
			||||||
 | 
					  "RSNX": 244,
 | 
				
			||||||
 | 
					  "EXTENSION": 255
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -6,11 +6,11 @@ import {
 | 
				
			|||||||
  CModalTitle,
 | 
					  CModalTitle,
 | 
				
			||||||
  CModalBody,
 | 
					  CModalBody,
 | 
				
			||||||
  CRow,
 | 
					  CRow,
 | 
				
			||||||
  CForm,
 | 
					 | 
				
			||||||
  CSwitch,
 | 
					  CSwitch,
 | 
				
			||||||
  CCol,
 | 
					  CCol,
 | 
				
			||||||
  CSpinner,
 | 
					  CSpinner,
 | 
				
			||||||
  CPopover,
 | 
					  CPopover,
 | 
				
			||||||
 | 
					  CSelect,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
import { cilCloudDownload, cilGauge, cilX } from '@coreui/icons';
 | 
					import { cilCloudDownload, cilGauge, cilX } from '@coreui/icons';
 | 
				
			||||||
@@ -20,20 +20,27 @@ import PropTypes from 'prop-types';
 | 
				
			|||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
import eventBus from 'utils/eventBus';
 | 
					import eventBus from 'utils/eventBus';
 | 
				
			||||||
import { prettyDateForFile } from 'utils/helper';
 | 
					import { prettyDateForFile } from 'utils/helper';
 | 
				
			||||||
import { useAuth, useDevice } from 'ucentral-libs';
 | 
					import { useAuth, useDevice, useToast } from 'ucentral-libs';
 | 
				
			||||||
import WifiChannelTable from 'components/WifiScanResultModal/WifiChannelTable';
 | 
					import WifiChannelTable from 'components/WifiScanResultModal/WifiChannelTable';
 | 
				
			||||||
import 'react-widgets/styles.css';
 | 
					import 'react-widgets/styles.css';
 | 
				
			||||||
import { CSVLink } from 'react-csv';
 | 
					import { CSVLink } from 'react-csv';
 | 
				
			||||||
 | 
					import IeDisplay from 'components/WifiScanResultModal/IeDisplay';
 | 
				
			||||||
 | 
					import IE_OPTIONS from './IE_OPTIONS.json';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const allIes = Object.entries(IE_OPTIONS).map(([, value]) => value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const WifiScanModal = ({ show, toggleModal }) => {
 | 
					const WifiScanModal = ({ show, toggleModal }) => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
  const { deviceSerialNumber } = useDevice();
 | 
					  const { deviceSerialNumber } = useDevice();
 | 
				
			||||||
 | 
					  const { addToast } = useToast();
 | 
				
			||||||
  const [hadSuccess, setHadSuccess] = useState(false);
 | 
					  const [hadSuccess, setHadSuccess] = useState(false);
 | 
				
			||||||
 | 
					  const [selectedIes, setSelectedIes] = useState(undefined);
 | 
				
			||||||
  const [hadFailure, setHadFailure] = useState(false);
 | 
					  const [hadFailure, setHadFailure] = useState(false);
 | 
				
			||||||
  const [errorCode, setErrorCode] = useState(0);
 | 
					  const [errorCode, setErrorCode] = useState(0);
 | 
				
			||||||
  const [waiting, setWaiting] = useState(false);
 | 
					  const [waiting, setWaiting] = useState(false);
 | 
				
			||||||
  const [dfs, setDfs] = useState(true);
 | 
					  const [dfs, setDfs] = useState(true);
 | 
				
			||||||
 | 
					  const [bandwidth, setBandwidth] = useState('');
 | 
				
			||||||
  const [activeScan, setActiveScan] = useState(false);
 | 
					  const [activeScan, setActiveScan] = useState(false);
 | 
				
			||||||
  const [hideOptions, setHideOptions] = useState(false);
 | 
					  const [hideOptions, setHideOptions] = useState(false);
 | 
				
			||||||
  const [channelList, setChannelList] = useState([]);
 | 
					  const [channelList, setChannelList] = useState([]);
 | 
				
			||||||
@@ -53,10 +60,12 @@ const WifiScanModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
    setWaiting(false);
 | 
					    setWaiting(false);
 | 
				
			||||||
    setChannelList([]);
 | 
					    setChannelList([]);
 | 
				
			||||||
    setCsvData(null);
 | 
					    setCsvData(null);
 | 
				
			||||||
 | 
					    setBandwidth('');
 | 
				
			||||||
    setDfs(true);
 | 
					    setDfs(true);
 | 
				
			||||||
    setActiveScan(false);
 | 
					    setActiveScan(false);
 | 
				
			||||||
    setHideOptions(false);
 | 
					    setHideOptions(false);
 | 
				
			||||||
    setErrorCode(0);
 | 
					    setErrorCode(0);
 | 
				
			||||||
 | 
					    setSelectedIes(undefined);
 | 
				
			||||||
  }, [show]);
 | 
					  }, [show]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const parseThroughList = (scanList) => {
 | 
					  const parseThroughList = (scanList) => {
 | 
				
			||||||
@@ -84,6 +93,7 @@ const WifiScanModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
            deviceToAdd.SSID = device.meshid && device.meshid.length > 0 ? device.meshid : 'N/A';
 | 
					            deviceToAdd.SSID = device.meshid && device.meshid.length > 0 ? device.meshid : 'N/A';
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          deviceToAdd.Signal = (dbmNumber - device.signal) * -1;
 | 
					          deviceToAdd.Signal = (dbmNumber - device.signal) * -1;
 | 
				
			||||||
 | 
					          deviceToAdd.ies = device.ies;
 | 
				
			||||||
          channel.devices.push(deviceToAdd);
 | 
					          channel.devices.push(deviceToAdd);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
@@ -120,7 +130,7 @@ const WifiScanModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
          deviceToAdd.Signal = (dbmNumber - device.signal) * -1;
 | 
					          deviceToAdd.Signal = (dbmNumber - device.signal) * -1;
 | 
				
			||||||
          channel.devices.push(deviceToAdd);
 | 
					          channel.devices.push(deviceToAdd);
 | 
				
			||||||
          listCsv.push({ ...deviceToAdd, ...device });
 | 
					          listCsv.push({ ...device, ...deviceToAdd, ies: JSON.stringify(device.ies, null, 4) });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -135,7 +145,9 @@ const WifiScanModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
    const parameters = {
 | 
					    const parameters = {
 | 
				
			||||||
      serialNumber: deviceSerialNumber,
 | 
					      serialNumber: deviceSerialNumber,
 | 
				
			||||||
      override_dfs: dfs,
 | 
					      override_dfs: dfs,
 | 
				
			||||||
 | 
					      bandwidth: bandwidth !== '' ? bandwidth : undefined,
 | 
				
			||||||
      activeScan,
 | 
					      activeScan,
 | 
				
			||||||
 | 
					      ies: allIes,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const headers = {
 | 
					    const headers = {
 | 
				
			||||||
      Accept: 'application/json',
 | 
					      Accept: 'application/json',
 | 
				
			||||||
@@ -161,7 +173,18 @@ const WifiScanModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
          setHadFailure(true);
 | 
					          setHadFailure(true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        if (e.response?.data?.ErrorDescription !== undefined) {
 | 
				
			||||||
 | 
					          const split = e.response?.data?.ErrorDescription.split(':');
 | 
				
			||||||
 | 
					          if (split !== undefined && split.length >= 2) {
 | 
				
			||||||
 | 
					            addToast({
 | 
				
			||||||
 | 
					              title: t('common.error'),
 | 
				
			||||||
 | 
					              body: split[1],
 | 
				
			||||||
 | 
					              color: 'danger',
 | 
				
			||||||
 | 
					              autohide: true,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        setHadFailure(true);
 | 
					        setHadFailure(true);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .finally(() => {
 | 
					      .finally(() => {
 | 
				
			||||||
@@ -219,15 +242,13 @@ const WifiScanModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
              <p className="pl-2">{t('wifi_analysis.override_dfs')}:</p>
 | 
					              <p className="pl-2">{t('wifi_analysis.override_dfs')}:</p>
 | 
				
			||||||
            </CCol>
 | 
					            </CCol>
 | 
				
			||||||
            <CCol>
 | 
					            <CCol>
 | 
				
			||||||
              <CForm className="pl-4">
 | 
					              <CSwitch
 | 
				
			||||||
                <CSwitch
 | 
					                color="primary"
 | 
				
			||||||
                  color="primary"
 | 
					                defaultChecked={dfs}
 | 
				
			||||||
                  defaultChecked={dfs}
 | 
					                onClick={toggleDfs}
 | 
				
			||||||
                  onClick={toggleDfs}
 | 
					                labelOn={t('common.on')}
 | 
				
			||||||
                  labelOn={t('common.on')}
 | 
					                labelOff={t('common.off')}
 | 
				
			||||||
                  labelOff={t('common.off')}
 | 
					              />
 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
              </CForm>
 | 
					 | 
				
			||||||
            </CCol>
 | 
					            </CCol>
 | 
				
			||||||
          </CRow>
 | 
					          </CRow>
 | 
				
			||||||
          <CRow className="mt-3">
 | 
					          <CRow className="mt-3">
 | 
				
			||||||
@@ -235,15 +256,31 @@ const WifiScanModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
              <p className="pl-2">{t('scan.active')}:</p>
 | 
					              <p className="pl-2">{t('scan.active')}:</p>
 | 
				
			||||||
            </CCol>
 | 
					            </CCol>
 | 
				
			||||||
            <CCol>
 | 
					            <CCol>
 | 
				
			||||||
              <CForm className="pl-4">
 | 
					              <CSwitch
 | 
				
			||||||
                <CSwitch
 | 
					                color="primary"
 | 
				
			||||||
                  color="primary"
 | 
					                defaultChecked={activeScan}
 | 
				
			||||||
                  defaultChecked={activeScan}
 | 
					                onClick={toggleActiveScan}
 | 
				
			||||||
                  onClick={toggleActiveScan}
 | 
					                labelOn={t('common.on')}
 | 
				
			||||||
                  labelOn={t('common.on')}
 | 
					                labelOff={t('common.off')}
 | 
				
			||||||
                  labelOff={t('common.off')}
 | 
					              />
 | 
				
			||||||
                />
 | 
					            </CCol>
 | 
				
			||||||
              </CForm>
 | 
					          </CRow>
 | 
				
			||||||
 | 
					          <CRow className="mt-3">
 | 
				
			||||||
 | 
					            <CCol md="3">
 | 
				
			||||||
 | 
					              <p className="pl-2">Bandwidth:</p>
 | 
				
			||||||
 | 
					            </CCol>
 | 
				
			||||||
 | 
					            <CCol>
 | 
				
			||||||
 | 
					              <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>
 | 
				
			||||||
            </CCol>
 | 
					            </CCol>
 | 
				
			||||||
          </CRow>
 | 
					          </CRow>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@@ -272,7 +309,10 @@ const WifiScanModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
            </CRow>
 | 
					            </CRow>
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
          <WifiChannelTable channels={channelList} />
 | 
					          {selectedIes || channelList === null ? null : (
 | 
				
			||||||
 | 
					            <WifiChannelTable channels={channelList} setIes={setSelectedIes} />
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					          {selectedIes && <IeDisplay ies={selectedIes} setIes={setSelectedIes} />}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </CModalBody>
 | 
					      </CModalBody>
 | 
				
			||||||
    </CModal>
 | 
					    </CModal>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										90
									
								
								src/components/WifiScanResultModal/IeDisplay.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/components/WifiScanResultModal/IeDisplay.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					import React, { useMemo } from 'react';
 | 
				
			||||||
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					import { CButton, CRow, CCol } from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilArrowLeft } from '@coreui/icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const IeDisplay = ({ ies, setIes }) => {
 | 
				
			||||||
 | 
					  const handleClick = () => {
 | 
				
			||||||
 | 
					    setIes(undefined);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const display = useMemo(
 | 
				
			||||||
 | 
					    () =>
 | 
				
			||||||
 | 
					      ies.map((ie) => {
 | 
				
			||||||
 | 
					        if (ie.byteArr) {
 | 
				
			||||||
 | 
					          return (
 | 
				
			||||||
 | 
					            <CCol sm="6">
 | 
				
			||||||
 | 
					              <h5
 | 
				
			||||||
 | 
					                style={{
 | 
				
			||||||
 | 
					                  textDecoration: 'underline',
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                {ie.name}:{' '}
 | 
				
			||||||
 | 
					              </h5>
 | 
				
			||||||
 | 
					              <pre>
 | 
				
			||||||
 | 
					                {ie.byteArr.map((arr, i) => {
 | 
				
			||||||
 | 
					                  const offset = (i * 8).toString(16);
 | 
				
			||||||
 | 
					                  return (
 | 
				
			||||||
 | 
					                    <pre
 | 
				
			||||||
 | 
					                      className="mb-0"
 | 
				
			||||||
 | 
					                      style={{
 | 
				
			||||||
 | 
					                        overflowY: 'auto',
 | 
				
			||||||
 | 
					                        maxHeight: '200px',
 | 
				
			||||||
 | 
					                      }}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      {offset.length === 1 ? `0${offset}` : offset}: {arr.join('  ')}
 | 
				
			||||||
 | 
					                    </pre>
 | 
				
			||||||
 | 
					                  );
 | 
				
			||||||
 | 
					                })}
 | 
				
			||||||
 | 
					              </pre>
 | 
				
			||||||
 | 
					            </CCol>
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          <CCol sm="6">
 | 
				
			||||||
 | 
					            <h5
 | 
				
			||||||
 | 
					              style={{
 | 
				
			||||||
 | 
					                textDecoration: 'underline',
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              {ie.name}:{' '}
 | 
				
			||||||
 | 
					            </h5>
 | 
				
			||||||
 | 
					            <pre
 | 
				
			||||||
 | 
					              style={{
 | 
				
			||||||
 | 
					                overflowY: 'auto',
 | 
				
			||||||
 | 
					                maxHeight: '200px',
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              {JSON.stringify(ie.data, null, 4)}
 | 
				
			||||||
 | 
					            </pre>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    [ies],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <CRow>
 | 
				
			||||||
 | 
					        <CCol>
 | 
				
			||||||
 | 
					          <h4>Information Elements</h4>
 | 
				
			||||||
 | 
					        </CCol>
 | 
				
			||||||
 | 
					        <CCol className="text-right">
 | 
				
			||||||
 | 
					          <CButton color="primary" variant="outline" className="ml-2" onClick={handleClick}>
 | 
				
			||||||
 | 
					            Go Back
 | 
				
			||||||
 | 
					            <CIcon className="ml-2" content={cilArrowLeft} />
 | 
				
			||||||
 | 
					          </CButton>
 | 
				
			||||||
 | 
					        </CCol>
 | 
				
			||||||
 | 
					      </CRow>
 | 
				
			||||||
 | 
					      <CRow>{display}</CRow>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					IeDisplay.propTypes = {
 | 
				
			||||||
 | 
					  ies: PropTypes.instanceOf(Array).isRequired,
 | 
				
			||||||
 | 
					  setIes: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default IeDisplay;
 | 
				
			||||||
@@ -1,8 +1,18 @@
 | 
				
			|||||||
import { CCard, CCardTitle, CCardBody, CDataTable, CCardHeader } from '@coreui/react';
 | 
					import {
 | 
				
			||||||
 | 
					  CCard,
 | 
				
			||||||
 | 
					  CCardTitle,
 | 
				
			||||||
 | 
					  CCardBody,
 | 
				
			||||||
 | 
					  CDataTable,
 | 
				
			||||||
 | 
					  CButton,
 | 
				
			||||||
 | 
					  CPopover,
 | 
				
			||||||
 | 
					  CCardHeader,
 | 
				
			||||||
 | 
					} from '@coreui/react';
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import 'react-widgets/styles.css';
 | 
					import 'react-widgets/styles.css';
 | 
				
			||||||
 | 
					import { Buffer } from 'buffer';
 | 
				
			||||||
 | 
					import IE_OPTIONS from '../WifiScanModal/IE_OPTIONS.json';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const parseDbm = (value) => {
 | 
					const parseDbm = (value) => {
 | 
				
			||||||
  if (!value) return '-';
 | 
					  if (!value) return '-';
 | 
				
			||||||
@@ -10,9 +20,66 @@ const parseDbm = (value) => {
 | 
				
			|||||||
  return 4294967295 + value;
 | 
					  return 4294967295 + value;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const WifiChannelCard = ({ channel }) => {
 | 
					const getIeName = (type) => {
 | 
				
			||||||
 | 
					  for (const [key, value] of Object.entries(IE_OPTIONS)) {
 | 
				
			||||||
 | 
					    if (value === type) return `${key} (${type})`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return type;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parseIe = ({ name, type, data, content }) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    if (data) {
 | 
				
			||||||
 | 
					      const ie = Buffer.from(data, 'base64');
 | 
				
			||||||
 | 
					      const arr = new Uint16Array(ie);
 | 
				
			||||||
 | 
					      const finalArr = [];
 | 
				
			||||||
 | 
					      for (let i = 0; i < arr.length; i += 8) {
 | 
				
			||||||
 | 
					        const slice = arr.slice(i, i + 8);
 | 
				
			||||||
 | 
					        finalArr.push(
 | 
				
			||||||
 | 
					          Object.keys(slice).map((k) => {
 | 
				
			||||||
 | 
					            const num = slice[k].toString(16);
 | 
				
			||||||
 | 
					            return num.length === 1 ? `0${num}` : num;
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const finalName = name ? `${name} (${type})` : getIeName(type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return { name: finalName, byteArr: finalArr };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (content) {
 | 
				
			||||||
 | 
					      return { name: name ? `${name} (${type})` : getIeName(type), data: content };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return { name: name ? `${name} (${type})` : getIeName(type), data: content ?? data };
 | 
				
			||||||
 | 
					  } catch {
 | 
				
			||||||
 | 
					    return { name: name ? `${name} (${type}, error while parsing)` : getIeName(type), data };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const WifiChannelCard = ({ channel, setIes }) => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const columns = [{ key: 'SSID', _style: { width: '70%' } }, { key: 'Signal' }];
 | 
					  const columns = [{ key: 'SSID', _style: { width: '70%' } }, { key: 'Signal' }, { key: 'IE' }];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const displayIe = (ies) => {
 | 
				
			||||||
 | 
					    const parsedIes = ies.map((ie) => parseIe(ie));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const str = ies.map(({ type, data }) => `${type}: ${data}\n`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <td className="ignore-overflow text-center align-middle">
 | 
				
			||||||
 | 
					        {str.length > 0 ? (
 | 
				
			||||||
 | 
					          <CPopover content="View IEs">
 | 
				
			||||||
 | 
					            <CButton color="primary" size="sm" onClick={() => setIes(parsedIes)}>
 | 
				
			||||||
 | 
					              {ies.length}
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        ) : (
 | 
				
			||||||
 | 
					          ies.length
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </td>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CCard>
 | 
					    <CCard>
 | 
				
			||||||
@@ -29,7 +96,8 @@ const WifiChannelCard = ({ channel }) => {
 | 
				
			|||||||
            fields={columns}
 | 
					            fields={columns}
 | 
				
			||||||
            className="text-white"
 | 
					            className="text-white"
 | 
				
			||||||
            scopedSlots={{
 | 
					            scopedSlots={{
 | 
				
			||||||
              Signal: (item) => <td>{parseDbm(item.Signal)}</td>,
 | 
					              Signal: (item) => <td className="align-middle">{parseDbm(item.Signal)}</td>,
 | 
				
			||||||
 | 
					              IE: (item) => displayIe(item.ies),
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@@ -40,6 +108,7 @@ const WifiChannelCard = ({ channel }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
WifiChannelCard.propTypes = {
 | 
					WifiChannelCard.propTypes = {
 | 
				
			||||||
  channel: PropTypes.instanceOf(Object).isRequired,
 | 
					  channel: PropTypes.instanceOf(Object).isRequired,
 | 
				
			||||||
 | 
					  setIes: PropTypes.func.isRequired,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default WifiChannelCard;
 | 
					export default WifiChannelCard;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ import { v4 as createUuid } from 'uuid';
 | 
				
			|||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import WifiChannelCard from './WifiChannelCard';
 | 
					import WifiChannelCard from './WifiChannelCard';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const WifiChannelTable = ({ channels }) => {
 | 
					const WifiChannelTable = ({ channels, setIes }) => {
 | 
				
			||||||
  const sortChannels = () => {
 | 
					  const sortChannels = () => {
 | 
				
			||||||
    channels.sort((a, b) => (a.channel > b.channel ? 1 : -1));
 | 
					    channels.sort((a, b) => (a.channel > b.channel ? 1 : -1));
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -17,13 +17,15 @@ const WifiChannelTable = ({ channels }) => {
 | 
				
			|||||||
    <CRow>
 | 
					    <CRow>
 | 
				
			||||||
      <CCol>
 | 
					      <CCol>
 | 
				
			||||||
        {channels.map((channel, index) => {
 | 
					        {channels.map((channel, index) => {
 | 
				
			||||||
          if (index % 2 === 0) return <WifiChannelCard key={createUuid()} channel={channel} />;
 | 
					          if (index % 2 === 0)
 | 
				
			||||||
 | 
					            return <WifiChannelCard key={createUuid()} channel={channel} setIes={setIes} />;
 | 
				
			||||||
          return <div key={createUuid()} />;
 | 
					          return <div key={createUuid()} />;
 | 
				
			||||||
        })}
 | 
					        })}
 | 
				
			||||||
      </CCol>
 | 
					      </CCol>
 | 
				
			||||||
      <CCol>
 | 
					      <CCol>
 | 
				
			||||||
        {channels.map((channel, index) => {
 | 
					        {channels.map((channel, index) => {
 | 
				
			||||||
          if (index % 2 === 1) return <WifiChannelCard key={createUuid()} channel={channel} />;
 | 
					          if (index % 2 === 1)
 | 
				
			||||||
 | 
					            return <WifiChannelCard key={createUuid()} channel={channel} setIes={setIes} />;
 | 
				
			||||||
          return <div key={createUuid()} />;
 | 
					          return <div key={createUuid()} />;
 | 
				
			||||||
        })}
 | 
					        })}
 | 
				
			||||||
      </CCol>
 | 
					      </CCol>
 | 
				
			||||||
@@ -33,6 +35,7 @@ const WifiChannelTable = ({ channels }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
WifiChannelTable.propTypes = {
 | 
					WifiChannelTable.propTypes = {
 | 
				
			||||||
  channels: PropTypes.instanceOf(Array),
 | 
					  channels: PropTypes.instanceOf(Array),
 | 
				
			||||||
 | 
					  setIes: PropTypes.func.isRequired,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WifiChannelTable.defaultProps = {
 | 
					WifiChannelTable.defaultProps = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
/* eslint-disable-rule prefer-destructuring */
 | 
					/* eslint-disable-rule prefer-destructuring */
 | 
				
			||||||
import React, { useCallback } from 'react';
 | 
					import React, { useEffect, useMemo, useState } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
 | 
					import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
@@ -9,12 +9,14 @@ import { prettyDate, prettyDateForFile } from 'utils/helper';
 | 
				
			|||||||
import { useDevice } from 'ucentral-libs';
 | 
					import { useDevice } from 'ucentral-libs';
 | 
				
			||||||
import { CSVLink } from 'react-csv';
 | 
					import { CSVLink } from 'react-csv';
 | 
				
			||||||
import WifiChannelTable from './WifiChannelTable';
 | 
					import WifiChannelTable from './WifiChannelTable';
 | 
				
			||||||
 | 
					import IeDisplay from './IeDisplay';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
 | 
					const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { deviceSerialNumber } = useDevice();
 | 
					  const { deviceSerialNumber } = useDevice();
 | 
				
			||||||
 | 
					  const [selectedIes, setSelectedIes] = useState(undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getData = useCallback(() => {
 | 
					  const getData = useMemo(() => {
 | 
				
			||||||
    if (scanResults === null || scanResults.length === 0) return [];
 | 
					    if (scanResults === null || scanResults.length === 0) return [];
 | 
				
			||||||
    const dbmNumber = 4294967295;
 | 
					    const dbmNumber = 4294967295;
 | 
				
			||||||
    const listOfChannels = [];
 | 
					    const listOfChannels = [];
 | 
				
			||||||
@@ -31,7 +33,6 @@ const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
 | 
				
			|||||||
        channel: channelNumber,
 | 
					        channel: channelNumber,
 | 
				
			||||||
        devices: [],
 | 
					        devices: [],
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					 | 
				
			||||||
      scanResults.forEach((device) => {
 | 
					      scanResults.forEach((device) => {
 | 
				
			||||||
        if (device.channel === channelNumber) {
 | 
					        if (device.channel === channelNumber) {
 | 
				
			||||||
          const deviceToAdd = {};
 | 
					          const deviceToAdd = {};
 | 
				
			||||||
@@ -41,14 +42,15 @@ const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
          deviceToAdd.Signal = (dbmNumber - device.signal) * -1;
 | 
					          deviceToAdd.Signal = (dbmNumber - device.signal) * -1;
 | 
				
			||||||
          channel.devices.push(deviceToAdd);
 | 
					          channel.devices.push(deviceToAdd);
 | 
				
			||||||
          listCsv.push({ ...deviceToAdd, ...device });
 | 
					          listCsv.push({ ...deviceToAdd, ...device, ies: JSON.stringify(device.ies, null, 4) });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    return listCsv;
 | 
					    return listCsv;
 | 
				
			||||||
  }, [scanResults]);
 | 
					  }, [scanResults]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const parseThroughList = useCallback(() => {
 | 
					  const parseThroughList = useMemo(() => {
 | 
				
			||||||
 | 
					    if (!scanResults) return null;
 | 
				
			||||||
    const dbmNumber = 4294967295;
 | 
					    const dbmNumber = 4294967295;
 | 
				
			||||||
    const listOfChannels = [];
 | 
					    const listOfChannels = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,6 +75,7 @@ const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
 | 
				
			|||||||
            deviceToAdd.SSID = device.meshid && device.meshid.length > 0 ? device.meshid : 'N/A';
 | 
					            deviceToAdd.SSID = device.meshid && device.meshid.length > 0 ? device.meshid : 'N/A';
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          deviceToAdd.Signal = (dbmNumber - device.signal) * -1;
 | 
					          deviceToAdd.Signal = (dbmNumber - device.signal) * -1;
 | 
				
			||||||
 | 
					          deviceToAdd.ies = device.ies;
 | 
				
			||||||
          channel.devices.push(deviceToAdd);
 | 
					          channel.devices.push(deviceToAdd);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
@@ -82,6 +85,10 @@ const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
 | 
				
			|||||||
    return finalList;
 | 
					    return finalList;
 | 
				
			||||||
  }, [scanResults]);
 | 
					  }, [scanResults]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setSelectedIes(undefined);
 | 
				
			||||||
 | 
					  }, [show]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal size="lg" show={show} onClose={toggle}>
 | 
					    <CModal size="lg" show={show} onClose={toggle}>
 | 
				
			||||||
      <CModalHeader>
 | 
					      <CModalHeader>
 | 
				
			||||||
@@ -94,7 +101,7 @@ const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
 | 
				
			|||||||
              filename={`wifi_scan_${deviceSerialNumber}_${
 | 
					              filename={`wifi_scan_${deviceSerialNumber}_${
 | 
				
			||||||
                date !== '' ? prettyDateForFile(date) : ''
 | 
					                date !== '' ? prettyDateForFile(date) : ''
 | 
				
			||||||
              }.csv`}
 | 
					              }.csv`}
 | 
				
			||||||
              data={getData()}
 | 
					              data={getData}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              <CButton color="primary" variant="outline" className="ml-2">
 | 
					              <CButton color="primary" variant="outline" className="ml-2">
 | 
				
			||||||
                <CIcon content={cilCloudDownload} />
 | 
					                <CIcon content={cilCloudDownload} />
 | 
				
			||||||
@@ -109,7 +116,10 @@ const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      <CModalBody>
 | 
					      <CModalBody>
 | 
				
			||||||
        {scanResults === null ? null : <WifiChannelTable channels={parseThroughList()} />}
 | 
					        {selectedIes && <IeDisplay ies={selectedIes} setIes={setSelectedIes} />}
 | 
				
			||||||
 | 
					        {selectedIes || scanResults === null ? null : (
 | 
				
			||||||
 | 
					          <WifiChannelTable channels={parseThroughList} setIes={setSelectedIes} />
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
      </CModalBody>
 | 
					      </CModalBody>
 | 
				
			||||||
    </CModal>
 | 
					    </CModal>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								src/constants.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/constants.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					export const AUTH_EXPIRED_TOKEN_CODE = 9;
 | 
				
			||||||
 | 
					export const AUTH_INVALID_TOKEN_CODE = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const LOGOUT_ON_SEC_ERROR_CODES = [AUTH_EXPIRED_TOKEN_CODE, AUTH_INVALID_TOKEN_CODE];
 | 
				
			||||||
@@ -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;
 | 
				
			||||||
							
								
								
									
										99
									
								
								src/contexts/WebSocketProvider/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/contexts/WebSocketProvider/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					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, setNewConnectionData }) => {
 | 
				
			||||||
 | 
					  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 === 'device_connections_statistics') {
 | 
				
			||||||
 | 
					      setNewConnectionData(result.content);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    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 });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onStartWebSocket = () => {
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					      setTimeout(onStartWebSocket, 3000);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    ws.current.onerror = () => {
 | 
				
			||||||
 | 
					      setIsOpen(false);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // useEffect for created the WebSocket and 'storing' it in useRef
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (endpoints?.owgw !== undefined) {
 | 
				
			||||||
 | 
					      onStartWebSocket();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const wsCurrent = ws?.current;
 | 
				
			||||||
 | 
					    return () => wsCurrent?.close();
 | 
				
			||||||
 | 
					  }, [endpoints]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // 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,
 | 
				
			||||||
 | 
					  setNewConnectionData: PropTypes.func.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;
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/contexts/WebSocketProvider/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/contexts/WebSocketProvider/utils.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					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' };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (data.notification.type === 'device_connections_statistics') {
 | 
				
			||||||
 | 
					      return { content: data.notification.content, type: 'device_connections_statistics' };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } catch {
 | 
				
			||||||
 | 
					    return undefined;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return undefined;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/hooks/useToggle/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/hooks/useToggle/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import { useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const useToggle = (initialState) => {
 | 
				
			||||||
 | 
					  const [value, setValue] = useState(initialState);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return [
 | 
				
			||||||
 | 
					    value,
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
 | 
					      setValue(!value);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    (newValue) => {
 | 
				
			||||||
 | 
					      setValue(newValue);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useToggle;
 | 
				
			||||||
							
								
								
									
										120
									
								
								src/layout/Devices.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/layout/Devices.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
				
			|||||||
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
 | 
					import { useAuth } from 'ucentral-libs';
 | 
				
			||||||
 | 
					import { CPopover } from '@coreui/react';
 | 
				
			||||||
 | 
					import { extraCompactSecondsToDetailed, secondsToDetailed } from 'utils/helper';
 | 
				
			||||||
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const propTypes = {
 | 
				
			||||||
 | 
					  newData: PropTypes.instanceOf(Object),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const defaultProps = {
 | 
				
			||||||
 | 
					  newData: undefined,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SidebarDevices = ({ newData }) => {
 | 
				
			||||||
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
 | 
					  const [stats, setStats] = useState();
 | 
				
			||||||
 | 
					  const [lastUpdate, setLastUpdate] = useState();
 | 
				
			||||||
 | 
					  const [lastTime, setLastTime] = useState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const getInitialStats = async () => {
 | 
				
			||||||
 | 
					    const options = {
 | 
				
			||||||
 | 
					      headers: {
 | 
				
			||||||
 | 
					        Accept: 'application/json',
 | 
				
			||||||
 | 
					        Authorization: `Bearer ${currentToken}`,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    axiosInstance
 | 
				
			||||||
 | 
					      .get(`${endpoints.owgw}/api/v1/devices?connectionStatistics=true`, options)
 | 
				
			||||||
 | 
					      .then(({ data }) => {
 | 
				
			||||||
 | 
					        setStats(data);
 | 
				
			||||||
 | 
					        setLastUpdate(new Date());
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch(() => {});
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const getTime = () => {
 | 
				
			||||||
 | 
					    if (lastTime === undefined || lastUpdate === undefined) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const seconds = lastTime.getTime() - lastUpdate.getTime();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Math.max(0, Math.floor(seconds / 1000));
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (newData !== undefined && Object.keys(newData).length > 0) {
 | 
				
			||||||
 | 
					      setStats({ ...newData });
 | 
				
			||||||
 | 
					      setLastUpdate(new Date());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [newData]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    getInitialStats();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const interval = setInterval(() => {
 | 
				
			||||||
 | 
					      setLastTime(new Date());
 | 
				
			||||||
 | 
					    }, 1000);
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      clearInterval(interval);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!stats) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      style={{
 | 
				
			||||||
 | 
					        position: 'absolute',
 | 
				
			||||||
 | 
					        bottom: '0px',
 | 
				
			||||||
 | 
					        width: '100%',
 | 
				
			||||||
 | 
					        background: '#2f3d54 !important',
 | 
				
			||||||
 | 
					        backgroundColor: '#2f3d54 !important',
 | 
				
			||||||
 | 
					        borderTop: '3px solid #d8dbe0',
 | 
				
			||||||
 | 
					        color: 'white',
 | 
				
			||||||
 | 
					        textAlign: 'center',
 | 
				
			||||||
 | 
					        paddingTop: '15px',
 | 
				
			||||||
 | 
					        paddingBottom: '25px',
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <h3 style={{ marginBottom: '0px' }}>{stats?.connectedDevices ?? stats?.numberOfDevices}</h3>
 | 
				
			||||||
 | 
					      <h6>Connected Devices</h6>
 | 
				
			||||||
 | 
					      <CPopover
 | 
				
			||||||
 | 
					        content={secondsToDetailed(
 | 
				
			||||||
 | 
					          stats?.averageConnectionTime ?? stats?.averageConnectedTime,
 | 
				
			||||||
 | 
					          t('common.day'),
 | 
				
			||||||
 | 
					          t('common.days'),
 | 
				
			||||||
 | 
					          t('common.hour'),
 | 
				
			||||||
 | 
					          t('common.hours'),
 | 
				
			||||||
 | 
					          t('common.minute'),
 | 
				
			||||||
 | 
					          t('common.minutes'),
 | 
				
			||||||
 | 
					          t('common.second'),
 | 
				
			||||||
 | 
					          t('common.seconds'),
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <h3 style={{ marginBottom: '0px' }}>
 | 
				
			||||||
 | 
					          {extraCompactSecondsToDetailed(
 | 
				
			||||||
 | 
					            stats?.averageConnectionTime ?? stats?.averageConnectedTime,
 | 
				
			||||||
 | 
					            t('common.day'),
 | 
				
			||||||
 | 
					            t('common.days'),
 | 
				
			||||||
 | 
					            t('common.seconds'),
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </h3>
 | 
				
			||||||
 | 
					      </CPopover>
 | 
				
			||||||
 | 
					      <h6>Avg. Connection Time</h6>
 | 
				
			||||||
 | 
					      <h7 style={{ color: '#ebedef', fontStyle: 'italic' }}>{getTime()} seconds ago</h7>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SidebarDevices.propTypes = propTypes;
 | 
				
			||||||
 | 
					SidebarDevices.defaultProps = defaultProps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default React.memo(SidebarDevices);
 | 
				
			||||||
							
								
								
									
										49
									
								
								src/layout/Sidebar/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/layout/Sidebar/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import { CSidebar, CSidebarBrand, CSidebarNav } from '@coreui/react';
 | 
				
			||||||
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					import styles from './index.module.scss';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Sidebar = ({
 | 
				
			||||||
 | 
					  showSidebar,
 | 
				
			||||||
 | 
					  setShowSidebar,
 | 
				
			||||||
 | 
					  logo,
 | 
				
			||||||
 | 
					  options,
 | 
				
			||||||
 | 
					  redirectTo,
 | 
				
			||||||
 | 
					  logoHeight,
 | 
				
			||||||
 | 
					  logoWidth,
 | 
				
			||||||
 | 
					}) => (
 | 
				
			||||||
 | 
					  <CSidebar show={showSidebar} onShowChange={(val) => setShowSidebar(val)}>
 | 
				
			||||||
 | 
					    <CSidebarBrand className="d-md-down-none" to={redirectTo}>
 | 
				
			||||||
 | 
					      <img
 | 
				
			||||||
 | 
					        className={[styles.sidebarImgFull, 'c-sidebar-brand-full'].join(' ')}
 | 
				
			||||||
 | 
					        style={{ height: logoHeight ?? undefined, width: logoWidth ?? undefined }}
 | 
				
			||||||
 | 
					        src={logo}
 | 
				
			||||||
 | 
					        alt="OpenWifi"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      <img
 | 
				
			||||||
 | 
					        className={[styles.sidebarImgMinimized, 'c-sidebar-brand-minimized'].join(' ')}
 | 
				
			||||||
 | 
					        style={{ height: logoHeight ?? undefined, width: logoWidth ?? undefined }}
 | 
				
			||||||
 | 
					        src={logo}
 | 
				
			||||||
 | 
					        alt="OpenWifi"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </CSidebarBrand>
 | 
				
			||||||
 | 
					    <CSidebarNav>{options}</CSidebarNav>
 | 
				
			||||||
 | 
					  </CSidebar>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Sidebar.propTypes = {
 | 
				
			||||||
 | 
					  showSidebar: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  setShowSidebar: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					  logo: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  options: PropTypes.node.isRequired,
 | 
				
			||||||
 | 
					  redirectTo: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					  logoHeight: PropTypes.string,
 | 
				
			||||||
 | 
					  logoWidth: PropTypes.string,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Sidebar.defaultProps = {
 | 
				
			||||||
 | 
					  logoHeight: null,
 | 
				
			||||||
 | 
					  logoWidth: null,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default React.memo(Sidebar);
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/layout/Sidebar/index.module.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/layout/Sidebar/index.module.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					.sidebarImgFull {
 | 
				
			||||||
 | 
					  height: 75px;
 | 
				
			||||||
 | 
					  width: 175px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sidebarImgMinimized {
 | 
				
			||||||
 | 
					  height: 75px;
 | 
				
			||||||
 | 
					  width: 75px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,12 +4,20 @@ import routes from 'routes';
 | 
				
			|||||||
import { CSidebarNavItem } from '@coreui/react';
 | 
					import { CSidebarNavItem } from '@coreui/react';
 | 
				
			||||||
import { cilBarcode, cilRouter, cilSave, cilSettings, cilPeople } from '@coreui/icons';
 | 
					import { cilBarcode, cilRouter, cilSave, cilSettings, cilPeople } from '@coreui/icons';
 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
import { Header, Sidebar, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs';
 | 
					import { Header, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs';
 | 
				
			||||||
 | 
					import { WebSocketProvider } from 'contexts/WebSocketProvider';
 | 
				
			||||||
 | 
					import Sidebar from './Sidebar';
 | 
				
			||||||
 | 
					import SidebarDevices from './Devices';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TheLayout = () => {
 | 
					const TheLayout = () => {
 | 
				
			||||||
  const [showSidebar, setShowSidebar] = useState('responsive');
 | 
					  const [showSidebar, setShowSidebar] = useState('responsive');
 | 
				
			||||||
  const { endpoints, currentToken, user, avatar, logout } = useAuth();
 | 
					  const { endpoints, currentToken, user, avatar, logout } = useAuth();
 | 
				
			||||||
  const { t, i18n } = useTranslation();
 | 
					  const { t, i18n } = useTranslation();
 | 
				
			||||||
 | 
					  const [newConnectionData, setNewConnectionData] = useState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onConnectionDataChange = React.useCallback((newData) => {
 | 
				
			||||||
 | 
					    setNewConnectionData({ ...newData });
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="c-app c-default-layout">
 | 
					    <div className="c-app c-default-layout">
 | 
				
			||||||
@@ -49,6 +57,7 @@ const TheLayout = () => {
 | 
				
			|||||||
              to="/system"
 | 
					              to="/system"
 | 
				
			||||||
              icon={<CIcon content={cilSettings} size="xl" className="mr-3" />}
 | 
					              icon={<CIcon content={cilSettings} size="xl" className="mr-3" />}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
 | 
					            <SidebarDevices newData={newConnectionData} />
 | 
				
			||||||
          </>
 | 
					          </>
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        redirectTo="/devices"
 | 
					        redirectTo="/devices"
 | 
				
			||||||
@@ -70,7 +79,9 @@ const TheLayout = () => {
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
        <div className="c-body">
 | 
					        <div className="c-body">
 | 
				
			||||||
          <ToastProvider>
 | 
					          <ToastProvider>
 | 
				
			||||||
            <PageContainer t={t} routes={routes} redirectTo="/devices" />
 | 
					            <WebSocketProvider setNewConnectionData={onConnectionDataChange}>
 | 
				
			||||||
 | 
					              <PageContainer t={t} routes={routes} redirectTo="/devices" />
 | 
				
			||||||
 | 
					            </WebSocketProvider>
 | 
				
			||||||
          </ToastProvider>
 | 
					          </ToastProvider>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <Footer t={t} version={process.env.VERSION} />
 | 
					        <Footer t={t} version={process.env.VERSION} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,12 +15,17 @@ import {
 | 
				
			|||||||
import CIcon from '@coreui/icons-react';
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
import { cilSync } from '@coreui/icons';
 | 
					import { cilSync } from '@coreui/icons';
 | 
				
			||||||
import { prettyDate } from 'utils/helper';
 | 
					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';
 | 
					import styles from './index.module.scss';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats }) => {
 | 
					const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats }) => {
 | 
				
			||||||
  const [showPassword, setShowPassword] = useState(false);
 | 
					  const [showPassword, setShowPassword] = useState(false);
 | 
				
			||||||
 | 
					  const [subName, setSubName] = useState('');
 | 
				
			||||||
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const toggleShowPassword = () => {
 | 
					  const toggleShowPassword = () => {
 | 
				
			||||||
    setShowPassword(!showPassword);
 | 
					    setShowPassword(!showPassword);
 | 
				
			||||||
@@ -32,21 +37,51 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats })
 | 
				
			|||||||
    return showPassword ? password : '******';
 | 
					    return showPassword ? password : '******';
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const displayExtra = (key, value, extraData) => {
 | 
					  const getSubData = async (subId) => {
 | 
				
			||||||
    if (!extraData || !extraData[key]) return value;
 | 
					    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 (
 | 
					    return (
 | 
				
			||||||
      <CLink
 | 
					      <CLink
 | 
				
			||||||
        className="c-subheader-nav-link align-self-center"
 | 
					        className="c-subheader-nav-link align-self-center"
 | 
				
			||||||
        aria-current="page"
 | 
					        aria-current="page"
 | 
				
			||||||
        href={`${localStorage.getItem('owprov-ui')}/#/${key === 'entity' ? 'entity' : 'venue'}/${
 | 
					        href={`${localStorage.getItem('owprov-ui')}/#/subscriber/${deviceConfig.subscriber}`}
 | 
				
			||||||
          extraData[key].id
 | 
					 | 
				
			||||||
        }`}
 | 
					 | 
				
			||||||
        target="_blank"
 | 
					        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>
 | 
					      </CLink>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -99,13 +134,13 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats })
 | 
				
			|||||||
                />
 | 
					                />
 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
              <CCol className="border-left" lg="2" xl="1" xxl="1">
 | 
					              <CCol className="border-left" lg="2" xl="1" xxl="1">
 | 
				
			||||||
                <CLabel>{t('configuration.owner')}:</CLabel>
 | 
					                <CLabel>{t('inventory.subscriber')}:</CLabel>
 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
              <CCol lg="2" xl="3" xxl="3">
 | 
					              <CCol lg="2" xl="3" xxl="3">
 | 
				
			||||||
                {deviceConfig?.owner}
 | 
					                {getSubscriber()}
 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
              <CCol lg="2" xl="1" xxl="1">
 | 
					              <CCol lg="2" xl="1" xxl="1">
 | 
				
			||||||
                <CLabel>{t('common.mac')}:</CLabel>
 | 
					                <CLabel>MAC:</CLabel>
 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
              <CCol className="border-right" lg="2" xl="3" xxl="3">
 | 
					              <CCol className="border-right" lg="2" xl="3" xxl="3">
 | 
				
			||||||
                {deviceConfig?.macAddress}
 | 
					                {deviceConfig?.macAddress}
 | 
				
			||||||
@@ -117,25 +152,16 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats })
 | 
				
			|||||||
                {deviceConfig?.deviceType}
 | 
					                {deviceConfig?.deviceType}
 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
              <CCol className="border-left" lg="2" xl="1" xxl="1">
 | 
					              <CCol className="border-left" lg="2" xl="1" xxl="1">
 | 
				
			||||||
                <CLabel>
 | 
					                <CLabel>{t('entity.entity')}:</CLabel>
 | 
				
			||||||
                  {deviceConfig?.venue?.substring(0, 3) === 'ent'
 | 
					 | 
				
			||||||
                    ? t('entity.entity')
 | 
					 | 
				
			||||||
                    : t('inventory.venue')}
 | 
					 | 
				
			||||||
                  :
 | 
					 | 
				
			||||||
                </CLabel>
 | 
					 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
              <CCol lg="2" xl="3" xxl="3">
 | 
					              <CCol lg="2" xl="3" xxl="3">
 | 
				
			||||||
                {deviceConfig?.venue?.substring(0, 3) === 'ent'
 | 
					                {deviceConfig?.entity !== ''
 | 
				
			||||||
                  ? displayExtra(
 | 
					                  ? displayExtra(
 | 
				
			||||||
                      'entity',
 | 
					                      'entity',
 | 
				
			||||||
                      deviceConfig?.venue?.slice(4),
 | 
					                      deviceConfig?.venue?.slice(4),
 | 
				
			||||||
                      deviceConfig?.extendedInfo,
 | 
					                      deviceConfig?.extendedInfo,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                  : displayExtra(
 | 
					                  : ''}
 | 
				
			||||||
                      'venue',
 | 
					 | 
				
			||||||
                      deviceConfig?.venue?.slice(4),
 | 
					 | 
				
			||||||
                      deviceConfig?.extendedInfo,
 | 
					 | 
				
			||||||
                    )}
 | 
					 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
              <CCol lg="2" xl="1" xxl="1">
 | 
					              <CCol lg="2" xl="1" xxl="1">
 | 
				
			||||||
                <CLabel>{t('common.manufacturer')}:</CLabel>
 | 
					                <CLabel>{t('common.manufacturer')}:</CLabel>
 | 
				
			||||||
@@ -149,6 +175,37 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats })
 | 
				
			|||||||
              <CCol lg="2" xl="3" xxl="3">
 | 
					              <CCol lg="2" xl="3" xxl="3">
 | 
				
			||||||
                {prettyDate(deviceConfig?.createdTimestamp)}
 | 
					                {prettyDate(deviceConfig?.createdTimestamp)}
 | 
				
			||||||
              </CCol>
 | 
					              </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">
 | 
					              <CCol className="border-left" lg="2" xl="1" xxl="1">
 | 
				
			||||||
                <CLabel>{t('configuration.location')}:</CLabel>
 | 
					                <CLabel>{t('configuration.location')}:</CLabel>
 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import React, { useEffect, useState } from 'react';
 | 
					import React, { useEffect, useState } from 'react';
 | 
				
			||||||
import { useParams } from 'react-router-dom';
 | 
					import { useHistory, useParams } from 'react-router-dom';
 | 
				
			||||||
import { CRow, CCol, CCard, CCardBody, CNav, CNavLink, CTabPane, CTabContent } from '@coreui/react';
 | 
					import { CRow, CCol, CCard, CCardBody, CNav, CNavLink, CTabPane, CTabContent } from '@coreui/react';
 | 
				
			||||||
import DeviceHealth from 'components/DeviceHealth';
 | 
					import DeviceHealth from 'components/DeviceHealth';
 | 
				
			||||||
import CommandHistory from 'components/CommandHistory';
 | 
					import CommandHistory from 'components/CommandHistory';
 | 
				
			||||||
@@ -7,11 +7,12 @@ import DeviceLogs from 'components/DeviceLogs';
 | 
				
			|||||||
import DeviceStatisticsCard from 'components/InterfaceStatistics';
 | 
					import DeviceStatisticsCard from 'components/InterfaceStatistics';
 | 
				
			||||||
import DeviceActionCard from 'components/DeviceActionCard';
 | 
					import DeviceActionCard from 'components/DeviceActionCard';
 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
import { DeviceProvider, useAuth } from 'ucentral-libs';
 | 
					import { DeviceProvider, useAuth, useToast } from 'ucentral-libs';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import ConfigurationDisplay from 'components/ConfigurationDisplay';
 | 
					import ConfigurationDisplay from 'components/ConfigurationDisplay';
 | 
				
			||||||
import WifiAnalysis from 'components/WifiAnalysis';
 | 
					import WifiAnalysis from 'components/WifiAnalysis';
 | 
				
			||||||
import CapabilitiesDisplay from 'components/CapabilitiesDisplay';
 | 
					import CapabilitiesDisplay from 'components/CapabilitiesDisplay';
 | 
				
			||||||
 | 
					import { useGlobalWebSocket } from 'contexts/WebSocketProvider';
 | 
				
			||||||
import NotesTab from './NotesTab';
 | 
					import NotesTab from './NotesTab';
 | 
				
			||||||
import DeviceDetails from './Details';
 | 
					import DeviceDetails from './Details';
 | 
				
			||||||
import DeviceStatusCard from './DeviceStatusCard';
 | 
					import DeviceStatusCard from './DeviceStatusCard';
 | 
				
			||||||
@@ -22,10 +23,13 @@ const DevicePage = () => {
 | 
				
			|||||||
  const [index, setIndex] = useState(0);
 | 
					  const [index, setIndex] = useState(0);
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
  const [lastStats, setLastStats] = useState(null);
 | 
					  const [lastStats, setLastStats] = useState(null);
 | 
				
			||||||
 | 
					  const { addToast } = useToast();
 | 
				
			||||||
  const [status, setStatus] = useState(null);
 | 
					  const [status, setStatus] = useState(null);
 | 
				
			||||||
 | 
					  const history = useHistory();
 | 
				
			||||||
  const [deviceConfig, setDeviceConfig] = useState(null);
 | 
					  const [deviceConfig, setDeviceConfig] = useState(null);
 | 
				
			||||||
  const [error, setError] = useState(false);
 | 
					  const [error, setError] = useState(false);
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
 | 
					  const { addDeviceListener, removeDeviceListener } = useGlobalWebSocket();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const updateNav = (target) => {
 | 
					  const updateNav = (target) => {
 | 
				
			||||||
    sessionStorage.setItem('devicePageIndex', target);
 | 
					    sessionStorage.setItem('devicePageIndex', target);
 | 
				
			||||||
@@ -62,7 +66,16 @@ const DevicePage = () => {
 | 
				
			|||||||
      .then((response) => {
 | 
					      .then((response) => {
 | 
				
			||||||
        if (response) setDeviceConfig({ ...deviceInfo, extendedInfo: response.data.extendedInfo });
 | 
					        if (response) setDeviceConfig({ ...deviceInfo, extendedInfo: response.data.extendedInfo });
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        if (e.response?.status === 404 || e.response?.status === 400) {
 | 
				
			||||||
 | 
					          addToast({
 | 
				
			||||||
 | 
					            title: t('common.error'),
 | 
				
			||||||
 | 
					            body: t('device.mac_not_found'),
 | 
				
			||||||
 | 
					            color: 'danger',
 | 
				
			||||||
 | 
					            autohide: true,
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          history.push('/devices');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        setDeviceConfig(deviceInfo);
 | 
					        setDeviceConfig(deviceInfo);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -115,9 +128,26 @@ const DevicePage = () => {
 | 
				
			|||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    setError(false);
 | 
					    setError(false);
 | 
				
			||||||
    if (deviceId) {
 | 
					    if (deviceId) {
 | 
				
			||||||
      getDevice();
 | 
					      addDeviceListener({
 | 
				
			||||||
      getData();
 | 
					        serialNumber: deviceId,
 | 
				
			||||||
 | 
					        types: [
 | 
				
			||||||
 | 
					          'device_connection',
 | 
				
			||||||
 | 
					          'device_disconnection',
 | 
				
			||||||
 | 
					          'device_firmware_upgrade',
 | 
				
			||||||
 | 
					          'device_statistics',
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        onTrigger: () => refresh(),
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      refresh();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      if (deviceId) {
 | 
				
			||||||
 | 
					        removeDeviceListener({
 | 
				
			||||||
 | 
					          serialNumber: deviceId,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
  }, [deviceId]);
 | 
					  }, [deviceId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										189
									
								
								src/pages/SystemPage/ApiStatusCard/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								src/pages/SystemPage/ApiStatusCard/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
				
			|||||||
 | 
					import React, { useState } from 'react';
 | 
				
			||||||
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  CCard,
 | 
				
			||||||
 | 
					  CCardBody,
 | 
				
			||||||
 | 
					  CCardHeader,
 | 
				
			||||||
 | 
					  CRow,
 | 
				
			||||||
 | 
					  CCol,
 | 
				
			||||||
 | 
					  CButton,
 | 
				
			||||||
 | 
					  CPopover,
 | 
				
			||||||
 | 
					  CModal,
 | 
				
			||||||
 | 
					  CModalBody,
 | 
				
			||||||
 | 
					  CModalHeader,
 | 
				
			||||||
 | 
					  CModalTitle,
 | 
				
			||||||
 | 
					  CDataTable,
 | 
				
			||||||
 | 
					} from '@coreui/react';
 | 
				
			||||||
 | 
					import Select from 'react-select';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilSync, cilX } from '@coreui/icons';
 | 
				
			||||||
 | 
					import { prettyDate } from 'utils/helper';
 | 
				
			||||||
 | 
					import useToggle from 'hooks/useToggle';
 | 
				
			||||||
 | 
					import FormattedDate from 'components/FormattedDate';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ApiStatusCard = ({ t, info, reload }) => {
 | 
				
			||||||
 | 
					  const [types, setTypes] = useState([]);
 | 
				
			||||||
 | 
					  const [showCerts, toggleCerts] = useToggle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const submit = () => {
 | 
				
			||||||
 | 
					    reload(
 | 
				
			||||||
 | 
					      types.map((v) => v.value),
 | 
				
			||||||
 | 
					      info.endpoint,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <CCard>
 | 
				
			||||||
 | 
					      <CCardHeader className="dark-header">
 | 
				
			||||||
 | 
					        <div style={{ fontWeight: '600' }} className=" text-value-lg float-left">
 | 
				
			||||||
 | 
					          {info.title}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </CCardHeader>
 | 
				
			||||||
 | 
					      <CCardBody>
 | 
				
			||||||
 | 
					        <CRow>
 | 
				
			||||||
 | 
					          <CCol sm="4">
 | 
				
			||||||
 | 
					            <div block="true">{t('common.endpoint')}:</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					          <CCol>
 | 
				
			||||||
 | 
					            <div block="true">{info.endpoint}</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					        </CRow>
 | 
				
			||||||
 | 
					        <CRow>
 | 
				
			||||||
 | 
					          <CCol sm="4">
 | 
				
			||||||
 | 
					            <div block="true">{t('system.hostname')}:</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					          <CCol>
 | 
				
			||||||
 | 
					            <div block="true">{info.hostname}</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					        </CRow>
 | 
				
			||||||
 | 
					        <CRow>
 | 
				
			||||||
 | 
					          <CCol sm="4">
 | 
				
			||||||
 | 
					            <div block="true">{t('system.os')}:</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					          <CCol>
 | 
				
			||||||
 | 
					            <div block="true">{info.os}</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					        </CRow>
 | 
				
			||||||
 | 
					        <CRow>
 | 
				
			||||||
 | 
					          <CCol sm="4">
 | 
				
			||||||
 | 
					            <div block="true">{t('system.processors')}:</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					          <CCol>
 | 
				
			||||||
 | 
					            <div block="true">{info.processors}</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					        </CRow>
 | 
				
			||||||
 | 
					        <CRow>
 | 
				
			||||||
 | 
					          <CCol sm="4">
 | 
				
			||||||
 | 
					            <div block="true">{t('common.start')}:</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					          <CCol>
 | 
				
			||||||
 | 
					            <div block="true">
 | 
				
			||||||
 | 
					              {info.start ? <FormattedDate date={info.start} /> : t('common.unknown')}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					        </CRow>
 | 
				
			||||||
 | 
					        <CRow>
 | 
				
			||||||
 | 
					          <CCol sm="4">
 | 
				
			||||||
 | 
					            <div block="true">{t('status.uptime')}:</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					          <CCol>
 | 
				
			||||||
 | 
					            <div block="true">{info.uptime}</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					        </CRow>
 | 
				
			||||||
 | 
					        <CRow>
 | 
				
			||||||
 | 
					          <CCol sm="4">
 | 
				
			||||||
 | 
					            <div block="true">{t('footer.version')}:</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					          <CCol>
 | 
				
			||||||
 | 
					            <div block="true">{info.version}</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					        </CRow>
 | 
				
			||||||
 | 
					        <CRow>
 | 
				
			||||||
 | 
					          <CCol sm="4">
 | 
				
			||||||
 | 
					            <div block="true">{t('common.certificates')}:</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					          <CCol>
 | 
				
			||||||
 | 
					            <div block="true">
 | 
				
			||||||
 | 
					              {info.certificates?.length > 0 ? (
 | 
				
			||||||
 | 
					                <CButton className="ml-0 pl-0 py-0" color="link" onClick={toggleCerts}>
 | 
				
			||||||
 | 
					                  {t('common.details')} ({info.certificates.length})
 | 
				
			||||||
 | 
					                </CButton>
 | 
				
			||||||
 | 
					              ) : (
 | 
				
			||||||
 | 
					                <div>{t('common.unknown')}</div>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					        </CRow>
 | 
				
			||||||
 | 
					        <CRow>
 | 
				
			||||||
 | 
					          <CCol sm="4" className="pt-1">
 | 
				
			||||||
 | 
					            <div block="true">{t('system.reload_subsystems')}:</div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					          <CCol>
 | 
				
			||||||
 | 
					            <div block="true">
 | 
				
			||||||
 | 
					              {info.subsystems.length === 0 ? (
 | 
				
			||||||
 | 
					                t('common.unknown')
 | 
				
			||||||
 | 
					              ) : (
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                  <div className="float-left" style={{ width: '85%' }}>
 | 
				
			||||||
 | 
					                    <Select
 | 
				
			||||||
 | 
					                      isMulti
 | 
				
			||||||
 | 
					                      closeMenuOnSelect={false}
 | 
				
			||||||
 | 
					                      name="Subsystems"
 | 
				
			||||||
 | 
					                      options={info.subsystems.map((sys) => ({ value: sys, label: sys }))}
 | 
				
			||||||
 | 
					                      onChange={setTypes}
 | 
				
			||||||
 | 
					                      value={types}
 | 
				
			||||||
 | 
					                      className="basic-multi-select"
 | 
				
			||||||
 | 
					                      classNamePrefix="select"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                  <div className="float-left text-right" style={{ width: '15%' }}>
 | 
				
			||||||
 | 
					                    <CPopover content={t('system.reload')}>
 | 
				
			||||||
 | 
					                      <CButton
 | 
				
			||||||
 | 
					                        color="primary"
 | 
				
			||||||
 | 
					                        variant="outline"
 | 
				
			||||||
 | 
					                        onClick={submit}
 | 
				
			||||||
 | 
					                        disabled={types.length === 0}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        <CIcon content={cilSync} />
 | 
				
			||||||
 | 
					                      </CButton>
 | 
				
			||||||
 | 
					                    </CPopover>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					        </CRow>
 | 
				
			||||||
 | 
					      </CCardBody>
 | 
				
			||||||
 | 
					      <CModal size="lg" show={showCerts} onClose={toggleCerts}>
 | 
				
			||||||
 | 
					        <CModalHeader className="p-1">
 | 
				
			||||||
 | 
					          <CModalTitle className="pl-1 pt-1">{t('common.certificates')}</CModalTitle>
 | 
				
			||||||
 | 
					          <div className="text-right">
 | 
				
			||||||
 | 
					            <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					              <CButton color="primary" variant="outline" className="ml-2" onClick={toggleCerts}>
 | 
				
			||||||
 | 
					                <CIcon content={cilX} />
 | 
				
			||||||
 | 
					              </CButton>
 | 
				
			||||||
 | 
					            </CPopover>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </CModalHeader>
 | 
				
			||||||
 | 
					        <CModalBody>
 | 
				
			||||||
 | 
					          <CDataTable
 | 
				
			||||||
 | 
					            addTableClasses="table-sm"
 | 
				
			||||||
 | 
					            border
 | 
				
			||||||
 | 
					            items={info?.certificates.map((cert) => ({
 | 
				
			||||||
 | 
					              ...cert,
 | 
				
			||||||
 | 
					              expiresOn: prettyDate(cert.expiresOn),
 | 
				
			||||||
 | 
					            }))}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </CModalBody>
 | 
				
			||||||
 | 
					      </CModal>
 | 
				
			||||||
 | 
					    </CCard>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ApiStatusCard.propTypes = {
 | 
				
			||||||
 | 
					  t: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					  info: PropTypes.instanceOf(Object).isRequired,
 | 
				
			||||||
 | 
					  reload: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default React.memo(ApiStatusCard);
 | 
				
			||||||
@@ -1,22 +1,135 @@
 | 
				
			|||||||
import React from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
 | 
					import { v4 as createUuid } from 'uuid';
 | 
				
			||||||
 | 
					import { CRow, CCol } from '@coreui/react';
 | 
				
			||||||
 | 
					import { useAuth, useToast } from 'ucentral-libs';
 | 
				
			||||||
 | 
					import { secondsToDetailed } from 'utils/helper';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import { SystemPage as Page, useToast, useAuth } from 'ucentral-libs';
 | 
					 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
 | 
					import ApiStatusCard from './ApiStatusCard';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SystemPage = () => {
 | 
					const SystemPage = () => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
  const { addToast } = useToast();
 | 
					  const { addToast } = useToast();
 | 
				
			||||||
 | 
					  const [endpointsInfo, setEndpointsInfo] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const getSystemInfo = async (key, endpoint) => {
 | 
				
			||||||
 | 
					    let systemInfo = {
 | 
				
			||||||
 | 
					      title: key,
 | 
				
			||||||
 | 
					      endpoint,
 | 
				
			||||||
 | 
					      hostname: t('common.unknown'),
 | 
				
			||||||
 | 
					      os: t('common.unknown'),
 | 
				
			||||||
 | 
					      processors: t('common.unknown'),
 | 
				
			||||||
 | 
					      uptime: t('common.unknown'),
 | 
				
			||||||
 | 
					      version: t('common.unknown'),
 | 
				
			||||||
 | 
					      certificates: [],
 | 
				
			||||||
 | 
					      subsystems: [],
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const options = {
 | 
				
			||||||
 | 
					      headers: {
 | 
				
			||||||
 | 
					        Accept: 'application/json',
 | 
				
			||||||
 | 
					        Authorization: `Bearer ${currentToken}`,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await axiosInstance
 | 
				
			||||||
 | 
					      .get(`${endpoint}/api/v1/system?command=info`, options)
 | 
				
			||||||
 | 
					      .then((newInfo) => {
 | 
				
			||||||
 | 
					        systemInfo = { ...systemInfo, ...newInfo.data };
 | 
				
			||||||
 | 
					        systemInfo.uptime = secondsToDetailed(
 | 
				
			||||||
 | 
					          newInfo.data.uptime,
 | 
				
			||||||
 | 
					          t('common.day'),
 | 
				
			||||||
 | 
					          t('common.days'),
 | 
				
			||||||
 | 
					          t('common.hour'),
 | 
				
			||||||
 | 
					          t('common.hours'),
 | 
				
			||||||
 | 
					          t('common.minute'),
 | 
				
			||||||
 | 
					          t('common.minutes'),
 | 
				
			||||||
 | 
					          t('common.second'),
 | 
				
			||||||
 | 
					          t('common.seconds'),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        systemInfo.start = newInfo.data.start;
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch(() => {});
 | 
				
			||||||
 | 
					    await axiosInstance
 | 
				
			||||||
 | 
					      .post(`${endpoint}/api/v1/system`, { command: 'getsubsystemnames' }, options)
 | 
				
			||||||
 | 
					      .then((newSubs) => {
 | 
				
			||||||
 | 
					        systemInfo.subsystems = newSubs.data.list.sort((a, b) => {
 | 
				
			||||||
 | 
					          if (a < b) return -1;
 | 
				
			||||||
 | 
					          if (a > b) return 1;
 | 
				
			||||||
 | 
					          return 0;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch(() => {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return systemInfo;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const reload = (subsystems, endpoint) => {
 | 
				
			||||||
 | 
					    const options = {
 | 
				
			||||||
 | 
					      headers: {
 | 
				
			||||||
 | 
					        Accept: 'application/json',
 | 
				
			||||||
 | 
					        Authorization: `Bearer ${currentToken}`,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const parameters = {
 | 
				
			||||||
 | 
					      command: 'reload',
 | 
				
			||||||
 | 
					      subsystems,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    axiosInstance
 | 
				
			||||||
 | 
					      .post(`${endpoint}/api/v1/system?command=info`, parameters, options)
 | 
				
			||||||
 | 
					      .then(() => {
 | 
				
			||||||
 | 
					        addToast({
 | 
				
			||||||
 | 
					          title: t('common.success'),
 | 
				
			||||||
 | 
					          body: t('system.success_reload'),
 | 
				
			||||||
 | 
					          color: 'success',
 | 
				
			||||||
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        addToast({
 | 
				
			||||||
 | 
					          title: t('common.error'),
 | 
				
			||||||
 | 
					          body: t('system.error_reloading', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
 | 
					          color: 'danger',
 | 
				
			||||||
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const getAllInfo = async () => {
 | 
				
			||||||
 | 
					    const promises = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const [key, value] of Object.entries(endpoints)) {
 | 
				
			||||||
 | 
					      promises.push(getSystemInfo(key, value));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const results = await Promise.all(promises);
 | 
				
			||||||
 | 
					      setEndpointsInfo(results);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      addToast({
 | 
				
			||||||
 | 
					        title: t('common.error'),
 | 
				
			||||||
 | 
					        body: t('system.error_fetching'),
 | 
				
			||||||
 | 
					        color: 'danger',
 | 
				
			||||||
 | 
					        autohide: true,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    getAllInfo();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Page
 | 
					    <CRow>
 | 
				
			||||||
      t={t}
 | 
					      {endpointsInfo.map((info) => (
 | 
				
			||||||
      currentToken={currentToken}
 | 
					        <CCol sm="12" lg="6" xxl="4" key={createUuid()}>
 | 
				
			||||||
      endpoints={endpoints}
 | 
					          <ApiStatusCard t={t} info={info} reload={reload} />
 | 
				
			||||||
      addToast={addToast}
 | 
					        </CCol>
 | 
				
			||||||
      axiosInstance={axiosInstance}
 | 
					      ))}
 | 
				
			||||||
    />
 | 
					    </CRow>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default SystemPage;
 | 
					export default SystemPage;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ const Routes = () => {
 | 
				
			|||||||
      path="/"
 | 
					      path="/"
 | 
				
			||||||
      name="Devices"
 | 
					      name="Devices"
 | 
				
			||||||
      render={(props) =>
 | 
					      render={(props) =>
 | 
				
			||||||
        currentToken !== '' && Object.keys(endpoints).length !== 0 ? (
 | 
					        currentToken !== '' && endpoints && Object.keys(endpoints).length !== 0 ? (
 | 
				
			||||||
          <TheLayout {...props} />
 | 
					          <TheLayout {...props} />
 | 
				
			||||||
        ) : (
 | 
					        ) : (
 | 
				
			||||||
          <ToastProvider>
 | 
					          <ToastProvider>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import * as axios from 'axios';
 | 
					import * as axios from 'axios';
 | 
				
			||||||
import axiosRetry from 'axios-retry';
 | 
					import axiosRetry from 'axios-retry';
 | 
				
			||||||
 | 
					import { LOGOUT_ON_SEC_ERROR_CODES } from 'constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const axiosInstance = axios.create();
 | 
					const axiosInstance = axios.create();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,7 +9,7 @@ axiosRetry(axiosInstance, {
 | 
				
			|||||||
  retryDelay: () => axiosRetry.exponentialDelay,
 | 
					  retryDelay: () => axiosRetry.exponentialDelay,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
axiosInstance.defaults.timeout = 60000;
 | 
					axiosInstance.defaults.timeout = 160000;
 | 
				
			||||||
axiosInstance.defaults.headers.get.Accept = 'application/json';
 | 
					axiosInstance.defaults.headers.get.Accept = 'application/json';
 | 
				
			||||||
axiosInstance.defaults.headers.post.Accept = 'application/json';
 | 
					axiosInstance.defaults.headers.post.Accept = 'application/json';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,7 +28,7 @@ axiosInstance.interceptors.response.use(
 | 
				
			|||||||
          retries += 1;
 | 
					          retries += 1;
 | 
				
			||||||
          localStorage.setItem('sec_retries', retries);
 | 
					          localStorage.setItem('sec_retries', retries);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (error.response.data?.ErrorCode === 9) {
 | 
					        if (LOGOUT_ON_SEC_ERROR_CODES.includes(error.response.data?.ErrorCode)) {
 | 
				
			||||||
          localStorage.removeItem('access_token');
 | 
					          localStorage.removeItem('access_token');
 | 
				
			||||||
          localStorage.removeItem('gateway_endpoints');
 | 
					          localStorage.removeItem('gateway_endpoints');
 | 
				
			||||||
          sessionStorage.clear();
 | 
					          sessionStorage.clear();
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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 ?? '';
 | 
				
			||||||
@@ -1,5 +1,3 @@
 | 
				
			|||||||
export const cleanTimestamp = (timestamp) => timestamp.replace('T', ' ').replace('Z', ' ');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const cleanBytesString = (bytes, decimals = 2) => {
 | 
					export const cleanBytesString = (bytes, decimals = 2) => {
 | 
				
			||||||
  const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
 | 
					  const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
 | 
				
			||||||
  if (!bytes || bytes === 0) {
 | 
					  if (!bytes || bytes === 0) {
 | 
				
			||||||
@@ -59,6 +57,14 @@ export const checkIfJson = (string) => {
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const toJson = (string) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    return JSON.parse(string);
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    return undefined;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const secondsToDetailed = (
 | 
					export const secondsToDetailed = (
 | 
				
			||||||
  seconds,
 | 
					  seconds,
 | 
				
			||||||
  dayLabel,
 | 
					  dayLabel,
 | 
				
			||||||
@@ -124,6 +130,25 @@ export const compactSecondsToDetailed = (seconds, dayLabel, daysLabel, secondsLa
 | 
				
			|||||||
  return finalString;
 | 
					  return finalString;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const extraCompactSecondsToDetailed = (seconds) => {
 | 
				
			||||||
 | 
					  let secondsLeft = seconds;
 | 
				
			||||||
 | 
					  const days = Math.floor(secondsLeft / (3600 * 24));
 | 
				
			||||||
 | 
					  secondsLeft -= days * (3600 * 24);
 | 
				
			||||||
 | 
					  const hours = Math.floor(secondsLeft / 3600);
 | 
				
			||||||
 | 
					  secondsLeft -= hours * 3600;
 | 
				
			||||||
 | 
					  const minutes = Math.floor(secondsLeft / 60);
 | 
				
			||||||
 | 
					  secondsLeft -= minutes * 60;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let finalString = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  finalString = `${finalString}${prettyNumber(days)}:`;
 | 
				
			||||||
 | 
					  finalString = `${finalString}${prettyNumber(hours)}:`;
 | 
				
			||||||
 | 
					  finalString = `${finalString}${prettyNumber(minutes)}:`;
 | 
				
			||||||
 | 
					  finalString = `${finalString}${prettyNumber(secondsLeft)}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return finalString;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const validateEmail = (email) => {
 | 
					export const validateEmail = (email) => {
 | 
				
			||||||
  const regex = /\S+@\S+\.\S+/;
 | 
					  const regex = /\S+@\S+\.\S+/;
 | 
				
			||||||
  return regex.test(email);
 | 
					  return regex.test(email);
 | 
				
			||||||
@@ -135,3 +160,25 @@ export const testRegex = (value, regexString) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const datesSameDay = (first, second) => first.getDate() === second.getDate();
 | 
					export const datesSameDay = (first, second) => first.getDate() === second.getDate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const units = {
 | 
				
			||||||
 | 
					  year: 24 * 60 * 60 * 1000 * 365,
 | 
				
			||||||
 | 
					  month: (24 * 60 * 60 * 1000 * 365) / 12,
 | 
				
			||||||
 | 
					  day: 24 * 60 * 60 * 1000,
 | 
				
			||||||
 | 
					  hour: 60 * 60 * 1000,
 | 
				
			||||||
 | 
					  minute: 60 * 1000,
 | 
				
			||||||
 | 
					  second: 1000,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rtf = new Intl.RelativeTimeFormat('en', { localeMatcher: 'best fit', style: 'long' });
 | 
				
			||||||
 | 
					export const formatDaysAgo = (d1, d2 = new Date()) => {
 | 
				
			||||||
 | 
					  const convertedTimestamp = unixToDateString(d1);
 | 
				
			||||||
 | 
					  const date = new Date(convertedTimestamp);
 | 
				
			||||||
 | 
					  const elapsed = date - d2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const [key] of Object.entries(units))
 | 
				
			||||||
 | 
					    if (Math.abs(elapsed) > units[key] || key === 'second')
 | 
				
			||||||
 | 
					      return rtf.format(Math.round(elapsed / units[key]), key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return prettyDate(date);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user