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