Compare commits
	
		
			2 Commits
		
	
	
		
			v3.2.1-RC1
			...
			v3.0.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 9e3df09fb2 | ||
|   | 2445935627 | 
							
								
								
									
										1
									
								
								.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | VITE_UCENTRALSEC_URL="https://ucentral.dpaas.arilia.com:16001" | ||||||
| @@ -17,9 +17,7 @@ metadata: | |||||||
|   {{- end }} |   {{- end }} | ||||||
|  |  | ||||||
| spec: | spec: | ||||||
| {{- if $ingressValue.className }} |  | ||||||
|   ingressClassName: {{ $ingressValue.className }} |  | ||||||
| {{- end }} |  | ||||||
| {{- if $ingressValue.tls }} | {{- if $ingressValue.tls }} | ||||||
|   tls: |   tls: | ||||||
|   {{- range $ingressValue.tls }} |   {{- range $ingressValue.tls }} | ||||||
|   | |||||||
| @@ -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: v3.2.1-RC1 |     tag: v3.0.0 | ||||||
|     pullPolicy: Always |     pullPolicy: Always | ||||||
|  |  | ||||||
| services: | services: | ||||||
|   | |||||||
							
								
								
									
										379
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						| @@ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-client", |   "name": "ucentral-client", | ||||||
|   "version": "3.2.0", |   "version": "3.0.0(1)", | ||||||
|   "lockfileVersion": 3, |   "lockfileVersion": 3, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "packages": { |   "packages": { | ||||||
|     "": { |     "": { | ||||||
|       "name": "ucentral-client", |       "name": "ucentral-client", | ||||||
|       "version": "3.2.0", |       "version": "3.0.0(1)", | ||||||
|       "license": "ISC", |       "license": "ISC", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@chakra-ui/anatomy": "^2.1.1", |         "@chakra-ui/anatomy": "^2.1.1", | ||||||
| @@ -88,7 +88,6 @@ | |||||||
|         "lint-staged": "^13.2.1", |         "lint-staged": "^13.2.1", | ||||||
|         "prettier": "^2.8.7", |         "prettier": "^2.8.7", | ||||||
|         "vite-plugin-pwa": "^0.14.7", |         "vite-plugin-pwa": "^0.14.7", | ||||||
|         "vite-plugin-svgr": "^4.2.0", |  | ||||||
|         "vite-tsconfig-paths": "^4.2.0" |         "vite-tsconfig-paths": "^4.2.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
| @@ -3540,7 +3539,7 @@ | |||||||
|     }, |     }, | ||||||
|     "node_modules/@jridgewell/resolve-uri": { |     "node_modules/@jridgewell/resolve-uri": { | ||||||
|       "version": "3.1.0", |       "version": "3.1.0", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
|       "engines": { |       "engines": { | ||||||
|         "node": ">=6.0.0" |         "node": ">=6.0.0" | ||||||
| @@ -3548,7 +3547,7 @@ | |||||||
|     }, |     }, | ||||||
|     "node_modules/@jridgewell/set-array": { |     "node_modules/@jridgewell/set-array": { | ||||||
|       "version": "1.1.2", |       "version": "1.1.2", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
|       "engines": { |       "engines": { | ||||||
|         "node": ">=6.0.0" |         "node": ">=6.0.0" | ||||||
| @@ -3556,7 +3555,7 @@ | |||||||
|     }, |     }, | ||||||
|     "node_modules/@jridgewell/source-map": { |     "node_modules/@jridgewell/source-map": { | ||||||
|       "version": "0.3.2", |       "version": "0.3.2", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@jridgewell/gen-mapping": "^0.3.0", |         "@jridgewell/gen-mapping": "^0.3.0", | ||||||
| @@ -3565,7 +3564,7 @@ | |||||||
|     }, |     }, | ||||||
|     "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { |     "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { | ||||||
|       "version": "0.3.2", |       "version": "0.3.2", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@jridgewell/set-array": "^1.0.1", |         "@jridgewell/set-array": "^1.0.1", | ||||||
| @@ -3578,12 +3577,12 @@ | |||||||
|     }, |     }, | ||||||
|     "node_modules/@jridgewell/sourcemap-codec": { |     "node_modules/@jridgewell/sourcemap-codec": { | ||||||
|       "version": "1.4.14", |       "version": "1.4.14", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "MIT" |       "license": "MIT" | ||||||
|     }, |     }, | ||||||
|     "node_modules/@jridgewell/trace-mapping": { |     "node_modules/@jridgewell/trace-mapping": { | ||||||
|       "version": "0.3.17", |       "version": "0.3.17", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@jridgewell/resolve-uri": "3.1.0", |         "@jridgewell/resolve-uri": "3.1.0", | ||||||
| @@ -3956,9 +3955,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@rollup/pluginutils": { |     "node_modules/@rollup/pluginutils": { | ||||||
|       "version": "5.1.0", |       "version": "5.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", |       "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", | ||||||
|       "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", |       "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@types/estree": "^1.0.0", |         "@types/estree": "^1.0.0", | ||||||
| @@ -3969,7 +3968,7 @@ | |||||||
|         "node": ">=14.0.0" |         "node": ">=14.0.0" | ||||||
|       }, |       }, | ||||||
|       "peerDependencies": { |       "peerDependencies": { | ||||||
|         "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" |         "rollup": "^1.20.0||^2.0.0||^3.0.0" | ||||||
|       }, |       }, | ||||||
|       "peerDependenciesMeta": { |       "peerDependenciesMeta": { | ||||||
|         "rollup": { |         "rollup": { | ||||||
| @@ -3998,245 +3997,6 @@ | |||||||
|         "sourcemap-codec": "^1.4.8" |         "sourcemap-codec": "^1.4.8" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@svgr/babel-plugin-add-jsx-attribute": { |  | ||||||
|       "version": "8.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", |  | ||||||
|       "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", |  | ||||||
|       "dev": true, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "@babel/core": "^7.0.0-0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { |  | ||||||
|       "version": "8.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", |  | ||||||
|       "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", |  | ||||||
|       "dev": true, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "@babel/core": "^7.0.0-0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { |  | ||||||
|       "version": "8.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", |  | ||||||
|       "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", |  | ||||||
|       "dev": true, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "@babel/core": "^7.0.0-0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { |  | ||||||
|       "version": "8.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", |  | ||||||
|       "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", |  | ||||||
|       "dev": true, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "@babel/core": "^7.0.0-0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/babel-plugin-svg-dynamic-title": { |  | ||||||
|       "version": "8.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", |  | ||||||
|       "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", |  | ||||||
|       "dev": true, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "@babel/core": "^7.0.0-0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/babel-plugin-svg-em-dimensions": { |  | ||||||
|       "version": "8.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", |  | ||||||
|       "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", |  | ||||||
|       "dev": true, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "@babel/core": "^7.0.0-0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/babel-plugin-transform-react-native-svg": { |  | ||||||
|       "version": "8.1.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", |  | ||||||
|       "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", |  | ||||||
|       "dev": true, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "@babel/core": "^7.0.0-0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/babel-plugin-transform-svg-component": { |  | ||||||
|       "version": "8.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", |  | ||||||
|       "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", |  | ||||||
|       "dev": true, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=12" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "@babel/core": "^7.0.0-0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/babel-preset": { |  | ||||||
|       "version": "8.1.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", |  | ||||||
|       "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", |  | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |  | ||||||
|         "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", |  | ||||||
|         "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", |  | ||||||
|         "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", |  | ||||||
|         "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", |  | ||||||
|         "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", |  | ||||||
|         "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", |  | ||||||
|         "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", |  | ||||||
|         "@svgr/babel-plugin-transform-svg-component": "8.0.0" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "@babel/core": "^7.0.0-0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/core": { |  | ||||||
|       "version": "8.1.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", |  | ||||||
|       "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", |  | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |  | ||||||
|         "@babel/core": "^7.21.3", |  | ||||||
|         "@svgr/babel-preset": "8.1.0", |  | ||||||
|         "camelcase": "^6.2.0", |  | ||||||
|         "cosmiconfig": "^8.1.3", |  | ||||||
|         "snake-case": "^3.0.4" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/core/node_modules/cosmiconfig": { |  | ||||||
|       "version": "8.3.6", |  | ||||||
|       "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", |  | ||||||
|       "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", |  | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |  | ||||||
|         "import-fresh": "^3.3.0", |  | ||||||
|         "js-yaml": "^4.1.0", |  | ||||||
|         "parse-json": "^5.2.0", |  | ||||||
|         "path-type": "^4.0.0" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "url": "https://github.com/sponsors/d-fischer" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "typescript": ">=4.9.5" |  | ||||||
|       }, |  | ||||||
|       "peerDependenciesMeta": { |  | ||||||
|         "typescript": { |  | ||||||
|           "optional": true |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/hast-util-to-babel-ast": { |  | ||||||
|       "version": "8.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", |  | ||||||
|       "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", |  | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |  | ||||||
|         "@babel/types": "^7.21.3", |  | ||||||
|         "entities": "^4.4.0" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@svgr/plugin-jsx": { |  | ||||||
|       "version": "8.1.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", |  | ||||||
|       "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", |  | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |  | ||||||
|         "@babel/core": "^7.21.3", |  | ||||||
|         "@svgr/babel-preset": "8.1.0", |  | ||||||
|         "@svgr/hast-util-to-babel-ast": "8.0.0", |  | ||||||
|         "svg-parser": "^2.0.4" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=14" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "type": "github", |  | ||||||
|         "url": "https://github.com/sponsors/gregberge" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "@svgr/core": "*" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@tanstack/query-core": { |     "node_modules/@tanstack/query-core": { | ||||||
|       "version": "4.29.1", |       "version": "4.29.1", | ||||||
|       "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.1.tgz", |       "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.1.tgz", | ||||||
| @@ -4374,7 +4134,7 @@ | |||||||
|       "version": "18.15.11", |       "version": "18.15.11", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", |       "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", | ||||||
|       "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", |       "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", | ||||||
|       "dev": true |       "devOptional": true | ||||||
|     }, |     }, | ||||||
|     "node_modules/@types/parse-json": { |     "node_modules/@types/parse-json": { | ||||||
|       "version": "4.0.0", |       "version": "4.0.0", | ||||||
| @@ -4419,7 +4179,7 @@ | |||||||
|       "version": "18.0.11", |       "version": "18.0.11", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", |       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", | ||||||
|       "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", |       "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@types/react": "*" |         "@types/react": "*" | ||||||
|       } |       } | ||||||
| @@ -4753,7 +4513,7 @@ | |||||||
|     }, |     }, | ||||||
|     "node_modules/acorn": { |     "node_modules/acorn": { | ||||||
|       "version": "8.8.0", |       "version": "8.8.0", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
|       "bin": { |       "bin": { | ||||||
|         "acorn": "bin/acorn" |         "acorn": "bin/acorn" | ||||||
| @@ -5174,7 +4934,7 @@ | |||||||
|     }, |     }, | ||||||
|     "node_modules/buffer-from": { |     "node_modules/buffer-from": { | ||||||
|       "version": "1.1.2", |       "version": "1.1.2", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "MIT" |       "license": "MIT" | ||||||
|     }, |     }, | ||||||
|     "node_modules/builtin-modules": { |     "node_modules/builtin-modules": { | ||||||
| @@ -5209,18 +4969,6 @@ | |||||||
|         "node": ">=6" |         "node": ">=6" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/camelcase": { |  | ||||||
|       "version": "6.3.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", |  | ||||||
|       "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", |  | ||||||
|       "dev": true, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=10" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "url": "https://github.com/sponsors/sindresorhus" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/caniuse-lite": { |     "node_modules/caniuse-lite": { | ||||||
|       "version": "1.0.30001480", |       "version": "1.0.30001480", | ||||||
|       "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001480.tgz", |       "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001480.tgz", | ||||||
| @@ -5651,16 +5399,6 @@ | |||||||
|         "csstype": "^3.0.2" |         "csstype": "^3.0.2" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/dot-case": { |  | ||||||
|       "version": "3.0.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", |  | ||||||
|       "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", |  | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |  | ||||||
|         "no-case": "^3.0.4", |  | ||||||
|         "tslib": "^2.0.3" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/duplexer": { |     "node_modules/duplexer": { | ||||||
|       "version": "0.1.2", |       "version": "0.1.2", | ||||||
|       "license": "MIT" |       "license": "MIT" | ||||||
| @@ -5671,9 +5409,8 @@ | |||||||
|       "license": "MIT" |       "license": "MIT" | ||||||
|     }, |     }, | ||||||
|     "node_modules/ejs": { |     "node_modules/ejs": { | ||||||
|       "version": "3.1.10", |       "version": "3.1.8", | ||||||
|       "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", |       "license": "Apache-2.0", | ||||||
|       "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", |  | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "jake": "^10.8.5" |         "jake": "^10.8.5" | ||||||
|       }, |       }, | ||||||
| @@ -5694,18 +5431,6 @@ | |||||||
|       "dev": true, |       "dev": true, | ||||||
|       "license": "MIT" |       "license": "MIT" | ||||||
|     }, |     }, | ||||||
|     "node_modules/entities": { |  | ||||||
|       "version": "4.5.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", |  | ||||||
|       "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", |  | ||||||
|       "dev": true, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=0.12" |  | ||||||
|       }, |  | ||||||
|       "funding": { |  | ||||||
|         "url": "https://github.com/fb55/entities?sponsor=1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/error-ex": { |     "node_modules/error-ex": { | ||||||
|       "version": "1.3.2", |       "version": "1.3.2", | ||||||
|       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", |       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", | ||||||
| @@ -6683,15 +6408,14 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/follow-redirects": { |     "node_modules/follow-redirects": { | ||||||
|       "version": "1.15.6", |       "version": "1.15.2", | ||||||
|       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", |  | ||||||
|       "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", |  | ||||||
|       "funding": [ |       "funding": [ | ||||||
|         { |         { | ||||||
|           "type": "individual", |           "type": "individual", | ||||||
|           "url": "https://github.com/sponsors/RubenVerborgh" |           "url": "https://github.com/sponsors/RubenVerborgh" | ||||||
|         } |         } | ||||||
|       ], |       ], | ||||||
|  |       "license": "MIT", | ||||||
|       "engines": { |       "engines": { | ||||||
|         "node": ">=4.0" |         "node": ">=4.0" | ||||||
|       }, |       }, | ||||||
| @@ -8221,15 +7945,6 @@ | |||||||
|         "loose-envify": "cli.js" |         "loose-envify": "cli.js" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/lower-case": { |  | ||||||
|       "version": "2.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", |  | ||||||
|       "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", |  | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |  | ||||||
|         "tslib": "^2.0.3" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/lru-cache": { |     "node_modules/lru-cache": { | ||||||
|       "version": "6.0.0", |       "version": "6.0.0", | ||||||
|       "dev": true, |       "dev": true, | ||||||
| @@ -8369,16 +8084,6 @@ | |||||||
|       "dev": true, |       "dev": true, | ||||||
|       "license": "MIT" |       "license": "MIT" | ||||||
|     }, |     }, | ||||||
|     "node_modules/no-case": { |  | ||||||
|       "version": "3.0.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", |  | ||||||
|       "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", |  | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |  | ||||||
|         "lower-case": "^2.0.2", |  | ||||||
|         "tslib": "^2.0.3" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/node-fetch": { |     "node_modules/node-fetch": { | ||||||
|       "version": "2.6.7", |       "version": "2.6.7", | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
| @@ -9663,16 +9368,6 @@ | |||||||
|         "url": "https://github.com/chalk/ansi-styles?sponsor=1" |         "url": "https://github.com/chalk/ansi-styles?sponsor=1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/snake-case": { |  | ||||||
|       "version": "3.0.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", |  | ||||||
|       "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", |  | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |  | ||||||
|         "dot-case": "^3.0.4", |  | ||||||
|         "tslib": "^2.0.3" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/source-map": { |     "node_modules/source-map": { | ||||||
|       "version": "0.5.7", |       "version": "0.5.7", | ||||||
|       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", |       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", | ||||||
| @@ -9781,7 +9476,7 @@ | |||||||
|     }, |     }, | ||||||
|     "node_modules/source-map-support": { |     "node_modules/source-map-support": { | ||||||
|       "version": "0.5.21", |       "version": "0.5.21", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "buffer-from": "^1.0.0", |         "buffer-from": "^1.0.0", | ||||||
| @@ -9790,7 +9485,7 @@ | |||||||
|     }, |     }, | ||||||
|     "node_modules/source-map-support/node_modules/source-map": { |     "node_modules/source-map-support/node_modules/source-map": { | ||||||
|       "version": "0.6.1", |       "version": "0.6.1", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "BSD-3-Clause", |       "license": "BSD-3-Clause", | ||||||
|       "engines": { |       "engines": { | ||||||
|         "node": ">=0.10.0" |         "node": ">=0.10.0" | ||||||
| @@ -10000,12 +9695,6 @@ | |||||||
|         "url": "https://github.com/sponsors/ljharb" |         "url": "https://github.com/sponsors/ljharb" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/svg-parser": { |  | ||||||
|       "version": "2.0.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", |  | ||||||
|       "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", |  | ||||||
|       "dev": true |  | ||||||
|     }, |  | ||||||
|     "node_modules/temp": { |     "node_modules/temp": { | ||||||
|       "version": "0.9.4", |       "version": "0.9.4", | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
| @@ -10080,7 +9769,7 @@ | |||||||
|     }, |     }, | ||||||
|     "node_modules/terser": { |     "node_modules/terser": { | ||||||
|       "version": "5.15.1", |       "version": "5.15.1", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "BSD-2-Clause", |       "license": "BSD-2-Clause", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@jridgewell/source-map": "^0.3.2", |         "@jridgewell/source-map": "^0.3.2", | ||||||
| @@ -10097,7 +9786,7 @@ | |||||||
|     }, |     }, | ||||||
|     "node_modules/terser/node_modules/commander": { |     "node_modules/terser/node_modules/commander": { | ||||||
|       "version": "2.20.3", |       "version": "2.20.3", | ||||||
|       "dev": true, |       "devOptional": true, | ||||||
|       "license": "MIT" |       "license": "MIT" | ||||||
|     }, |     }, | ||||||
|     "node_modules/text-table": { |     "node_modules/text-table": { | ||||||
| @@ -10443,9 +10132,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/vite": { |     "node_modules/vite": { | ||||||
|       "version": "4.5.3", |       "version": "4.4.9", | ||||||
|       "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", |       "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", | ||||||
|       "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", |       "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "esbuild": "^0.18.10", |         "esbuild": "^0.18.10", | ||||||
|         "postcss": "^8.4.27", |         "postcss": "^8.4.27", | ||||||
| @@ -10519,20 +10208,6 @@ | |||||||
|         "workbox-window": "^6.5.4" |         "workbox-window": "^6.5.4" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/vite-plugin-svgr": { |  | ||||||
|       "version": "4.2.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", |  | ||||||
|       "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", |  | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |  | ||||||
|         "@rollup/pluginutils": "^5.0.5", |  | ||||||
|         "@svgr/core": "^8.1.0", |  | ||||||
|         "@svgr/plugin-jsx": "^8.1.0" |  | ||||||
|       }, |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "vite": "^2.6.0 || 3 || 4 || 5" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/vite-tsconfig-paths": { |     "node_modules/vite-tsconfig-paths": { | ||||||
|       "version": "4.2.0", |       "version": "4.2.0", | ||||||
|       "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.2.0.tgz", |       "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.2.0.tgz", | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-client", |   "name": "ucentral-client", | ||||||
|   "version": "3.2.0", |   "version": "3.0.0(1)", | ||||||
|   "description": "", |   "description": "", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "main": "index.tsx", |   "main": "index.tsx", | ||||||
| @@ -94,7 +94,6 @@ | |||||||
|     "lint-staged": "^13.2.1", |     "lint-staged": "^13.2.1", | ||||||
|     "prettier": "^2.8.7", |     "prettier": "^2.8.7", | ||||||
|     "vite-plugin-pwa": "^0.14.7", |     "vite-plugin-pwa": "^0.14.7", | ||||||
|     "vite-plugin-svgr": "^4.2.0", |  | ||||||
|     "vite-tsconfig-paths": "^4.2.0" |     "vite-tsconfig-paths": "^4.2.0" | ||||||
|   }, |   }, | ||||||
|   "browserslist": { |   "browserslist": { | ||||||
|   | |||||||
| Before Width: | Height: | Size: 294 KiB | 
| Before Width: | Height: | Size: 394 KiB | 
| Before Width: | Height: | Size: 141 KiB | 
| Before Width: | Height: | Size: 138 KiB | 
| Before Width: | Height: | Size: 245 KiB | 
| Before Width: | Height: | Size: 245 KiB | 
| Before Width: | Height: | Size: 245 KiB | 
| Before Width: | Height: | Size: 239 KiB | 
| Before Width: | Height: | Size: 239 KiB | 
| Before Width: | Height: | Size: 239 KiB | 
| Before Width: | Height: | Size: 100 KiB | 
| Before Width: | Height: | Size: 100 KiB | 
| Before Width: | Height: | Size: 123 KiB | 
| Before Width: | Height: | Size: 79 KiB | 
| Before Width: | Height: | Size: 31 KiB | 
| Before Width: | Height: | Size: 31 KiB | 
| Before Width: | Height: | Size: 140 KiB | 
| Before Width: | Height: | Size: 121 KiB | 
| Before Width: | Height: | Size: 129 KiB | 
| Before Width: | Height: | Size: 129 KiB | 
| Before Width: | Height: | Size: 286 KiB | 
| Before Width: | Height: | Size: 502 KiB | 
| Before Width: | Height: | Size: 637 KiB | 
| Before Width: | Height: | Size: 324 KiB | 
| Before Width: | Height: | Size: 313 KiB | 
| Before Width: | Height: | Size: 314 KiB | 
| Before Width: | Height: | Size: 283 KiB | 
| Before Width: | Height: | Size: 374 KiB | 
| Before Width: | Height: | Size: 33 KiB | 
| Before Width: | Height: | Size: 267 KiB | 
| Before Width: | Height: | Size: 331 KiB | 
| Before Width: | Height: | Size: 194 KiB | 
| Before Width: | Height: | Size: 194 KiB | 
| Before Width: | Height: | Size: 133 KiB | 
| Before Width: | Height: | Size: 133 KiB | 
| Before Width: | Height: | Size: 194 KiB | 
| @@ -54,7 +54,6 @@ const DeviceActionDropdown = ({ | |||||||
| }: Props) => { | }: Props) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const toast = useToast(); |   const toast = useToast(); | ||||||
|   const deviceType = device?.deviceType ?? 'ap'; |  | ||||||
|   const connectColor = useColorModeValue('blackAlpha', 'gray'); |   const connectColor = useColorModeValue('blackAlpha', 'gray'); | ||||||
|   const addEventListeners = useControllerStore((state) => state.addEventListeners); |   const addEventListeners = useControllerStore((state) => state.addEventListeners); | ||||||
|   const { refetch: getRtty, isFetching: isRtty } = useGetDeviceRtty({ |   const { refetch: getRtty, isFetching: isRtty } = useGetDeviceRtty({ | ||||||
| @@ -206,7 +205,7 @@ const DeviceActionDropdown = ({ | |||||||
|           isDisabled={isDisabled} |           isDisabled={isDisabled} | ||||||
|           onClick={handleOpenScan} |           onClick={handleOpenScan} | ||||||
|           colorScheme="teal" |           colorScheme="teal" | ||||||
|           hidden={isCompact || deviceType !== 'ap'} |           hidden={isCompact} | ||||||
|         /> |         /> | ||||||
|       </Tooltip> |       </Tooltip> | ||||||
|       <Menu> |       <Menu> | ||||||
| @@ -222,7 +221,7 @@ const DeviceActionDropdown = ({ | |||||||
|         <Portal> |         <Portal> | ||||||
|           <MenuList maxH="315px" overflowY="auto"> |           <MenuList maxH="315px" overflowY="auto"> | ||||||
|             <MenuItem onClick={handleBlinkClick}>{t('commands.blink')}</MenuItem> |             <MenuItem onClick={handleBlinkClick}>{t('commands.blink')}</MenuItem> | ||||||
|             <MenuItem onClick={handleOpenConfigure} hidden={!isCompact || deviceType !== 'ap'}> |             <MenuItem onClick={handleOpenConfigure} hidden={!isCompact}> | ||||||
|               {t('controller.configure.title')} |               {t('controller.configure.title')} | ||||||
|             </MenuItem> |             </MenuItem> | ||||||
|             <MenuItem onClick={handleConnectClick} hidden={!isCompact}> |             <MenuItem onClick={handleConnectClick} hidden={!isCompact}> | ||||||
| @@ -240,7 +239,7 @@ const DeviceActionDropdown = ({ | |||||||
|             <MenuItem onClick={handleUpdateToLatest} hidden> |             <MenuItem onClick={handleUpdateToLatest} hidden> | ||||||
|               {t('premium.toolbox.upgrade_to_latest')} |               {t('premium.toolbox.upgrade_to_latest')} | ||||||
|             </MenuItem> |             </MenuItem> | ||||||
|             <MenuItem onClick={handleOpenScan} hidden={!isCompact || deviceType !== 'ap'}> |             <MenuItem onClick={handleOpenScan} hidden={!isCompact}> | ||||||
|               {t('commands.wifiscan')} |               {t('commands.wifiscan')} | ||||||
|             </MenuItem> |             </MenuItem> | ||||||
|           </MenuList> |           </MenuList> | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ export const ResponsiveTag = React.memo(({ label, icon, tooltip, isCompact, ...p | |||||||
|   return ( |   return ( | ||||||
|     <Tooltip label={tooltip ?? label}> |     <Tooltip label={tooltip ?? label}> | ||||||
|       <Tag size="lg" colorScheme="blue" {...props}> |       <Tag size="lg" colorScheme="blue" {...props}> | ||||||
|         <TagLeftIcon boxSize="18px" as={icon} mt={-0.5} /> |         <TagLeftIcon boxSize="18px" as={icon} /> | ||||||
|         <TagLabel>{label}</TagLabel> |         <TagLabel>{label}</TagLabel> | ||||||
|       </Tag> |       </Tag> | ||||||
|     </Tooltip> |     </Tooltip> | ||||||
|   | |||||||
| @@ -2,8 +2,7 @@ import * as React from 'react'; | |||||||
| import { ColumnDef, PaginationState, SortingColumnDef, SortingState, VisibilityState } from '@tanstack/react-table'; | import { ColumnDef, PaginationState, SortingColumnDef, SortingState, VisibilityState } from '@tanstack/react-table'; | ||||||
| import { useAuth } from 'contexts/AuthProvider'; | import { useAuth } from 'contexts/AuthProvider'; | ||||||
|  |  | ||||||
| const getDefaultSettings = ({ settings, showAllRows }: { settings?: string; showAllRows?: boolean }) => { | const getDefaultSettings = (settings?: string) => { | ||||||
|   if (showAllRows) return { pageSize: 1000, pageIndex: 0 }; |  | ||||||
|   let limit = 10; |   let limit = 10; | ||||||
|   let index = 0; |   let index = 0; | ||||||
|  |  | ||||||
| @@ -55,10 +54,9 @@ export type UseDataGridProps = { | |||||||
|   tableSettingsId: string; |   tableSettingsId: string; | ||||||
|   defaultOrder: string[]; |   defaultOrder: string[]; | ||||||
|   defaultSortBy?: SortingState; |   defaultSortBy?: SortingState; | ||||||
|   showAllRows?: boolean; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const useDataGrid = ({ tableSettingsId, defaultSortBy, defaultOrder, showAllRows }: UseDataGridProps) => { | export const useDataGrid = ({ tableSettingsId, defaultSortBy, defaultOrder }: UseDataGridProps) => { | ||||||
|   const orderSetting = `${tableSettingsId}.order`; |   const orderSetting = `${tableSettingsId}.order`; | ||||||
|   const hiddenColumnSetting = `${tableSettingsId}.hiddenColumns`; |   const hiddenColumnSetting = `${tableSettingsId}.hiddenColumns`; | ||||||
|   const pageSetting = `${tableSettingsId}.page`; |   const pageSetting = `${tableSettingsId}.page`; | ||||||
| @@ -68,9 +66,8 @@ export const useDataGrid = ({ tableSettingsId, defaultSortBy, defaultOrder, show | |||||||
|   const [columnOrder, setColumnOrder] = React.useState<string[]>( |   const [columnOrder, setColumnOrder] = React.useState<string[]>( | ||||||
|     getSavedColumnOrder(defaultOrder ?? [], tableSettingsId), |     getSavedColumnOrder(defaultOrder ?? [], tableSettingsId), | ||||||
|   ); |   ); | ||||||
|   const [pageInfo, setPageInfo] = React.useState<PaginationState>( |   const [pageInfo, setPageInfo] = React.useState<PaginationState>(getDefaultSettings(tableSettingsId)); | ||||||
|     getDefaultSettings({ settings: tableSettingsId, showAllRows }), |  | ||||||
|   ); |  | ||||||
|   const setNewColumnOrder = React.useCallback( |   const setNewColumnOrder = React.useCallback( | ||||||
|     (newOrder: string[]) => { |     (newOrder: string[]) => { | ||||||
|       setColumnOrder(newOrder); |       setColumnOrder(newOrder); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import { FormControl, FormErrorMessage, FormLabel } from '@chakra-ui/react'; | import { FormControl, FormErrorMessage, FormLabel } from '@chakra-ui/react'; | ||||||
| import { CreatableSelect, Select } from 'chakra-react-select'; | import { Select } from 'chakra-react-select'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import isEqual from 'react-fast-compare'; | import isEqual from 'react-fast-compare'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| @@ -25,7 +25,6 @@ const propTypes = { | |||||||
|   isHidden: PropTypes.bool, |   isHidden: PropTypes.bool, | ||||||
|   isPortal: PropTypes.bool.isRequired, |   isPortal: PropTypes.bool.isRequired, | ||||||
|   definitionKey: PropTypes.string, |   definitionKey: PropTypes.string, | ||||||
|   isCreatable: PropTypes.bool, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const defaultProps = { | const defaultProps = { | ||||||
| @@ -37,7 +36,6 @@ const defaultProps = { | |||||||
|   isDisabled: false, |   isDisabled: false, | ||||||
|   isHidden: false, |   isHidden: false, | ||||||
|   definitionKey: null, |   definitionKey: null, | ||||||
|   isCreatable: false, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const FastMultiSelectInput = ({ | const FastMultiSelectInput = ({ | ||||||
| @@ -52,7 +50,6 @@ const FastMultiSelectInput = ({ | |||||||
|   isRequired, |   isRequired, | ||||||
|   isDisabled, |   isDisabled, | ||||||
|   isHidden, |   isHidden, | ||||||
|   isCreatable, |  | ||||||
|   isPortal, |   isPortal, | ||||||
|   definitionKey, |   definitionKey, | ||||||
| }) => { | }) => { | ||||||
| @@ -64,32 +61,6 @@ const FastMultiSelectInput = ({ | |||||||
|         {label} |         {label} | ||||||
|         <ConfigurationFieldExplanation definitionKey={definitionKey} /> |         <ConfigurationFieldExplanation definitionKey={definitionKey} /> | ||||||
|       </FormLabel> |       </FormLabel> | ||||||
|       {isCreatable ? ( |  | ||||||
|         <CreatableSelect |  | ||||||
|           chakraStyles={{ |  | ||||||
|             control: (provided, { isDisabled: isControlDisabled }) => ({ |  | ||||||
|               ...provided, |  | ||||||
|               borderRadius: '15px', |  | ||||||
|               opacity: isControlDisabled ? '0.8 !important' : '1', |  | ||||||
|               border: '2px solid', |  | ||||||
|             }), |  | ||||||
|             dropdownIndicator: (provided) => ({ |  | ||||||
|               ...provided, |  | ||||||
|               backgroundColor: 'unset', |  | ||||||
|               border: 'unset', |  | ||||||
|             }), |  | ||||||
|           }} |  | ||||||
|           classNamePrefix={isPortal ? 'chakra-react-select' : ''} |  | ||||||
|           menuPortalTarget={isPortal ? document.body : undefined} |  | ||||||
|           isMulti |  | ||||||
|           closeMenuOnSelect={false} |  | ||||||
|           options={options} |  | ||||||
|           value={value} |  | ||||||
|           onChange={onChange} |  | ||||||
|           onBlur={onBlur} |  | ||||||
|           isDisabled={isDisabled} |  | ||||||
|         /> |  | ||||||
|       ) : ( |  | ||||||
|       <Select |       <Select | ||||||
|         chakraStyles={{ |         chakraStyles={{ | ||||||
|           control: (provided, { isDisabled: isControlDisabled }) => ({ |           control: (provided, { isDisabled: isControlDisabled }) => ({ | ||||||
| @@ -119,7 +90,6 @@ const FastMultiSelectInput = ({ | |||||||
|         onBlur={onBlur} |         onBlur={onBlur} | ||||||
|         isDisabled={isDisabled} |         isDisabled={isDisabled} | ||||||
|       /> |       /> | ||||||
|       )} |  | ||||||
|       <FormErrorMessage>{error}</FormErrorMessage> |       <FormErrorMessage>{error}</FormErrorMessage> | ||||||
|     </FormControl> |     </FormControl> | ||||||
|   ); |   ); | ||||||
|   | |||||||
| @@ -20,7 +20,6 @@ const propTypes = { | |||||||
|   canSelectAll: PropTypes.bool, |   canSelectAll: PropTypes.bool, | ||||||
|   isPortal: PropTypes.bool, |   isPortal: PropTypes.bool, | ||||||
|   definitionKey: PropTypes.string, |   definitionKey: PropTypes.string, | ||||||
|   isCreatable: PropTypes.bool, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const defaultProps = { | const defaultProps = { | ||||||
| @@ -32,7 +31,6 @@ const defaultProps = { | |||||||
|   canSelectAll: false, |   canSelectAll: false, | ||||||
|   isPortal: false, |   isPortal: false, | ||||||
|   definitionKey: null, |   definitionKey: null, | ||||||
|   isCreatable: false, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const MultiSelectField = ({ | const MultiSelectField = ({ | ||||||
| @@ -45,23 +43,12 @@ const MultiSelectField = ({ | |||||||
|   emptyIsUndefined, |   emptyIsUndefined, | ||||||
|   canSelectAll, |   canSelectAll, | ||||||
|   hasVirtualAll, |   hasVirtualAll, | ||||||
|   isCreatable, |  | ||||||
|   isPortal, |   isPortal, | ||||||
|   definitionKey, |   definitionKey, | ||||||
| }) => { | }) => { | ||||||
|   const [{ value }, { touched, error }, { setValue, setTouched }] = useField(name); |   const [{ value }, { touched, error }, { setValue, setTouched }] = useField(name); | ||||||
|  |  | ||||||
|   const onChange = useCallback( |   const onChange = useCallback((option) => { | ||||||
|     (option) => { |  | ||||||
|       if (isCreatable) { |  | ||||||
|         if (typeof option === 'string') { |  | ||||||
|           setValue([...value, option]); |  | ||||||
|         } else { |  | ||||||
|           setValue(option); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // setValue([...value, option]); |  | ||||||
|       } else { |  | ||||||
|     const allIndex = option.findIndex((opt) => opt.value === '*'); |     const allIndex = option.findIndex((opt) => opt.value === '*'); | ||||||
|     if (option.length === 0 && emptyIsUndefined) { |     if (option.length === 0 && emptyIsUndefined) { | ||||||
|       setValue(undefined); |       setValue(undefined); | ||||||
| @@ -74,10 +61,7 @@ const MultiSelectField = ({ | |||||||
|     } else if (option.length > 0) setValue(option.map((val) => val.value)); |     } else if (option.length > 0) setValue(option.map((val) => val.value)); | ||||||
|     else setValue([]); |     else setValue([]); | ||||||
|     setTouched(true); |     setTouched(true); | ||||||
|       } |   }, []); | ||||||
|     }, |  | ||||||
|     [value], |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   const onFieldBlur = useCallback(() => { |   const onFieldBlur = useCallback(() => { | ||||||
|     setTouched(true); |     setTouched(true); | ||||||
| @@ -98,7 +82,6 @@ const MultiSelectField = ({ | |||||||
|       isHidden={isHidden} |       isHidden={isHidden} | ||||||
|       isPortal={isPortal} |       isPortal={isPortal} | ||||||
|       definitionKey={definitionKey} |       definitionKey={definitionKey} | ||||||
|       isCreatable={isCreatable} |  | ||||||
|     /> |     /> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,257 +0,0 @@ | |||||||
| import React from 'react'; |  | ||||||
| import { |  | ||||||
|   Modal, |  | ||||||
|   Text, |  | ||||||
|   ModalOverlay, |  | ||||||
|   ModalContent, |  | ||||||
|   ModalBody, |  | ||||||
|   Center, |  | ||||||
|   Spinner, |  | ||||||
|   Checkbox, |  | ||||||
|   Stack, |  | ||||||
|   Table, |  | ||||||
|   Thead, |  | ||||||
|   Tbody, |  | ||||||
|   Tr, |  | ||||||
|   Th, |  | ||||||
|   Td, |  | ||||||
| } from '@chakra-ui/react'; |  | ||||||
| import { PlugsConnected } from '@phosphor-icons/react'; |  | ||||||
| import { useTranslation } from 'react-i18next'; |  | ||||||
| import { CloseButton } from 'components/Buttons/CloseButton'; |  | ||||||
| import { ResponsiveButton } from 'components/Buttons/ResponsiveButton'; |  | ||||||
| import { ModalHeader } from 'components/Containers/Modal/ModalHeader'; |  | ||||||
| import { useCableDiagnostics } from 'hooks/Network/Devices'; |  | ||||||
| import { ModalProps } from 'models/Modal'; |  | ||||||
| import Button from 'theme/components/button'; |  | ||||||
| import { DataGridColumn, useDataGrid } from 'components/DataTables/DataGrid/useDataGrid'; |  | ||||||
| import { DataGrid } from 'components/DataTables/DataGrid'; |  | ||||||
|  |  | ||||||
| export type CableDiagnosticsModalProps = { |  | ||||||
|   modalProps: ModalProps; |  | ||||||
|   serialNumber: string; |  | ||||||
|   port: string; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| type DiagnosticsRow = { |  | ||||||
|   port: string; |  | ||||||
|   linkStatus: string; |  | ||||||
|   pairA: string; |  | ||||||
|   pairB: string; |  | ||||||
|   pairC: string; |  | ||||||
|   pairD: string; |  | ||||||
|   type: string; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| type OpticalRow = { |  | ||||||
|   port: string; |  | ||||||
|   vendorName: string; |  | ||||||
|   formFactor: string; |  | ||||||
|   partNumber: string; |  | ||||||
|   serialNumber: string; |  | ||||||
|   temperature: string; |  | ||||||
|   txPower: string; |  | ||||||
|   rxPower: string; |  | ||||||
|   revision: string; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const CableDiagnosticsModal = ({ |  | ||||||
|   modalProps: { isOpen, onClose }, |  | ||||||
|   serialNumber, |  | ||||||
|   port, |  | ||||||
| }: CableDiagnosticsModalProps) => { |  | ||||||
|   const { t } = useTranslation(); |  | ||||||
|   const [selectedPorts, setSelectedPorts] = React.useState<string[]>([]); |  | ||||||
|   const [diagnosticsResult, setDiagnosticsResult] = React.useState<any>(null); |  | ||||||
|   const { mutateAsync: diagnose, isLoading } = useCableDiagnostics({ serialNumber }); |  | ||||||
|  |  | ||||||
|   const handlePortToggle = (port: string) => { |  | ||||||
|     setSelectedPorts((prev) => (prev.includes(port) ? prev.filter((p) => p !== port) : [...prev, port])); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   const handleDiagnose = async () => { |  | ||||||
|     if (port) { |  | ||||||
|       try { |  | ||||||
|         const result = await diagnose([port]); |  | ||||||
|         setDiagnosticsResult(result); |  | ||||||
|       } catch (error) { |  | ||||||
|         console.error('Error diagnosing cable:', error); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   const tableController = useDataGrid({ |  | ||||||
|     tableSettingsId: 'cable.diagnostics.table', |  | ||||||
|     defaultOrder: ['port', 'linkStatus', 'pairA', 'pairB', 'pairC', 'pairD', 'type'], |  | ||||||
|     showAllRows: true, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const columns: DataGridColumn<DiagnosticsRow | OpticalRow>[] = React.useMemo(() => { |  | ||||||
|     const data = diagnosticsResult?.results?.status?.text?.[port]; |  | ||||||
|     const isOpticalData = data && 'form-factor' in data; |  | ||||||
|  |  | ||||||
|     return isOpticalData |  | ||||||
|       ? [ |  | ||||||
|           { |  | ||||||
|             id: 'vendorName', |  | ||||||
|             header: 'Vendor Name', |  | ||||||
|             accessorKey: 'vendorName', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'formFactor', |  | ||||||
|             header: 'Form Factor', |  | ||||||
|             accessorKey: 'formFactor', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'partNumber', |  | ||||||
|             header: 'Part Number', |  | ||||||
|             accessorKey: 'partNumber', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'serialNumber', |  | ||||||
|             header: 'Serial Number', |  | ||||||
|             accessorKey: 'serialNumber', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'temperature', |  | ||||||
|             header: 'Temperature', |  | ||||||
|             accessorKey: 'temperature', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'txPower', |  | ||||||
|             header: 'TX Power', |  | ||||||
|             accessorKey: 'txPower', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'rxPower', |  | ||||||
|             header: 'RX Power', |  | ||||||
|             accessorKey: 'rxPower', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'revision', |  | ||||||
|             header: 'Revision', |  | ||||||
|             accessorKey: 'revision', |  | ||||||
|           }, |  | ||||||
|         ] |  | ||||||
|       : [ |  | ||||||
|           { |  | ||||||
|             id: 'port', |  | ||||||
|             header: 'Port', |  | ||||||
|             accessorKey: 'port', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'linkStatus', |  | ||||||
|             header: 'Link Status', |  | ||||||
|             accessorKey: 'linkStatus', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'pairA', |  | ||||||
|             header: 'Pair A', |  | ||||||
|             accessorKey: 'pairA', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'pairB', |  | ||||||
|             header: 'Pair B', |  | ||||||
|             accessorKey: 'pairB', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'pairC', |  | ||||||
|             header: 'Pair C', |  | ||||||
|             accessorKey: 'pairC', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'pairD', |  | ||||||
|             header: 'Pair D', |  | ||||||
|             accessorKey: 'pairD', |  | ||||||
|           }, |  | ||||||
|           { |  | ||||||
|             id: 'type', |  | ||||||
|             header: 'Type', |  | ||||||
|             accessorKey: 'type', |  | ||||||
|           }, |  | ||||||
|         ]; |  | ||||||
|   }, [diagnosticsResult]); |  | ||||||
|  |  | ||||||
|   const formatDiagnosticsData = (result: any): (DiagnosticsRow | OpticalRow)[] => { |  | ||||||
|     if (!result?.results?.status?.text?.[port]) return []; |  | ||||||
|  |  | ||||||
|     const data = result.results.status.text[port]; |  | ||||||
|  |  | ||||||
|     if (data['form-factor']) { |  | ||||||
|       return [ |  | ||||||
|         { |  | ||||||
|           port, |  | ||||||
|           vendorName: data['vendor-name'] || 'N/A', |  | ||||||
|           formFactor: data['form-factor'] || 'N/A', |  | ||||||
|           partNumber: data['part-number'] || 'N/A', |  | ||||||
|           serialNumber: data['serial-number'] || 'N/A', |  | ||||||
|           temperature: data.temperature ? `${data.temperature.toFixed(2)}` : 'N/A', |  | ||||||
|           txPower: data['tx-optical-power'] ? `${data['tx-optical-power']}` : 'N/A', |  | ||||||
|           rxPower: data['rx-optical-power'] ? `${data['rx-optical-power']}` : 'N/A', |  | ||||||
|           revision: data.revision || 'N/A', |  | ||||||
|         }, |  | ||||||
|       ]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return [ |  | ||||||
|       { |  | ||||||
|         port, |  | ||||||
|         linkStatus: data['link-status'], |  | ||||||
|         pairA: `${data['pair-A'].meters} (${data['pair-A'].status})`, |  | ||||||
|         pairB: `${data['pair-B'].meters} (${data['pair-B'].status})`, |  | ||||||
|         pairC: `${data['pair-C'].meters} (${data['pair-C'].status})`, |  | ||||||
|         pairD: `${data['pair-D'].meters} (${data['pair-D'].status})`, |  | ||||||
|         type: data.type, |  | ||||||
|       }, |  | ||||||
|     ]; |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <Modal onClose={onClose} isOpen={isOpen} size="xl"> |  | ||||||
|       <ModalOverlay /> |  | ||||||
|       <ModalContent maxW="50vw"> |  | ||||||
|         <ModalHeader title={t('commands.cable_diagnostics')} right={<CloseButton onClick={onClose} />} /> |  | ||||||
|         <ModalBody pb={6}> |  | ||||||
|           {isLoading ? ( |  | ||||||
|             <Center my={4} flexDirection="column" gap={4}> |  | ||||||
|               <Spinner size="lg" /> |  | ||||||
|               <Text>Please wait...</Text> |  | ||||||
|               <Text fontSize="sm" color="gray.500"> |  | ||||||
|                 Please do not close this window. This may take a few seconds. |  | ||||||
|               </Text> |  | ||||||
|             </Center> |  | ||||||
|           ) : ( |  | ||||||
|             <Center flexDirection="column" gap={4}> |  | ||||||
|               <ResponsiveButton |  | ||||||
|                 color="blue" |  | ||||||
|                 icon={<PlugsConnected size={20} />} |  | ||||||
|                 label={`${ |  | ||||||
|                   diagnosticsResult && formatDiagnosticsData(diagnosticsResult).length > 0 ? 'Retake' : 'Start' |  | ||||||
|                 } Test for Port ${port}`} |  | ||||||
|                 onClick={handleDiagnose} |  | ||||||
|                 isLoading={isLoading} |  | ||||||
|                 isDisabled={!port} |  | ||||||
|                 isCompact={false} |  | ||||||
|               /> |  | ||||||
|               {diagnosticsResult && formatDiagnosticsData(diagnosticsResult).length > 0 && ( |  | ||||||
|                 <DataGrid<DiagnosticsRow | OpticalRow> |  | ||||||
|                   controller={tableController} |  | ||||||
|                   header={{ |  | ||||||
|                     title: '', |  | ||||||
|                     objectListed: 'Cable Diagnostics', |  | ||||||
|                   }} |  | ||||||
|                   columns={columns} |  | ||||||
|                   isLoading={isLoading} |  | ||||||
|                   data={formatDiagnosticsData(diagnosticsResult)} |  | ||||||
|                   options={{ |  | ||||||
|                     isHidingControls: true, |  | ||||||
|                   }} |  | ||||||
|                 /> |  | ||||||
|               )} |  | ||||||
|             </Center> |  | ||||||
|           )} |  | ||||||
|         </ModalBody> |  | ||||||
|       </ModalContent> |  | ||||||
|     </Modal> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| @@ -59,44 +59,17 @@ const _ConfigureModal = ({ serialNumber, modalProps }: ConfigureModalProps) => { | |||||||
|     try { |     try { | ||||||
|       const config = JSON.parse(newConfig); |       const config = JSON.parse(newConfig); | ||||||
|       configure.mutate(config, { |       configure.mutate(config, { | ||||||
|         onSuccess: (data) => { |         onSuccess: () => { | ||||||
|           if (data.errorCode === 0) { |  | ||||||
|           toast({ |           toast({ | ||||||
|             id: `configure-success-${serialNumber}`, |             id: `configure-success-${serialNumber}`, | ||||||
|             title: t('common.success'), |             title: t('common.success'), | ||||||
|               description: |             description: t('controller.configure.success'), | ||||||
|                 data.status === 'pending' |  | ||||||
|                   ? 'Command is pending! It will execute once the device connects' |  | ||||||
|                   : t('controller.configure.success'), |  | ||||||
|             status: 'success', |             status: 'success', | ||||||
|             duration: 5000, |             duration: 5000, | ||||||
|             isClosable: true, |             isClosable: true, | ||||||
|             position: 'top-right', |             position: 'top-right', | ||||||
|           }); |           }); | ||||||
|           modalProps.onClose(); |           modalProps.onClose(); | ||||||
|           } else if (data.errorCode === 1) { |  | ||||||
|             toast({ |  | ||||||
|               id: `configure-warning-${serialNumber}`, |  | ||||||
|               title: 'Warning', |  | ||||||
|               description: `${data?.errorText ?? 'Unknown Warning'}`, |  | ||||||
|               status: 'warning', |  | ||||||
|               duration: 5000, |  | ||||||
|               isClosable: true, |  | ||||||
|               position: 'top-right', |  | ||||||
|             }); |  | ||||||
|             modalProps.onClose(); |  | ||||||
|           } else { |  | ||||||
|             toast({ |  | ||||||
|               id: `config-error-${serialNumber}`, |  | ||||||
|               title: t('common.error'), |  | ||||||
|               description: `${data?.errorText ?? 'Unknown Error'} (Code ${data.errorCode})`, |  | ||||||
|               status: 'error', |  | ||||||
|               duration: 5000, |  | ||||||
|               isClosable: true, |  | ||||||
|               position: 'top-right', |  | ||||||
|             }); |  | ||||||
|           } |  | ||||||
|           modalProps.onClose(); |  | ||||||
|         }, |         }, | ||||||
|       }); |       }); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|   | |||||||
| @@ -25,7 +25,6 @@ import { useTranslation } from 'react-i18next'; | |||||||
| import { Modal } from '../Modal'; | import { Modal } from '../Modal'; | ||||||
| import { lowercaseFirstLetter } from 'helpers/stringHelper'; | import { lowercaseFirstLetter } from 'helpers/stringHelper'; | ||||||
| import { useTelemetry } from 'hooks/Network/Telemetry'; | import { useTelemetry } from 'hooks/Network/Telemetry'; | ||||||
| import { secondsDuration } from 'helpers/dateFormatting'; |  | ||||||
|  |  | ||||||
| export type TelemetryModalProps = { | export type TelemetryModalProps = { | ||||||
|   serialNumber: string; |   serialNumber: string; | ||||||
| @@ -147,7 +146,8 @@ const _TelemetryModal = ({ serialNumber, modalProps }: TelemetryModalProps) => { | |||||||
|               {t('controller.telemetry.interval')}: {form.interval} {lowercaseFirstLetter(t('common.seconds'))} |               {t('controller.telemetry.interval')}: {form.interval} {lowercaseFirstLetter(t('common.seconds'))} | ||||||
|             </p> |             </p> | ||||||
|             <p> |             <p> | ||||||
|               {t('controller.telemetry.duration')}: {secondsDuration(form.lifetime, t)} |               {t('controller.telemetry.duration')}: {form.interval}{' '} | ||||||
|  |               {lowercaseFirstLetter(t('controller.telemetry.minutes'))} | ||||||
|             </p> |             </p> | ||||||
|             <p> |             <p> | ||||||
|               {t('controller.telemetry.types')}: {form.types.join(', ')} |               {t('controller.telemetry.types')}: {form.types.join(', ')} | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								src/custom.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -8,4 +8,3 @@ declare module '*.png' { | |||||||
|   const value: string; |   const value: string; | ||||||
|   export = value; |   export = value; | ||||||
| } | } | ||||||
| /// <reference types="vite-plugin-svgr/client" /> |  | ||||||
|   | |||||||
| @@ -174,37 +174,12 @@ export const useGetEventQueue = () => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const configureDevice = (serialNumber: string) => async (configuration: Record<string, unknown>) => | const configureDevice = (serialNumber: string) => async (configuration: Record<string, unknown>) => | ||||||
|   axiosGw |   axiosGw.post<unknown>(`device/${serialNumber}/configure`, { | ||||||
|     .post<unknown>(`device/${serialNumber}/configure`, { |  | ||||||
|     when: 0, |     when: 0, | ||||||
|     UUID: 1, |     UUID: 1, | ||||||
|     serialNumber, |     serialNumber, | ||||||
|     configuration, |     configuration, | ||||||
|     }) |   }); | ||||||
|     .then( |  | ||||||
|       (res) => |  | ||||||
|         res.data as Partial<{ |  | ||||||
|           UUID: string; |  | ||||||
|           attachFile: number; |  | ||||||
|           command: string; |  | ||||||
|           completed: number; |  | ||||||
|           custom: number; |  | ||||||
|           deferred: boolean; |  | ||||||
|           details: Record<string, unknown>; |  | ||||||
|           errorCode: number; |  | ||||||
|           errorText: string; |  | ||||||
|           executed: number; |  | ||||||
|           executionTime: number; |  | ||||||
|           lastTry: number; |  | ||||||
|           results: Record<string, unknown>; |  | ||||||
|           serialNumber: string; |  | ||||||
|           status: string; |  | ||||||
|           submitted: number; |  | ||||||
|           submittedBy: string; |  | ||||||
|           waitingForFile: number; |  | ||||||
|           when: number; |  | ||||||
|         }>, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
| export const useConfigureDevice = ({ serialNumber }: { serialNumber: string }) => { | export const useConfigureDevice = ({ serialNumber }: { serialNumber: string }) => { | ||||||
|   const queryClient = useQueryClient(); |   const queryClient = useQueryClient(); | ||||||
|   | |||||||
| @@ -3,12 +3,10 @@ import { axiosGw } from 'constants/axiosInstances'; | |||||||
| import { useEndpointStatus } from 'hooks/useEndpointStatus'; | import { useEndpointStatus } from 'hooks/useEndpointStatus'; | ||||||
| import { AxiosError } from 'models/Axios'; | import { AxiosError } from 'models/Axios'; | ||||||
| import { DeviceConfiguration } from 'models/Device'; | import { DeviceConfiguration } from 'models/Device'; | ||||||
| import { DevicePlatform } from './Devices'; |  | ||||||
|  |  | ||||||
| export type DefaultConfigurationResponse = { | export type DefaultConfigurationResponse = { | ||||||
|   configuration: DeviceConfiguration; |   configuration: DeviceConfiguration; | ||||||
|   created: number; |   created: number; | ||||||
|   platform: DevicePlatform; |  | ||||||
|   description: string; |   description: string; | ||||||
|   lastModified: number; |   lastModified: number; | ||||||
|   modelIds: string[]; |   modelIds: string[]; | ||||||
|   | |||||||
| @@ -9,21 +9,15 @@ import { AxiosError } from 'models/Axios'; | |||||||
| import { DeviceRttyApiResponse, GatewayDevice, WifiScanCommand, WifiScanResult } from 'models/Device'; | import { DeviceRttyApiResponse, GatewayDevice, WifiScanCommand, WifiScanResult } from 'models/Device'; | ||||||
| import { Note } from 'models/Note'; | import { Note } from 'models/Note'; | ||||||
| import { PageInfo } from 'models/Table'; | import { PageInfo } from 'models/Table'; | ||||||
| import { DeviceCommandHistory } from './Commands'; |  | ||||||
|  |  | ||||||
| export const DEVICE_PLATFORMS = ['all', 'ap', 'switch'] as const; | const getDeviceCount = () => | ||||||
| export type DevicePlatform = (typeof DEVICE_PLATFORMS)[number]; |   axiosGw.get('devices?countOnly=true').then((response) => response.data) as Promise<{ count: number }>; | ||||||
|  |  | ||||||
| const getDeviceCount = (platform: DevicePlatform) => | export const useGetDeviceCount = ({ enabled }: { enabled: boolean }) => { | ||||||
|   axiosGw.get(`devices?countOnly=true&platform=${platform}`).then((response) => response.data) as Promise<{ |  | ||||||
|     count: number; |  | ||||||
|   }>; |  | ||||||
|  |  | ||||||
| export const useGetDeviceCount = ({ enabled, platform = 'all' }: { enabled: boolean; platform?: DevicePlatform }) => { |  | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const toast = useToast(); |   const toast = useToast(); | ||||||
|  |  | ||||||
|   return useQuery(['devices', 'count', { platform }], () => getDeviceCount(platform), { |   return useQuery(['devices', 'count'], getDeviceCount, { | ||||||
|     enabled, |     enabled, | ||||||
|     onError: (e: AxiosError) => { |     onError: (e: AxiosError) => { | ||||||
|       if (!toast.isActive('inventory-fetching-error')) |       if (!toast.isActive('inventory-fetching-error')) | ||||||
| @@ -48,14 +42,13 @@ export type DeviceWithStatus = { | |||||||
|   associations_2G: number; |   associations_2G: number; | ||||||
|   associations_5G: number; |   associations_5G: number; | ||||||
|   associations_6G: number; |   associations_6G: number; | ||||||
|   blackListed?: boolean; |  | ||||||
|   compatible: string; |   compatible: string; | ||||||
|   connected: boolean; |   connected: boolean; | ||||||
|   connectReason?: string; |   connectReason?: string; | ||||||
|   certificateExpiryDate?: number; |   certificateExpiryDate?: number; | ||||||
|   createdTimestamp: number; |   createdTimestamp: number; | ||||||
|   devicePassword: string; |   devicePassword: string; | ||||||
|   deviceType: 'ap' | 'switch'; |   deviceType: 'AP' | 'SWITCH' | 'IOT' | 'MESH'; | ||||||
|   entity: string; |   entity: string; | ||||||
|   firmware: string; |   firmware: string; | ||||||
|   fwUpdatePolicy: string; |   fwUpdatePolicy: string; | ||||||
| @@ -102,27 +95,25 @@ export const getSingleDeviceWithStatus = (serialNumber: string) => | |||||||
|     }) |     }) | ||||||
|     .catch(() => undefined); |     .catch(() => undefined); | ||||||
|  |  | ||||||
| const getDevices = (limit: number, offset: number, platform: DevicePlatform) => | const getDevices = (limit: number, offset: number) => | ||||||
|   axiosGw |   axiosGw | ||||||
|     .get(`devices?deviceWithStatus=true&limit=${limit}&offset=${offset}&platform=${platform}`) |     .get(`devices?deviceWithStatus=true&limit=${limit}&offset=${offset}`) | ||||||
|     .then((response) => response.data) as Promise<{ devicesWithStatus: DeviceWithStatus[] }>; |     .then((response) => response.data) as Promise<{ devicesWithStatus: DeviceWithStatus[] }>; | ||||||
|  |  | ||||||
| export const useGetDevices = ({ | export const useGetDevices = ({ | ||||||
|   pageInfo, |   pageInfo, | ||||||
|   enabled, |   enabled, | ||||||
|   onError, |   onError, | ||||||
|   platform = 'all', |  | ||||||
| }: { | }: { | ||||||
|   pageInfo?: PageInfo; |   pageInfo?: PageInfo; | ||||||
|   enabled: boolean; |   enabled: boolean; | ||||||
|   onError?: (e: AxiosError) => void; |   onError?: (e: AxiosError) => void; | ||||||
|   platform?: DevicePlatform; |  | ||||||
| }) => { | }) => { | ||||||
|   const offset = pageInfo?.limit !== undefined ? pageInfo.limit * pageInfo.index : 0; |   const offset = pageInfo?.limit !== undefined ? pageInfo.limit * pageInfo.index : 0; | ||||||
|  |  | ||||||
|   return useQuery( |   return useQuery( | ||||||
|     ['devices', 'all', { limit: pageInfo?.limit, offset, platform }], |     ['devices', 'all', { limit: pageInfo?.limit, offset }], | ||||||
|     () => getDevices(pageInfo?.limit || 0, offset, platform), |     () => getDevices(pageInfo?.limit || 0, offset), | ||||||
|     { |     { | ||||||
|       keepPreviousData: true, |       keepPreviousData: true, | ||||||
|       enabled: enabled && pageInfo !== undefined, |       enabled: enabled && pageInfo !== undefined, | ||||||
| @@ -132,28 +123,22 @@ export const useGetDevices = ({ | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const getAllDevices = async (platform: DevicePlatform) => { | const getAllDevices = async () => { | ||||||
|   let offset = 0; |   let offset = 0; | ||||||
|   let devices: DeviceWithStatus[] = []; |   let devices: DeviceWithStatus[] = []; | ||||||
|   let devicesResponse: { devicesWithStatus: DeviceWithStatus[] }; |   let devicesResponse: { devicesWithStatus: DeviceWithStatus[] }; | ||||||
|   do { |   do { | ||||||
|     // eslint-disable-next-line no-await-in-loop |     // eslint-disable-next-line no-await-in-loop | ||||||
|     devicesResponse = await getDevices(500, offset, platform); |     devicesResponse = await getDevices(500, offset); | ||||||
|     devices = devices.concat(devicesResponse.devicesWithStatus); |     devices = devices.concat(devicesResponse.devicesWithStatus); | ||||||
|     offset += 500; |     offset += 500; | ||||||
|   } while (devicesResponse.devicesWithStatus.length === 500); |   } while (devicesResponse.devicesWithStatus.length === 500); | ||||||
|   return devices; |   return devices; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const useGetAllDevicesWithStatus = ({ | export const useGetAllDevicesWithStatus = ({ onError }: { onError?: (e: AxiosError) => void }) => { | ||||||
|   onError, |  | ||||||
|   platform = 'all', |  | ||||||
| }: { |  | ||||||
|   onError?: (e: AxiosError) => void; |  | ||||||
|   platform?: DevicePlatform; |  | ||||||
| }) => { |  | ||||||
|   const { isReady } = useEndpointStatus('owgw'); |   const { isReady } = useEndpointStatus('owgw'); | ||||||
|   return useQuery(['devices', 'all', 'full', { platform }], () => getAllDevices(platform), { |   return useQuery(['devices', 'all', 'full'], getAllDevices, { | ||||||
|     enabled: isReady && false, |     enabled: isReady && false, | ||||||
|     onError, |     onError, | ||||||
|   }); |   }); | ||||||
| @@ -377,40 +362,6 @@ export const useWifiScanDevice = ({ serialNumber }: { serialNumber: string }) => | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const useCableDiagnostics = ({ serialNumber }: { serialNumber: string }) => { |  | ||||||
|   const toast = useToast(); |  | ||||||
|   const { t } = useTranslation(); |  | ||||||
|  |  | ||||||
|   return useMutation( |  | ||||||
|     (ports: string[]): Promise<unknown> => |  | ||||||
|       axiosGw |  | ||||||
|         .post(`device/${serialNumber}/cable-diagnostics`, { |  | ||||||
|           serial: serialNumber, |  | ||||||
|           ports, |  | ||||||
|           when: 0, |  | ||||||
|         }) |  | ||||||
|         .then(({ data }) => data), |  | ||||||
|     { |  | ||||||
|       onSuccess: (data) => { |  | ||||||
|         console.log('Success data: ', data); |  | ||||||
|       }, |  | ||||||
|       onError: (e: AxiosError) => { |  | ||||||
|         toast({ |  | ||||||
|           id: uuid(), |  | ||||||
|           title: t('common.error'), |  | ||||||
|           description: t('commands.cablediagnostics_error', { |  | ||||||
|             e: e?.response?.data?.ErrorDescription, |  | ||||||
|           }), |  | ||||||
|           status: 'error', |  | ||||||
|           duration: 5000, |  | ||||||
|           isClosable: true, |  | ||||||
|           position: 'top-right', |  | ||||||
|         }); |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const useGetDeviceRtty = ({ serialNumber, extraId }: { serialNumber: string; extraId: string | number }) => { | export const useGetDeviceRtty = ({ serialNumber, extraId }: { serialNumber: string; extraId: string | number }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const toast = useToast(); |   const toast = useToast(); | ||||||
| @@ -480,45 +431,3 @@ export const useUpdateDevice = ({ serialNumber }: { serialNumber: string }) => { | |||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const deleteDeviceBatch = async (pattern: string) => { |  | ||||||
|   if (pattern.length < 6) throw new Error('Pattern must be at least 6 characters long'); |  | ||||||
|  |  | ||||||
|   axiosGw.delete(`devices?macPattern=${pattern}`); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const useDeleteDeviceBatch = () => { |  | ||||||
|   const queryClient = useQueryClient(); |  | ||||||
|  |  | ||||||
|   return useMutation(deleteDeviceBatch, { |  | ||||||
|     onSuccess: () => { |  | ||||||
|       queryClient.invalidateQueries(['devices']); |  | ||||||
|     }, |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export type PowerCyclePort = { |  | ||||||
|   /** Ex.: Ethernet0 */ |  | ||||||
|   name: string; |  | ||||||
|   /** Cycle length in MS. Default is 10 000 */ |  | ||||||
|   cycle?: number; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export type PowerCycleRequest = { |  | ||||||
|   serial: string; |  | ||||||
|   when: number; |  | ||||||
|   ports: PowerCyclePort[]; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const usePowerCycle = () => { |  | ||||||
|   const queryClient = useQueryClient(); |  | ||||||
|   return useMutation( |  | ||||||
|     (request: PowerCycleRequest) => |  | ||||||
|       axiosGw.post(`device/${request.serial}/powercycle`, request).then((res) => res.data as DeviceCommandHistory), |  | ||||||
|     { |  | ||||||
|       onSettled: () => { |  | ||||||
|         queryClient.invalidateQueries(['commands']); |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|   | |||||||
| @@ -70,34 +70,15 @@ export const useUpdateDeviceFirmware = ({ serialNumber, onClose }: { serialNumbe | |||||||
|  |  | ||||||
|   return useMutation( |   return useMutation( | ||||||
|     ({ keepRedirector, uri, signature }: { keepRedirector: boolean; uri: string; signature?: string }) => |     ({ keepRedirector, uri, signature }: { keepRedirector: boolean; uri: string; signature?: string }) => | ||||||
|       axiosGw |       axiosGw.post(`device/${serialNumber}/upgrade${signature ? `?FWsignature=${signature}` : ''}`, { | ||||||
|         .post(`device/${serialNumber}/upgrade${signature ? `?FWsignature=${signature}` : ''}`, { |  | ||||||
|         serialNumber, |         serialNumber, | ||||||
|         when: 0, |         when: 0, | ||||||
|         keepRedirector, |         keepRedirector, | ||||||
|         uri, |         uri, | ||||||
|         signature, |         signature, | ||||||
|         }) |       }), | ||||||
|         .then( |  | ||||||
|           (response) => |  | ||||||
|             response as { |  | ||||||
|               data: { |  | ||||||
|                 errorCode: number; |  | ||||||
|                 errorText: string; |  | ||||||
|                 status: string; |  | ||||||
|                 results?: { |  | ||||||
|                   status?: { |  | ||||||
|                     error?: number; |  | ||||||
|                     resultCode?: number; |  | ||||||
|                     text?: string; |  | ||||||
|                   }; |  | ||||||
|                 }; |  | ||||||
|               }; |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|     { |     { | ||||||
|       onSuccess: ({ data }) => { |       onSuccess: () => { | ||||||
|         if (data.errorCode === 0) { |  | ||||||
|         toast({ |         toast({ | ||||||
|           id: `device-upgrade-success-${uuid()}`, |           id: `device-upgrade-success-${uuid()}`, | ||||||
|           title: t('common.success'), |           title: t('common.success'), | ||||||
| @@ -108,28 +89,6 @@ export const useUpdateDeviceFirmware = ({ serialNumber, onClose }: { serialNumbe | |||||||
|           position: 'top-right', |           position: 'top-right', | ||||||
|         }); |         }); | ||||||
|         onClose(); |         onClose(); | ||||||
|         } else if (data.errorCode === 1) { |  | ||||||
|           toast({ |  | ||||||
|             id: `device-upgrade-warning-${uuid()}`, |  | ||||||
|             title: 'Warning', |  | ||||||
|             description: `${data?.errorText ?? 'Unknown Warning'}`, |  | ||||||
|             status: 'warning', |  | ||||||
|             duration: 5000, |  | ||||||
|             isClosable: true, |  | ||||||
|             position: 'top-right', |  | ||||||
|           }); |  | ||||||
|           onClose(); |  | ||||||
|         } else { |  | ||||||
|           toast({ |  | ||||||
|             id: `device-upgrade-error-${uuid()}`, |  | ||||||
|             title: t('common.error'), |  | ||||||
|             description: `${data?.errorText ?? 'Unknown Error'} (Code ${data.errorCode})`, |  | ||||||
|             status: 'error', |  | ||||||
|             duration: 5000, |  | ||||||
|             isClosable: true, |  | ||||||
|             position: 'top-right', |  | ||||||
|           }); |  | ||||||
|         } |  | ||||||
|       }, |       }, | ||||||
|       onError: (e: AxiosError) => { |       onError: (e: AxiosError) => { | ||||||
|         toast({ |         toast({ | ||||||
|   | |||||||
| @@ -2,24 +2,7 @@ import { useQuery } from '@tanstack/react-query'; | |||||||
| import { axiosGw } from 'constants/axiosInstances'; | import { axiosGw } from 'constants/axiosInstances'; | ||||||
| import { AxiosError } from 'models/Axios'; | import { AxiosError } from 'models/Axios'; | ||||||
|  |  | ||||||
| export type DeviceLinkState = { | type DeviceInterfaceStatistics = { | ||||||
|   carrier?: number; |  | ||||||
|   counters?: { |  | ||||||
|     collisions: number; |  | ||||||
|     multicast: number; |  | ||||||
|     rx_bytes: number; |  | ||||||
|     rx_dropped: number; |  | ||||||
|     rx_errors: number; |  | ||||||
|     rx_packets: number; |  | ||||||
|     tx_bytes: number; |  | ||||||
|     tx_dropped: number; |  | ||||||
|     tx_errors: number; |  | ||||||
|     tx_packets: number; |  | ||||||
|   }; |  | ||||||
|   duplex?: string; |  | ||||||
|   speed?: number; |  | ||||||
| }; |  | ||||||
| export type DeviceInterfaceStatistics = { |  | ||||||
|   clients: { |   clients: { | ||||||
|     ipv4_addresses?: string[]; |     ipv4_addresses?: string[]; | ||||||
|     ipv6_addresses?: string[]; |     ipv6_addresses?: string[]; | ||||||
| @@ -59,7 +42,6 @@ export type DeviceInterfaceStatistics = { | |||||||
|       dynamic_vlan?: number; |       dynamic_vlan?: number; | ||||||
|       inactive: number; |       inactive: number; | ||||||
|       ipaddr_v4: string; |       ipaddr_v4: string; | ||||||
|       fingerprint?: object; |  | ||||||
|       rssi: number; |       rssi: number; | ||||||
|       rx_bytes: number; |       rx_bytes: number; | ||||||
|       rx_duration: number; |       rx_duration: number; | ||||||
| @@ -130,21 +112,11 @@ export type DeviceStatistics = { | |||||||
|     channel: number; |     channel: number; | ||||||
|     band?: string[]; |     band?: string[]; | ||||||
|     channel_width: string; |     channel_width: string; | ||||||
|     noise?: number; |     noise: number; | ||||||
|     phy: string; |     phy: string; | ||||||
|     receive_ms: number; |     receive_ms: number; | ||||||
|     transmit_ms: number; |     transmit_ms: number; | ||||||
|     temperature?: number; |  | ||||||
|     tx_power: number; |     tx_power: number; | ||||||
|     frequency?: number[]; |  | ||||||
|     survey?: { |  | ||||||
|       busy: number; |  | ||||||
|       frequency: number; |  | ||||||
|       noise: number; |  | ||||||
|       time: number; |  | ||||||
|       time_rx: number; |  | ||||||
|       time_tx: number; |  | ||||||
|     }[]; |  | ||||||
|   }[]; |   }[]; | ||||||
|   dynamic_vlans?: { |   dynamic_vlans?: { | ||||||
|     vid: number; |     vid: number; | ||||||
| @@ -166,10 +138,18 @@ export type DeviceStatistics = { | |||||||
|   }; |   }; | ||||||
|   'link-state'?: { |   'link-state'?: { | ||||||
|     downstream: { |     downstream: { | ||||||
|       [key: string]: DeviceLinkState; |       eth1?: { | ||||||
|  |         carrier?: number; | ||||||
|  |         duplex?: string; | ||||||
|  |         speed?: number; | ||||||
|  |       }; | ||||||
|     }; |     }; | ||||||
|     upstream: { |     upstream: { | ||||||
|       [key: string]: DeviceLinkState; |       eth0?: { | ||||||
|  |         carrier?: number; | ||||||
|  |         duplex?: string; | ||||||
|  |         speed?: number; | ||||||
|  |       }; | ||||||
|     }; |     }; | ||||||
|   }; |   }; | ||||||
|   'lldp-peers'?: { |   'lldp-peers'?: { | ||||||
| @@ -210,7 +190,6 @@ export const useGetDeviceLastStats = ({ | |||||||
|   useQuery(['device', serialNumber, 'last-statistics'], () => getLastStats(serialNumber), { |   useQuery(['device', serialNumber, 'last-statistics'], () => getLastStats(serialNumber), { | ||||||
|     enabled: serialNumber !== undefined && serialNumber !== '', |     enabled: serialNumber !== undefined && serialNumber !== '', | ||||||
|     staleTime: 1000 * 60, |     staleTime: 1000 * 60, | ||||||
|     refetchInterval: 1000 * 60, |  | ||||||
|     onError, |     onError, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,16 @@ | |||||||
| import * as React from 'react'; | import * as React from 'react'; | ||||||
| import { Flex, Heading, Icon, Text, Tooltip, VStack } from '@chakra-ui/react'; | import { | ||||||
| import { ArrowSquareDown, ArrowSquareUp } from '@phosphor-icons/react'; |   Box, | ||||||
|  |   CircularProgress, | ||||||
|  |   CircularProgressLabel, | ||||||
|  |   Flex, | ||||||
|  |   Heading, | ||||||
|  |   Icon, | ||||||
|  |   Text, | ||||||
|  |   Tooltip, | ||||||
|  |   VStack, | ||||||
|  | } from '@chakra-ui/react'; | ||||||
|  | import { ArrowSquareDown, ArrowSquareUp, Clock } from '@phosphor-icons/react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import { Card } from 'components/Containers/Card'; | import { Card } from 'components/Containers/Card'; | ||||||
| import { compactSecondsToDetailed, minimalSecondsToDetailed } from 'helpers/dateFormatting'; | import { compactSecondsToDetailed, minimalSecondsToDetailed } from 'helpers/dateFormatting'; | ||||||
| @@ -10,26 +20,74 @@ import { useGetDevicesStats } from 'hooks/Network/Devices'; | |||||||
| const SidebarDevices = () => { | const SidebarDevices = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const getStats = useGetDevicesStats({}); |   const getStats = useGetDevicesStats({}); | ||||||
|  |   const [lastTime, setLastTime] = React.useState<Date | undefined>(); | ||||||
|  |   const [lastUpdate, setLastUpdate] = React.useState<Date | undefined>(); | ||||||
|  |  | ||||||
|  |   const time = React.useMemo(() => { | ||||||
|  |     if (lastTime === undefined || lastUpdate === undefined) return null; | ||||||
|  |  | ||||||
|  |     const seconds = lastTime.getTime() - lastUpdate.getTime(); | ||||||
|  |  | ||||||
|  |     return Math.max(0, Math.floor(seconds / 1000)); | ||||||
|  |   }, [lastTime, lastUpdate]); | ||||||
|  |  | ||||||
|  |   const circleColor = () => { | ||||||
|  |     if (time === null) return 'gray.300'; | ||||||
|  |     if (time < 10) return 'green.300'; | ||||||
|  |     if (time < 30) return 'yellow.300'; | ||||||
|  |     return 'red.300'; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   React.useEffect(() => { | ||||||
|  |     setLastUpdate(new Date()); | ||||||
|  |   }, [getStats.data]); | ||||||
|  |  | ||||||
|  |   React.useEffect(() => { | ||||||
|  |     const interval = setInterval(() => { | ||||||
|  |       setLastTime(new Date()); | ||||||
|  |     }, 1000); | ||||||
|  |     return () => { | ||||||
|  |       clearInterval(interval); | ||||||
|  |     }; | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|   if (!getStats.data) return null; |   if (!getStats.data) return null; | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Card p={4}> |     <Card p={4}> | ||||||
|  |       <Tooltip hasArrow label={t('controller.stats.seconds_ago', { s: time })}> | ||||||
|  |         <CircularProgress | ||||||
|  |           isIndeterminate | ||||||
|  |           color={circleColor()} | ||||||
|  |           position="absolute" | ||||||
|  |           right="6px" | ||||||
|  |           top="6px" | ||||||
|  |           w="unset" | ||||||
|  |           size={6} | ||||||
|  |           thickness="14px" | ||||||
|  |         > | ||||||
|  |           <CircularProgressLabel fontSize="1.9em">{time}s</CircularProgressLabel> | ||||||
|  |         </CircularProgress> | ||||||
|  |       </Tooltip> | ||||||
|  |       <Tooltip hasArrow label={t('controller.stats.seconds_ago', { s: time })}> | ||||||
|  |         <Box position="absolute" right="8px" top="8px" w="unset" hidden> | ||||||
|  |           <Clock size={16} /> | ||||||
|  |         </Box> | ||||||
|  |       </Tooltip> | ||||||
|       <VStack mb={-1}> |       <VStack mb={-1}> | ||||||
|         <Flex flexDir="column" textAlign="center"> |         <Flex flexDir="column" textAlign="center"> | ||||||
|  |           <Heading size="md">{getStats.data.connectedDevices}</Heading> | ||||||
|           <Heading size="xs" display="flex" justifyContent="center"> |           <Heading size="xs" display="flex" justifyContent="center"> | ||||||
|             <Text> |             <Text> | ||||||
|               {t('common.connected')} {t('devices.title')}{' '} |               {t('common.connected')} {t('devices.title')}{' '} | ||||||
|             </Text>{' '} |             </Text>{' '} | ||||||
|           </Heading> |           </Heading> | ||||||
|           <Heading size="md">{getStats.data.connectedDevices}</Heading> |  | ||||||
|           <Heading size="xs">{t('controller.devices.average_uptime')}</Heading> |  | ||||||
|           <Tooltip hasArrow label={compactSecondsToDetailed(getStats.data.averageConnectionTime, t)}> |           <Tooltip hasArrow label={compactSecondsToDetailed(getStats.data.averageConnectionTime, t)}> | ||||||
|             <Heading size="md" textAlign="center" mt={1}> |             <Heading size="md" textAlign="center" mt={1}> | ||||||
|               {minimalSecondsToDetailed(getStats.data.averageConnectionTime, t)} |               {minimalSecondsToDetailed(getStats.data.averageConnectionTime, t)} | ||||||
|             </Heading> |             </Heading> | ||||||
|           </Tooltip> |           </Tooltip> | ||||||
|  |           <Heading size="xs">{t('controller.devices.average_uptime')}</Heading> | ||||||
|           <Flex fontSize="sm" fontWeight="bold" alignItems="center" justifyContent="center" mt={1}> |           <Flex fontSize="sm" fontWeight="bold" alignItems="center" justifyContent="center" mt={1}> | ||||||
|             <Tooltip hasArrow label="Rx"> |             <Tooltip hasArrow label="Rx"> | ||||||
|               <Flex alignItems="center" mr={1}> |               <Flex alignItems="center" mr={1}> | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ import { Note } from './Note'; | |||||||
|  |  | ||||||
| export interface GatewayDevice { | export interface GatewayDevice { | ||||||
|   UUID: number; |   UUID: number; | ||||||
|   blackListed?: boolean; |  | ||||||
|   certificateExpiryDate: number; |   certificateExpiryDate: number; | ||||||
|   compatible: string; |   compatible: string; | ||||||
|   configuration: unknown; |   configuration: unknown; | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import * as React from 'react'; | import * as React from 'react'; | ||||||
| import { Box, Flex, useBoolean, useDisclosure, useToast } from '@chakra-ui/react'; | import { Box, SimpleGrid, useBoolean, useDisclosure, useToast } from '@chakra-ui/react'; | ||||||
| import { Formik, FormikProps } from 'formik'; | import { Formik, FormikProps } from 'formik'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import { v4 as uuid } from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
| @@ -16,7 +16,6 @@ import { useGetDeviceTypes } from 'hooks/Network/Firmware'; | |||||||
| import { useFormModal } from 'hooks/useFormModal'; | import { useFormModal } from 'hooks/useFormModal'; | ||||||
| import { useFormRef } from 'hooks/useFormRef'; | import { useFormRef } from 'hooks/useFormRef'; | ||||||
| import { AxiosError } from 'models/Axios'; | import { AxiosError } from 'models/Axios'; | ||||||
| import { SelectField } from 'components/Form/Fields/SelectField'; |  | ||||||
|  |  | ||||||
| const CreateDefaultConfigurationModal = () => { | const CreateDefaultConfigurationModal = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
| @@ -69,9 +68,7 @@ const CreateDefaultConfigurationModal = () => { | |||||||
|             key={formKey} |             key={formKey} | ||||||
|             validationSchema={DefaultConfigurationSchema(t)} |             validationSchema={DefaultConfigurationSchema(t)} | ||||||
|             onSubmit={(data, { setSubmitting, resetForm }) => { |             onSubmit={(data, { setSubmitting, resetForm }) => { | ||||||
|               createConfig.mutateAsync( |               createConfig.mutateAsync(data, { | ||||||
|                 { ...data, modelIds: data.modelIds.map((v) => v.value) }, |  | ||||||
|                 { |  | ||||||
|                 onSuccess: () => { |                 onSuccess: () => { | ||||||
|                   toast({ |                   toast({ | ||||||
|                     id: `config-create-success`, |                     id: `config-create-success`, | ||||||
| @@ -99,33 +96,14 @@ const CreateDefaultConfigurationModal = () => { | |||||||
|                   }); |                   }); | ||||||
|                   setSubmitting(false); |                   setSubmitting(false); | ||||||
|                 }, |                 }, | ||||||
|                 }, |               }); | ||||||
|               ); |  | ||||||
|             }} |             }} | ||||||
|           > |           > | ||||||
|             <Box> |             <Box> | ||||||
|               <Flex mb={4}> |               <SimpleGrid spacing={4} minChildWidth="200px"> | ||||||
|                 <StringField |                 <StringField name="name" label={t('common.name')} isRequired isDisabled={isDisabled} /> | ||||||
|                   name="name" |                 <StringField name="description" label={t('common.description')} isDisabled={isDisabled} /> | ||||||
|                   label={t('common.name')} |               </SimpleGrid> | ||||||
|                   isRequired |  | ||||||
|                   isDisabled={isDisabled} |  | ||||||
|                   maxW="340px" |  | ||||||
|                   mr={4} |  | ||||||
|                 /> |  | ||||||
|                 <SelectField |  | ||||||
|                   name="platform" |  | ||||||
|                   label="Platform" |  | ||||||
|                   options={[ |  | ||||||
|                     { label: 'AP', value: 'ap' }, |  | ||||||
|                     { label: 'Switch', value: 'switch' }, |  | ||||||
|                   ]} |  | ||||||
|                   isRequired |  | ||||||
|                   isDisabled={isDisabled} |  | ||||||
|                   w="max-content" |  | ||||||
|                 /> |  | ||||||
|               </Flex> |  | ||||||
|               <StringField name="description" label={t('common.description')} isDisabled={isDisabled} mb={4} /> |  | ||||||
|               <MultiSelectField |               <MultiSelectField | ||||||
|                 name="modelIds" |                 name="modelIds" | ||||||
|                 label={t('controller.dashboard.device_types')} |                 label={t('controller.dashboard.device_types')} | ||||||
| @@ -136,10 +114,9 @@ const CreateDefaultConfigurationModal = () => { | |||||||
|                     value: devType, |                     value: devType, | ||||||
|                   })) ?? [] |                   })) ?? [] | ||||||
|                 } |                 } | ||||||
|                 isCreatable |  | ||||||
|                 isRequired |                 isRequired | ||||||
|               /> |               /> | ||||||
|               <StringField name="configuration" label={t('configurations.one')} isArea isDisabled={isDisabled} mt={4} /> |               <StringField name="configuration" label={t('configurations.one')} isArea isDisabled={isDisabled} /> | ||||||
|             </Box> |             </Box> | ||||||
|           </Formik> |           </Formik> | ||||||
|         </Box> |         </Box> | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import * as React from 'react'; | import * as React from 'react'; | ||||||
| import { Box, Flex, useBoolean, UseDisclosureReturn, useToast } from '@chakra-ui/react'; | import { Box, SimpleGrid, useBoolean, UseDisclosureReturn, useToast } from '@chakra-ui/react'; | ||||||
| import { Formik, FormikProps } from 'formik'; | import { Formik, FormikProps } from 'formik'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import { v4 as uuid } from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
| @@ -15,7 +15,6 @@ import { useGetDeviceTypes } from 'hooks/Network/Firmware'; | |||||||
| import { useFormModal } from 'hooks/useFormModal'; | import { useFormModal } from 'hooks/useFormModal'; | ||||||
| import { useFormRef } from 'hooks/useFormRef'; | import { useFormRef } from 'hooks/useFormRef'; | ||||||
| import { AxiosError } from 'models/Axios'; | import { AxiosError } from 'models/Axios'; | ||||||
| import { SelectField } from 'components/Form/Fields/SelectField'; |  | ||||||
|  |  | ||||||
| type Props = { | type Props = { | ||||||
|   modalProps: UseDisclosureReturn; |   modalProps: UseDisclosureReturn; | ||||||
| @@ -70,15 +69,12 @@ const EditDefaultConfiguration = ({ modalProps, config }: Props) => { | |||||||
|               innerRef={formRef as React.Ref<FormikProps<DefaultConfigurationResponse>>} |               innerRef={formRef as React.Ref<FormikProps<DefaultConfigurationResponse>>} | ||||||
|               initialValues={{ |               initialValues={{ | ||||||
|                 ...config, |                 ...config, | ||||||
|                 modelIds: config.modelIds.map((v) => ({ label: v, value: v })), |  | ||||||
|                 configuration: JSON.stringify(config.configuration, null, 2), |                 configuration: JSON.stringify(config.configuration, null, 2), | ||||||
|               }} |               }} | ||||||
|               key={formKey} |               key={formKey} | ||||||
|               validationSchema={DefaultConfigurationSchema(t)} |               validationSchema={DefaultConfigurationSchema(t)} | ||||||
|               onSubmit={(data, { setSubmitting, resetForm }) => { |               onSubmit={(data, { setSubmitting, resetForm }) => { | ||||||
|                 updateConfig.mutateAsync( |                 updateConfig.mutateAsync(data, { | ||||||
|                   { ...data, modelIds: data.modelIds.map((v) => v.value) }, |  | ||||||
|                   { |  | ||||||
|                   onSuccess: () => { |                   onSuccess: () => { | ||||||
|                     toast({ |                     toast({ | ||||||
|                       id: `config-edit-success`, |                       id: `config-edit-success`, | ||||||
| @@ -106,33 +102,14 @@ const EditDefaultConfiguration = ({ modalProps, config }: Props) => { | |||||||
|                     }); |                     }); | ||||||
|                     setSubmitting(false); |                     setSubmitting(false); | ||||||
|                   }, |                   }, | ||||||
|                   }, |                 }); | ||||||
|                 ); |  | ||||||
|               }} |               }} | ||||||
|             > |             > | ||||||
|               <Box> |               <Box> | ||||||
|                 <Flex mb={4}> |                 <SimpleGrid spacing={4} minChildWidth="200px"> | ||||||
|                   <StringField |                   <StringField name="name" label={t('common.name')} isRequired isDisabled={isDisabled} /> | ||||||
|                     name="name" |                   <StringField name="description" label={t('common.description')} isDisabled={isDisabled} /> | ||||||
|                     label={t('common.name')} |                 </SimpleGrid> | ||||||
|                     isRequired |  | ||||||
|                     isDisabled={isDisabled} |  | ||||||
|                     maxW="340px" |  | ||||||
|                     mr={4} |  | ||||||
|                   /> |  | ||||||
|                   <SelectField |  | ||||||
|                     name="platform" |  | ||||||
|                     label="Platform" |  | ||||||
|                     options={[ |  | ||||||
|                       { label: 'AP', value: 'ap' }, |  | ||||||
|                       { label: 'Switch', value: 'switch' }, |  | ||||||
|                     ]} |  | ||||||
|                     isRequired |  | ||||||
|                     isDisabled |  | ||||||
|                     w="max-content" |  | ||||||
|                   /> |  | ||||||
|                 </Flex> |  | ||||||
|                 <StringField name="description" label={t('common.description')} isDisabled={isDisabled} mb={4} /> |  | ||||||
|                 <MultiSelectField |                 <MultiSelectField | ||||||
|                   name="modelIds" |                   name="modelIds" | ||||||
|                   label={t('controller.dashboard.device_types')} |                   label={t('controller.dashboard.device_types')} | ||||||
| @@ -143,16 +120,9 @@ const EditDefaultConfiguration = ({ modalProps, config }: Props) => { | |||||||
|                       value: devType, |                       value: devType, | ||||||
|                     })) ?? [] |                     })) ?? [] | ||||||
|                   } |                   } | ||||||
|                   isCreatable |  | ||||||
|                   isRequired |                   isRequired | ||||||
|                 /> |                 /> | ||||||
|                 <StringField |                 <StringField name="configuration" label={t('configurations.one')} isArea isDisabled={isDisabled} /> | ||||||
|                   name="configuration" |  | ||||||
|                   label={t('configurations.one')} |  | ||||||
|                   isArea |  | ||||||
|                   isDisabled={isDisabled} |  | ||||||
|                   mt={4} |  | ||||||
|                 /> |  | ||||||
|               </Box> |               </Box> | ||||||
|             </Formik> |             </Formik> | ||||||
|           )} |           )} | ||||||
|   | |||||||
| @@ -58,14 +58,6 @@ const DefaultConfigurationsList = () => { | |||||||
|         Cell: ({ cell }) => dateCell(cell.row.original.lastModified), |         Cell: ({ cell }) => dateCell(cell.row.original.lastModified), | ||||||
|         customWidth: '50px', |         customWidth: '50px', | ||||||
|       }, |       }, | ||||||
|       { |  | ||||||
|         id: 'platform', |  | ||||||
|         Header: 'Platform', |  | ||||||
|         Footer: '', |  | ||||||
|         accessor: 'platform', |  | ||||||
|         Cell: ({ cell }) => cell.row.original.platform.toUpperCase(), |  | ||||||
|         customWidth: '50px', |  | ||||||
|       }, |  | ||||||
|       { |       { | ||||||
|         id: 'modelIds', |         id: 'modelIds', | ||||||
|         Header: t('controller.dashboard.device_types'), |         Header: t('controller.dashboard.device_types'), | ||||||
|   | |||||||
| @@ -6,8 +6,7 @@ export const DefaultConfigurationSchema = (t: (str: string) => string) => | |||||||
|     .shape({ |     .shape({ | ||||||
|       name: Yup.string().required(t('form.required')), |       name: Yup.string().required(t('form.required')), | ||||||
|       description: Yup.string(), |       description: Yup.string(), | ||||||
|       modelIds: Yup.array().of(Yup.object()).required(t('form.required')).min(1, t('form.required')), |       modelIds: Yup.array().of(Yup.string()).required(t('form.required')).min(1, t('form.required')), | ||||||
|       platform: Yup.string().oneOf(['ap', 'switch']).required(t('form.required')), |  | ||||||
|       configuration: Yup.string() |       configuration: Yup.string() | ||||||
|         .required(t('form.required')) |         .required(t('form.required')) | ||||||
|         .test('configuration', t('form.invalid_json'), (v) => testJson(v ?? '')), |         .test('configuration', t('form.invalid_json'), (v) => testJson(v ?? '')), | ||||||
| @@ -16,6 +15,5 @@ export const DefaultConfigurationSchema = (t: (str: string) => string) => | |||||||
|       name: '', |       name: '', | ||||||
|       description: '', |       description: '', | ||||||
|       modelIds: [], |       modelIds: [], | ||||||
|       platform: 'ap', |  | ||||||
|       configuration: '', |       configuration: '', | ||||||
|     }); |     }); | ||||||
|   | |||||||
| @@ -65,7 +65,11 @@ const DeviceSummary = ({ serialNumber }: Props) => { | |||||||
|   const getDeviceCompatible = () => { |   const getDeviceCompatible = () => { | ||||||
|     if (!getDevice.data?.compatible) return undefined; |     if (!getDevice.data?.compatible) return undefined; | ||||||
|  |  | ||||||
|     if (getDevice.data.compatible.includes(' ')) return getDevice.data.compatible.replaceAll(' ', '_'); |     if (!getDevice.data?.compatible.includes('-')) return getDevice.data?.compatible; | ||||||
|  |  | ||||||
|  |     const split = getDevice.data?.compatible.split('-'); | ||||||
|  |  | ||||||
|  |     if (split[split.length - 1]?.length === 2) return split[0]?.trim(); | ||||||
|  |  | ||||||
|     return getDevice.data?.compatible; |     return getDevice.data?.compatible; | ||||||
|   }; |   }; | ||||||
| @@ -125,7 +129,9 @@ const DeviceSummary = ({ serialNumber }: Props) => { | |||||||
|               <Heading size="sm">{t('controller.stats.load')}:</Heading> |               <Heading size="sm">{t('controller.stats.load')}:</Heading> | ||||||
|             </GridItem> |             </GridItem> | ||||||
|             <GridItem colSpan={1}> |             <GridItem colSpan={1}> | ||||||
|               {getStats.data?.unit?.load ? getStats.data?.unit.load.map((l) => `${l.toFixed(2)}`).join(' | ') : ''} |               {getStats.data?.unit?.load | ||||||
|  |                 ? getStats.data?.unit.load.map((l) => `${(l * 100).toFixed(2)}%`).join(' | ') | ||||||
|  |                 : ''} | ||||||
|             </GridItem> |             </GridItem> | ||||||
|             <GridItem colSpan={1} alignContent="center" alignItems="center"> |             <GridItem colSpan={1} alignContent="center" alignItems="center"> | ||||||
|               <Heading size="sm">{t('controller.devices.localtime')}:</Heading> |               <Heading size="sm">{t('controller.devices.localtime')}:</Heading> | ||||||
| @@ -176,7 +182,7 @@ const DeviceSummary = ({ serialNumber }: Props) => { | |||||||
|             </GridItem> |             </GridItem> | ||||||
|             <GridItem colSpan={1}> |             <GridItem colSpan={1}> | ||||||
|               {getStatus.data?.connectReason && getStatus.data?.connectReason.length > 0 |               {getStatus.data?.connectReason && getStatus.data?.connectReason.length > 0 | ||||||
|                 ? uppercaseFirstLetter(getStatus.data?.connectReason) |                 ? uppercaseFirstLetter(getStatus.data.connectReason) | ||||||
|                 : '-'} |                 : '-'} | ||||||
|             </GridItem> |             </GridItem> | ||||||
|           </Grid> |           </Grid> | ||||||
|   | |||||||
| @@ -1,83 +0,0 @@ | |||||||
| import * as React from 'react'; |  | ||||||
| import { IconButton, Tooltip, useToast } from '@chakra-ui/react'; |  | ||||||
| import { Power, PlugsConnected } from '@phosphor-icons/react'; |  | ||||||
| import { useTranslation } from 'react-i18next'; |  | ||||||
| import { usePowerCycle } from 'hooks/Network/Devices'; |  | ||||||
| import { useNotification } from 'hooks/useNotification'; |  | ||||||
| import { DeviceLinkState } from 'hooks/Network/Statistics'; |  | ||||||
| import { CableDiagnosticsModalProps } from 'components/Modals/CableDiagnosticsModal'; |  | ||||||
|  |  | ||||||
| type Props = { |  | ||||||
|   state: DeviceLinkState & { name: string }; |  | ||||||
|   deviceSerialNumber: string; |  | ||||||
|   onOpenCableDiagnostics: (port: string) => void; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const LinkStateTableActions = ({ state, deviceSerialNumber, onOpenCableDiagnostics }: Props) => { |  | ||||||
|   const { t } = useTranslation(); |  | ||||||
|   const powerCycle = usePowerCycle(); |  | ||||||
|   const toast = useToast(); |  | ||||||
|   const { successToast, apiErrorToast } = useNotification(); |  | ||||||
|  |  | ||||||
|   const onPowerCycle = () => { |  | ||||||
|     powerCycle.mutate( |  | ||||||
|       { serial: deviceSerialNumber, when: 0, ports: [{ name: state.name, cycle: 10 * 1000 }] }, |  | ||||||
|       { |  | ||||||
|         onSuccess: (data) => { |  | ||||||
|           if (data.errorCode === 0) { |  | ||||||
|             successToast({ |  | ||||||
|               description: `Power cycle started for port ${state.name} for 10s`, |  | ||||||
|             }); |  | ||||||
|           } else if (data.errorCode === 1) { |  | ||||||
|             toast({ |  | ||||||
|               id: `powercycle-warning-${deviceSerialNumber}`, |  | ||||||
|               title: 'Warning', |  | ||||||
|               description: `${data?.errorText ?? 'Unknown Warning'}`, |  | ||||||
|               status: 'warning', |  | ||||||
|               duration: 5000, |  | ||||||
|               isClosable: true, |  | ||||||
|               position: 'top-right', |  | ||||||
|             }); |  | ||||||
|           } else { |  | ||||||
|             toast({ |  | ||||||
|               id: `powercycle-error-${deviceSerialNumber}`, |  | ||||||
|               title: t('common.error'), |  | ||||||
|               description: `${data?.errorText ?? 'Unknown Error'} (Code ${data.errorCode})`, |  | ||||||
|               status: 'error', |  | ||||||
|               duration: 5000, |  | ||||||
|               isClosable: true, |  | ||||||
|               position: 'top-right', |  | ||||||
|             }); |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         onError: (e) => apiErrorToast({ e }), |  | ||||||
|       }, |  | ||||||
|     ); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <> |  | ||||||
|       <Tooltip label="Power Cycle" placement="auto-start"> |  | ||||||
|         <IconButton |  | ||||||
|           aria-label="Power Cycle" |  | ||||||
|           icon={<Power size={20} />} |  | ||||||
|           colorScheme="green" |  | ||||||
|           onClick={onPowerCycle} |  | ||||||
|           isLoading={powerCycle.isLoading} |  | ||||||
|           size="xs" |  | ||||||
|         /> |  | ||||||
|       </Tooltip> |  | ||||||
|       <Tooltip label="Cable Diagnostics" placement="auto-start"> |  | ||||||
|         <IconButton |  | ||||||
|           aria-label="Cable Diagnostics" |  | ||||||
|           icon={<PlugsConnected size={20} />} |  | ||||||
|           colorScheme="blue" |  | ||||||
|           onClick={() => onOpenCableDiagnostics(state.name)} |  | ||||||
|           size="xs" |  | ||||||
|         /> |  | ||||||
|       </Tooltip> |  | ||||||
|     </> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default LinkStateTableActions; |  | ||||||
| @@ -1,208 +0,0 @@ | |||||||
| import * as React from 'react'; |  | ||||||
| import { Alert, AlertDescription, AlertIcon, Center } from '@chakra-ui/react'; |  | ||||||
| import { DeviceLinkState } from 'hooks/Network/Statistics'; |  | ||||||
| import DataCell from 'components/TableCells/DataCell'; |  | ||||||
| import { DataGridColumn, useDataGrid } from 'components/DataTables/DataGrid/useDataGrid'; |  | ||||||
| import { DataGrid } from 'components/DataTables/DataGrid'; |  | ||||||
| import { uppercaseFirstLetter } from 'helpers/stringHelper'; |  | ||||||
| import LinkStateTableActions from './Actions'; |  | ||||||
|  |  | ||||||
| type Row = DeviceLinkState & { name: string }; |  | ||||||
| const dataCell = (v: number) => <DataCell bytes={v} />; |  | ||||||
| const actionCell = (row: Row, serialNumber: string, onOpenCableDiagnostics: (port: string) => void) => ( |  | ||||||
|   <LinkStateTableActions |  | ||||||
|     state={row} |  | ||||||
|     deviceSerialNumber={serialNumber} |  | ||||||
|     onOpenCableDiagnostics={onOpenCableDiagnostics} |  | ||||||
|   /> |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| type Props = { |  | ||||||
|   statistics?: Row[]; |  | ||||||
|   refetch: () => void; |  | ||||||
|   isFetching: boolean; |  | ||||||
|   type: 'upstream' | 'downstream'; |  | ||||||
|   serialNumber: string; |  | ||||||
|   onOpenCableDiagnostics: (port: string) => void; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const LinkStateTable = ({ statistics, refetch, isFetching, type, serialNumber, onOpenCableDiagnostics }: Props) => { |  | ||||||
|   const tableController = useDataGrid({ |  | ||||||
|     tableSettingsId: 'switch.link-state.table', |  | ||||||
|     defaultOrder: [ |  | ||||||
|       'carrier', |  | ||||||
|       'name', |  | ||||||
|       'duplex', |  | ||||||
|       'speed', |  | ||||||
|       'rx_bytes', |  | ||||||
|       'rx_dropped', |  | ||||||
|       'rx_error', |  | ||||||
|       'rx_packets', |  | ||||||
|       'tx_bytes', |  | ||||||
|       'tx_dropped', |  | ||||||
|       'tx_error', |  | ||||||
|       'tx_packets', |  | ||||||
|       'actions', |  | ||||||
|     ], |  | ||||||
|     defaultSortBy: [{ id: 'name', desc: false }], |  | ||||||
|     showAllRows: true, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const columns: DataGridColumn<Row>[] = React.useMemo( |  | ||||||
|     (): DataGridColumn<Row>[] => [ |  | ||||||
|       { |  | ||||||
|         id: 'carrier', |  | ||||||
|         header: '', |  | ||||||
|         accessorKey: '', |  | ||||||
|         sortingFn: 'alphanumericCaseSensitive', |  | ||||||
|         cell: ({ cell }) => (cell.row.original.carrier ? '🟢' : '🔴'), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'name', |  | ||||||
|         header: 'Name', |  | ||||||
|         accessorKey: 'name', |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'duplex', |  | ||||||
|         header: 'Duplex', |  | ||||||
|         accessorKey: 'duplex', |  | ||||||
|         cell: ({ cell }) => (cell.row.original.duplex ? uppercaseFirstLetter(cell.row.original.duplex) : '-'), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'speed', |  | ||||||
|         header: 'Speed', |  | ||||||
|         accessorKey: 'speed', |  | ||||||
|         cell: ({ cell }) => `${(cell.row.original.speed ?? 0) / 1000} Gbps`, |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'rx_bytes', |  | ||||||
|         header: 'Rx', |  | ||||||
|         accessorKey: 'rx_bytes', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.rx_bytes ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'rx_dropped', |  | ||||||
|         header: 'Rx Dropped', |  | ||||||
|         accessorKey: 'rx_dropped', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.rx_dropped ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'rx_error', |  | ||||||
|         header: 'Rx Errors', |  | ||||||
|         accessorKey: 'rx_error', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.rx_errors ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'rx_packets', |  | ||||||
|         header: 'Rx Packets', |  | ||||||
|         accessorKey: 'counters.rx_packets', |  | ||||||
|         cell: ({ cell }) => (cell.row.original.counters?.rx_packets ?? 0).toLocaleString(), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'tx_bytes', |  | ||||||
|         header: 'Tx', |  | ||||||
|         accessorKey: 'tx_bytes', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.tx_bytes ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'tx_dropped', |  | ||||||
|         header: 'Tx Dropped', |  | ||||||
|         accessorKey: 'tx_dropped', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.tx_dropped ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'tx_error', |  | ||||||
|         header: 'Tx Errors', |  | ||||||
|         accessorKey: 'tx_error', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.tx_errors ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'tx_packets', |  | ||||||
|         header: 'Tx Packets', |  | ||||||
|         accessorKey: 'counters.tx_packets', |  | ||||||
|         cell: ({ cell }) => (cell.row.original.counters?.tx_packets ?? 0).toLocaleString(), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'actions', |  | ||||||
|         header: '', |  | ||||||
|         accessorKey: '', |  | ||||||
|         cell: ({ cell }) => ( |  | ||||||
|           <LinkStateTableActions |  | ||||||
|             state={cell.row.original} |  | ||||||
|             deviceSerialNumber={serialNumber} |  | ||||||
|             onOpenCableDiagnostics={onOpenCableDiagnostics} |  | ||||||
|           /> |  | ||||||
|         ), |  | ||||||
|       }, |  | ||||||
|     ], |  | ||||||
|     [onOpenCableDiagnostics], |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   if (!statistics || statistics?.length === 0) { |  | ||||||
|     return ( |  | ||||||
|       <Center> |  | ||||||
|         <Alert status="info"> |  | ||||||
|           <AlertIcon /> |  | ||||||
|           <AlertDescription> |  | ||||||
|             There are currently no {type} link-states provided in this devices statistics |  | ||||||
|           </AlertDescription> |  | ||||||
|         </Alert> |  | ||||||
|       </Center> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <DataGrid<Row> |  | ||||||
|       controller={tableController} |  | ||||||
|       header={{ |  | ||||||
|         title: '', |  | ||||||
|         objectListed: 'Statistics', |  | ||||||
|       }} |  | ||||||
|       columns={columns} |  | ||||||
|       isLoading={isFetching} |  | ||||||
|       data={statistics ?? []} |  | ||||||
|       options={{ |  | ||||||
|         refetch, |  | ||||||
|         isHidingControls: true, |  | ||||||
|       }} |  | ||||||
|     /> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default LinkStateTable; |  | ||||||
| @@ -1,172 +0,0 @@ | |||||||
| import * as React from 'react'; |  | ||||||
| import { Alert, AlertDescription, AlertIcon, Center } from '@chakra-ui/react'; |  | ||||||
| import { DeviceInterfaceStatistics, DeviceStatistics } from 'hooks/Network/Statistics'; |  | ||||||
| import DataCell from 'components/TableCells/DataCell'; |  | ||||||
| import { DataGridColumn, useDataGrid } from 'components/DataTables/DataGrid/useDataGrid'; |  | ||||||
| import DurationCell from 'components/TableCells/DurationCell'; |  | ||||||
| import { DataGrid } from 'components/DataTables/DataGrid'; |  | ||||||
|  |  | ||||||
| const dataCell = (v: number) => <DataCell bytes={v} />; |  | ||||||
|  |  | ||||||
| type Props = { |  | ||||||
|   statistics: DeviceStatistics; |  | ||||||
|   refetch: () => void; |  | ||||||
|   isFetching: boolean; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const SwitchInterfaceTable = ({ statistics, refetch, isFetching }: Props) => { |  | ||||||
|   const tableController = useDataGrid({ |  | ||||||
|     tableSettingsId: 'switch.interfaces.table', |  | ||||||
|     defaultOrder: [ |  | ||||||
|       'name', |  | ||||||
|       'uptime', |  | ||||||
|       'clients', |  | ||||||
|       'rx_bytes', |  | ||||||
|       'rx_dropped', |  | ||||||
|       'rx_error', |  | ||||||
|       'rx_packets', |  | ||||||
|       'tx_bytes', |  | ||||||
|       'tx_dropped', |  | ||||||
|       'tx_error', |  | ||||||
|     ], |  | ||||||
|     defaultSortBy: [{ id: 'name', desc: false }], |  | ||||||
|     showAllRows: true, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const columns: DataGridColumn<DeviceInterfaceStatistics>[] = React.useMemo( |  | ||||||
|     (): DataGridColumn<DeviceInterfaceStatistics>[] => [ |  | ||||||
|       { |  | ||||||
|         id: 'name', |  | ||||||
|         header: 'Name', |  | ||||||
|         accessorKey: 'name', |  | ||||||
|         sortingFn: 'alphanumericCaseSensitive', |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'uptime', |  | ||||||
|         header: 'Uptime', |  | ||||||
|  |  | ||||||
|         accessorKey: 'uptime', |  | ||||||
|         cell: ({ cell }) => <DurationCell seconds={cell.row.original.uptime} />, |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'clients', |  | ||||||
|         header: 'Clients', |  | ||||||
|  |  | ||||||
|         accessorKey: 'clients', |  | ||||||
|         cell: ({ cell }) => cell.row.original.clients?.length ?? 0, |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'rx_bytes', |  | ||||||
|         header: 'Rx', |  | ||||||
|         accessorKey: 'rx_bytes', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.rx_bytes ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'rx_dropped', |  | ||||||
|         header: 'Rx Dropped', |  | ||||||
|         accessorKey: 'rx_dropped', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.rx_dropped ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'rx_error', |  | ||||||
|         header: 'Rx Errors', |  | ||||||
|         accessorKey: 'rx_error', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.rx_errors ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'rx_packets', |  | ||||||
|         header: 'Rx Packets', |  | ||||||
|         accessorKey: 'counters.rx_packets', |  | ||||||
|         cell: ({ cell }) => (cell.row.original.counters?.rx_packets ?? 0).toLocaleString(), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'tx_bytes', |  | ||||||
|         header: 'Tx', |  | ||||||
|         accessorKey: 'tx_bytes', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.tx_bytes ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'tx_dropped', |  | ||||||
|         header: 'Tx Dropped', |  | ||||||
|         accessorKey: 'tx_dropped', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.tx_dropped ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'tx_error', |  | ||||||
|         header: 'Tx Errors', |  | ||||||
|         accessorKey: 'tx_error', |  | ||||||
|         cell: ({ cell }) => dataCell(cell.row.original.counters?.tx_errors ?? 0), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'tx_packets', |  | ||||||
|         header: 'Tx Packets', |  | ||||||
|         accessorKey: 'counters.tx_packets', |  | ||||||
|         cell: ({ cell }) => (cell.row.original.counters?.tx_packets ?? 0).toLocaleString(), |  | ||||||
|         meta: { |  | ||||||
|           customWidth: '35px', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|     ], |  | ||||||
|     [], |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   if (!statistics.interfaces) { |  | ||||||
|     return ( |  | ||||||
|       <Center> |  | ||||||
|         <Alert status="info"> |  | ||||||
|           <AlertIcon /> |  | ||||||
|           <AlertDescription>There are currently no interfaces provided in this devices statistics</AlertDescription> |  | ||||||
|         </Alert> |  | ||||||
|       </Center> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <DataGrid<DeviceInterfaceStatistics> |  | ||||||
|       controller={tableController} |  | ||||||
|       header={{ |  | ||||||
|         title: '', |  | ||||||
|         objectListed: 'Statistics', |  | ||||||
|       }} |  | ||||||
|       columns={columns} |  | ||||||
|       isLoading={isFetching} |  | ||||||
|       data={statistics.interfaces ?? []} |  | ||||||
|       options={{ |  | ||||||
|         refetch, |  | ||||||
|         isHidingControls: true, |  | ||||||
|       }} |  | ||||||
|     /> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default SwitchInterfaceTable; |  | ||||||
| @@ -1,112 +0,0 @@ | |||||||
| import * as React from 'react'; |  | ||||||
| import { Spinner, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react'; |  | ||||||
| import LinkStateTable from './LinkStateTable'; |  | ||||||
| import SwitchInterfaceTable from './SwitchInterfaceTable'; |  | ||||||
| import { DeviceLinkState, useGetDeviceLastStats } from 'hooks/Network/Statistics'; |  | ||||||
| import { Card } from 'components/Containers/Card'; |  | ||||||
| import { CardBody } from 'components/Containers/Card/CardBody'; |  | ||||||
| import { CableDiagnosticsModal } from 'components/Modals/CableDiagnosticsModal'; |  | ||||||
| import { useDisclosure } from '@chakra-ui/react'; |  | ||||||
|  |  | ||||||
| type Props = { |  | ||||||
|   serialNumber: string; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const SwitchPortExamination = ({ serialNumber }: Props) => { |  | ||||||
|   const [tabIndex, setTabIndex] = React.useState(0); |  | ||||||
|   const [selectedPort, setSelectedPort] = React.useState<string>(''); |  | ||||||
|   const cableDiagnosticsModalProps = useDisclosure(); |  | ||||||
|  |  | ||||||
|   const handleTabsChange = React.useCallback((index: number) => { |  | ||||||
|     setTabIndex(index); |  | ||||||
|   }, []); |  | ||||||
|   const getStats = useGetDeviceLastStats({ serialNumber }); |  | ||||||
|  |  | ||||||
|   const upLinkStates: (DeviceLinkState & { name: string })[] = React.useMemo(() => { |  | ||||||
|     if (!getStats.data || !getStats.data['link-state']?.upstream) return []; |  | ||||||
|  |  | ||||||
|     return Object.entries(getStats.data['link-state']?.upstream).map(([name, value]) => ({ |  | ||||||
|       ...value, |  | ||||||
|       name, |  | ||||||
|     })); |  | ||||||
|   }, [getStats.data]); |  | ||||||
|   const downLinkStates: (DeviceLinkState & { name: string })[] = React.useMemo(() => { |  | ||||||
|     if (!getStats.data || !getStats.data['link-state']?.downstream) return []; |  | ||||||
|  |  | ||||||
|     return Object.entries(getStats.data['link-state']?.downstream).map(([name, value]) => ({ |  | ||||||
|       ...value, |  | ||||||
|       name, |  | ||||||
|     })); |  | ||||||
|   }, [getStats.data]); |  | ||||||
|  |  | ||||||
|   const handleOpenCableDiagnostics = React.useCallback((port: string) => { |  | ||||||
|     setSelectedPort(port); |  | ||||||
|     cableDiagnosticsModalProps.onOpen(); |  | ||||||
|   }, []); |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <> |  | ||||||
|       <Card p={0} mb={4}> |  | ||||||
|         <CardBody p={0} display="block"> |  | ||||||
|           <Tabs index={tabIndex} onChange={handleTabsChange} variant="enclosed" w="100%"> |  | ||||||
|             <TabList> |  | ||||||
|               <Tab fontSize="lg" fontWeight="bold"> |  | ||||||
|                 Interfaces |  | ||||||
|               </Tab> |  | ||||||
|               <Tab fontSize="lg" fontWeight="bold"> |  | ||||||
|                 Link-State (Up) |  | ||||||
|               </Tab> |  | ||||||
|               <Tab fontSize="lg" fontWeight="bold"> |  | ||||||
|                 Link-State (Down) |  | ||||||
|               </Tab> |  | ||||||
|             </TabList> |  | ||||||
|             <TabPanels> |  | ||||||
|               <TabPanel> |  | ||||||
|                 {getStats.data ? ( |  | ||||||
|                   <SwitchInterfaceTable |  | ||||||
|                     statistics={getStats.data} |  | ||||||
|                     refetch={getStats.refetch} |  | ||||||
|                     isFetching={getStats.isFetching} |  | ||||||
|                   /> |  | ||||||
|                 ) : ( |  | ||||||
|                   <Spinner size="xl" /> |  | ||||||
|                 )} |  | ||||||
|               </TabPanel> |  | ||||||
|               <TabPanel> |  | ||||||
|                 {getStats.data ? ( |  | ||||||
|                   <LinkStateTable |  | ||||||
|                     statistics={upLinkStates} |  | ||||||
|                     refetch={getStats.refetch} |  | ||||||
|                     isFetching={getStats.isFetching} |  | ||||||
|                     type="upstream" |  | ||||||
|                     serialNumber={serialNumber} |  | ||||||
|                     onOpenCableDiagnostics={handleOpenCableDiagnostics} |  | ||||||
|                   /> |  | ||||||
|                 ) : ( |  | ||||||
|                   <Spinner size="xl" /> |  | ||||||
|                 )} |  | ||||||
|               </TabPanel> |  | ||||||
|               <TabPanel> |  | ||||||
|                 {getStats.data ? ( |  | ||||||
|                   <LinkStateTable |  | ||||||
|                     statistics={downLinkStates} |  | ||||||
|                     refetch={getStats.refetch} |  | ||||||
|                     isFetching={getStats.isFetching} |  | ||||||
|                     type="downstream" |  | ||||||
|                     serialNumber={serialNumber} |  | ||||||
|                     onOpenCableDiagnostics={handleOpenCableDiagnostics} |  | ||||||
|                   /> |  | ||||||
|                 ) : ( |  | ||||||
|                   <Spinner size="xl" /> |  | ||||||
|                 )} |  | ||||||
|               </TabPanel> |  | ||||||
|             </TabPanels> |  | ||||||
|           </Tabs> |  | ||||||
|         </CardBody> |  | ||||||
|       </Card> |  | ||||||
|       <CableDiagnosticsModal modalProps={cableDiagnosticsModalProps} serialNumber={serialNumber} port={selectedPort} /> |  | ||||||
|     </> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default SwitchPortExamination; |  | ||||||
| @@ -28,7 +28,6 @@ export type ParsedAssociation = { | |||||||
|   txNss: number | string; |   txNss: number | string; | ||||||
|   recorded: number; |   recorded: number; | ||||||
|   dynamicVlan?: number; |   dynamicVlan?: number; | ||||||
|   fingerprint?: object; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| type Props = { | type Props = { | ||||||
| @@ -78,14 +77,6 @@ const WifiAnalysisAssociationsTable = ({ data, ouis, isSingle }: Props) => { | |||||||
|         isMonospace: true, |         isMonospace: true, | ||||||
|         alwaysShow: true, |         alwaysShow: true, | ||||||
|       }, |       }, | ||||||
|       { |  | ||||||
|         id: 'ssid', |  | ||||||
|         Header: 'SSID', |  | ||||||
|         Footer: '', |  | ||||||
|         accessor: 'ssid', |  | ||||||
|         customWidth: '35px', |  | ||||||
|         alwaysShow: true, |  | ||||||
|       }, |  | ||||||
|       { |       { | ||||||
|         id: 'ips', |         id: 'ips', | ||||||
|         Header: 'IPs', |         Header: 'IPs', | ||||||
| @@ -93,13 +84,6 @@ const WifiAnalysisAssociationsTable = ({ data, ouis, isSingle }: Props) => { | |||||||
|         Cell: (v) => ipCell(v.cell.row.original), |         Cell: (v) => ipCell(v.cell.row.original), | ||||||
|         disableSortBy: true, |         disableSortBy: true, | ||||||
|       }, |       }, | ||||||
|       { |  | ||||||
|         id: 'fingerprint', |  | ||||||
|         Header: 'Fingerprint', |  | ||||||
|         Footer: '', |  | ||||||
|         Cell: (v) => Object.values(v.cell.row.original.fingerprint ?? {}).join(', '), |  | ||||||
|         disableSortBy: true, |  | ||||||
|       }, |  | ||||||
|       { |       { | ||||||
|         id: 'vendor', |         id: 'vendor', | ||||||
|         Header: t('controller.wifi.vendor'), |         Header: t('controller.wifi.vendor'), | ||||||
| @@ -182,7 +166,7 @@ const WifiAnalysisAssociationsTable = ({ data, ouis, isSingle }: Props) => { | |||||||
|         customWidth: '35px', |         customWidth: '35px', | ||||||
|       }, |       }, | ||||||
|     ], |     ], | ||||||
|     [t, ouis], |     [t], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|   | |||||||
| @@ -17,18 +17,14 @@ export type ParsedRadio = { | |||||||
|   activeMs: string; |   activeMs: string; | ||||||
|   busyMs: string; |   busyMs: string; | ||||||
|   receiveMs: string; |   receiveMs: string; | ||||||
|   sendMs: string; |  | ||||||
|   phy: string; |   phy: string; | ||||||
|   frequency: string; |  | ||||||
|   temperature: string; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| type Props = { | type Props = { | ||||||
|   data?: ParsedRadio[]; |   data?: ParsedRadio[]; | ||||||
|   isSingle?: boolean; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const WifiAnalysisRadioTable = ({ data, isSingle }: Props) => { | const WifiAnalysisRadioTable = ({ data }: Props) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]); |   const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]); | ||||||
|  |  | ||||||
| @@ -48,27 +44,19 @@ const WifiAnalysisRadioTable = ({ data, isSingle }: Props) => { | |||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         id: 'channel', |         id: 'channel', | ||||||
|         Header: 'Ch.', |         Header: 'Ch', | ||||||
|         Footer: '', |         Footer: '', | ||||||
|         accessor: 'channel', |         accessor: 'channel', | ||||||
|         customWidth: '35px', |         customWidth: '35px', | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         id: 'channelWidth', |         id: 'channelWidth', | ||||||
|         Header: 'Ch. W', |         Header: t('controller.wifi.channel_width'), | ||||||
|         Footer: '', |         Footer: '', | ||||||
|         accessor: 'channelWidth', |         accessor: 'channelWidth', | ||||||
|         customWidth: '35px', |         customWidth: '35px', | ||||||
|         disableSortBy: true, |         disableSortBy: true, | ||||||
|       }, |       }, | ||||||
|       { |  | ||||||
|         id: 'tx-power', |  | ||||||
|         Header: 'Tx Pow.', |  | ||||||
|         Footer: '', |  | ||||||
|         accessor: 'txPower', |  | ||||||
|         customWidth: '35px', |  | ||||||
|         disableSortBy: true, |  | ||||||
|       }, |  | ||||||
|       { |       { | ||||||
|         id: 'noise', |         id: 'noise', | ||||||
|         Header: t('controller.wifi.noise'), |         Header: t('controller.wifi.noise'), | ||||||
| @@ -79,49 +67,25 @@ const WifiAnalysisRadioTable = ({ data, isSingle }: Props) => { | |||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         id: 'activeMs', |         id: 'activeMs', | ||||||
|         Header: 'Active (ms)', |         Header: t('controller.wifi.active_ms'), | ||||||
|         Footer: '', |         Footer: '', | ||||||
|         accessor: 'activeMs', |         accessor: 'activeMs', | ||||||
|         customWidth: '105px', |  | ||||||
|         disableSortBy: true, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'busyMs', |  | ||||||
|         Header: 'Busy (ms)', |  | ||||||
|         Footer: '', |  | ||||||
|         accessor: 'busyMs', |  | ||||||
|         customWidth: '105px', |  | ||||||
|         disableSortBy: true, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'receiveMs', |  | ||||||
|         Header: 'Receive (ms)', |  | ||||||
|         Footer: '', |  | ||||||
|         accessor: 'receiveMs', |  | ||||||
|         customWidth: '105px', |  | ||||||
|         disableSortBy: true, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'sendMs', |  | ||||||
|         Header: 'Send (ms)', |  | ||||||
|         Footer: '', |  | ||||||
|         accessor: 'sendMs', |  | ||||||
|         customWidth: '105px', |  | ||||||
|         disableSortBy: true, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         id: 'temperature', |  | ||||||
|         Header: 'Temp.', |  | ||||||
|         Footer: '', |  | ||||||
|         accessor: 'temperature', |  | ||||||
|         customWidth: '35px', |         customWidth: '35px', | ||||||
|         disableSortBy: true, |         disableSortBy: true, | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         id: 'frequency', |         id: 'busyMs', | ||||||
|         Header: 'Frequency', |         Header: t('controller.wifi.busy_ms'), | ||||||
|         Footer: '', |         Footer: '', | ||||||
|         accessor: 'frequency', |         accessor: 'busyMs', | ||||||
|  |         customWidth: '35px', | ||||||
|  |         disableSortBy: true, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         id: 'receiveMs', | ||||||
|  |         Header: t('controller.wifi.receive_ms'), | ||||||
|  |         Footer: '', | ||||||
|  |         accessor: 'receiveMs', | ||||||
|         customWidth: '35px', |         customWidth: '35px', | ||||||
|         disableSortBy: true, |         disableSortBy: true, | ||||||
|       }, |       }, | ||||||
| @@ -133,7 +97,7 @@ const WifiAnalysisRadioTable = ({ data, isSingle }: Props) => { | |||||||
|     <> |     <> | ||||||
|       <Flex> |       <Flex> | ||||||
|         <Heading size="sm" mt={2} my="auto"> |         <Heading size="sm" mt={2} my="auto"> | ||||||
|           {isSingle ? 'Radio' : `${t('configurations.radios')} (${data?.length})`} |           {t('configurations.radios')} ({data?.length}) | ||||||
|         </Heading> |         </Heading> | ||||||
|         <Spacer /> |         <Spacer /> | ||||||
|         <ColumnPicker |         <ColumnPicker | ||||||
|   | |||||||
| @@ -16,29 +16,11 @@ type Props = { | |||||||
|   serialNumber: string; |   serialNumber: string; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const parseRadios = (_: (str: string) => string, data: { data: DeviceStatistics; recorded: number }) => { | const parseRadios = (t: (str: string) => string, data: { data: DeviceStatistics; recorded: number }) => { | ||||||
|   const radios: ParsedRadio[] = []; |   const radios: ParsedRadio[] = []; | ||||||
|   if (data.data.radios) { |   if (data.data.radios) { | ||||||
|     for (let i = 0; i < data.data.radios.length; i += 1) { |     for (let i = 0; i < data.data.radios.length; i += 1) { | ||||||
|       const radio = data.data.radios[i]; |       const radio = data.data.radios[i]; | ||||||
|       let temperature = radio?.temperature; |  | ||||||
|       if (temperature) temperature = temperature > 1000 ? Math.round(temperature / 1000) : temperature; |  | ||||||
|  |  | ||||||
|       const tempNoise = radio?.noise ?? radio?.survey?.[0]?.noise; |  | ||||||
|       const noise = tempNoise ? parseDbm(tempNoise) : '-'; |  | ||||||
|  |  | ||||||
|       const tempActiveMs = radio?.survey?.[0]?.time ?? radio?.active_ms; |  | ||||||
|       const activeMs = tempActiveMs?.toLocaleString() ?? '-'; |  | ||||||
|  |  | ||||||
|       const tempBusyMs = radio?.survey?.[0]?.busy ?? radio?.busy_ms; |  | ||||||
|       const busyMs = tempBusyMs?.toLocaleString() ?? '-'; |  | ||||||
|  |  | ||||||
|       const tempReceiveMs = radio?.survey?.[0]?.time_rx ?? radio?.receive_ms; |  | ||||||
|       const receiveMs = tempReceiveMs?.toLocaleString() ?? '-'; |  | ||||||
|  |  | ||||||
|       const tempSendMs = radio?.survey?.[0]?.time_tx; |  | ||||||
|       const sendMs = tempSendMs?.toLocaleString() ?? '-'; |  | ||||||
|  |  | ||||||
|       if (radio) { |       if (radio) { | ||||||
|         radios.push({ |         radios.push({ | ||||||
|           recorded: data.recorded, |           recorded: data.recorded, | ||||||
| @@ -47,15 +29,12 @@ const parseRadios = (_: (str: string) => string, data: { data: DeviceStatistics; | |||||||
|           deductedBand: radio.channel && radio.channel > 16 ? '5G' : '2G', |           deductedBand: radio.channel && radio.channel > 16 ? '5G' : '2G', | ||||||
|           channel: radio.channel, |           channel: radio.channel, | ||||||
|           channelWidth: radio.channel_width, |           channelWidth: radio.channel_width, | ||||||
|           noise, |           noise: radio.noise ? parseDbm(radio.noise) : '-', | ||||||
|           txPower: radio.tx_power ?? '-', |           txPower: radio.tx_power ?? '-', | ||||||
|           activeMs, |           activeMs: compactSecondsToDetailed(radio?.active_ms ? Math.floor(radio.active_ms / 1000) : 0, t), | ||||||
|           busyMs, |           busyMs: compactSecondsToDetailed(radio?.busy_ms ? Math.floor(radio.busy_ms / 1000) : 0, t), | ||||||
|           receiveMs, |           receiveMs: compactSecondsToDetailed(radio?.receive_ms ? Math.floor(radio.receive_ms / 1000) : 0, t), | ||||||
|           sendMs, |  | ||||||
|           phy: radio.phy, |           phy: radio.phy, | ||||||
|           temperature: temperature ? temperature.toString() : '-', |  | ||||||
|           frequency: radio.frequency?.join(', ') ?? '-', |  | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -105,7 +84,6 @@ const parseAssociations = (data: { data: DeviceStatistics; recorded: number }, r | |||||||
|           txNss: association.tx_rate.nss ?? '-', |           txNss: association.tx_rate.nss ?? '-', | ||||||
|           recorded: data.recorded, |           recorded: data.recorded, | ||||||
|           dynamicVlan: association.dynamic_vlan, |           dynamicVlan: association.dynamic_vlan, | ||||||
|           fingerprint: association.fingerprint, |  | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -42,13 +42,10 @@ import FactoryResetModal from 'components/Modals/FactoryResetModal'; | |||||||
| import { FirmwareUpgradeModal } from 'components/Modals/FirmwareUpgradeModal'; | import { FirmwareUpgradeModal } from 'components/Modals/FirmwareUpgradeModal'; | ||||||
| import { RebootModal } from 'components/Modals/RebootModal'; | import { RebootModal } from 'components/Modals/RebootModal'; | ||||||
| import { useScriptModal } from 'components/Modals/ScriptModal/useScriptModal'; | import { useScriptModal } from 'components/Modals/ScriptModal/useScriptModal'; | ||||||
| import ethernetConnected from './ethernetIconConnected.svg?react'; |  | ||||||
| import ethernetDisconnected from './ethernetIconDisconnected.svg?react'; |  | ||||||
| import { TelemetryModal } from 'components/Modals/TelemetryModal'; | import { TelemetryModal } from 'components/Modals/TelemetryModal'; | ||||||
| import { TraceModal } from 'components/Modals/TraceModal'; | import { TraceModal } from 'components/Modals/TraceModal'; | ||||||
| import { WifiScanModal } from 'components/Modals/WifiScanModal'; | import { WifiScanModal } from 'components/Modals/WifiScanModal'; | ||||||
| import { useDeleteDevice, useGetDevice, useGetDeviceHealthChecks, useGetDeviceStatus } from 'hooks/Network/Devices'; | import { useDeleteDevice, useGetDevice, useGetDeviceHealthChecks, useGetDeviceStatus } from 'hooks/Network/Devices'; | ||||||
| import SwitchPortExamination from './SwitchPortExamination'; |  | ||||||
|  |  | ||||||
| type Props = { | type Props = { | ||||||
|   serialNumber: string; |   serialNumber: string; | ||||||
| @@ -68,7 +65,6 @@ const DevicePageWrapper = ({ serialNumber }: Props) => { | |||||||
|   const getHealth = useGetDeviceHealthChecks({ serialNumber, limit: 1 }); |   const getHealth = useGetDeviceHealthChecks({ serialNumber, limit: 1 }); | ||||||
|   const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure(); |   const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure(); | ||||||
|   const scanModalProps = useDisclosure(); |   const scanModalProps = useDisclosure(); | ||||||
|   const cableDiagnosticsModalProps = useDisclosure(); |  | ||||||
|   const resetModalProps = useDisclosure(); |   const resetModalProps = useDisclosure(); | ||||||
|   const eventQueueProps = useDisclosure(); |   const eventQueueProps = useDisclosure(); | ||||||
|   const configureModalProps = useDisclosure(); |   const configureModalProps = useDisclosure(); | ||||||
| @@ -81,8 +77,19 @@ const DevicePageWrapper = ({ serialNumber }: Props) => { | |||||||
|   const isCompact = breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md'; |   const isCompact = breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md'; | ||||||
|   const boxShadow = useColorModeValue('0px 7px 23px rgba(0, 0, 0, 0.05)', 'none'); |   const boxShadow = useColorModeValue('0px 7px 23px rgba(0, 0, 0, 0.05)', 'none'); | ||||||
|  |  | ||||||
|   const handleDeleteClick = () => { |   const handleDeleteClick = () => | ||||||
|     deleteDevice(serialNumber, { |     deleteDevice(serialNumber, { | ||||||
|  |       onSuccess: () => { | ||||||
|  |         toast({ | ||||||
|  |           id: `delete-device-success-${serialNumber}`, | ||||||
|  |           title: t('common.success'), | ||||||
|  |           status: 'success', | ||||||
|  |           duration: 5000, | ||||||
|  |           isClosable: true, | ||||||
|  |           position: 'top-right', | ||||||
|  |         }); | ||||||
|  |         navigate('/devices'); | ||||||
|  |       }, | ||||||
|       onError: (e) => { |       onError: (e) => { | ||||||
|         if (axios.isAxiosError(e)) { |         if (axios.isAxiosError(e)) { | ||||||
|           toast({ |           toast({ | ||||||
| @@ -97,43 +104,18 @@ const DevicePageWrapper = ({ serialNumber }: Props) => { | |||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|     toast({ |  | ||||||
|       id: `delete-device-success-${serialNumber}`, |  | ||||||
|       title: t('common.success'), |  | ||||||
|       status: 'success', |  | ||||||
|       duration: 5000, |  | ||||||
|       isClosable: true, |  | ||||||
|       position: 'top-right', |  | ||||||
|     }); |  | ||||||
|     navigate('/'); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   const connectedTag = React.useMemo(() => { |   const connectedTag = React.useMemo(() => { | ||||||
|     if (!getStatus.data) return null; |     if (!getStatus.data) return null; | ||||||
|  |  | ||||||
|     if (getDevice.data?.blackListed) { |  | ||||||
|       return ( |  | ||||||
|         <ResponsiveTag |  | ||||||
|           label="Blacklisted" |  | ||||||
|           tooltip="This device is blacklisted, it will not be able to connect to the network. Please visit the Blacklist page if you wish to remove it from the blacklist." |  | ||||||
|           colorScheme="red" |  | ||||||
|           icon={LockSimple} |  | ||||||
|         /> |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     let icon = getStatus.data.connected ? WifiHigh : WifiSlash; |  | ||||||
|     if (getDevice.data?.deviceType === 'switch') |  | ||||||
|       icon = getStatus.data.connected ? ethernetConnected : ethernetDisconnected; |  | ||||||
|  |  | ||||||
|     return ( |     return ( | ||||||
|       <ResponsiveTag |       <ResponsiveTag | ||||||
|         label={getStatus?.data?.connected ? t('common.connected') : t('common.disconnected')} |         label={getStatus?.data?.connected ? t('common.connected') : t('common.disconnected')} | ||||||
|         colorScheme={getStatus?.data?.connected ? 'green' : 'red'} |         colorScheme={getStatus?.data?.connected ? 'green' : 'red'} | ||||||
|         icon={icon} |         icon={getStatus.data.connected ? WifiHigh : WifiSlash} | ||||||
|       /> |       /> | ||||||
|     ); |     ); | ||||||
|   }, [getStatus.data, getDevice.data]); |   }, [getStatus.data]); | ||||||
|  |  | ||||||
|   const healthTag = React.useMemo(() => { |   const healthTag = React.useMemo(() => { | ||||||
|     if (!getStatus.data || !getStatus.data.connected || !getHealth.data || getHealth.data?.values?.length === 0) |     if (!getStatus.data || !getStatus.data.connected || !getHealth.data || getHealth.data?.values?.length === 0) | ||||||
| @@ -325,8 +307,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => { | |||||||
|           <DeviceSummary serialNumber={serialNumber} /> |           <DeviceSummary serialNumber={serialNumber} /> | ||||||
|           <DeviceDetails serialNumber={serialNumber} /> |           <DeviceDetails serialNumber={serialNumber} /> | ||||||
|           <DeviceStatisticsCard serialNumber={serialNumber} /> |           <DeviceStatisticsCard serialNumber={serialNumber} /> | ||||||
|           {getDevice.data?.deviceType === 'ap' ? <WifiAnalysisCard serialNumber={serialNumber} /> : null} |           <WifiAnalysisCard serialNumber={serialNumber} /> | ||||||
|           {getDevice.data?.deviceType === 'switch' ? <SwitchPortExamination serialNumber={serialNumber} /> : null} |  | ||||||
|           <DeviceLogsCard serialNumber={serialNumber} /> |           <DeviceLogsCard serialNumber={serialNumber} /> | ||||||
|           {getDevice.data && getDevice.data?.hasRADIUSSessions > 0 ? ( |           {getDevice.data && getDevice.data?.hasRADIUSSessions > 0 ? ( | ||||||
|             <RadiusClientsCard serialNumber={serialNumber} /> |             <RadiusClientsCard serialNumber={serialNumber} /> | ||||||
|   | |||||||
| @@ -1,2 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> |  | ||||||
| <svg viewBox="0 0 24 24" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;stroke:#48BB78;stroke-miterlimit:10;stroke-width:1.91px;}</style></defs><rect class="cls-1" x="1.5" y="1.5" width="21" height="21" rx="1.91"/><polygon class="cls-1" points="5.32 6.27 5.32 13.91 7.23 13.91 7.23 15.82 10.09 15.82 10.09 17.73 13.91 17.73 13.91 15.82 16.77 15.82 16.77 13.91 18.68 13.91 18.68 6.27 5.32 6.27"/><line class="cls-1" x1="8.18" y1="9.14" x2="8.18" y2="6.27"/><line class="cls-1" x1="12" y1="9.14" x2="12" y2="6.27"/><line class="cls-1" x1="15.82" y1="9.14" x2="15.82" y2="6.27"/></svg> |  | ||||||
| Before Width: | Height: | Size: 673 B | 
| @@ -1,2 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> |  | ||||||
| <svg viewBox="0 0 24 24" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;stroke:#FC8181;stroke-miterlimit:10;stroke-width:1.91px;}</style></defs><rect class="cls-1" x="1.5" y="1.5" width="21" height="21" rx="1.91"/><polygon class="cls-1" points="5.32 6.27 5.32 13.91 7.23 13.91 7.23 15.82 10.09 15.82 10.09 17.73 13.91 17.73 13.91 15.82 16.77 15.82 16.77 13.91 18.68 13.91 18.68 6.27 5.32 6.27"/><line class="cls-1" x1="8.18" y1="9.14" x2="8.18" y2="6.27"/><line class="cls-1" x1="12" y1="9.14" x2="12" y2="6.27"/><line class="cls-1" x1="15.82" y1="9.14" x2="15.82" y2="6.27"/></svg> |  | ||||||
| Before Width: | Height: | Size: 673 B | 
| Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.6 KiB | 
| @@ -1,16 +1,5 @@ | |||||||
| import * as React from 'react'; | import * as React from 'react'; | ||||||
| import { | import { Box, Center, Image, Link, Tag, TagLabel, TagRightIcon, Tooltip, useDisclosure } from '@chakra-ui/react'; | ||||||
|   Box, |  | ||||||
|   Center, |  | ||||||
|   Image, |  | ||||||
|   Link, |  | ||||||
|   Select, |  | ||||||
|   Tag, |  | ||||||
|   TagLabel, |  | ||||||
|   TagRightIcon, |  | ||||||
|   Tooltip, |  | ||||||
|   useDisclosure, |  | ||||||
| } from '@chakra-ui/react'; |  | ||||||
| import { | import { | ||||||
|   CheckCircle, |   CheckCircle, | ||||||
|   Heart, |   Heart, | ||||||
| @@ -19,7 +8,6 @@ import { | |||||||
|   ThermometerCold, |   ThermometerCold, | ||||||
|   ThermometerHot, |   ThermometerHot, | ||||||
|   WarningCircle, |   WarningCircle, | ||||||
|   XCircle, |  | ||||||
| } from '@phosphor-icons/react'; | } from '@phosphor-icons/react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||||
| @@ -49,7 +37,7 @@ import { TraceModal } from 'components/Modals/TraceModal'; | |||||||
| import { WifiScanModal } from 'components/Modals/WifiScanModal'; | import { WifiScanModal } from 'components/Modals/WifiScanModal'; | ||||||
| import DataCell from 'components/TableCells/DataCell'; | import DataCell from 'components/TableCells/DataCell'; | ||||||
| import NumberCell from 'components/TableCells/NumberCell'; | import NumberCell from 'components/TableCells/NumberCell'; | ||||||
| import { DevicePlatform, DeviceWithStatus, useGetDeviceCount, useGetDevices } from 'hooks/Network/Devices'; | import { DeviceWithStatus, useGetDeviceCount, useGetDevices } from 'hooks/Network/Devices'; | ||||||
| import { FirmwareAgeResponse, useGetFirmwareAges } from 'hooks/Network/Firmware'; | import { FirmwareAgeResponse, useGetFirmwareAges } from 'hooks/Network/Firmware'; | ||||||
|  |  | ||||||
| const fourDigitNumber = (v?: number) => { | const fourDigitNumber = (v?: number) => { | ||||||
| @@ -75,7 +63,6 @@ const BADGE_COLORS: Record<string, string> = { | |||||||
|   NO_CERTIFICATE: 'red', |   NO_CERTIFICATE: 'red', | ||||||
|   MISMATCH_SERIAL: 'yellow', |   MISMATCH_SERIAL: 'yellow', | ||||||
|   VERIFIED: 'green', |   VERIFIED: 'green', | ||||||
|   BLACKLISTED: 'white', |  | ||||||
|   SIMULATED: 'purple', |   SIMULATED: 'purple', | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -83,7 +70,6 @@ const DeviceListCard = () => { | |||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const navigate = useNavigate(); |   const navigate = useNavigate(); | ||||||
|   const [serialNumber, setSerialNumber] = React.useState<string>(''); |   const [serialNumber, setSerialNumber] = React.useState<string>(''); | ||||||
|   const [platform, setPlatform] = React.useState<DevicePlatform>('ALL'); |  | ||||||
|   const scanModalProps = useDisclosure(); |   const scanModalProps = useDisclosure(); | ||||||
|   const resetModalProps = useDisclosure(); |   const resetModalProps = useDisclosure(); | ||||||
|   const upgradeModalProps = useDisclosure(); |   const upgradeModalProps = useDisclosure(); | ||||||
| @@ -122,14 +108,13 @@ const DeviceListCard = () => { | |||||||
|       'actions', |       'actions', | ||||||
|     ], |     ], | ||||||
|   }); |   }); | ||||||
|   const getCount = useGetDeviceCount({ enabled: true, platform }); |   const getCount = useGetDeviceCount({ enabled: true }); | ||||||
|   const getDevices = useGetDevices({ |   const getDevices = useGetDevices({ | ||||||
|     pageInfo: { |     pageInfo: { | ||||||
|       limit: tableController.pageInfo.pageSize, |       limit: tableController.pageInfo.pageSize, | ||||||
|       index: tableController.pageInfo.pageIndex, |       index: tableController.pageInfo.pageIndex, | ||||||
|     }, |     }, | ||||||
|     enabled: true, |     enabled: true, | ||||||
|     platform, |  | ||||||
|   }); |   }); | ||||||
|   const getAges = useGetFirmwareAges({ |   const getAges = useGetFirmwareAges({ | ||||||
|     serialNumbers: getDevices.data?.devicesWithStatus.map((device) => device.serialNumber), |     serialNumbers: getDevices.data?.devicesWithStatus.map((device) => device.serialNumber), | ||||||
| @@ -174,32 +159,12 @@ const DeviceListCard = () => { | |||||||
|         h="35px" |         h="35px" | ||||||
|         w="35px" |         w="35px" | ||||||
|         borderRadius="50em" |         borderRadius="50em" | ||||||
|         bgColor={ |         bgColor={BADGE_COLORS[device.simulated ? 'SIMULATED' : device.verifiedCertificate] ?? 'red'} | ||||||
|           BADGE_COLORS[ |  | ||||||
|             device.simulated ? 'SIMULATED' : device.blackListed ? 'BLACKLISTED' : device.verifiedCertificate |  | ||||||
|           ] ?? 'red' |  | ||||||
|         } |  | ||||||
|         alignItems="center" |         alignItems="center" | ||||||
|         display="inline-flex" |         display="inline-flex" | ||||||
|         justifyContent="center" |         justifyContent="center" | ||||||
|         position="relative" |         position="relative" | ||||||
|       > |       > | ||||||
|         {device.blackListed ? ( |  | ||||||
|           <Tooltip label="This device is blacklisted. If this was done by mistake, please visit the Blacklist page to correct."> |  | ||||||
|             <XCircle |  | ||||||
|               size={44} |  | ||||||
|               color="#ff2600" |  | ||||||
|               weight="duotone" |  | ||||||
|               style={{ |  | ||||||
|                 position: 'absolute', |  | ||||||
|                 // Center vertically and horizontally |  | ||||||
|                 top: '50%', |  | ||||||
|                 left: '50%', |  | ||||||
|                 transform: 'translate(-50%, -50%)', |  | ||||||
|               }} |  | ||||||
|             /> |  | ||||||
|           </Tooltip> |  | ||||||
|         ) : null} |  | ||||||
|         <Tooltip |         <Tooltip | ||||||
|           label={`${device.simulated ? 'SIMULATED' : device.verifiedCertificate} - ${ |           label={`${device.simulated ? 'SIMULATED' : device.verifiedCertificate} - ${ | ||||||
|             device.connected ? t('common.connected') : t('common.disconnected') |             device.connected ? t('common.connected') : t('common.disconnected') | ||||||
| @@ -217,7 +182,6 @@ const DeviceListCard = () => { | |||||||
|           bottom={0} |           bottom={0} | ||||||
|           borderColor="gray.200" |           borderColor="gray.200" | ||||||
|           borderWidth={1} |           borderWidth={1} | ||||||
|           hidden={device.blackListed} |  | ||||||
|         /> |         /> | ||||||
|         {device.restrictedDevice && ( |         {device.restrictedDevice && ( | ||||||
|           <Box |           <Box | ||||||
| @@ -569,7 +533,12 @@ const DeviceListCard = () => { | |||||||
|         header: t('analytics.last_connected'), |         header: t('analytics.last_connected'), | ||||||
|         footer: '', |         footer: '', | ||||||
|         accessorKey: 'lastRecordedContact', |         accessorKey: 'lastRecordedContact', | ||||||
|         cell: (v) => dateCell(v.cell.row.original.lastRecordedContact), |         cell: (v) => | ||||||
|  |           dateCell( | ||||||
|  |             v.cell.row.original.lastContact !== 0 | ||||||
|  |               ? v.cell.row.original.lastContact | ||||||
|  |               : v.cell.row.original.lastRecordedContact, | ||||||
|  |           ), | ||||||
|         enableSorting: false, |         enableSorting: false, | ||||||
|         meta: { |         meta: { | ||||||
|           headerOptions: { |           headerOptions: { | ||||||
| @@ -727,21 +696,7 @@ const DeviceListCard = () => { | |||||||
|         header={{ |         header={{ | ||||||
|           title: `${getCount.data?.count ?? 0} ${t('devices.title')}`, |           title: `${getCount.data?.count ?? 0} ${t('devices.title')}`, | ||||||
|           objectListed: t('devices.title'), |           objectListed: t('devices.title'), | ||||||
|           leftContent: ( |           leftContent: <GlobalSearchBar />, | ||||||
|             <> |  | ||||||
|               <GlobalSearchBar /> |  | ||||||
|               <Select |  | ||||||
|                 value={platform} |  | ||||||
|                 onChange={(e) => setPlatform(e.target.value as DevicePlatform)} |  | ||||||
|                 w="max-content" |  | ||||||
|                 ml={2} |  | ||||||
|               > |  | ||||||
|                 <option value="ALL">All</option> |  | ||||||
|                 <option value="ap">APs</option> |  | ||||||
|                 <option value="switch">Switches</option> |  | ||||||
|               </Select> |  | ||||||
|             </> |  | ||||||
|           ), |  | ||||||
|           otherButtons: ( |           otherButtons: ( | ||||||
|             <ExportDevicesTableButton currentPageSerialNumbers={data.map((device) => device.serialNumber)} /> |             <ExportDevicesTableButton currentPageSerialNumbers={data.map((device) => device.serialNumber)} /> | ||||||
|           ), |           ), | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ import { defineConfig } from 'vite'; | |||||||
| import tsconfigPaths from 'vite-tsconfig-paths'; | import tsconfigPaths from 'vite-tsconfig-paths'; | ||||||
| import react from '@vitejs/plugin-react'; | import react from '@vitejs/plugin-react'; | ||||||
| import { VitePWA } from 'vite-plugin-pwa'; | import { VitePWA } from 'vite-plugin-pwa'; | ||||||
| import svgr from 'vite-plugin-svgr'; |  | ||||||
|  |  | ||||||
| export default defineConfig({ | export default defineConfig({ | ||||||
|   plugins: [ |   plugins: [ | ||||||
| @@ -45,7 +44,6 @@ export default defineConfig({ | |||||||
|         ], |         ], | ||||||
|       }, |       }, | ||||||
|     }), |     }), | ||||||
|     svgr(), |  | ||||||
|   ], |   ], | ||||||
|   build: { |   build: { | ||||||
|     outDir: './build', |     outDir: './build', | ||||||
|   | |||||||