Compare commits

..

2 Commits

Author SHA1 Message Date
TIP Automation User
9e3df09fb2 Chg: update image tag in helm values to v3.0.0 2023-12-29 15:19:32 +00:00
TIP Automation User
2445935627 Chg: update image tag in helm values to v3.0.0-RC1 2023-11-27 17:38:20 +00:00
75 changed files with 333 additions and 1895 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
VITE_UCENTRALSEC_URL="https://ucentral.dpaas.arilia.com:16001"

View File

@@ -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 }}

View File

@@ -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
View File

@@ -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",

View File

@@ -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": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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>
); );

View File

@@ -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}
/> />
); );
}; };

View File

@@ -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>
);
};

View File

@@ -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) {

View File

@@ -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
View File

@@ -8,4 +8,3 @@ declare module '*.png' {
const value: string; const value: string;
export = value; export = value;
} }
/// <reference types="vite-plugin-svgr/client" />

View File

@@ -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();

View File

@@ -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[];

View File

@@ -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']);
},
},
);
};

View File

@@ -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({

View File

@@ -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,
}); });

View File

@@ -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}>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>
)} )}

View File

@@ -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'),

View File

@@ -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: '',
}); });

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 (

View File

@@ -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

View File

@@ -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,
}); });
} }
} }

View File

@@ -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} />

View File

@@ -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

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -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)} />
), ),

View File

@@ -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',