Compare commits
150 Commits
v2.8.0-RC1
...
release/v4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4743b6db5 | ||
|
|
b3880f7e7e | ||
|
|
30fffdfe52 | ||
|
|
c8d6540ca6 | ||
|
|
2b2f08c231 | ||
|
|
0cfed90a7b | ||
|
|
01008dc1aa | ||
|
|
26b90cfdba | ||
|
|
b218051104 | ||
|
|
a2fa93938f | ||
|
|
c220d11dd0 | ||
|
|
40d533ecc5 | ||
|
|
d1a1c96e74 | ||
|
|
1a18985c0d | ||
|
|
8eede7b559 | ||
|
|
caab40b08e | ||
|
|
18fa320b19 | ||
|
|
6f9f6638d6 | ||
|
|
5688e2f7bc | ||
|
|
4738097178 | ||
|
|
591ecc3664 | ||
|
|
b9089a39ac | ||
|
|
b7bdf89d37 | ||
|
|
849ea9f7b2 | ||
|
|
bd737ef563 | ||
|
|
e250bd38f8 | ||
|
|
7083da702a | ||
|
|
3d01c20339 | ||
|
|
3b74649206 | ||
|
|
a10f0c992e | ||
|
|
32974620c4 | ||
|
|
0781e3ad8e | ||
|
|
0ce107eea0 | ||
|
|
73e3efd92f | ||
|
|
69bff8d8fe | ||
|
|
22b223f82f | ||
|
|
7b0d43c8b8 | ||
|
|
7c64fb7a11 | ||
|
|
61f8b69f02 | ||
|
|
c32fedeb4c | ||
|
|
4ba3bed742 | ||
|
|
810318b584 | ||
|
|
863fda3ef3 | ||
|
|
deb7715ea1 | ||
|
|
adaebb17e7 | ||
|
|
e3f6ab43ff | ||
|
|
cf977b7612 | ||
|
|
fedb60fc8f | ||
|
|
f8ddf88b8c | ||
|
|
301581da63 | ||
|
|
88cb945760 | ||
|
|
c61d0052a9 | ||
|
|
147c3a1153 | ||
|
|
e9f1e4d8da | ||
|
|
f3a995f68f | ||
|
|
a967163d28 | ||
|
|
d3514213ca | ||
|
|
a55341f406 | ||
|
|
1c9a5bfa18 | ||
|
|
179900fab0 | ||
|
|
9011e30521 | ||
|
|
418f4ce576 | ||
|
|
9eb65237f9 | ||
|
|
89a667569b | ||
|
|
b87091a33a | ||
|
|
d9a659acbc | ||
|
|
ec8347fd7d | ||
|
|
b161729c46 | ||
|
|
2194a7fc23 | ||
|
|
03c6471e97 | ||
|
|
be52ed7d44 | ||
|
|
3afc9db5d3 | ||
|
|
30d882e1c0 | ||
|
|
4836279b77 | ||
|
|
4a74bfebc4 | ||
|
|
653cd758f4 | ||
|
|
e65f577202 | ||
|
|
3f9478de30 | ||
|
|
070a03c73e | ||
|
|
244692e766 | ||
|
|
a154fffcce | ||
|
|
ae0c529fca | ||
|
|
edcca87acf | ||
|
|
356188a350 | ||
|
|
cafb950aa7 | ||
|
|
549627a355 | ||
|
|
e6307648da | ||
|
|
fab4467bfd | ||
|
|
37666c5075 | ||
|
|
871efc88b5 | ||
|
|
db5611233b | ||
|
|
caa1fd4d9b | ||
|
|
be3f5548f4 | ||
|
|
a33740c372 | ||
|
|
130d71d5a0 | ||
|
|
bcd9c692e6 | ||
|
|
5947f3362d | ||
|
|
4bbfbb82bc | ||
|
|
6f7876d3e7 | ||
|
|
d4aff8067e | ||
|
|
eaca70d29b | ||
|
|
a1889c88d3 | ||
|
|
53b3926e29 | ||
|
|
745e76db79 | ||
|
|
82e153c277 | ||
|
|
b080b73b97 | ||
|
|
1c05d8df28 | ||
|
|
efc80a183b | ||
|
|
8a92912035 | ||
|
|
b870cf828a | ||
|
|
4cb4fe53a5 | ||
|
|
f70992e9a1 | ||
|
|
eb48d77636 | ||
|
|
df1686a2ae | ||
|
|
8781c78c15 | ||
|
|
ad5b0ce2a0 | ||
|
|
039e641046 | ||
|
|
f1f62efe6f | ||
|
|
b3053f32b2 | ||
|
|
09184b0402 | ||
|
|
98562fd967 | ||
|
|
65e9e64cb4 | ||
|
|
573ecbd58d | ||
|
|
a801fcca49 | ||
|
|
e9d16ee172 | ||
|
|
db4dfc93e8 | ||
|
|
cf17f03ae0 | ||
|
|
64f3ee797e | ||
|
|
e287705e88 | ||
|
|
9583b2bae0 | ||
|
|
2698993a6d | ||
|
|
a14b595e8c | ||
|
|
d7957b85ae | ||
|
|
227a51423d | ||
|
|
ea0e7340cc | ||
|
|
999680e94b | ||
|
|
566dbbb157 | ||
|
|
75d995d54e | ||
|
|
908faa491b | ||
|
|
7a254e343e | ||
|
|
016ac336b9 | ||
|
|
1cfd3a10ad | ||
|
|
1838029d22 | ||
|
|
7767043a5a | ||
|
|
b1cfa6db19 | ||
|
|
623d5a5546 | ||
|
|
8c676eb965 | ||
|
|
1e4ccce36c | ||
|
|
1808206e74 | ||
|
|
0fbc2b92aa |
2
.github/workflows/ci.yml
vendored
@@ -20,7 +20,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io
|
||||
DOCKER_REGISTRY_USERNAME: ucentral
|
||||
|
||||
2
.github/workflows/release.yml
vendored
@@ -11,7 +11,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
helm-package:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
HELM_REPO_URL: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
|
||||
HELM_REPO_USERNAME: ucentral
|
||||
|
||||
1
.gitignore
vendored
@@ -18,3 +18,4 @@
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
.vscode/settings.json
|
||||
|
||||
@@ -3,3 +3,4 @@ build
|
||||
dist
|
||||
node_modules
|
||||
.github
|
||||
/helm
|
||||
|
||||
@@ -17,7 +17,9 @@ metadata:
|
||||
{{- end }}
|
||||
|
||||
spec:
|
||||
|
||||
{{- if $ingressValue.className }}
|
||||
ingressClassName: {{ $ingressValue.className }}
|
||||
{{- end }}
|
||||
{{- if $ingressValue.tls }}
|
||||
tls:
|
||||
{{- range $ingressValue.tls }}
|
||||
|
||||
@@ -8,7 +8,7 @@ fullnameOverride: ""
|
||||
images:
|
||||
owgwui:
|
||||
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
|
||||
tag: main
|
||||
tag: v4.0.0
|
||||
pullPolicy: Always
|
||||
|
||||
services:
|
||||
|
||||
10124
package-lock.json
generated
101
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "ucentral-client",
|
||||
"version": "2.8.0(44)",
|
||||
"version": "4.0.0",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"main": "index.tsx",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"format": "prettier --write \"src/**/*.js\"",
|
||||
"format": "prettier --write \"src/**/*x.{ts,tsx,js,jsx}\"",
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||
"lint": "TIMING=1 eslint \"src/**/*.{ts,tsx,js,jsx}\" --fix",
|
||||
"clean": "rm -rf node_modules && rm -rf build"
|
||||
@@ -15,78 +15,87 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@chakra-ui/icons": "^2.0.11",
|
||||
"@chakra-ui/anatomy": "^2.1.1",
|
||||
"@chakra-ui/icons": "^2.0.18",
|
||||
"@chakra-ui/react": "^2.3.6",
|
||||
"@chakra-ui/styled-system": "^2.9.0",
|
||||
"@chakra-ui/theme-tools": "^2.0.12",
|
||||
"@chakra-ui/utils": "^2.0.11",
|
||||
"@emotion/react": "^11.10.4",
|
||||
"@emotion/styled": "^11.10.4",
|
||||
"@fontsource/inter": "^4.5.14",
|
||||
"@react-spring/web": "^9.5.5",
|
||||
"axios": "^1.1.3",
|
||||
"@chakra-ui/utils": "^2.0.14",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@fontsource/inter": "^4.5.15",
|
||||
"@googlemaps/react-wrapper": "^1.1.35",
|
||||
"@googlemaps/typescript-guards": "^2.0.3",
|
||||
"@hello-pangea/dnd": "^16.2.0",
|
||||
"@phosphor-icons/react": "^2.0.8",
|
||||
"@react-spring/web": "^9.7.2",
|
||||
"@tanstack/react-query": "^4.29.3",
|
||||
"@tanstack/react-table": "^8.8.5",
|
||||
"@textea/json-viewer": "^2.16.2",
|
||||
"axios": "^1.3.5",
|
||||
"buffer": "^6.0.3",
|
||||
"chakra-react-select": "^4.3.0",
|
||||
"chakra-react-select": "^4.6.0",
|
||||
"chart.js": "^3.9.1",
|
||||
"dagre": "^0.8.5",
|
||||
"fast-equals": "^5.0.1",
|
||||
"formik": "^2.2.9",
|
||||
"framer-motion": "^7.6.1",
|
||||
"i18next": "^22.0.0",
|
||||
"i18next-browser-languagedetector": "^6.1.8",
|
||||
"i18next-http-backend": "^1.4.4",
|
||||
"libphonenumber-js": "^1.10.14",
|
||||
"phosphor-react": "^1.4.1",
|
||||
"framer-motion": "^10.12.2",
|
||||
"i18next": "^22.4.14",
|
||||
"i18next-browser-languagedetector": "^7.0.1",
|
||||
"i18next-http-backend": "^2.2.0",
|
||||
"libphonenumber-js": "^1.10.26",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-chartjs-2": "^4.3.1",
|
||||
"chart.js": "^3.9.1",
|
||||
"react-country-flag": "^3.0.2",
|
||||
"react-country-flag": "^3.1.0",
|
||||
"react-csv": "^2.2.2",
|
||||
"react-datepicker": "^4.8.0",
|
||||
"react-datepicker": "^4.11.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@textea/json-viewer": "^2.10.0",
|
||||
"react-fast-compare": "^3.2.0",
|
||||
"react-i18next": "^11.18.6",
|
||||
"react-fast-compare": "^3.2.1",
|
||||
"react-i18next": "^12.2.0",
|
||||
"react-masonry-css": "^1.0.16",
|
||||
"@tanstack/react-query": "^4.12.0",
|
||||
"react-router-dom": "^6.4.2",
|
||||
"react-router-dom": "^6.10.0",
|
||||
"react-table": "^7.8.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.7",
|
||||
"react-window": "^1.8.8",
|
||||
"react-virtualized-auto-sizer": "^1.0.15",
|
||||
"react-window": "^1.8.9",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^3.1.8",
|
||||
"typescript": "^4.8.4",
|
||||
"typescript": "^5.0.4",
|
||||
"uuid": "^9.0.0",
|
||||
"vite": "^4.2.1",
|
||||
"yup": "^0.32.11",
|
||||
"zustand": "^4.1.2"
|
||||
"zustand": "^4.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.2",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/google.maps": "^3.52.5",
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/react": "^18.0.37",
|
||||
"@types/react-csv": "^1.1.3",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-table": "^7.7.12",
|
||||
"@types/react-datepicker": "4.8.0",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/react-datepicker": "4.10.0",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-table": "^7.7.14",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"eslint": "8.25.0",
|
||||
"vite-tsconfig-paths": "^3.5.1",
|
||||
"lint-staged": "^13.0.3",
|
||||
"@vitejs/plugin-react": "^2.1.0",
|
||||
"vite-plugin-pwa": "^0.13.1",
|
||||
"prettier": "^2.7.1",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"eslint": "8.38.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-config-airbnb-typescript-prettier": "^5.0.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-plugin-babel": "^5.3.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-no-inline-styles": "^1.0.5",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.31.10",
|
||||
"eslint-plugin-react-hooks": "^4.6.0"
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"lint-staged": "^13.2.1",
|
||||
"prettier": "^2.8.7",
|
||||
"vite-plugin-pwa": "^0.14.7",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"vite-tsconfig-paths": "^4.2.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
BIN
public/devices/asterfusion_CX204Y-24GT-M-SWP4.png
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
public/devices/asterfusion_CX204Y-48GT-M-SWP4.png
Normal file
|
After Width: | Height: | Size: 394 KiB |
BIN
public/devices/cig_wf186h.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
public/devices/cig_wf186w.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
public/devices/cig_wf188n-ca-ath12.png
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
public/devices/cig_wf188n-ca.png
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
public/devices/cig_wf188n-us.png
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
public/devices/cig_wf196-ca-ath12.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
public/devices/cig_wf196-ca.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
public/devices/cig_wf196-us.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
public/devices/cig_wf196.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/devices/cig_wf610d.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
public/devices/cig_wf660a.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
public/devices/cybertan_eww631-a1.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
public/devices/cybertan_eww631-b1.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
public/devices/cybertan_skf224-c1.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
public/devices/cybertan_skf424-c1.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
public/devices/edgecore_eap101-ath12.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
public/devices/edgecore_eap102-ath12.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
public/devices/edgecore_eap104.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
public/devices/edgecore_eap104_ath12.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
public/devices/edgecore_eap111.png
Normal file
|
After Width: | Height: | Size: 286 KiB |
BIN
public/devices/edgecore_ecs2100-10p.png
Normal file
|
After Width: | Height: | Size: 502 KiB |
BIN
public/devices/edgecore_ecs2100-10t.png
Normal file
|
After Width: | Height: | Size: 637 KiB |
BIN
public/devices/edgecore_ecs2100-28p.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
public/devices/edgecore_ecs2100-28pp.png
Normal file
|
After Width: | Height: | Size: 313 KiB |
BIN
public/devices/edgecore_ecs2100-28t.png
Normal file
|
After Width: | Height: | Size: 314 KiB |
BIN
public/devices/edgecore_ecs2100-52t.png
Normal file
|
After Width: | Height: | Size: 283 KiB |
BIN
public/devices/edgecore_ecs4125-10p.png
Normal file
|
After Width: | Height: | Size: 374 KiB |
BIN
public/devices/edgecore_ecs4125.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
public/devices/edgecore_ecs4150-28t.png
Normal file
|
After Width: | Height: | Size: 267 KiB |
BIN
public/devices/edgecore_ecs4150-58p.png
Normal file
|
After Width: | Height: | Size: 331 KiB |
BIN
public/devices/edgecore_oap101-6e.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
public/devices/edgecore_oap101.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
public/devices/edgecore_oap101e-6e.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
public/devices/edgecore_oap101e.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
public/devices/edgecore_oap102.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
public/devices/generic_ap.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/devices/hfcl_ion4x.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
public/devices/hfcl_ion4x_2.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
public/devices/hfcl_ion4x_w.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
public/devices/hfcl_ion4xe.png
Normal file
|
After Width: | Height: | Size: 421 KiB |
BIN
public/devices/hfcl_ion4xi.png
Normal file
|
After Width: | Height: | Size: 817 KiB |
BIN
public/devices/hfcl_ion4xi_HMR.png
Normal file
|
After Width: | Height: | Size: 879 KiB |
BIN
public/devices/hfcl_ion4xi_w.png
Normal file
|
After Width: | Height: | Size: 664 KiB |
BIN
public/devices/hfcl_ion4xi_wp.png
Normal file
|
After Width: | Height: | Size: 304 KiB |
@@ -64,6 +64,8 @@
|
||||
"health": "Gesundheit",
|
||||
"inactive": "Inaktiv",
|
||||
"interval": "Intervall",
|
||||
"last_connected": "Zuletzt verbunden",
|
||||
"last_connected_tooltip": "Das letzte Mal, wann dieses Gerät mit dem Controller verbunden war. Dies kann verwendet werden, um abzuschätzen, wann ein Gerät getrennt wurde",
|
||||
"last_connection": "Letzte Verbindung",
|
||||
"last_contact": "Letzter Kontakt",
|
||||
"last_disconnection": "Letzte Trennung",
|
||||
@@ -79,8 +81,11 @@
|
||||
"live_view_help": "Hilfe zur Live-Ansicht",
|
||||
"memory": "Erinnerung",
|
||||
"memory_used": "Verwendeter Speicher",
|
||||
"missing_board": "Die Analytics-Überwachung an diesem Veranstaltungsort ist nicht mehr aktiv. Bitte starten Sie die Überwachung über das obere Menü neu",
|
||||
"missing_board": "Analytics-Überwachung an diesem Ort ist nicht mehr aktiv. Klicken Sie hier, um die Überwachung neu zu starten",
|
||||
"mode": "Modus",
|
||||
"monitoring": "Überwachung",
|
||||
"no_board": "Keine Überwachung",
|
||||
"no_board_description": "Sie überwachen diesen Veranstaltungsort derzeit nicht, klicken Sie hier, um zu beginnen",
|
||||
"noise": "Lärm",
|
||||
"packets": "Pakete",
|
||||
"radio": "RADIO",
|
||||
@@ -91,6 +96,8 @@
|
||||
"retries": "Wiederholungen",
|
||||
"search_serials": "Zeitschriften suchen",
|
||||
"stop_monitoring": "Beenden Sie die Überwachung",
|
||||
"stop_monitoring_success": "Überwachungsort gestoppt!",
|
||||
"stop_monitoring_warning": "Bist du sicher? Dadurch werden alle aufgezeichneten Überwachungsdaten für diesen Veranstaltungsort gelöscht",
|
||||
"temperature": "Temperatur",
|
||||
"title": "ANALYTICS",
|
||||
"total_data": "Gesamtdaten",
|
||||
@@ -175,6 +182,7 @@
|
||||
"other": "Befehle",
|
||||
"override_dfs": "DFS überschreiben",
|
||||
"reboot": "Starten Sie neu",
|
||||
"reboot_description": "Möchten Sie dieses Gerät neu starten?",
|
||||
"reboot_error": "Fehler beim Senden des Neustartbefehls: {{e}}",
|
||||
"reboot_success": "Neustartbefehl erfolgreich gesendet!",
|
||||
"revision": "Revision",
|
||||
@@ -215,6 +223,7 @@
|
||||
"day": "Tag",
|
||||
"days": "Tage",
|
||||
"default": "Standard",
|
||||
"defaults": "Standardeinstellungen",
|
||||
"description": "Beschreibung",
|
||||
"details": "Einzelheiten",
|
||||
"device_details": "Gerätedetails",
|
||||
@@ -235,6 +244,7 @@
|
||||
"error_download": "Fehler beim Downloadversuch: {{e}}",
|
||||
"errors": "Fehler",
|
||||
"exit_fullscreen": "Ausgang",
|
||||
"export": "Export",
|
||||
"finished": "Fertig",
|
||||
"fullscreen": "Vollbildschirm",
|
||||
"general_error": "Fehler beim Verbinden mit dem Server. Bitte wenden Sie sich an Ihren Administrator.",
|
||||
@@ -259,6 +269,7 @@
|
||||
"map": "Karte",
|
||||
"max": "Max",
|
||||
"min": "MINDEST",
|
||||
"miscellaneous": "Verschiedenes",
|
||||
"mode": "Modus",
|
||||
"model": "Modell",
|
||||
"modified": "Geändert",
|
||||
@@ -350,6 +361,7 @@
|
||||
"error_pushes_one": "Fehler (könnte an einer fehlerhaften Konfiguration liegen): {{count}}",
|
||||
"error_pushes_other": "Fehler (können auf eine fehlerhafte Konfiguration zurückzuführen sein): {{count}}",
|
||||
"expert_name": "Expertenmodus",
|
||||
"expert_name_explanation": "Sie können den Expertenmodus verwenden, um Ihre Konfiguration direkt zu ändern, einschließlich des Hinzufügens von Werten, die derzeit nicht von der Benutzeroberfläche unterstützt werden. Bitte verwenden Sie dieses Format: { \"interfaces\": [ ... ], \"globals\": { ... }, ...etc }",
|
||||
"explanation": "Erläuterung",
|
||||
"firewall": "Firewall",
|
||||
"firmware_upgrade": "Firmware-Aktualisierung",
|
||||
@@ -391,6 +403,7 @@
|
||||
"warning_pushes_one": "Warten auf Geräteverbindung: {{count}}",
|
||||
"warning_pushes_other": "Warten auf Geräteverbindung: {{count}}",
|
||||
"weight": "Gewicht",
|
||||
"wifi_bands_max": "Es können nicht mehr als 8 SSIDs dieses WLAN-Band verwenden",
|
||||
"wifi_frames": "WiFi-Frames"
|
||||
},
|
||||
"contacts": {
|
||||
@@ -515,6 +528,7 @@
|
||||
"ouis_explanation": "OUIs von Geräten, die sich mit diesem Firmware-Server verbunden haben",
|
||||
"outdated_one": "Firmware {{count}} Tag alt",
|
||||
"outdated_other": "Firmware {{count}} Tage alt",
|
||||
"outdated_unknown": "Firmware unbekannten Alters",
|
||||
"release": "Veröffentlichung",
|
||||
"show_dev_releases": "Entwicklerversionen",
|
||||
"status_explanation": "Verbindungsstatus von Geräten, die sich mit diesem Firmware-Server verbunden haben",
|
||||
@@ -530,6 +544,16 @@
|
||||
"queue": {
|
||||
"title": "Ereigniswarteschlange"
|
||||
},
|
||||
"radius": {
|
||||
"calling_station_id": "Stations",
|
||||
"disconnect": "Trennen",
|
||||
"disconnect_success": "Radius-Sitzung getrennt!",
|
||||
"input_octets": "Eingang",
|
||||
"output_octets": "Ausgabe",
|
||||
"radius_clients": "Radius-Kunden",
|
||||
"session_time": "Sitzungszeit",
|
||||
"username": "Nutzername"
|
||||
},
|
||||
"stats": {
|
||||
"load": "Belastung (1 | 5 | 15 m.)",
|
||||
"seconds_ago": " Vor {{s}} Sekunden",
|
||||
@@ -600,6 +624,7 @@
|
||||
"certificate_expires_in": "Zertifikat läuft ab in",
|
||||
"certificate_expiry": "Zert. Läuft ab in",
|
||||
"connected": "In Verbindung gebracht",
|
||||
"crash_logs": "Absturzprotokolle",
|
||||
"create_errors": "Fehler beim Versuch, Geräte zu erstellen",
|
||||
"create_success": " Geräte erfolgreich erstellt",
|
||||
"current_firmware": "Aktuelle Firmware",
|
||||
@@ -613,6 +638,7 @@
|
||||
"import_device_warning": "Bitte stellen Sie sicher, dass am Anfang oder Ende von Werten keine zusätzlichen Leerzeichen stehen, es sei denn, es handelt sich um einen Teil des gewünschten Werts",
|
||||
"import_explanation": "Für den Massenimport von Geräten müssen Sie eine CSV-Datei mit den folgenden Spalten verwenden: SerialNumber, DeviceType, Name, Description, Note",
|
||||
"invalid_serial_number": "Ungültige Seriennummer (muss 12 HEX-Zeichen lang sein)",
|
||||
"logs_one": "Log",
|
||||
"new_devices": "Neue Geräte",
|
||||
"no_model_image": "Kein Modellbild gefunden",
|
||||
"not_connected": "Nicht verbunden",
|
||||
@@ -620,7 +646,10 @@
|
||||
"notifications": "Gerätebenachrichtigungen",
|
||||
"one": "Gerät",
|
||||
"reassign_already_owned": "Geräte neu zuweisen, die bereits vorhanden sind und einem anderen Unternehmen/Veranstaltungsort/Abonnenten gehören?",
|
||||
"reboot_logs": "Neustartprotokolle",
|
||||
"restricted": "Beschränkt",
|
||||
"restricted_overriden": "Dies ist ein eingeschränktes Gerät, aber es befindet sich im Entwicklungsmodus. Alle Einschränkungen werden derzeit ignoriert",
|
||||
"restrictions_overriden_title": "Dev-Modus",
|
||||
"sanity": "Gesundheit",
|
||||
"start_import": "Geräteimport starten",
|
||||
"test_batch": "Testen Sie Importdaten",
|
||||
@@ -671,7 +700,36 @@
|
||||
"test_digicert_creds": "Anmeldeinformationen testen",
|
||||
"title": "Entitäten",
|
||||
"tree": "Entitätsbaum",
|
||||
"venues_under_root": "Veranstaltungsorte können nicht direkt unter der Root-Entität erstellt werden. Bitte erstellen Sie neue Entitäten und erstellen Sie Veranstaltungsorte unter diesen."
|
||||
"update_success": "Entität aktualisiert!",
|
||||
"venues_under_root": "Veranstaltungsorte können nicht direkt unter der Root-Entität erstellt werden"
|
||||
},
|
||||
"firmware": {
|
||||
"confirm_default_data": "Bitte bestätigen Sie die untenstehenden Informationen und klicken Sie auf „Bestätigen“, sobald Sie bereit sind, den Vorgang zu starten",
|
||||
"create_success": "Neue Standard-Firmware-Einstellungen erstellt!",
|
||||
"db_update_warning": "Dieser Vorgang wird täglich automatisch durchgeführt, ohne dass dieses manuelle Update verwendet werden muss. Die Aktualisierung dieser Datenbank kann bis zu 25 Minuten dauern",
|
||||
"default_created_error_one": "{{count}} Fehler beim Versuch, eine neue Einstellung zu erstellen",
|
||||
"default_created_error_other": "{{count}} Fehler beim Versuch, eine neue Einstellung zu erstellen",
|
||||
"default_created_one": "{{count}} Standard-Firmware-Einstellung erstellt",
|
||||
"default_created_other": "{{count}} Standard-Firmware-Einstellungen erstellt",
|
||||
"default_found_one": "Für den Gerätetyp {{count}} wurde eine gültige Revision gefunden",
|
||||
"default_found_other": "Gültige Revisionen für {{count}} Gerätetypen gefunden",
|
||||
"default_mass_delete_success_one": " {{count}} Standard-Firmware-Einstellung gelöscht!",
|
||||
"default_mass_delete_success_other": " {{count}} Standard-Firmware-Einstellungen gelöscht!",
|
||||
"default_not_found_one": "Keine gültigen Firmware-Versionen für den Gerätetyp {{count}} ",
|
||||
"default_not_found_other": "Keine gültigen Firmware-Versionen für {{count}} Gerätetypen",
|
||||
"default_title": "",
|
||||
"default_update_success": "Standard-Firmware für {{deviceType}}aktualisiert!",
|
||||
"delete_success": "Standard-Firmware-Einstellung gelöscht!",
|
||||
"edit_default_title": "Dies ist die aktuelle Firmware, die als Mindestversion für neue APs vom Typ {{deviceType}}verwendet wird. Wenn ein neuer {{deviceType}} AP eine Verbindung zum Gateway herstellt, wird er automatisch auf diese Version aktualisiert.",
|
||||
"fetching_defaults": "Alle verfügbaren Firmware für ausgewählte Gerätetypen werden abgerufen...",
|
||||
"last_db_update_modal": "Firmware-Datenbank",
|
||||
"last_db_update_title": "Datenbank",
|
||||
"one": "Firmware",
|
||||
"select_default_device_types": "Bitte wählen Sie alle Gerätetypen aus, auf die Sie diese neue Standard-Firmware-Regel anwenden möchten. Wenn Sie den gewünschten Gerätetyp nicht finden können, bedeutet dies, dass bereits eine Regel angewendet wurde.",
|
||||
"select_default_revision": "Sie können jetzt die Mindestversion auswählen, auf die Ihre Gerätetypen abzielen sollen",
|
||||
"start_db_update": "Datenbankaktualisierung starten",
|
||||
"started_db_update": "Datenbankaktualisierung gestartet, dieser Vorgang sollte bis zu 25 Minuten dauern",
|
||||
"update_success": "Standard-Firmware-Informationen gespeichert!"
|
||||
},
|
||||
"footer": {
|
||||
"powered_by": "Unterstützt von",
|
||||
@@ -680,6 +738,7 @@
|
||||
"form": {
|
||||
"captive_web_root_explanation": "Bitte verwenden Sie nur .tar-Dateien (keine komprimierten Dateien wie z. B. .targz)",
|
||||
"certificate_file_explanation": "Bitte verwenden Sie eine .pem-Datei, die mit „-----BEGIN CERTIFICATE-----“ beginnt und mit „-----END CERTIFICATE-----“ endet.",
|
||||
"invalid_alphanumeric_with_dash": "Akzeptierte Zeichen. sind nur alphanumerisch (Buchstaben & Zahlen)",
|
||||
"invalid_cidr": "Ungültige CIDR-IPv4-Adresse. Beispiel: 192.168.0.1/12",
|
||||
"invalid_email": "Ungültige E-Mail",
|
||||
"invalid_file_content": "Ungültiger Dateiinhalt, bitte bestätigen Sie, dass es sich um ein gültiges Format handelt",
|
||||
@@ -693,7 +752,7 @@
|
||||
"invalid_ipv6": "Ungültige IPv6-Adresse (Bsp.: 2001:db8:3333:4444:5555:6666:7777:8888)",
|
||||
"invalid_json": "Ungültige JSON-Zeichenfolge",
|
||||
"invalid_lease_time": "Ungültiger Lease-Time-Wert! Sie müssen im digitUnit-Format vorliegen. Zum Beispiel: 6d2h5m, was 6 Tage, 2 Stunden und 5 Minuten bedeutet. Hier sind die akzeptierten Einheiten: m, h, d. Wenn Sie eine Einheit nicht verwenden möchten, lassen Sie sie vollständig weg. Anstatt also 0d2h0m zu sagen, verwenden Sie 2h",
|
||||
"invalid_mac_uc": "Ungültiger UC-MAC-Wert, zum Beispiel: 00:00:5e:00:53:af",
|
||||
"invalid_mac_uc": "Ungültiger MAC-Wert, zum Beispiel: 00:00:5e:00:53:af",
|
||||
"invalid_password": "Ungültiges Passwort, bitte sehen Sie sich die Passwortrichtlinie an",
|
||||
"invalid_phone_number": "Ungültige Telefonnummer",
|
||||
"invalid_phone_numbers": "Mindestens eine der Telefonnummern ist ungültig. Bitte geben Sie sie ohne Symbole und Leerzeichen oder in diesem Format an: +1(123)123-1234",
|
||||
@@ -706,7 +765,11 @@
|
||||
"invalid_static_ipv4_e": "Ungültige Adresse, dieser Bereich ist für Experimente reserviert (Klasse E). Das erste Oktett sollte 223 oder niedriger sein",
|
||||
"invalid_third_party": "Ungültige Drittanbieter-JSON-Zeichenfolge. Bitte bestätigen Sie, dass Ihr Wert ein gültiges JSON ist",
|
||||
"key_file_explanation": "Bitte verwenden Sie eine .pem-Datei, die mit „-----BEGIN PRIVATE KEY-----“ beginnt und mit „-----END PRIVATE KEY-----“ endet.",
|
||||
"max_length": "Maximale Länge von {{max}} Zeichen.",
|
||||
"max_value": "Maximalwert von {{max}}",
|
||||
"min_length": "Mindestlänge von {{min}} Zeichen.",
|
||||
"min_max_string": "Der Wert muss eine Länge zwischen {{min}} (einschließlich) und {{max}} (einschließlich) haben.",
|
||||
"min_value": "Mindestwert von {{min}}",
|
||||
"missing_interface_upstream": "Sie müssen mindestens eine Upstream-Schnittstelle haben. Im Moment sind alle Ihre Schnittstellen nachgelagert",
|
||||
"new_email_to_notify": "Neue E-Mail zur Benachrichtigung",
|
||||
"new_phone_to_notify": "Neues Telefon zu benachrichtigen",
|
||||
@@ -765,13 +828,17 @@
|
||||
"city": "Stadt",
|
||||
"claim_explanation": "Um Standorte zu beanspruchen, können Sie die folgende Tabelle verwenden",
|
||||
"country": "Land",
|
||||
"elevation": "Elevation",
|
||||
"geocode": "Geo-Code",
|
||||
"lat": "Breite",
|
||||
"longitude": "Längengrad",
|
||||
"one": "Ort",
|
||||
"other": "Standorte",
|
||||
"postal": "Postleitzahl",
|
||||
"state": "Bundesstaat / Provinz",
|
||||
"title": "Standorte",
|
||||
"to_claim": "Standorte zu beanspruchen"
|
||||
"to_claim": "Standorte zu beanspruchen",
|
||||
"view_gps": ""
|
||||
},
|
||||
"login": {
|
||||
"access_policy": "Zugangsrichtlinien",
|
||||
@@ -797,6 +864,7 @@
|
||||
"reset_password": "Passwort zurücksetzen",
|
||||
"sign_in": "Einloggen",
|
||||
"sms_instructions": "Sie sollten bald einen 6-stelligen Code auf Ihrem Telefon erhalten. Bitte geben Sie es unten ein, um sich anzumelden",
|
||||
"suspended_error": "Konto gesperrt, wenden Sie sich bitte an Ihren Administrator",
|
||||
"verification": "Bestätigen Sie Ihre Anmeldung",
|
||||
"waiting_for_email_verification": "Konto noch nicht per E-Mail validiert. Bitte sehen Sie in Ihrem Posteingang nach oder bitten Sie Ihren Administrator, eine Bestätigung erneut zu senden",
|
||||
"welcome_back": "Willkommen zurück!",
|
||||
@@ -843,6 +911,11 @@
|
||||
"one": "Benachrichtigung",
|
||||
"other": "Benachrichtigungen"
|
||||
},
|
||||
"openroaming": {
|
||||
"pool_strategy": "Pool-Strategie",
|
||||
"radius_endpoint_one": "Radiusendpunkt",
|
||||
"radius_endpoint_other": "Radiusendpunkte"
|
||||
},
|
||||
"operator": {
|
||||
"delete_explanation": "Möchten Sie diesen Operator wirklich löschen? Dieser Vorgang ist nicht umkehrbar",
|
||||
"delete_operator": "Betreiber löschen",
|
||||
@@ -902,12 +975,33 @@
|
||||
"dfs": "DFS-Überschreibung",
|
||||
"gw_commands": "Gateway-Befehle",
|
||||
"identifier": "Identifikator",
|
||||
"key_verification": "Überprüfung des Signaturschlüssels",
|
||||
"key_verification": "Signieren von Schlüsselinformationen",
|
||||
"restricted": "Beschränkt",
|
||||
"signed_upgrade": "Nur signiertes Upgrade",
|
||||
"title": "Beschränkungen",
|
||||
"tty": "TTY-Zugriff"
|
||||
},
|
||||
"roaming": {
|
||||
"account_created": "Neues Konto erstellt!",
|
||||
"account_deleted": "Konto gelöscht!",
|
||||
"account_one": "Konto",
|
||||
"account_other": "Konten",
|
||||
"certificate_deleted": "Zertifikat gelöscht!",
|
||||
"certificate_one": "Zertifikat",
|
||||
"certificate_other": "Zertifikate",
|
||||
"city": "Stadt",
|
||||
"common_name": "Gemeinsamen Namen",
|
||||
"country": "Land",
|
||||
"global_reach": "Globale Reichweite",
|
||||
"global_reach_account_id": "Konto-ID",
|
||||
"invalid_certificate": "Ungültiges Zertifikat",
|
||||
"invalid_key": "Ungültiger privater Schlüssel",
|
||||
"location_details_title": "Ort",
|
||||
"organization": "Organisation",
|
||||
"private_key": "Privat Schlüssel",
|
||||
"province": "Provinz",
|
||||
"state": "Zustand"
|
||||
},
|
||||
"rrm": {
|
||||
"algorithm": "Algorithmus",
|
||||
"algorithm_other": "Algorithmen",
|
||||
@@ -966,6 +1060,11 @@
|
||||
"concurrent_devices": "Gleichzeitige Geräte",
|
||||
"controller": "Regler",
|
||||
"current_live_devices": "Aktuelle Live-Geräte",
|
||||
"currently_running_one": "Derzeit wird {{count}} Simulation ausgeführt",
|
||||
"currently_running_other": "Derzeit laufen {{count}} Simulationen",
|
||||
"delete_devices_confirm": "Sind Sie sicher, dass Sie alle Geräte und deren Statistiken vom Gateway entfernen möchten? Diese Aktion ist nicht rückgängig zu machen",
|
||||
"delete_devices_loading": "Dieser Vorgang kann bis zu 5 Minuten dauern",
|
||||
"delete_simulation_devices": "Geräte löschen",
|
||||
"delete_success": "Gelöschte Simulation!",
|
||||
"duration": "Dauer",
|
||||
"error_devices": "Fehler Geräte",
|
||||
@@ -973,6 +1072,7 @@
|
||||
"infinite": "Unendlich",
|
||||
"keep_alive": "Bleib am Leben",
|
||||
"mac_prefix": "MAC-Präfix",
|
||||
"mac_prefix_length": "Ihr MAC-Präfix muss gültige 6 HEX-Ziffern haben (z. B.: 00112233)",
|
||||
"max_associations": "max. Verbände",
|
||||
"max_clients": "Max. Kunden",
|
||||
"min_associations": "Mindest. Verbände",
|
||||
@@ -989,6 +1089,7 @@
|
||||
"rx_messages": "Rx-Meldungen",
|
||||
"sim_currently_running": "Simulation \"{{sim}}\" läuft",
|
||||
"sim_history": "{{sim}} Vorherige Läufe",
|
||||
"simulated": "Simuliert",
|
||||
"start": "Simulation starten",
|
||||
"start_success": "Simulationslauf gestartet!",
|
||||
"state_interval": "Zustandsintervall",
|
||||
@@ -1002,6 +1103,7 @@
|
||||
},
|
||||
"statistics": {
|
||||
"last_stats": "Letzte Statistik",
|
||||
"latest": "Neueste Statistiken",
|
||||
"memory": "Erinnerung"
|
||||
},
|
||||
"subscribers": {
|
||||
@@ -1021,7 +1123,9 @@
|
||||
"title": "Abonnenten"
|
||||
},
|
||||
"system": {
|
||||
"advanced": "Erweitert",
|
||||
"backend_logs": "Back-End-Protokolle",
|
||||
"configuration": "Aufbau",
|
||||
"could_not_retrieve": "Fehler: {{name}} Systeminformationen konnten nicht abgerufen werden",
|
||||
"endpoint": "Endpunkt",
|
||||
"hostname": "Hostname",
|
||||
@@ -1032,6 +1136,10 @@
|
||||
"os": "Betriebssystem",
|
||||
"processors": "Prozessoren",
|
||||
"reload_chosen_subsystems": "Ausgewählte Subsysteme neu laden",
|
||||
"secrets": "Geheimnisse",
|
||||
"secrets_create": "Geheimnis erstellen",
|
||||
"secrets_one": "Geheimnis",
|
||||
"services": "dienstleistungen",
|
||||
"start": "Start",
|
||||
"subsystems": "Subsysteme",
|
||||
"success_reload": "Reload-Befehl erfolgreich gesendet!",
|
||||
@@ -1043,19 +1151,31 @@
|
||||
"version": "Ausführung"
|
||||
},
|
||||
"table": {
|
||||
"columns": "Säulen",
|
||||
"columns_hidden_one": "{{count}} Spalte ausgeblendet",
|
||||
"columns_hidden_other": "{{count}} Spalten ausgeblendet",
|
||||
"display_column": "Anzeige",
|
||||
"drag_always_show": "Sie können diese Spalte nicht ausblenden, aber ihre Position ändern",
|
||||
"drag_explanation": "Ziehen und Ablegen zum Neuordnen und Ändern der Spaltensichtbarkeit",
|
||||
"drag_locked": "Diese Säule ist in ihrer Position arretiert",
|
||||
"export_current_page": "Nur aktuelle Seite",
|
||||
"first_page": "Erste Seite",
|
||||
"go_to_page": "Zur Seite gehen",
|
||||
"hide_column": "verbergen",
|
||||
"last_page": "Letzte Seite",
|
||||
"next_page": "Nächste Seite",
|
||||
"page": "Seite",
|
||||
"previous_page": "Vorherige Seite"
|
||||
"preferences": "Tabelleneinstellungen",
|
||||
"previous_page": "Vorherige Seite",
|
||||
"reset": "Einstellungen zurücksetzen",
|
||||
"settings": "die Einstellungen"
|
||||
},
|
||||
"user": {
|
||||
"email_not_validated": "E-Mail nicht validiert",
|
||||
"error_fetching": "Fehler beim Abrufen der Benutzerinformationen: {{e}}",
|
||||
"password": "Passwort",
|
||||
"role": "Rolle",
|
||||
"suspended": "Suspendiert",
|
||||
"title": "Nutzer"
|
||||
},
|
||||
"users": {
|
||||
@@ -1100,9 +1220,11 @@
|
||||
"successfully_update_devices": " {{num}} Geräte werden aktualisiert!",
|
||||
"title": "Veranstaltungsorte",
|
||||
"update_all_devices": "Alle Gerätekonfigurationen aktualisieren",
|
||||
"upgrade_all_devices": "Aktualisieren Sie alle Geräte auf die neueste Firmware",
|
||||
"update_success": "Veranstaltungsort aktualisiert!",
|
||||
"upgrade_all_devices": "Aktualisieren Sie die Firmware aller Geräte",
|
||||
"upgrade_all_devices_error": "Fehler beim Aktualisieren von Geräten: {{e}}",
|
||||
"upgrade_all_devices_success": "Upgrade von Geräten erfolgreich gestartet!",
|
||||
"upgrade_options_available": "Hier sind alle verfügbaren Revisionen, bitte wählen Sie diejenige aus, auf die ALLE Geräte dieses Veranstaltungsortes aktualisiert werden sollen",
|
||||
"use_existing": "Benutze existierendes",
|
||||
"use_existing_contacts": "Verwenden Sie vorhandene Kontakte",
|
||||
"use_this_contact": "Verwenden Sie diesen Kontakt"
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
"health": "Health",
|
||||
"inactive": "Inactive",
|
||||
"interval": "Interval",
|
||||
"last_connected": "Last Connected",
|
||||
"last_connected_tooltip": "Last time this device was connected to the controller. This can be used to estimate when a device disconnected",
|
||||
"last_connection": "Last Connection",
|
||||
"last_contact": "Last Contact",
|
||||
"last_disconnection": "Last Disconnection",
|
||||
@@ -79,8 +81,11 @@
|
||||
"live_view_help": "Live View Help",
|
||||
"memory": "Memory",
|
||||
"memory_used": "Memory Used",
|
||||
"missing_board": "Analytics monitoring on this venue is no longer active, please restart monitoring using the top menu",
|
||||
"missing_board": "Analytics monitoring on this venue is no longer active. Click here to restart monitoring",
|
||||
"mode": "Mode",
|
||||
"monitoring": "Monitoring",
|
||||
"no_board": "No Monitoring",
|
||||
"no_board_description": "You are not monitoring this Venue at the moment, click here to start",
|
||||
"noise": "Noise",
|
||||
"packets": "Packets",
|
||||
"radio": "Radio",
|
||||
@@ -91,6 +96,8 @@
|
||||
"retries": "Retries",
|
||||
"search_serials": "Search Serials",
|
||||
"stop_monitoring": "Stop Monitoring",
|
||||
"stop_monitoring_success": "Stopped Monitoring Venue!",
|
||||
"stop_monitoring_warning": "Are you sure? This will erase all recorded monitoring data for this venue",
|
||||
"temperature": "Temperature",
|
||||
"title": "Analytics",
|
||||
"total_data": "Total Data",
|
||||
@@ -175,6 +182,7 @@
|
||||
"other": "Commands",
|
||||
"override_dfs": "Override DFS",
|
||||
"reboot": "Reboot",
|
||||
"reboot_description": "Do you want to reboot this device?",
|
||||
"reboot_error": "Error while sending reboot command: {{e}}",
|
||||
"reboot_success": "Successfully sent reboot command!",
|
||||
"revision": "Revision",
|
||||
@@ -215,6 +223,7 @@
|
||||
"day": "Day",
|
||||
"days": "Days",
|
||||
"default": "Default",
|
||||
"defaults": "Defaults",
|
||||
"description": "Description",
|
||||
"details": "Details",
|
||||
"device_details": "Device Details",
|
||||
@@ -235,6 +244,7 @@
|
||||
"error_download": "Error while trying to download: {{e}}",
|
||||
"errors": "Errors",
|
||||
"exit_fullscreen": "Exit",
|
||||
"export": "Export",
|
||||
"finished": "Finished",
|
||||
"fullscreen": "Fullscreen",
|
||||
"general_error": "Error connecting to the server. Please consult your administrator.",
|
||||
@@ -259,6 +269,7 @@
|
||||
"map": "Map",
|
||||
"max": "Max",
|
||||
"min": "Min",
|
||||
"miscellaneous": "Miscellaneous",
|
||||
"mode": "Mode",
|
||||
"model": "Model",
|
||||
"modified": "Modified",
|
||||
@@ -350,6 +361,7 @@
|
||||
"error_pushes_one": "Error (could be because of bad configuration): {{count}}",
|
||||
"error_pushes_other": "Errors (could be because of bad configuration): {{count}}",
|
||||
"expert_name": "Expert Mode",
|
||||
"expert_name_explanation": "You can use expert mode to directly modify your configuration, including adding values that are not currently supported by the UI. Please use this format: { \"interfaces\": [ ... ], \"globals\": { ... }, ...etc }",
|
||||
"explanation": "Explanation",
|
||||
"firewall": "Firewall",
|
||||
"firmware_upgrade": "Firmware Upgrade",
|
||||
@@ -391,6 +403,7 @@
|
||||
"warning_pushes_one": "Waiting for devices to connect: {{count}}",
|
||||
"warning_pushes_other": "Waiting for devices to connect: {{count}}",
|
||||
"weight": "Weight",
|
||||
"wifi_bands_max": "There cannot be more than 8 SSIDs using this wifi-band",
|
||||
"wifi_frames": "WiFi Frames"
|
||||
},
|
||||
"contacts": {
|
||||
@@ -515,6 +528,7 @@
|
||||
"ouis_explanation": "OUIs of devices that have connected to this firmware server",
|
||||
"outdated_one": "Firmware {{count}} day old",
|
||||
"outdated_other": "Firmware {{count}} days old",
|
||||
"outdated_unknown": "Firmware of unknown age",
|
||||
"release": "Release",
|
||||
"show_dev_releases": "Dev Releases",
|
||||
"status_explanation": "Connection status of devices that have connected to this firmware server",
|
||||
@@ -530,6 +544,16 @@
|
||||
"queue": {
|
||||
"title": "Event Queue"
|
||||
},
|
||||
"radius": {
|
||||
"calling_station_id": "Station",
|
||||
"disconnect": "Disconnect",
|
||||
"disconnect_success": "Radius session disconnected!",
|
||||
"input_octets": "Input",
|
||||
"output_octets": "Output",
|
||||
"radius_clients": "Radius Clients",
|
||||
"session_time": "Session Time",
|
||||
"username": "Username"
|
||||
},
|
||||
"stats": {
|
||||
"load": "Load (1 | 5 | 15 m.)",
|
||||
"seconds_ago": "{{s}} seconds ago",
|
||||
@@ -600,6 +624,7 @@
|
||||
"certificate_expires_in": "Certificate Expiry",
|
||||
"certificate_expiry": "Cert. Expires In",
|
||||
"connected": "Connected",
|
||||
"crash_logs": "Crash Logs",
|
||||
"create_errors": "errors while trying to create devices",
|
||||
"create_success": " devices successfully created",
|
||||
"current_firmware": "Current Firmware",
|
||||
@@ -613,6 +638,7 @@
|
||||
"import_device_warning": "Please make sure there are no extra spaces at the start or end of any values unless it is part of the value desired",
|
||||
"import_explanation": "To bulk import devices, you need to use a CSV file with the following columns: SerialNumber, DeviceType, Name, Description, Note",
|
||||
"invalid_serial_number": "Invalid Serial Number (needs to be 12 HEX chars)",
|
||||
"logs_one": "Log",
|
||||
"new_devices": "new devices",
|
||||
"no_model_image": "No Model Image Found",
|
||||
"not_connected": "Not Connected",
|
||||
@@ -620,7 +646,10 @@
|
||||
"notifications": "Device Notifications",
|
||||
"one": "Device",
|
||||
"reassign_already_owned": "Reassign devices which already exist and are owned by another entity/venue/subscriber?",
|
||||
"reboot_logs": "Reboot Logs",
|
||||
"restricted": "Restricted",
|
||||
"restricted_overriden": "This is a restricted device, but it is in development mode. All restrictions are currently ignored",
|
||||
"restrictions_overriden_title": "Dev Mode",
|
||||
"sanity": "Sanity",
|
||||
"start_import": "Start Device Importation",
|
||||
"test_batch": "Test Import Data",
|
||||
@@ -671,7 +700,36 @@
|
||||
"test_digicert_creds": "Test Credentials",
|
||||
"title": "Entities",
|
||||
"tree": "Entity Tree",
|
||||
"venues_under_root": "Venues cannot be created directly under the root entity. Please create new entities and create venues under these."
|
||||
"update_success": "Entity updated!",
|
||||
"venues_under_root": "Venues cannot be created directly under the root entity"
|
||||
},
|
||||
"firmware": {
|
||||
"confirm_default_data": "Please confirm the information below and click 'Confirm' once you are ready to start the process",
|
||||
"create_success": "Created new default firmware settings!",
|
||||
"db_update_warning": "This operation is done daily automatically without need to use this manual update. Updating this database can take up to 25 minutes",
|
||||
"default_created_error_one": "{{count}} error while trying to create new setting",
|
||||
"default_created_error_other": "{{count}} errors while trying to create new setting",
|
||||
"default_created_one": "{{count}} default firmware setting created",
|
||||
"default_created_other": "{{count}} default firmware settings created",
|
||||
"default_found_one": "Found valid revision for {{count}} device type",
|
||||
"default_found_other": "Found valid revisions for {{count}} device types",
|
||||
"default_mass_delete_success_one": "Deleted {{count}} default firmware setting!",
|
||||
"default_mass_delete_success_other": "Deleted {{count}} default firmware settings!",
|
||||
"default_not_found_one": "No valid firmware versions for {{count}} device type",
|
||||
"default_not_found_other": "No valid firmware versions for {{count}} device types",
|
||||
"default_title": "Default Firmware",
|
||||
"default_update_success": "Updated default firmware for {{deviceType}}!",
|
||||
"delete_success": "Deleted default firmware setting!",
|
||||
"edit_default_title": "This is the current firmware that is used as the minimum version for new APs of type {{deviceType}}. If a new {{deviceType}} AP connects to the gateway, it will be automatically upgraded to this version.",
|
||||
"fetching_defaults": "Fetching all available firmware for selected device types...",
|
||||
"last_db_update_modal": "Firmware Database",
|
||||
"last_db_update_title": "Database",
|
||||
"one": "Firmware",
|
||||
"select_default_device_types": "Please select all device types that you want to target with this new default firmware rule. If you cannot find your desired device type, it means they already have an applied rule.",
|
||||
"select_default_revision": "You can now select the minimum revision you want your device types to target",
|
||||
"start_db_update": "Start Database Update",
|
||||
"started_db_update": "Started database update, this operation should take up to 25 minutes to complete",
|
||||
"update_success": "Saved default firmware information!"
|
||||
},
|
||||
"footer": {
|
||||
"powered_by": "Powered By",
|
||||
@@ -680,6 +738,7 @@
|
||||
"form": {
|
||||
"captive_web_root_explanation": "Please use .tar files only (no compressed files like .targz, for example)",
|
||||
"certificate_file_explanation": "Please use a .pem file that starts with \"-----BEGIN CERTIFICATE-----\" and ends with \"-----END CERTIFICATE-----\"",
|
||||
"invalid_alphanumeric_with_dash": "Accepted chars. are only alphanumeric (letters & numbers)",
|
||||
"invalid_cidr": "Invalid CIDR IPv4 address. Example: 192.168.0.1/12",
|
||||
"invalid_email": "Invalid Email",
|
||||
"invalid_file_content": "Invalid file content, please confirm that it is of the valid format",
|
||||
@@ -693,7 +752,7 @@
|
||||
"invalid_ipv6": "Invalid IPv6 address (ex.: 2001:db8:3333:4444:5555:6666:7777:8888)",
|
||||
"invalid_json": "Invalid JSON string",
|
||||
"invalid_lease_time": "Invalid lease time value! They need to be in the digitUnit format. For example: 6d2h5m, which means 6 days, 2 hours and 5 minutes. Here are the accepted units: m, h, d. If you do not want to use a unit, omit it completely. So instead of saying 0d2h0m, use 2h",
|
||||
"invalid_mac_uc": "Invalid UC-MAC value, for example: 00:00:5e:00:53:af",
|
||||
"invalid_mac_uc": "Invalid MAC value, for example: 00:00:5e:00:53:af",
|
||||
"invalid_password": "Invalid password, please look at the password policy",
|
||||
"invalid_phone_number": "Invalid Phone Number",
|
||||
"invalid_phone_numbers": "One or more of the phone numbers are invalid. Please provide them without symbols and spaces, or in this format: +1(123)123-1234",
|
||||
@@ -706,7 +765,11 @@
|
||||
"invalid_static_ipv4_e": "Invalid address, this range reserved for experiments (class E). The first octet should be 223 or lower",
|
||||
"invalid_third_party": "Invalid Third Party JSON string. Please confirm that your value is a valid JSON",
|
||||
"key_file_explanation": "Please use a .pem file that starts with \"-----BEGIN PRIVATE KEY-----\" and ends with \"-----END PRIVATE KEY-----\"",
|
||||
"max_length": "Maximum length of {{max}} chars.",
|
||||
"max_value": "Maximum value of {{max}}",
|
||||
"min_length": "Minimum length of {{min}} chars.",
|
||||
"min_max_string": "Value needs to be of a length between {{min}} (inclusive) and {{max}} (inclusive)",
|
||||
"min_value": "Minimum value of {{min}}",
|
||||
"missing_interface_upstream": "You need to have at least one upstream interface. At the moment, all your interfaces are downstream",
|
||||
"new_email_to_notify": "New email to notify",
|
||||
"new_phone_to_notify": "New phone to notify",
|
||||
@@ -734,8 +797,8 @@
|
||||
"success_remove_claim": "Successfully removed claim on: {{serial}}",
|
||||
"successful_reboots": "Started Rebooting: {{count}}",
|
||||
"successful_upgrades": "Successful upgrades: {{count}}",
|
||||
"tag_one": "Tag",
|
||||
"tags": "Inventory Tags",
|
||||
"tag_one": "Device",
|
||||
"tags": "Inventory Devices",
|
||||
"title": "Inventory",
|
||||
"warning_reboots": "Not connected: {{count}}",
|
||||
"warning_upgrades": "Devices not connected: {{count}}"
|
||||
@@ -765,13 +828,17 @@
|
||||
"city": "City",
|
||||
"claim_explanation": "To claim locations you can use the table below",
|
||||
"country": "Country",
|
||||
"elevation": "Elevation",
|
||||
"geocode": "Geo Code",
|
||||
"lat": "Latitude",
|
||||
"longitude": "Longitude",
|
||||
"one": "Location",
|
||||
"other": "Locations",
|
||||
"postal": "ZIP/Postal Code",
|
||||
"state": "State/Province",
|
||||
"title": "Locations",
|
||||
"to_claim": "Locations to claim"
|
||||
"to_claim": "Locations to claim",
|
||||
"view_gps": "View GPS Location"
|
||||
},
|
||||
"login": {
|
||||
"access_policy": "Access Policy",
|
||||
@@ -797,6 +864,7 @@
|
||||
"reset_password": "Reset Password",
|
||||
"sign_in": "Sign In",
|
||||
"sms_instructions": "You should receive a 6-digit code on your phone soon. Please enter it below to login",
|
||||
"suspended_error": "Suspended account, please contact your administrator",
|
||||
"verification": "Verify your login",
|
||||
"waiting_for_email_verification": "Account not yet email validated. Please look at your inbox or ask your administrator to resend a validation",
|
||||
"welcome_back": "Welcome Back!",
|
||||
@@ -843,6 +911,11 @@
|
||||
"one": "Notification",
|
||||
"other": "Notifications"
|
||||
},
|
||||
"openroaming": {
|
||||
"pool_strategy": "Pool Strategy",
|
||||
"radius_endpoint_one": "Radius Endpoint",
|
||||
"radius_endpoint_other": "Radius Endpoints"
|
||||
},
|
||||
"operator": {
|
||||
"delete_explanation": "Are you sure you want to delete this operator? This operation is not reversible",
|
||||
"delete_operator": "Delete Operator",
|
||||
@@ -902,12 +975,33 @@
|
||||
"dfs": "DFS Override",
|
||||
"gw_commands": "Gateway Commands",
|
||||
"identifier": "Identifier",
|
||||
"key_verification": "Signing Key Verification",
|
||||
"key_verification": "Signing Key Information",
|
||||
"restricted": "Restricted",
|
||||
"signed_upgrade": "Signed Upgrade Only",
|
||||
"title": "Restrictions",
|
||||
"tty": "TTY Access"
|
||||
},
|
||||
"roaming": {
|
||||
"account_created": "New account created!",
|
||||
"account_deleted": "Deleted account!",
|
||||
"account_one": "Account",
|
||||
"account_other": "Accounts",
|
||||
"certificate_deleted": "Deleted certificate!",
|
||||
"certificate_one": "Certificate",
|
||||
"certificate_other": "Certificates",
|
||||
"city": "City",
|
||||
"common_name": "Common Name",
|
||||
"country": "Country",
|
||||
"global_reach": "GlobalReach",
|
||||
"global_reach_account_id": " Account ID",
|
||||
"invalid_certificate": "Invalid certificate",
|
||||
"invalid_key": "Invalid private key",
|
||||
"location_details_title": "Location",
|
||||
"organization": "Organization",
|
||||
"private_key": "Private Key",
|
||||
"province": "Province",
|
||||
"state": "State"
|
||||
},
|
||||
"rrm": {
|
||||
"algorithm": "Algorithm",
|
||||
"algorithm_other": "Algorithms",
|
||||
@@ -966,6 +1060,11 @@
|
||||
"concurrent_devices": "Concurrent Devices",
|
||||
"controller": "Controller",
|
||||
"current_live_devices": "Current Live Devices",
|
||||
"currently_running_one": "There is currently {{count}} simulation running",
|
||||
"currently_running_other": "There are currently {{count}} simulations running",
|
||||
"delete_devices_confirm": "Are you sure you want to remove all devices and their statistics from the gateway? This action is not reversible",
|
||||
"delete_devices_loading": "This process may take up to 5 minutes",
|
||||
"delete_simulation_devices": "Delete Devices",
|
||||
"delete_success": "Deleted Simulation!",
|
||||
"duration": "Duration",
|
||||
"error_devices": "Error Devices",
|
||||
@@ -973,6 +1072,7 @@
|
||||
"infinite": "Infinite",
|
||||
"keep_alive": "Keep Alive",
|
||||
"mac_prefix": "MAC Prefix",
|
||||
"mac_prefix_length": "Your MAC prefix needs to be valid 6 HEX digits (ex.: 00112233)",
|
||||
"max_associations": "Max. Associations",
|
||||
"max_clients": "Max. Clients",
|
||||
"min_associations": "Min. Associations",
|
||||
@@ -989,6 +1089,7 @@
|
||||
"rx_messages": "Rx Messages",
|
||||
"sim_currently_running": "Simulation \"{{sim}}\" in progress",
|
||||
"sim_history": "{{sim}} Previous Runs",
|
||||
"simulated": "Simulated",
|
||||
"start": "Start Simulation",
|
||||
"start_success": "Started simulation run!",
|
||||
"state_interval": "State Interval",
|
||||
@@ -1002,6 +1103,7 @@
|
||||
},
|
||||
"statistics": {
|
||||
"last_stats": "Last Statistics",
|
||||
"latest": "Latest Statistics",
|
||||
"memory": "Memory"
|
||||
},
|
||||
"subscribers": {
|
||||
@@ -1021,7 +1123,9 @@
|
||||
"title": "Subscribers"
|
||||
},
|
||||
"system": {
|
||||
"advanced": "Advanced",
|
||||
"backend_logs": "Back-End Logs",
|
||||
"configuration": "Configuration",
|
||||
"could_not_retrieve": "Error: could not retrieve {{name}} system information",
|
||||
"endpoint": "Endpoint",
|
||||
"hostname": "Host Name",
|
||||
@@ -1032,6 +1136,10 @@
|
||||
"os": "Operating System",
|
||||
"processors": "Processors",
|
||||
"reload_chosen_subsystems": "Reload Chosen Subsystems",
|
||||
"secrets": "Secrets",
|
||||
"secrets_create": "Create Secret",
|
||||
"secrets_one": "Secret",
|
||||
"services": "Services",
|
||||
"start": "Start",
|
||||
"subsystems": "Subsystems",
|
||||
"success_reload": "Successfully sent reload command!",
|
||||
@@ -1043,19 +1151,31 @@
|
||||
"version": "Version"
|
||||
},
|
||||
"table": {
|
||||
"columns": "Columns",
|
||||
"columns_hidden_one": "{{count}} Column Hidden",
|
||||
"columns_hidden_other": "{{count}} Columns Hidden",
|
||||
"display_column": "Display",
|
||||
"drag_always_show": "You cannot hide this column but can change its position ",
|
||||
"drag_explanation": "Drag and drop to reorder and change column visibility",
|
||||
"drag_locked": "This column is locked in its position",
|
||||
"export_current_page": "Current Page Only",
|
||||
"first_page": "First Page",
|
||||
"go_to_page": "Go to page",
|
||||
"hide_column": "Hide",
|
||||
"last_page": "Last Page",
|
||||
"next_page": "Next Page",
|
||||
"page": "Page",
|
||||
"previous_page": "Previous Page"
|
||||
"preferences": "Table Preferences",
|
||||
"previous_page": "Previous Page",
|
||||
"reset": "Reset Preferences",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"user": {
|
||||
"email_not_validated": "email not validated",
|
||||
"error_fetching": "Error fetching user information: {{e}}",
|
||||
"password": "Password",
|
||||
"role": "Role",
|
||||
"suspended": "suspended",
|
||||
"title": "User"
|
||||
},
|
||||
"users": {
|
||||
@@ -1100,9 +1220,11 @@
|
||||
"successfully_update_devices": "Updating {{num}} devices!",
|
||||
"title": "Venues",
|
||||
"update_all_devices": "Update All Device Configurations",
|
||||
"upgrade_all_devices": "Upgrade All Devices to Latest Firmware",
|
||||
"update_success": "Venue updated!",
|
||||
"upgrade_all_devices": "Upgrade All Devices Firmware",
|
||||
"upgrade_all_devices_error": "Error upgrading devices: {{e}}",
|
||||
"upgrade_all_devices_success": "Successfully started upgrading devices!",
|
||||
"upgrade_options_available": "Here are all available revisions, please select the one you want ALL of this venue's devices to be upgrade to",
|
||||
"use_existing": "Use Existing",
|
||||
"use_existing_contacts": "Use Existing Contacts",
|
||||
"use_this_contact": "Use this contact"
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
"health": "salud",
|
||||
"inactive": "inactivo",
|
||||
"interval": "intervalo",
|
||||
"last_connected": "Última conexion",
|
||||
"last_connected_tooltip": "La última vez que se conectó este dispositivo al controlador. Esto se puede usar para estimar cuándo se desconectó un dispositivo",
|
||||
"last_connection": "Última conexion",
|
||||
"last_contact": "Último contacto",
|
||||
"last_disconnection": "Última desconexión",
|
||||
@@ -79,8 +81,11 @@
|
||||
"live_view_help": "Ayuda de visualización en vivo",
|
||||
"memory": "Memoria",
|
||||
"memory_used": "Memoria usada",
|
||||
"missing_board": "El monitoreo analítico en este lugar ya no está activo, reinicie el monitoreo usando el menú superior",
|
||||
"missing_board": "El monitoreo analítico en este lugar ya no está activo. Haga clic aquí para reiniciar el monitoreo",
|
||||
"mode": "Modo",
|
||||
"monitoring": "Vigilancia",
|
||||
"no_board": "Sin monitoreo",
|
||||
"no_board_description": "No está monitoreando este lugar en este momento, haga clic aquí para comenzar",
|
||||
"noise": "Ruido",
|
||||
"packets": "Paquetes",
|
||||
"radio": "RADIO",
|
||||
@@ -91,6 +96,8 @@
|
||||
"retries": "Reintentos",
|
||||
"search_serials": "Buscar seriales",
|
||||
"stop_monitoring": "Dejar de monitorear",
|
||||
"stop_monitoring_success": "¡Se detuvo el lugar de monitoreo!",
|
||||
"stop_monitoring_warning": "¿Está seguro? Esto borrará todos los datos de monitoreo grabados para este lugar.",
|
||||
"temperature": "temperatura",
|
||||
"title": "ANALÍTICA",
|
||||
"total_data": "Datos totales",
|
||||
@@ -175,6 +182,7 @@
|
||||
"other": "comandos",
|
||||
"override_dfs": "Anular DFS",
|
||||
"reboot": "Reiniciar",
|
||||
"reboot_description": "¿Quieres reiniciar este dispositivo?",
|
||||
"reboot_error": "Error al enviar el comando de reinicio: {{e}}",
|
||||
"reboot_success": "¡Comando de reinicio enviado con éxito!",
|
||||
"revision": "revisión",
|
||||
@@ -215,6 +223,7 @@
|
||||
"day": "Día",
|
||||
"days": "días",
|
||||
"default": "Defecto",
|
||||
"defaults": "Valores predeterminados",
|
||||
"description": "Descripción",
|
||||
"details": "Detalles",
|
||||
"device_details": "Detalles del dispositivo",
|
||||
@@ -235,6 +244,7 @@
|
||||
"error_download": "Error al intentar descargar: {{e}}",
|
||||
"errors": "Los errores",
|
||||
"exit_fullscreen": "salida",
|
||||
"export": "Exportar",
|
||||
"finished": "terminado",
|
||||
"fullscreen": "Pantalla Completa",
|
||||
"general_error": "Error al conectar con el servidor. Consulte a su administrador.",
|
||||
@@ -259,6 +269,7 @@
|
||||
"map": "Mapa",
|
||||
"max": "Max",
|
||||
"min": "Min",
|
||||
"miscellaneous": "Diverso",
|
||||
"mode": "Modo",
|
||||
"model": "Modelo",
|
||||
"modified": "Modificado",
|
||||
@@ -350,6 +361,7 @@
|
||||
"error_pushes_one": "Error (podría deberse a una mala configuración): {{count}}",
|
||||
"error_pushes_other": "Errores (pueden deberse a una mala configuración): {{count}}",
|
||||
"expert_name": "Modo experto",
|
||||
"expert_name_explanation": "Puede usar el modo experto para modificar directamente su configuración, incluida la adición de valores que actualmente no son compatibles con la interfaz de usuario. Utilice este formato: { \"interfaces\": [ ... ], \"globals\": { ... }, ...etc }",
|
||||
"explanation": "Explicación",
|
||||
"firewall": "cortafuegos",
|
||||
"firmware_upgrade": "Actualización de firmware",
|
||||
@@ -391,6 +403,7 @@
|
||||
"warning_pushes_one": "Esperando a que los dispositivos se conecten: {{count}}",
|
||||
"warning_pushes_other": "Esperando a que los dispositivos se conecten: {{count}}",
|
||||
"weight": "Peso",
|
||||
"wifi_bands_max": "No puede haber más de 8 SSID usando esta banda wifi",
|
||||
"wifi_frames": "Marcos WiFi"
|
||||
},
|
||||
"contacts": {
|
||||
@@ -515,6 +528,7 @@
|
||||
"ouis_explanation": "OUI de dispositivos que se han conectado a este servidor de firmware",
|
||||
"outdated_one": "Firmware {{count}} día de antigüedad",
|
||||
"outdated_other": "Firmware de {{count}} días de antigüedad",
|
||||
"outdated_unknown": "Firmware de antigüedad desconocida",
|
||||
"release": "Lanzamiento",
|
||||
"show_dev_releases": "Lanzamientos de desarrollo",
|
||||
"status_explanation": "Estado de conexión de los dispositivos que se han conectado a este servidor de firmware",
|
||||
@@ -530,6 +544,16 @@
|
||||
"queue": {
|
||||
"title": "Cola de eventos"
|
||||
},
|
||||
"radius": {
|
||||
"calling_station_id": "Estación",
|
||||
"disconnect": "desconectar",
|
||||
"disconnect_success": "¡Sesión de radio desconectada!",
|
||||
"input_octets": "entrada",
|
||||
"output_octets": "salida",
|
||||
"radius_clients": "Clientes de radio",
|
||||
"session_time": "Tiempo de sesión",
|
||||
"username": "Nombre de usuario"
|
||||
},
|
||||
"stats": {
|
||||
"load": "Carga (1 | 5 | 15 m.)",
|
||||
"seconds_ago": " Hace {{s}} segundos",
|
||||
@@ -600,6 +624,7 @@
|
||||
"certificate_expires_in": "El certificado caduca en",
|
||||
"certificate_expiry": "Cert. Expira en",
|
||||
"connected": "Conectado",
|
||||
"crash_logs": "Registros de fallas",
|
||||
"create_errors": "errores al intentar crear dispositivos",
|
||||
"create_success": " dispositivos creados con éxito",
|
||||
"current_firmware": "Firmware actual",
|
||||
@@ -613,6 +638,7 @@
|
||||
"import_device_warning": "Asegúrese de que no haya espacios adicionales al principio o al final de ningún valor a menos que sea parte del valor deseado",
|
||||
"import_explanation": "Para importar dispositivos de forma masiva, debe usar un archivo CSV con las siguientes columnas: Número de serie, Tipo de dispositivo, Nombre, Descripción, Nota",
|
||||
"invalid_serial_number": "Número de serie no válido (debe tener 12 caracteres HEX)",
|
||||
"logs_one": "Iniciar sesión",
|
||||
"new_devices": "Nuevos dispositivos",
|
||||
"no_model_image": "No se encontró ninguna imagen de modelo",
|
||||
"not_connected": "No conectado",
|
||||
@@ -620,7 +646,10 @@
|
||||
"notifications": "notificaciones de dispositivos",
|
||||
"one": "Dispositivo",
|
||||
"reassign_already_owned": "¿Reasignar dispositivos que ya existen y son propiedad de otra entidad/lugar/suscriptor?",
|
||||
"reboot_logs": "Reiniciar registros",
|
||||
"restricted": "Restringido",
|
||||
"restricted_overriden": "Este es un dispositivo restringido, pero está en modo de desarrollo. Actualmente se ignoran todas las restricciones.",
|
||||
"restrictions_overriden_title": "MODO DE DESARROLLO",
|
||||
"sanity": "Cordura",
|
||||
"start_import": "Iniciar la importación de dispositivos",
|
||||
"test_batch": "Datos de importación de prueba",
|
||||
@@ -671,7 +700,36 @@
|
||||
"test_digicert_creds": "Credenciales de prueba",
|
||||
"title": "entidades",
|
||||
"tree": "Árbol de entidades",
|
||||
"venues_under_root": "Los lugares no se pueden crear directamente bajo la entidad raíz. Cree nuevas entidades y cree lugares bajo estas."
|
||||
"update_success": "¡Entidad actualizada!",
|
||||
"venues_under_root": "Los lugares no se pueden crear directamente bajo la entidad raíz"
|
||||
},
|
||||
"firmware": {
|
||||
"confirm_default_data": "Confirme la información a continuación y haga clic en 'Confirmar' una vez que esté listo para comenzar el proceso",
|
||||
"create_success": "¡Se crearon nuevas configuraciones de firmware predeterminadas!",
|
||||
"db_update_warning": "Esta operación se realiza automáticamente todos los días de forma automática sin necesidad de utilizar esta actualización manual. La actualización de esta base de datos puede tardar hasta 25 minutos",
|
||||
"default_created_error_one": "{{count}} error al intentar crear una nueva configuración",
|
||||
"default_created_error_other": "{{count}} errores al intentar crear una nueva configuración",
|
||||
"default_created_one": "{{count}} configuración de firmware predeterminada creada",
|
||||
"default_created_other": "{{count}} ajustes de firmware predeterminados creados",
|
||||
"default_found_one": "Se encontró una revisión válida para el tipo de dispositivo {{count}} ",
|
||||
"default_found_other": "Se encontraron revisiones válidas para {{count}} tipos de dispositivos",
|
||||
"default_mass_delete_success_one": "¡Se eliminó {{count}} configuración de firmware predeterminada!",
|
||||
"default_mass_delete_success_other": "¡Se eliminaron {{count}} configuraciones de firmware predeterminadas!",
|
||||
"default_not_found_one": "No hay versiones de firmware válidas para el tipo de dispositivo {{count}} ",
|
||||
"default_not_found_other": "No hay versiones de firmware válidas para {{count}} tipos de dispositivos",
|
||||
"default_title": "",
|
||||
"default_update_success": "¡Firmware predeterminado actualizado para {{deviceType}}!",
|
||||
"delete_success": "¡Configuración de firmware predeterminada eliminada!",
|
||||
"edit_default_title": "Este es el firmware actual que se utiliza como versión mínima para los nuevos AP de tipo {{deviceType}}. Si un nuevo AP {{deviceType}} se conecta a la puerta de enlace, se actualizará automáticamente a esta versión.",
|
||||
"fetching_defaults": "Obteniendo todo el firmware disponible para los tipos de dispositivos seleccionados...",
|
||||
"last_db_update_modal": "Base de datos de firmware",
|
||||
"last_db_update_title": "Base de datos",
|
||||
"one": "Firmware",
|
||||
"select_default_device_types": "Seleccione todos los tipos de dispositivos a los que desea apuntar con esta nueva regla de firmware predeterminada. Si no puede encontrar el tipo de dispositivo deseado, significa que ya tienen una regla aplicada.",
|
||||
"select_default_revision": "Ahora puede seleccionar la revisión mínima a la que desea que se dirijan sus tipos de dispositivos",
|
||||
"start_db_update": "Iniciar actualización de la base de datos",
|
||||
"started_db_update": "Actualización de la base de datos iniciada, esta operación debería tardar hasta 25 minutos en completarse",
|
||||
"update_success": "¡Información de firmware predeterminada guardada!"
|
||||
},
|
||||
"footer": {
|
||||
"powered_by": "energizado por",
|
||||
@@ -680,6 +738,7 @@
|
||||
"form": {
|
||||
"captive_web_root_explanation": "Utilice únicamente archivos .tar (no archivos comprimidos como .targz, por ejemplo)",
|
||||
"certificate_file_explanation": "Utilice un archivo .pem que comience con \"-----BEGIN CERTIFICATE-----\" y termine con \"-----END CERTIFICATE-----\"",
|
||||
"invalid_alphanumeric_with_dash": "Caracteres aceptados. son solo alfanuméricos (letras y números)",
|
||||
"invalid_cidr": "Dirección IPv4 CIDR no válida. Ejemplo: 192.168.0.1/12",
|
||||
"invalid_email": "Email inválido",
|
||||
"invalid_file_content": "Contenido de archivo no válido, confirme que tiene un formato válido",
|
||||
@@ -693,7 +752,7 @@
|
||||
"invalid_ipv6": "Dirección IPv6 no válida (ej.: 2001:db8:3333:4444:5555:6666:7777:8888)",
|
||||
"invalid_json": "Cadena JSON no válida",
|
||||
"invalid_lease_time": "¡Valor de tiempo de arrendamiento no válido! Deben estar en el formato digitUnit. Por ejemplo: 6d2h5m, lo que significa 6 días, 2 horas y 5 minutos. Estas son las unidades aceptadas: m, h, d. Si no desea utilizar una unidad, omítala por completo. Entonces, en lugar de decir 0d2h0m, usa 2h",
|
||||
"invalid_mac_uc": "Valor de UC-MAC no válido, por ejemplo: 00:00:5e:00:53:af",
|
||||
"invalid_mac_uc": "Valor de MAC no válido, por ejemplo: 00:00:5e:00:53:af",
|
||||
"invalid_password": "Contraseña no válida, consulte la política de contraseñas",
|
||||
"invalid_phone_number": "Numero de telefono invalido",
|
||||
"invalid_phone_numbers": "Uno o más de los números de teléfono no son válidos. Proporciónelos sin símbolos ni espacios, o en este formato: +1(123)123-1234",
|
||||
@@ -706,7 +765,11 @@
|
||||
"invalid_static_ipv4_e": "Dirección no válida, este rango reservado para experimentos (clase E). El primer octeto debe ser 223 o inferior",
|
||||
"invalid_third_party": "Cadena JSON de terceros no válida. Confirme que su valor es un JSON válido",
|
||||
"key_file_explanation": "Utilice un archivo .pem que comience con \"-----BEGIN PRIVATE KEY-----\" y termine con \"-----END PRIVATE KEY-----\"",
|
||||
"max_length": "Longitud máxima de {{max}} caracteres.",
|
||||
"max_value": "Valor máximo de {{max}}",
|
||||
"min_length": "Longitud mínima de {{min}} caracteres.",
|
||||
"min_max_string": "El valor debe tener una longitud entre {{min}} (inclusive) y {{max}} (inclusive)",
|
||||
"min_value": "Valor mínimo de {{min}}",
|
||||
"missing_interface_upstream": "Debe tener al menos una interfaz ascendente. Por el momento, todas sus interfaces están en sentido descendente",
|
||||
"new_email_to_notify": "Nuevo correo electrónico para notificar",
|
||||
"new_phone_to_notify": "Nuevo teléfono para avisar",
|
||||
@@ -765,13 +828,17 @@
|
||||
"city": "ciudad",
|
||||
"claim_explanation": "Para reclamar ubicaciones, puede usar la tabla a continuación",
|
||||
"country": "País",
|
||||
"elevation": "Elevación",
|
||||
"geocode": "Código geográfico",
|
||||
"lat": "Latitud",
|
||||
"longitude": "Longitud",
|
||||
"one": "Ubicación",
|
||||
"other": "Ubicaciones",
|
||||
"postal": "código postal",
|
||||
"state": "Provincia del estado",
|
||||
"title": "Ubicaciones",
|
||||
"to_claim": "Ubicaciones para reclamar"
|
||||
"to_claim": "Ubicaciones para reclamar",
|
||||
"view_gps": ""
|
||||
},
|
||||
"login": {
|
||||
"access_policy": "Política de acceso",
|
||||
@@ -797,6 +864,7 @@
|
||||
"reset_password": "Restablecer la contraseña",
|
||||
"sign_in": "Registrarse",
|
||||
"sms_instructions": "Debería recibir un código de 6 dígitos en su teléfono pronto. Por favor, introdúzcalo a continuación para iniciar sesión",
|
||||
"suspended_error": "Cuenta suspendida, comuníquese con su administrador",
|
||||
"verification": "Verifica tu inicio de sesión",
|
||||
"waiting_for_email_verification": "Cuenta aún no validada por correo electrónico. Mire su bandeja de entrada o solicite a su administrador que vuelva a enviar una validación.",
|
||||
"welcome_back": "¡Dar una buena acogida!",
|
||||
@@ -843,6 +911,11 @@
|
||||
"one": "Notificación",
|
||||
"other": "Notificaciones"
|
||||
},
|
||||
"openroaming": {
|
||||
"pool_strategy": "Estrategia de piscina",
|
||||
"radius_endpoint_one": "Punto final del radio",
|
||||
"radius_endpoint_other": "Puntos finales de radio"
|
||||
},
|
||||
"operator": {
|
||||
"delete_explanation": "¿Está seguro de que desea eliminar este operador? Esta operación no es reversible.",
|
||||
"delete_operator": "Eliminar operador",
|
||||
@@ -902,12 +975,33 @@
|
||||
"dfs": "Anulación de DFS",
|
||||
"gw_commands": "Comandos de puerta de enlace",
|
||||
"identifier": "Identificador",
|
||||
"key_verification": "Verificación de clave de firma",
|
||||
"key_verification": "Información clave de firma",
|
||||
"restricted": "Restringido",
|
||||
"signed_upgrade": "Solo actualización firmada",
|
||||
"title": "Las restricciones",
|
||||
"tty": "Acceso TTY"
|
||||
},
|
||||
"roaming": {
|
||||
"account_created": "¡Nueva cuenta creada!",
|
||||
"account_deleted": "¡Cuenta eliminada!",
|
||||
"account_one": "Cuenta",
|
||||
"account_other": "Cuentas",
|
||||
"certificate_deleted": "Certificado eliminado!",
|
||||
"certificate_one": "Certificado",
|
||||
"certificate_other": "Certificados",
|
||||
"city": "ciudad",
|
||||
"common_name": "Nombre común",
|
||||
"country": "País",
|
||||
"global_reach": "Alcance global",
|
||||
"global_reach_account_id": "ID de cuenta ",
|
||||
"invalid_certificate": "Certificado inválido",
|
||||
"invalid_key": "Clave privada no válida",
|
||||
"location_details_title": "Ubicación",
|
||||
"organization": "Organización",
|
||||
"private_key": "Llave privada",
|
||||
"province": "Provincia",
|
||||
"state": "Estado"
|
||||
},
|
||||
"rrm": {
|
||||
"algorithm": "Algoritmo",
|
||||
"algorithm_other": "Algoritmos",
|
||||
@@ -966,6 +1060,11 @@
|
||||
"concurrent_devices": "Dispositivos concurrentes",
|
||||
"controller": "Controlador",
|
||||
"current_live_devices": "Dispositivos activos actuales",
|
||||
"currently_running_one": "Actualmente hay {{count}} simulación en ejecución",
|
||||
"currently_running_other": "Actualmente hay {{count}} simulaciones ejecutándose",
|
||||
"delete_devices_confirm": "¿Está seguro de que desea eliminar todos los dispositivos y sus estadísticas de la puerta de enlace? Esta acción no es reversible",
|
||||
"delete_devices_loading": "Este proceso puede tardar hasta 5 minutos.",
|
||||
"delete_simulation_devices": "BORRAR DISPOSITIVOS",
|
||||
"delete_success": "¡Simulación eliminada!",
|
||||
"duration": "Duración",
|
||||
"error_devices": "Dispositivos de error",
|
||||
@@ -973,6 +1072,7 @@
|
||||
"infinite": "infinito",
|
||||
"keep_alive": "Mantener viva",
|
||||
"mac_prefix": "Prefijo MAC",
|
||||
"mac_prefix_length": "Su prefijo MAC debe tener 6 dígitos hexadecimales válidos (p. ej.: 00112233)",
|
||||
"max_associations": "Max. Asociaciones",
|
||||
"max_clients": "Max. Clientela",
|
||||
"min_associations": "Min. Asociaciones",
|
||||
@@ -989,6 +1089,7 @@
|
||||
"rx_messages": "Mensajes prescritos",
|
||||
"sim_currently_running": "Simulación \"{{sim}}\" en curso",
|
||||
"sim_history": "{{sim}} ejecuciones anteriores",
|
||||
"simulated": "Simulado",
|
||||
"start": "Iniciar simulación",
|
||||
"start_success": "¡Ejecución de simulación iniciada!",
|
||||
"state_interval": "Intervalo de estado",
|
||||
@@ -1002,6 +1103,7 @@
|
||||
},
|
||||
"statistics": {
|
||||
"last_stats": "Últimas estadísticas",
|
||||
"latest": "Últimas estadísticas",
|
||||
"memory": "Memoria"
|
||||
},
|
||||
"subscribers": {
|
||||
@@ -1021,7 +1123,9 @@
|
||||
"title": "Suscriptores"
|
||||
},
|
||||
"system": {
|
||||
"advanced": "Avanzado",
|
||||
"backend_logs": "Registros de back-end",
|
||||
"configuration": "Configuración",
|
||||
"could_not_retrieve": "Error: no se pudo recuperar la información del sistema {{name}} ",
|
||||
"endpoint": "punto final",
|
||||
"hostname": "Nombre de host",
|
||||
@@ -1032,6 +1136,10 @@
|
||||
"os": "sistema operativo",
|
||||
"processors": "Procesadores",
|
||||
"reload_chosen_subsystems": "Recargar subsistemas elegidos",
|
||||
"secrets": "Misterios",
|
||||
"secrets_create": "Crear secreto",
|
||||
"secrets_one": "secreto",
|
||||
"services": "Servicios",
|
||||
"start": "comienzo",
|
||||
"subsystems": "Subsistemas",
|
||||
"success_reload": "¡Comando de recarga enviado con éxito!",
|
||||
@@ -1043,19 +1151,31 @@
|
||||
"version": "Versión"
|
||||
},
|
||||
"table": {
|
||||
"columns": "Columnas",
|
||||
"columns_hidden_one": "{{count}} columna oculta",
|
||||
"columns_hidden_other": "{{count}} columnas ocultas",
|
||||
"display_column": "Monitor",
|
||||
"drag_always_show": "No puede ocultar esta columna pero puede cambiar su posición",
|
||||
"drag_explanation": "Arrastre y suelte para reordenar y cambiar la visibilidad de las columnas",
|
||||
"drag_locked": "Esta columna está bloqueada en su posición.",
|
||||
"export_current_page": "Solo página actual",
|
||||
"first_page": "Primera pagina",
|
||||
"go_to_page": "Ir a la página",
|
||||
"hide_column": "Esconder",
|
||||
"last_page": "Ultima pagina",
|
||||
"next_page": "Siguiente página",
|
||||
"page": "Página",
|
||||
"previous_page": "Página anterior"
|
||||
"preferences": "Preferencias de mesa",
|
||||
"previous_page": "Página anterior",
|
||||
"reset": "Reiniciar preferencias",
|
||||
"settings": "Ajustes"
|
||||
},
|
||||
"user": {
|
||||
"email_not_validated": "correo electrónico no validado",
|
||||
"error_fetching": "Error al obtener la información del usuario: {{e}}",
|
||||
"password": "Contraseña",
|
||||
"role": "papel",
|
||||
"suspended": "Suspendido",
|
||||
"title": "Usuario"
|
||||
},
|
||||
"users": {
|
||||
@@ -1100,9 +1220,11 @@
|
||||
"successfully_update_devices": "¡Actualizando {{num}} dispositivos!",
|
||||
"title": "Sedes",
|
||||
"update_all_devices": "Actualizar todas las configuraciones de dispositivos",
|
||||
"upgrade_all_devices": "Actualice todos los dispositivos al firmware más reciente",
|
||||
"update_success": "Lugar actualizado!",
|
||||
"upgrade_all_devices": "Actualizar el firmware de todos los dispositivos",
|
||||
"upgrade_all_devices_error": "Error al actualizar dispositivos: {{e}}",
|
||||
"upgrade_all_devices_success": "¡Comenzó con éxito la actualización de dispositivos!",
|
||||
"upgrade_options_available": "Aquí están todas las revisiones disponibles, seleccione la que desea que TODOS los dispositivos de este lugar se actualicen",
|
||||
"use_existing": "Utilizar existente",
|
||||
"use_existing_contacts": "Usar contactos existentes",
|
||||
"use_this_contact": "Usa este contacto"
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
"health": "santé",
|
||||
"inactive": "Inactif",
|
||||
"interval": "Intervalle",
|
||||
"last_connected": "Dernière connexion",
|
||||
"last_connected_tooltip": "La dernière fois que cet appareil a été connecté au contrôleur. Cela peut être utilisé pour estimer quand un appareil s'est déconnecté",
|
||||
"last_connection": "Dernière connexion",
|
||||
"last_contact": "Dernier contact",
|
||||
"last_disconnection": "Dernière déconnexion",
|
||||
@@ -79,8 +81,11 @@
|
||||
"live_view_help": "Aide sur l'affichage en direct",
|
||||
"memory": "mémoire",
|
||||
"memory_used": "Mémoire utilisée",
|
||||
"missing_board": "La surveillance analytique sur ce lieu n'est plus active, veuillez redémarrer la surveillance en utilisant le menu du haut",
|
||||
"missing_board": "La surveillance analytique sur ce site n'est plus active. Cliquez ici pour redémarrer la surveillance",
|
||||
"mode": "Mode",
|
||||
"monitoring": "surveillance",
|
||||
"no_board": "Aucune surveillance",
|
||||
"no_board_description": "Vous ne surveillez pas ce lieu pour le moment, cliquez ici pour commencer",
|
||||
"noise": "Bruit",
|
||||
"packets": "Paquets",
|
||||
"radio": "Radio",
|
||||
@@ -91,6 +96,8 @@
|
||||
"retries": "Tentatives",
|
||||
"search_serials": "Rechercher des publications en série",
|
||||
"stop_monitoring": "Arrêter la surveillance",
|
||||
"stop_monitoring_success": "Lieu de surveillance arrêté !",
|
||||
"stop_monitoring_warning": "Êtes-vous sûr? Cela effacera toutes les données de surveillance enregistrées pour ce lieu",
|
||||
"temperature": "Température",
|
||||
"title": "ANALYTIQUE",
|
||||
"total_data": "Données totales",
|
||||
@@ -175,6 +182,7 @@
|
||||
"other": "Les commandes",
|
||||
"override_dfs": "Remplacer DFS",
|
||||
"reboot": "Redémarrer",
|
||||
"reboot_description": "Voulez-vous redémarrer cet appareil ?",
|
||||
"reboot_error": "Erreur lors de l'envoi de la commande de redémarrage : {{e}}",
|
||||
"reboot_success": "Commande de redémarrage envoyée avec succès !",
|
||||
"revision": "Révision",
|
||||
@@ -215,6 +223,7 @@
|
||||
"day": "journée",
|
||||
"days": "Journées",
|
||||
"default": "Défaut",
|
||||
"defaults": "Valeurs par défaut",
|
||||
"description": "La description",
|
||||
"details": "Détails",
|
||||
"device_details": "Détails de l'appareil",
|
||||
@@ -235,6 +244,7 @@
|
||||
"error_download": "Erreur lors de la tentative de téléchargement : {{e}}",
|
||||
"errors": "les erreurs",
|
||||
"exit_fullscreen": "Sortie",
|
||||
"export": "Exportation",
|
||||
"finished": "fini",
|
||||
"fullscreen": "Plein écran",
|
||||
"general_error": "Erreur de connexion au serveur. Veuillez consulter votre administrateur.",
|
||||
@@ -259,6 +269,7 @@
|
||||
"map": "Carte",
|
||||
"max": "Max",
|
||||
"min": "Min",
|
||||
"miscellaneous": "Divers",
|
||||
"mode": "Mode",
|
||||
"model": "Modèle",
|
||||
"modified": "Modifié",
|
||||
@@ -350,6 +361,7 @@
|
||||
"error_pushes_one": "Erreur (peut être due à une mauvaise configuration) : {{count}}",
|
||||
"error_pushes_other": "Erreurs (peut-être dues à une mauvaise configuration) : {{count}}",
|
||||
"expert_name": "Mode expert",
|
||||
"expert_name_explanation": "Vous pouvez utiliser le mode expert pour modifier directement votre configuration, notamment en ajoutant des valeurs qui ne sont pas actuellement prises en charge par l'interface utilisateur. Veuillez utiliser ce format : { \"interfaces\": [ ... ], \"globals\": { ... }, ...etc }",
|
||||
"explanation": "Explication",
|
||||
"firewall": "Pare-feu",
|
||||
"firmware_upgrade": "Mise à jour du firmware",
|
||||
@@ -391,6 +403,7 @@
|
||||
"warning_pushes_one": "En attente de connexion des appareils : {{count}}",
|
||||
"warning_pushes_other": "En attente de connexion des appareils : {{count}}",
|
||||
"weight": "Poids",
|
||||
"wifi_bands_max": "Il ne peut y avoir plus de 8 SSID utilisant cette bande wifi",
|
||||
"wifi_frames": "Cadres Wi-Fi"
|
||||
},
|
||||
"contacts": {
|
||||
@@ -515,6 +528,7 @@
|
||||
"ouis_explanation": "OUI des appareils qui se sont connectés à ce serveur de firmware",
|
||||
"outdated_one": "Micrologiciel vieux de {{count}} jours",
|
||||
"outdated_other": "Micrologiciel vieux de {{count}} jours",
|
||||
"outdated_unknown": "Firmware d'âge inconnu",
|
||||
"release": "libération",
|
||||
"show_dev_releases": "Versions de développement",
|
||||
"status_explanation": "État de connexion des appareils qui se sont connectés à ce serveur de micrologiciel",
|
||||
@@ -530,6 +544,16 @@
|
||||
"queue": {
|
||||
"title": "File d'attente d'événements"
|
||||
},
|
||||
"radius": {
|
||||
"calling_station_id": "Station",
|
||||
"disconnect": "déconnecter",
|
||||
"disconnect_success": "Session Radius déconnectée !",
|
||||
"input_octets": "Contribution",
|
||||
"output_octets": "Sortie",
|
||||
"radius_clients": "Clients rayon",
|
||||
"session_time": "Temps de session",
|
||||
"username": "Nom d'utilisateur"
|
||||
},
|
||||
"stats": {
|
||||
"load": "Charge (1 | 5 | 15 m.)",
|
||||
"seconds_ago": " Il y a {{s}} secondes",
|
||||
@@ -600,6 +624,7 @@
|
||||
"certificate_expires_in": "Le certificat expire dans",
|
||||
"certificate_expiry": "Cert. Expire dans",
|
||||
"connected": "Connecté",
|
||||
"crash_logs": "Journaux des plantages",
|
||||
"create_errors": "erreurs lors de la tentative de création d'appareils",
|
||||
"create_success": " appareils créés avec succès",
|
||||
"current_firmware": "Firmware actuel",
|
||||
@@ -613,6 +638,7 @@
|
||||
"import_device_warning": "Veuillez vous assurer qu'il n'y a pas d'espaces supplémentaires au début ou à la fin des valeurs, sauf si cela fait partie de la valeur souhaitée",
|
||||
"import_explanation": "Pour importer en masse des appareils, vous devez utiliser un fichier CSV avec les colonnes suivantes : SerialNumber, DeviceType, Name, Description, Note",
|
||||
"invalid_serial_number": "Numéro de série non valide (doit être composé de 12 caractères HEX)",
|
||||
"logs_one": "Bûche",
|
||||
"new_devices": "nouveaux appareils",
|
||||
"no_model_image": "Aucune image de modèle trouvée",
|
||||
"not_connected": "Pas connecté",
|
||||
@@ -620,7 +646,10 @@
|
||||
"notifications": "notifications de l'appareil",
|
||||
"one": "Dispositif",
|
||||
"reassign_already_owned": "Réattribuer des appareils qui existent déjà et qui appartiennent à une autre entité/salle/abonné ?",
|
||||
"reboot_logs": "Journaux de redémarrage",
|
||||
"restricted": "Limité",
|
||||
"restricted_overriden": "Il s'agit d'un appareil restreint, mais il est en mode développement. Toutes les restrictions sont actuellement ignorées",
|
||||
"restrictions_overriden_title": "Mode développement",
|
||||
"sanity": "Santé mentale",
|
||||
"start_import": "Démarrer l'importation de l'appareil",
|
||||
"test_batch": "Tester les données d'importation",
|
||||
@@ -671,7 +700,36 @@
|
||||
"test_digicert_creds": "Tester les informations d'identification",
|
||||
"title": "Entités",
|
||||
"tree": "Arborescence des entités",
|
||||
"venues_under_root": "Les sites ne peuvent pas être créés directement sous l'entité racine. Veuillez créer de nouvelles entités et créer des lieux sous celles-ci."
|
||||
"update_success": "Entité mise à jour !",
|
||||
"venues_under_root": "Les lieux ne peuvent pas être créés directement sous l'entité racine"
|
||||
},
|
||||
"firmware": {
|
||||
"confirm_default_data": "Veuillez confirmer les informations ci-dessous et cliquez sur \"Confirmer\" une fois que vous êtes prêt à démarrer le processus",
|
||||
"create_success": "Création de nouveaux paramètres de firmware par défaut !",
|
||||
"db_update_warning": "Cette opération se fait automatiquement quotidiennement sans avoir besoin d'utiliser cette mise à jour manuelle. La mise à jour de cette base de données peut prendre jusqu'à 25 minutes",
|
||||
"default_created_error_one": "{{count}} erreur lors de la tentative de création d'un nouveau paramètre",
|
||||
"default_created_error_other": "{{count}} erreurs lors de la tentative de création d'un nouveau paramètre",
|
||||
"default_created_one": "{{count}} paramètre de micrologiciel par défaut créé",
|
||||
"default_created_other": "{{count}} paramètres de micrologiciel par défaut créés",
|
||||
"default_found_one": "Révision valide trouvée pour le type d'appareil {{count}} ",
|
||||
"default_found_other": "Révisions valides trouvées pour {{count}} types d'appareils",
|
||||
"default_mass_delete_success_one": "Paramètre de micrologiciel par défaut {{count}} supprimé !",
|
||||
"default_mass_delete_success_other": " {{count}} paramètres de micrologiciel par défaut supprimés !",
|
||||
"default_not_found_one": "Aucune version de micrologiciel valide pour le type d'appareil {{count}} ",
|
||||
"default_not_found_other": "Aucune version de micrologiciel valide pour {{count}} types d'appareils",
|
||||
"default_title": "",
|
||||
"default_update_success": "Firmware par défaut mis à jour pour {{deviceType}} !",
|
||||
"delete_success": "Paramètre de micrologiciel par défaut supprimé !",
|
||||
"edit_default_title": "Il s'agit du micrologiciel actuel utilisé comme version minimale pour les nouveaux points d'accès de type {{deviceType}}. Si un nouveau point d'accès {{deviceType}} se connecte à la passerelle, il sera automatiquement mis à niveau vers cette version.",
|
||||
"fetching_defaults": "Récupération de tous les micrologiciels disponibles pour les types d'appareils sélectionnés...",
|
||||
"last_db_update_modal": "Base de données du micrologiciel",
|
||||
"last_db_update_title": "Base de données",
|
||||
"one": "Micrologiciel",
|
||||
"select_default_device_types": "Veuillez sélectionner tous les types d'appareils que vous souhaitez cibler avec cette nouvelle règle de micrologiciel par défaut. Si vous ne trouvez pas le type d'appareil souhaité, cela signifie qu'une règle est déjà appliquée.",
|
||||
"select_default_revision": "Vous pouvez maintenant sélectionner la révision minimale que vous souhaitez que vos types d'appareils ciblent",
|
||||
"start_db_update": "Démarrer la mise à jour de la base de données",
|
||||
"started_db_update": "Mise à jour de la base de données démarrée, cette opération devrait prendre jusqu'à 25 minutes",
|
||||
"update_success": "Informations sur le micrologiciel par défaut enregistrées !"
|
||||
},
|
||||
"footer": {
|
||||
"powered_by": "Alimenté par",
|
||||
@@ -680,6 +738,7 @@
|
||||
"form": {
|
||||
"captive_web_root_explanation": "Veuillez utiliser uniquement des fichiers .tar (pas de fichiers compressés comme .targz, par exemple)",
|
||||
"certificate_file_explanation": "Veuillez utiliser un fichier .pem qui commence par \"-----BEGIN CERTIFICATE-----\" et se termine par \"-----END CERTIFICATE-----\"",
|
||||
"invalid_alphanumeric_with_dash": "Caractères acceptés. sont uniquement alphanumériques (lettres et chiffres)",
|
||||
"invalid_cidr": "Adresse IPv4 CIDR non valide. Exemple : 192.168.0.1/12",
|
||||
"invalid_email": "Email Invalide",
|
||||
"invalid_file_content": "Contenu de fichier non valide, veuillez confirmer qu'il est au format valide",
|
||||
@@ -693,7 +752,7 @@
|
||||
"invalid_ipv6": "Adresse IPv6 invalide (ex. : 2001:db8:3333:4444:5555:6666:7777:8888)",
|
||||
"invalid_json": "Chaîne JSON non valide",
|
||||
"invalid_lease_time": "Valeur de durée de bail non valide ! Ils doivent être au format digitUnit. Par exemple : 6d2h5m, ce qui signifie 6 jours, 2 heures et 5 minutes. Voici les unités acceptées : m, h, d. Si vous ne voulez pas utiliser une unité, omettez-la complètement. Donc au lieu de dire 0d2h0m, utilisez 2h",
|
||||
"invalid_mac_uc": "Valeur UC-MAC non valide, par exemple : 00:00:5e:00:53:af",
|
||||
"invalid_mac_uc": "Valeur MAC non valide, par exemple : 00:00:5e:00:53:af",
|
||||
"invalid_password": "Mot de passe invalide, veuillez consulter la politique de mot de passe",
|
||||
"invalid_phone_number": "Numéro de téléphone invalide",
|
||||
"invalid_phone_numbers": "Un ou plusieurs des numéros de téléphone sont invalides. Veuillez les fournir sans symboles ni espaces, ou dans ce format : +1(123)123-1234",
|
||||
@@ -706,7 +765,11 @@
|
||||
"invalid_static_ipv4_e": "Adresse invalide, cette plage est réservée aux expérimentations (classe E). Le premier octet doit être 223 ou moins",
|
||||
"invalid_third_party": "Chaîne JSON tierce non valide. Veuillez confirmer que votre valeur est un JSON valide",
|
||||
"key_file_explanation": "Veuillez utiliser un fichier .pem qui commence par \"-----BEGIN PRIVATE KEY-----\" et se termine par \"-----END PRIVATE KEY-----\"",
|
||||
"max_length": "Longueur maximale de {{max}} caractères.",
|
||||
"max_value": "Valeur maximale de {{max}}",
|
||||
"min_length": "Longueur minimale de {{min}} caractères.",
|
||||
"min_max_string": "La valeur doit être d'une longueur comprise entre {{min}} (inclus) et {{max}} (inclus)",
|
||||
"min_value": "Valeur minimale de {{min}}",
|
||||
"missing_interface_upstream": "Vous devez avoir au moins une interface en amont. Pour le moment, toutes vos interfaces sont en aval",
|
||||
"new_email_to_notify": "Nouvel e-mail à notifier",
|
||||
"new_phone_to_notify": "Nouveau téléphone à notifier",
|
||||
@@ -765,13 +828,17 @@
|
||||
"city": "Ville",
|
||||
"claim_explanation": "Pour revendiquer des emplacements, vous pouvez utiliser le tableau ci-dessous",
|
||||
"country": "Pays",
|
||||
"elevation": "Élévation",
|
||||
"geocode": "Geo code",
|
||||
"lat": "Latitude",
|
||||
"longitude": "Longitude",
|
||||
"one": "Emplacement",
|
||||
"other": "Emplacements",
|
||||
"postal": "Zip / code postal",
|
||||
"state": "Etat / Province",
|
||||
"title": "Emplacements",
|
||||
"to_claim": "Emplacements à réclamer"
|
||||
"to_claim": "Emplacements à réclamer",
|
||||
"view_gps": ""
|
||||
},
|
||||
"login": {
|
||||
"access_policy": "Politique d'accès",
|
||||
@@ -797,6 +864,7 @@
|
||||
"reset_password": "Réinitialiser le mot de passe",
|
||||
"sign_in": "se connecter",
|
||||
"sms_instructions": "Vous devriez bientôt recevoir un code à 6 chiffres sur votre téléphone. Veuillez le saisir ci-dessous pour vous connecter",
|
||||
"suspended_error": "Compte suspendu, veuillez contacter votre administrateur",
|
||||
"verification": "Vérifiez votre connexion",
|
||||
"waiting_for_email_verification": "Compte pas encore e-mail validé. Veuillez consulter votre boîte de réception ou demander à votre administrateur de renvoyer une validation",
|
||||
"welcome_back": "Nous saluons le retour!",
|
||||
@@ -843,6 +911,11 @@
|
||||
"one": "Notification",
|
||||
"other": "Les notifications"
|
||||
},
|
||||
"openroaming": {
|
||||
"pool_strategy": "Stratégie de pool",
|
||||
"radius_endpoint_one": "Point final de rayon",
|
||||
"radius_endpoint_other": "Points de terminaison du rayon"
|
||||
},
|
||||
"operator": {
|
||||
"delete_explanation": "Voulez-vous vraiment supprimer cet opérateur ? Cette opération n'est pas réversible",
|
||||
"delete_operator": "Supprimer l'opérateur",
|
||||
@@ -902,12 +975,33 @@
|
||||
"dfs": "Remplacement DFS",
|
||||
"gw_commands": "Commandes de passerelle",
|
||||
"identifier": "Identifiant",
|
||||
"key_verification": "Vérification de la clé de signature",
|
||||
"key_verification": "Signature des informations clés",
|
||||
"restricted": "Limité",
|
||||
"signed_upgrade": "Mise à niveau signée uniquement",
|
||||
"title": "Restrictions",
|
||||
"tty": "Accès ATS"
|
||||
},
|
||||
"roaming": {
|
||||
"account_created": "Nouveau compte créé !",
|
||||
"account_deleted": "Compte supprimé !",
|
||||
"account_one": "Compte",
|
||||
"account_other": "Comptes",
|
||||
"certificate_deleted": "Certificat supprimé !",
|
||||
"certificate_one": "Certificat",
|
||||
"certificate_other": "Certificats",
|
||||
"city": "Ville",
|
||||
"common_name": "Nom commun",
|
||||
"country": "Pays",
|
||||
"global_reach": "Portée mondiale",
|
||||
"global_reach_account_id": "ID de compte ",
|
||||
"invalid_certificate": "certificat invalide",
|
||||
"invalid_key": "Clé privée invalide",
|
||||
"location_details_title": "Emplacement",
|
||||
"organization": "Organisation",
|
||||
"private_key": "Clé privée",
|
||||
"province": "province",
|
||||
"state": "Etat"
|
||||
},
|
||||
"rrm": {
|
||||
"algorithm": "Algorithme",
|
||||
"algorithm_other": "Algorithmes",
|
||||
@@ -966,6 +1060,11 @@
|
||||
"concurrent_devices": "Périphériques simultanés",
|
||||
"controller": "Manette",
|
||||
"current_live_devices": "Appareils en direct actuels",
|
||||
"currently_running_one": "Il y a actuellement {{count}} simulation en cours",
|
||||
"currently_running_other": "Il y a actuellement {{count}} simulations en cours d'exécution",
|
||||
"delete_devices_confirm": "Voulez-vous vraiment supprimer tous les appareils et leurs statistiques de la passerelle ? Cette action n'est pas réversible",
|
||||
"delete_devices_loading": "Ce processus peut prendre jusqu'à 5 minutes",
|
||||
"delete_simulation_devices": "Supprimer des appareils",
|
||||
"delete_success": "Simulation supprimée !",
|
||||
"duration": "Durée",
|
||||
"error_devices": "Périphériques d'erreur",
|
||||
@@ -973,6 +1072,7 @@
|
||||
"infinite": "Infini",
|
||||
"keep_alive": "Rester en vie",
|
||||
"mac_prefix": "Préfixe MAC",
|
||||
"mac_prefix_length": "Votre préfixe MAC doit être valide à 6 chiffres HEX (ex. : 00112233)",
|
||||
"max_associations": "Max. Les associations",
|
||||
"max_clients": "Max. Clients",
|
||||
"min_associations": "Min. Les associations",
|
||||
@@ -989,6 +1089,7 @@
|
||||
"rx_messages": "Messages reçus",
|
||||
"sim_currently_running": "Simulation \"{{sim}}\" en cours",
|
||||
"sim_history": "{{sim}} courses précédentes",
|
||||
"simulated": "Simulé",
|
||||
"start": "Démarrer la simulation",
|
||||
"start_success": "Lancement de la simulation !",
|
||||
"state_interval": "Intervalle d'état",
|
||||
@@ -1002,6 +1103,7 @@
|
||||
},
|
||||
"statistics": {
|
||||
"last_stats": "Dernières statistiques",
|
||||
"latest": "Dernières statistiques",
|
||||
"memory": "mémoire"
|
||||
},
|
||||
"subscribers": {
|
||||
@@ -1021,7 +1123,9 @@
|
||||
"title": "Les abonnés"
|
||||
},
|
||||
"system": {
|
||||
"advanced": "Avancée",
|
||||
"backend_logs": "Journaux principaux",
|
||||
"configuration": "Configuration",
|
||||
"could_not_retrieve": "Erreur : impossible de récupérer les informations système {{name}} ",
|
||||
"endpoint": "Point final",
|
||||
"hostname": "nom d'hôte",
|
||||
@@ -1032,6 +1136,10 @@
|
||||
"os": "Système opérateur",
|
||||
"processors": "Processeurs",
|
||||
"reload_chosen_subsystems": "Recharger les sous-systèmes choisis",
|
||||
"secrets": "Secrets",
|
||||
"secrets_create": "Créer un secret",
|
||||
"secrets_one": "Secret",
|
||||
"services": "Prestations de service",
|
||||
"start": "Début",
|
||||
"subsystems": "Sous-systèmes",
|
||||
"success_reload": "Commande de rechargement envoyée avec succès !",
|
||||
@@ -1043,19 +1151,31 @@
|
||||
"version": "Version"
|
||||
},
|
||||
"table": {
|
||||
"columns": "Les colonnes",
|
||||
"columns_hidden_one": "{{count}} Colonne masquée",
|
||||
"columns_hidden_other": "{{count}} colonnes masquées",
|
||||
"display_column": "Afficher",
|
||||
"drag_always_show": "Vous ne pouvez pas masquer cette colonne, mais vous pouvez modifier sa position",
|
||||
"drag_explanation": "Glisser-déposer pour réorganiser et modifier la visibilité des colonnes",
|
||||
"drag_locked": "Cette colonne est verrouillée dans sa position",
|
||||
"export_current_page": "Page actuelle uniquement",
|
||||
"first_page": "Première page",
|
||||
"go_to_page": "Aller à la page",
|
||||
"hide_column": "Cacher",
|
||||
"last_page": "Dernière page",
|
||||
"next_page": "Page suivante",
|
||||
"page": "Page",
|
||||
"previous_page": "Page précédente"
|
||||
"preferences": "Préférences de tableau",
|
||||
"previous_page": "Page précédente",
|
||||
"reset": "Remettre à zéro les préférences",
|
||||
"settings": "Réglages"
|
||||
},
|
||||
"user": {
|
||||
"email_not_validated": "Mail non valide",
|
||||
"error_fetching": "Erreur lors de la récupération des informations utilisateur : {{e}}",
|
||||
"password": "Mot de passe",
|
||||
"role": "Rôle",
|
||||
"suspended": "Suspendu",
|
||||
"title": "Utilisateur"
|
||||
},
|
||||
"users": {
|
||||
@@ -1100,9 +1220,11 @@
|
||||
"successfully_update_devices": "Mise à jour de {{num}} appareils !",
|
||||
"title": "Les lieux",
|
||||
"update_all_devices": "Mettre à jour toutes les configurations de périphérique",
|
||||
"upgrade_all_devices": "Mettre à niveau tous les appareils vers le dernier micrologiciel",
|
||||
"update_success": "Lieu mis à jour !",
|
||||
"upgrade_all_devices": "Mettre à niveau le micrologiciel de tous les appareils",
|
||||
"upgrade_all_devices_error": "Erreur lors de la mise à jour des appareils : {{e}}",
|
||||
"upgrade_all_devices_success": "La mise à niveau des appareils a démarré avec succès !",
|
||||
"upgrade_options_available": "Voici toutes les révisions disponibles, veuillez sélectionner celle vers laquelle vous souhaitez que TOUS les appareils de ce lieu soient mis à niveau",
|
||||
"use_existing": "Utiliser l'existant",
|
||||
"use_existing_contacts": "Utiliser les contacts existants",
|
||||
"use_this_contact": "Utilisez ce contact"
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
"health": "Saúde",
|
||||
"inactive": "Inativo",
|
||||
"interval": "intervalo",
|
||||
"last_connected": "última conexão",
|
||||
"last_connected_tooltip": "Última vez que este dispositivo foi conectado ao controlador. Isso pode ser usado para estimar quando um dispositivo desconectado",
|
||||
"last_connection": "última conexão",
|
||||
"last_contact": "Último contato",
|
||||
"last_disconnection": "Última desconexão",
|
||||
@@ -79,8 +81,11 @@
|
||||
"live_view_help": "Ajuda da visualização ao vivo",
|
||||
"memory": "Memória",
|
||||
"memory_used": "Memória Usada",
|
||||
"missing_board": "O monitoramento analítico neste local não está mais ativo, reinicie o monitoramento usando o menu superior",
|
||||
"missing_board": "O monitoramento analítico neste local não está mais ativo. Clique aqui para reiniciar o monitoramento",
|
||||
"mode": "Modo",
|
||||
"monitoring": "Monitoramento",
|
||||
"no_board": "Sem monitoramento",
|
||||
"no_board_description": "Você não está monitorando este local no momento, clique aqui para começar",
|
||||
"noise": "Barulho",
|
||||
"packets": "Pacotes",
|
||||
"radio": "Rádio",
|
||||
@@ -91,6 +96,8 @@
|
||||
"retries": "Novas tentativas",
|
||||
"search_serials": "Pesquisar séries",
|
||||
"stop_monitoring": "Parar o monitoramento",
|
||||
"stop_monitoring_success": "Local de monitoramento interrompido!",
|
||||
"stop_monitoring_warning": "Tem certeza? Isso apagará todos os dados de monitoramento gravados para este local",
|
||||
"temperature": "Temperatura",
|
||||
"title": "Analytics",
|
||||
"total_data": "Dados totais",
|
||||
@@ -175,6 +182,7 @@
|
||||
"other": "comandos",
|
||||
"override_dfs": "Substituir DFS",
|
||||
"reboot": "Reiniciar",
|
||||
"reboot_description": "Deseja reiniciar este dispositivo?",
|
||||
"reboot_error": "Erro ao enviar o comando de reinicialização: {{e}}",
|
||||
"reboot_success": "Comando de reinicialização enviado com sucesso!",
|
||||
"revision": "revisão",
|
||||
@@ -215,6 +223,7 @@
|
||||
"day": "Dia",
|
||||
"days": "Dias",
|
||||
"default": "Padrão",
|
||||
"defaults": "Predefinições",
|
||||
"description": "Descrição",
|
||||
"details": "Detalhes",
|
||||
"device_details": "Detalhes do dispositivo",
|
||||
@@ -235,6 +244,7 @@
|
||||
"error_download": "Erro ao tentar fazer o download: {{e}}",
|
||||
"errors": "Erros",
|
||||
"exit_fullscreen": "Saída",
|
||||
"export": "Exportar",
|
||||
"finished": "acabado",
|
||||
"fullscreen": "Tela cheia",
|
||||
"general_error": "Erro ao se conectar ao servidor. Consulte seu administrador.",
|
||||
@@ -259,6 +269,7 @@
|
||||
"map": "Mapa",
|
||||
"max": "máximo",
|
||||
"min": "minuto",
|
||||
"miscellaneous": "Diversos",
|
||||
"mode": "Modo",
|
||||
"model": "Modelo",
|
||||
"modified": "Modificado",
|
||||
@@ -350,6 +361,7 @@
|
||||
"error_pushes_one": "Erro (pode ser devido à configuração incorreta): {{count}}",
|
||||
"error_pushes_other": "Erros (podem ser devido à configuração incorreta): {{count}}",
|
||||
"expert_name": "MODO EXPERT",
|
||||
"expert_name_explanation": "Você pode usar o modo especialista para modificar diretamente sua configuração, incluindo a adição de valores que não são atualmente suportados pela interface do usuário. Use este formato: { \"interfaces\": [ ... ], \"globals\": { ... }, ...etc }",
|
||||
"explanation": "Explicação",
|
||||
"firewall": "Firewall",
|
||||
"firmware_upgrade": "Atualização de firmware",
|
||||
@@ -391,6 +403,7 @@
|
||||
"warning_pushes_one": "Aguardando a conexão dos dispositivos: {{count}}",
|
||||
"warning_pushes_other": "Aguardando a conexão dos dispositivos: {{count}}",
|
||||
"weight": "Peso",
|
||||
"wifi_bands_max": "Não pode haver mais de 8 SSIDs usando esta banda wi-fi",
|
||||
"wifi_frames": "Quadros WiFi"
|
||||
},
|
||||
"contacts": {
|
||||
@@ -515,6 +528,7 @@
|
||||
"ouis_explanation": "OUIs de dispositivos que se conectaram a este servidor de firmware",
|
||||
"outdated_one": "Firmware com {{count}} dias",
|
||||
"outdated_other": "Firmware com {{count}} dias",
|
||||
"outdated_unknown": "Firmware de idade desconhecida",
|
||||
"release": "LANÇAMENTO",
|
||||
"show_dev_releases": "Lançamentos do desenvolvedor",
|
||||
"status_explanation": "Status da conexão dos dispositivos que se conectaram a este servidor de firmware",
|
||||
@@ -530,6 +544,16 @@
|
||||
"queue": {
|
||||
"title": "Fila de Eventos"
|
||||
},
|
||||
"radius": {
|
||||
"calling_station_id": "estação",
|
||||
"disconnect": "Desconectar",
|
||||
"disconnect_success": "Sessão Radius desconectada!",
|
||||
"input_octets": "Entrada",
|
||||
"output_octets": "Saída",
|
||||
"radius_clients": "Clientes Radius",
|
||||
"session_time": "Tempo de sessão",
|
||||
"username": "Nome de usuário"
|
||||
},
|
||||
"stats": {
|
||||
"load": "Carga (1 | 5 | 15 m.)",
|
||||
"seconds_ago": "{{s}} segundos atrás",
|
||||
@@ -600,6 +624,7 @@
|
||||
"certificate_expires_in": "Certificado expira em",
|
||||
"certificate_expiry": "Certificado expira em",
|
||||
"connected": "Conectado",
|
||||
"crash_logs": "Registros de falhas",
|
||||
"create_errors": "erros ao tentar criar dispositivos",
|
||||
"create_success": " dispositivos criados com sucesso",
|
||||
"current_firmware": "Firmware atual",
|
||||
@@ -613,6 +638,7 @@
|
||||
"import_device_warning": "Certifique-se de que não há espaços extras no início ou no final de nenhum valor, a menos que faça parte do valor desejado",
|
||||
"import_explanation": "Para importar dispositivos em massa, você precisa usar um arquivo CSV com as seguintes colunas: SerialNumber, DeviceType, Name, Description, Note",
|
||||
"invalid_serial_number": "Número de série inválido (precisa ter 12 caracteres HEX)",
|
||||
"logs_one": "Registro",
|
||||
"new_devices": "novos dispositivos",
|
||||
"no_model_image": "Nenhuma imagem de modelo encontrada",
|
||||
"not_connected": "Não conectado",
|
||||
@@ -620,7 +646,10 @@
|
||||
"notifications": "Notificações do dispositivo",
|
||||
"one": "Dispositivo",
|
||||
"reassign_already_owned": "Reatribuir dispositivos que já existem e são de propriedade de outra entidade/local/assinante?",
|
||||
"reboot_logs": "Registros de reinicialização",
|
||||
"restricted": "Restrito",
|
||||
"restricted_overriden": "Este é um dispositivo restrito, mas está em modo de desenvolvimento. Todas as restrições são atualmente ignoradas",
|
||||
"restrictions_overriden_title": "Modo de desenvolvedor",
|
||||
"sanity": "Sanidade",
|
||||
"start_import": "Iniciar importação de dispositivos",
|
||||
"test_batch": "Dados de importação de teste",
|
||||
@@ -671,7 +700,36 @@
|
||||
"test_digicert_creds": "Credenciais de teste",
|
||||
"title": "Entidades",
|
||||
"tree": "Árvore de entidades",
|
||||
"venues_under_root": "Os locais não podem ser criados diretamente na entidade raiz. Por favor, crie novas entidades e crie locais sob elas."
|
||||
"update_success": "Entidade atualizada!",
|
||||
"venues_under_root": "Os locais não podem ser criados diretamente na entidade raiz"
|
||||
},
|
||||
"firmware": {
|
||||
"confirm_default_data": "Confirme as informações abaixo e clique em 'Confirmar' quando estiver pronto para iniciar o processo",
|
||||
"create_success": "Criou novas configurações de firmware padrão!",
|
||||
"db_update_warning": "Esta operação é feita automaticamente diariamente sem necessidade de usar esta atualização manual. A atualização deste banco de dados pode levar até 25 minutos",
|
||||
"default_created_error_one": "{{count}} erro ao tentar criar uma nova configuração",
|
||||
"default_created_error_other": "{{count}} erros ao tentar criar uma nova configuração",
|
||||
"default_created_one": "{{count}} configuração de firmware padrão criada",
|
||||
"default_created_other": "{{count}} configurações de firmware padrão criadas",
|
||||
"default_found_one": "Revisão válida encontrada para {{count}} tipo de dispositivo",
|
||||
"default_found_other": "Foram encontradas revisões válidas para {{count}} tipos de dispositivo",
|
||||
"default_mass_delete_success_one": "Configuração de firmware padrão {{count}} excluída!",
|
||||
"default_mass_delete_success_other": "Excluídas {{count}} configurações de firmware padrão!",
|
||||
"default_not_found_one": "Nenhuma versão de firmware válida para {{count}} tipo de dispositivo",
|
||||
"default_not_found_other": "Nenhuma versão de firmware válida para {{count}} tipos de dispositivo",
|
||||
"default_title": "",
|
||||
"default_update_success": "Firmware padrão atualizado para {{deviceType}}!",
|
||||
"delete_success": "Configuração de firmware padrão excluída!",
|
||||
"edit_default_title": "Este é o firmware atual usado como versão mínima para novos APs do tipo {{deviceType}}. Se um novo AP {{deviceType}} se conectar ao gateway, ele será atualizado automaticamente para esta versão.",
|
||||
"fetching_defaults": "Buscando todo o firmware disponível para os tipos de dispositivos selecionados...",
|
||||
"last_db_update_modal": "banco de dados de firmware",
|
||||
"last_db_update_title": "base de dados",
|
||||
"one": "Firmware",
|
||||
"select_default_device_types": "Selecione todos os tipos de dispositivos que deseja segmentar com esta nova regra de firmware padrão. Se você não conseguir encontrar o tipo de dispositivo desejado, significa que eles já têm uma regra aplicada.",
|
||||
"select_default_revision": "Agora você pode selecionar a revisão mínima para a qual deseja que seus tipos de dispositivo sejam direcionados",
|
||||
"start_db_update": "Iniciar atualização do banco de dados",
|
||||
"started_db_update": "Atualização do banco de dados iniciada, esta operação deve levar até 25 minutos para ser concluída",
|
||||
"update_success": "Informações de firmware padrão salvas!"
|
||||
},
|
||||
"footer": {
|
||||
"powered_by": "Distribuído por",
|
||||
@@ -680,6 +738,7 @@
|
||||
"form": {
|
||||
"captive_web_root_explanation": "Por favor, use apenas arquivos .tar (sem arquivos compactados como .targz, por exemplo)",
|
||||
"certificate_file_explanation": "Use um arquivo .pem que comece com \"-----BEGIN CERTIFICATE-----\" e termine com \"-----END CERTIFICATE-----\"",
|
||||
"invalid_alphanumeric_with_dash": "Caracteres aceitos. são apenas alfanuméricos (letras e números)",
|
||||
"invalid_cidr": "Endereço CIDR IPv4 inválido. Exemplo: 192.168.0.1/12",
|
||||
"invalid_email": "E-mail inválido",
|
||||
"invalid_file_content": "Conteúdo de arquivo inválido. Confirme se está no formato válido",
|
||||
@@ -693,7 +752,7 @@
|
||||
"invalid_ipv6": "Endereço IPv6 inválido (ex.: 2001:db8:3333:4444:5555:6666:7777:8888)",
|
||||
"invalid_json": "Sequência JSON inválida",
|
||||
"invalid_lease_time": "Valor de tempo de locação inválido! Eles precisam estar no formato digitUnit. Por exemplo: 6d2h5m, que significa 6 dias, 2 horas e 5 minutos. Aqui estão as unidades aceitas: m, h, d. Se você não quiser usar uma unidade, omita-a completamente. Então, em vez de dizer 0d2h0m, use 2h",
|
||||
"invalid_mac_uc": "Valor UC-MAC inválido, por exemplo: 00:00:5e:00:53:af",
|
||||
"invalid_mac_uc": "Valor MAC inválido, por exemplo: 00:00:5e:00:53:af",
|
||||
"invalid_password": "Senha inválida, consulte a política de senha",
|
||||
"invalid_phone_number": "Número de telefone inválido",
|
||||
"invalid_phone_numbers": "Um ou mais números de telefone são inválidos. Forneça-os sem símbolos e espaços ou neste formato: +1(123)123-1234",
|
||||
@@ -706,7 +765,11 @@
|
||||
"invalid_static_ipv4_e": "Endereço inválido, este intervalo é reservado para experimentos (classe E). O primeiro octeto deve ser 223 ou inferior",
|
||||
"invalid_third_party": "String JSON de terceiros inválida. Confirme se seu valor é um JSON válido",
|
||||
"key_file_explanation": "Use um arquivo .pem que comece com \"-----BEGIN PRIVATE KEY-----\" e termine com \"-----END PRIVATE KEY-----\"",
|
||||
"max_length": "Comprimento máximo de {{max}} caracteres.",
|
||||
"max_value": "Valor máximo de {{max}}",
|
||||
"min_length": "Comprimento mínimo de {{min}} caracteres.",
|
||||
"min_max_string": "O valor precisa ter um comprimento entre {{min}} (inclusive) e {{max}} (inclusive)",
|
||||
"min_value": "Valor mínimo de {{min}}",
|
||||
"missing_interface_upstream": "Você precisa ter pelo menos uma interface upstream. No momento, todas as suas interfaces estão downstream",
|
||||
"new_email_to_notify": "Novo e-mail para notificar",
|
||||
"new_phone_to_notify": "Novo telefone para notificar",
|
||||
@@ -765,13 +828,17 @@
|
||||
"city": "Cidade",
|
||||
"claim_explanation": "Para reivindicar locais, você pode usar a tabela abaixo",
|
||||
"country": "País",
|
||||
"elevation": "elevação",
|
||||
"geocode": "Código geográfico",
|
||||
"lat": "Latitude",
|
||||
"longitude": "Longitude",
|
||||
"one": "Localização",
|
||||
"other": "Localizações",
|
||||
"postal": "CEP / Código Postal",
|
||||
"state": "Estado / Província",
|
||||
"title": "Localizações",
|
||||
"to_claim": "Locais para reivindicar"
|
||||
"to_claim": "Locais para reivindicar",
|
||||
"view_gps": ""
|
||||
},
|
||||
"login": {
|
||||
"access_policy": "Política de Acesso",
|
||||
@@ -797,6 +864,7 @@
|
||||
"reset_password": "Redefinir senha",
|
||||
"sign_in": "assinar em",
|
||||
"sms_instructions": "Você deve receber um código de 6 dígitos em seu telefone em breve. Por favor, insira-o abaixo para fazer login",
|
||||
"suspended_error": "Conta suspensa, entre em contato com seu administrador",
|
||||
"verification": "Verifique seu login",
|
||||
"waiting_for_email_verification": "Conta ainda não validada por e-mail. Verifique sua caixa de entrada ou peça ao administrador para reenviar uma validação",
|
||||
"welcome_back": "Bem vindo de volta!",
|
||||
@@ -843,6 +911,11 @@
|
||||
"one": "Notificação",
|
||||
"other": "Notificações"
|
||||
},
|
||||
"openroaming": {
|
||||
"pool_strategy": "Estratégia de pool",
|
||||
"radius_endpoint_one": "Ponto final do raio",
|
||||
"radius_endpoint_other": "Pontos finais de raio"
|
||||
},
|
||||
"operator": {
|
||||
"delete_explanation": "Tem certeza de que deseja excluir este operador? Esta operação não é reversível",
|
||||
"delete_operator": "Excluir operador",
|
||||
@@ -902,12 +975,33 @@
|
||||
"dfs": "Substituição DFS",
|
||||
"gw_commands": "Comandos de gateway",
|
||||
"identifier": "Identificador",
|
||||
"key_verification": "Verificação da chave de assinatura",
|
||||
"key_verification": "Informações Chave de Assinatura",
|
||||
"restricted": "Restrito",
|
||||
"signed_upgrade": "Somente atualização assinada",
|
||||
"title": "RESTRIÇÕES",
|
||||
"tty": "Acesso TTY"
|
||||
},
|
||||
"roaming": {
|
||||
"account_created": "Nova conta criada!",
|
||||
"account_deleted": "Conta excluída!",
|
||||
"account_one": "Conta",
|
||||
"account_other": "Contas",
|
||||
"certificate_deleted": "Certificado excluído!",
|
||||
"certificate_one": "Certificado",
|
||||
"certificate_other": "Certificados",
|
||||
"city": "Cidade",
|
||||
"common_name": "Nome comum",
|
||||
"country": "País",
|
||||
"global_reach": "Alcance global",
|
||||
"global_reach_account_id": "ID da conta",
|
||||
"invalid_certificate": "Certificado inválido",
|
||||
"invalid_key": "Chave privada inválida",
|
||||
"location_details_title": "Localização",
|
||||
"organization": "Organização",
|
||||
"private_key": "Chave privada",
|
||||
"province": "província",
|
||||
"state": "Estado"
|
||||
},
|
||||
"rrm": {
|
||||
"algorithm": "Algoritmo",
|
||||
"algorithm_other": "Algoritmos",
|
||||
@@ -966,6 +1060,11 @@
|
||||
"concurrent_devices": "Dispositivos Simultâneos",
|
||||
"controller": "Controlador",
|
||||
"current_live_devices": "Dispositivos ativos atuais",
|
||||
"currently_running_one": "Atualmente, há {{count}} simulação em execução",
|
||||
"currently_running_other": "Existem atualmente {{count}} simulações em execução",
|
||||
"delete_devices_confirm": "Tem certeza de que deseja remover todos os dispositivos e suas estatísticas do gateway? Esta ação não é reversível",
|
||||
"delete_devices_loading": "Este processo pode levar até 5 minutos",
|
||||
"delete_simulation_devices": "Apagar dispositivos",
|
||||
"delete_success": "Simulação excluída!",
|
||||
"duration": "Duração",
|
||||
"error_devices": "Dispositivos de Erro",
|
||||
@@ -973,6 +1072,7 @@
|
||||
"infinite": "Infinito",
|
||||
"keep_alive": "Mantenha vivo",
|
||||
"mac_prefix": "Prefixo MAC",
|
||||
"mac_prefix_length": "Seu prefixo MAC precisa ter 6 dígitos HEX válidos (ex.: 00112233)",
|
||||
"max_associations": "Máx. Associações",
|
||||
"max_clients": "Máx. Clientes",
|
||||
"min_associations": "Min. Associações",
|
||||
@@ -989,6 +1089,7 @@
|
||||
"rx_messages": "Mensagens Rx",
|
||||
"sim_currently_running": "Simulação \"{{sim}}\" em andamento",
|
||||
"sim_history": "{{sim}} execuções anteriores",
|
||||
"simulated": "Simulado",
|
||||
"start": "Iniciar simulação",
|
||||
"start_success": "Corrida de simulação iniciada!",
|
||||
"state_interval": "Intervalo de estado",
|
||||
@@ -1002,6 +1103,7 @@
|
||||
},
|
||||
"statistics": {
|
||||
"last_stats": "Últimas estatísticas",
|
||||
"latest": "Estatísticas mais recentes",
|
||||
"memory": "Memória"
|
||||
},
|
||||
"subscribers": {
|
||||
@@ -1021,7 +1123,9 @@
|
||||
"title": "Inscritos"
|
||||
},
|
||||
"system": {
|
||||
"advanced": "Avançado",
|
||||
"backend_logs": "Registros de back-end",
|
||||
"configuration": "Configuração",
|
||||
"could_not_retrieve": "Erro: não foi possível recuperar {{name}} informações do sistema",
|
||||
"endpoint": "Ponto final",
|
||||
"hostname": "Nome de anfitrião",
|
||||
@@ -1032,6 +1136,10 @@
|
||||
"os": "Sistema Operacional",
|
||||
"processors": "Processadores",
|
||||
"reload_chosen_subsystems": "Recarregar Subsistemas Escolhidos",
|
||||
"secrets": "Segredos",
|
||||
"secrets_create": "Criar Segredo",
|
||||
"secrets_one": "Segredo",
|
||||
"services": "Serviços",
|
||||
"start": "Começar",
|
||||
"subsystems": "Subsistemas",
|
||||
"success_reload": "Comando de recarga enviado com sucesso!",
|
||||
@@ -1043,19 +1151,31 @@
|
||||
"version": "Versão"
|
||||
},
|
||||
"table": {
|
||||
"columns": "Colunas",
|
||||
"columns_hidden_one": "{{count}} Coluna oculta",
|
||||
"columns_hidden_other": "{{count}} Colunas ocultas",
|
||||
"display_column": "Exibição",
|
||||
"drag_always_show": "Você não pode ocultar esta coluna, mas pode alterar sua posição",
|
||||
"drag_explanation": "Arraste e solte para reordenar e alterar a visibilidade da coluna",
|
||||
"drag_locked": "Esta coluna está travada em sua posição",
|
||||
"export_current_page": "Somente página atual",
|
||||
"first_page": "Primeira Página",
|
||||
"go_to_page": "Vá para página",
|
||||
"hide_column": "Ocultar",
|
||||
"last_page": "Última Página",
|
||||
"next_page": "Próxima página",
|
||||
"page": "Página",
|
||||
"previous_page": "Página anterior"
|
||||
"preferences": "Preferências de Tabela",
|
||||
"previous_page": "Página anterior",
|
||||
"reset": "Reiniciar preferências",
|
||||
"settings": "Definições"
|
||||
},
|
||||
"user": {
|
||||
"email_not_validated": "e-mail não validado",
|
||||
"error_fetching": "Erro ao buscar informações do usuário: {{e}}",
|
||||
"password": "Senha",
|
||||
"role": "Função",
|
||||
"suspended": "Suspenso",
|
||||
"title": "Do utilizador"
|
||||
},
|
||||
"users": {
|
||||
@@ -1100,9 +1220,11 @@
|
||||
"successfully_update_devices": "Atualizando {{num}} dispositivos!",
|
||||
"title": "Locais",
|
||||
"update_all_devices": "Atualizar todas as configurações do dispositivo",
|
||||
"upgrade_all_devices": "Atualize todos os dispositivos para o firmware mais recente",
|
||||
"update_success": "Local atualizado!",
|
||||
"upgrade_all_devices": "Atualize o firmware de todos os dispositivos",
|
||||
"upgrade_all_devices_error": "Erro ao atualizar dispositivos: {{e}}",
|
||||
"upgrade_all_devices_success": "Atualização de dispositivos iniciada com sucesso!",
|
||||
"upgrade_options_available": "Aqui estão todas as revisões disponíveis, selecione aquela para a qual você deseja que TODOS os dispositivos deste local sejam atualizados",
|
||||
"use_existing": "Usar existente",
|
||||
"use_existing_contacts": "Usar contatos existentes",
|
||||
"use_this_contact": "Use este contato"
|
||||
|
||||
28
src/@tanstack.react-table.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { BoxProps } from '@chakra-ui/react';
|
||||
import '@tanstack/react-table';
|
||||
|
||||
declare module '@tanstack/table-core' {
|
||||
interface ColumnMeta<TData extends RowData, TValue> {
|
||||
ref?: React.MutableRefObject<HTMLTableCellElement | null>;
|
||||
customMinWidth?: string;
|
||||
anchored?: boolean;
|
||||
stopPropagation?: boolean;
|
||||
alwaysShow?: boolean;
|
||||
hasPopover?: boolean;
|
||||
customMaxWidth?: string;
|
||||
customWidth?: string;
|
||||
isMonospace?: boolean;
|
||||
isCentered?: boolean;
|
||||
columnSelectorOptions?: {
|
||||
label?: string;
|
||||
};
|
||||
rowContentOptions?: {
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
headerOptions?: {
|
||||
tooltip?: string;
|
||||
};
|
||||
headerStyleProps?: BoxProps;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||
import { Warning } from 'phosphor-react';
|
||||
import { Warning } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ThemeProps } from 'models/Theme';
|
||||
|
||||
@@ -12,7 +12,14 @@ export interface AlertButtonProps extends ThemeProps {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const _AlertButton: React.FC<AlertButtonProps> = ({ onClick, isDisabled, isLoading, isCompact, label, ...props }) => {
|
||||
const _AlertButton: React.FC<AlertButtonProps> = ({
|
||||
onClick,
|
||||
isDisabled,
|
||||
isLoading,
|
||||
isCompact = true,
|
||||
label,
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const breakpoint = useBreakpoint();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { IconButton, SpaceProps } from '@chakra-ui/react';
|
||||
import { X } from 'phosphor-react';
|
||||
import { X } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface CloseButtonProps extends SpaceProps {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Button, IconButton, Tooltip, useBreakpoint, SpaceProps } from '@chakra-ui/react';
|
||||
import { Plus } from 'phosphor-react';
|
||||
import { Plus } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface CreateButtonProps extends SpaceProps {
|
||||
@@ -11,7 +11,14 @@ export interface CreateButtonProps extends SpaceProps {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const _CreateButton: React.FC<CreateButtonProps> = ({ onClick, isDisabled, isLoading, isCompact, label, ...props }) => {
|
||||
const _CreateButton: React.FC<CreateButtonProps> = ({
|
||||
onClick,
|
||||
isDisabled,
|
||||
isLoading,
|
||||
isCompact = true,
|
||||
label,
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const breakpoint = useBreakpoint();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||
import { Trash } from 'phosphor-react';
|
||||
import { Trash } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface DeleteButtonProps {
|
||||
@@ -16,7 +16,7 @@ const _DeleteButton: React.FC<DeleteButtonProps> = ({
|
||||
onClick,
|
||||
isDisabled,
|
||||
isLoading,
|
||||
isCompact,
|
||||
isCompact = true,
|
||||
label,
|
||||
ml,
|
||||
...props
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Button, IconButton, Menu, MenuButton, MenuItem, MenuList, Spinner, Tooltip, useToast } from '@chakra-ui/react';
|
||||
import {
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Portal,
|
||||
Tooltip,
|
||||
useColorModeValue,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import axios from 'axios';
|
||||
import { Wrench } from 'phosphor-react';
|
||||
import { Barcode, Power, TerminalWindow, WifiHigh, Wrench } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import RebootMenuItem from './RebootButton';
|
||||
import { useControllerStore } from 'contexts/ControllerSocketProvider/useStore';
|
||||
import { useBlinkDevice, useGetDeviceRtty } from 'hooks/Network/Devices';
|
||||
import { useUpdateDeviceToLatest } from 'hooks/Network/Firmware';
|
||||
@@ -22,6 +31,7 @@ interface Props {
|
||||
onOpenConfigureModal: (serialNumber: string) => void;
|
||||
onOpenTelemetryModal: (serialNumber: string) => void;
|
||||
onOpenScriptModal: (device: GatewayDevice) => void;
|
||||
onOpenRebootModal: (serialNumber: string) => void;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
isCompact?: boolean;
|
||||
}
|
||||
@@ -38,13 +48,16 @@ const DeviceActionDropdown = ({
|
||||
onOpenTelemetryModal,
|
||||
onOpenConfigureModal,
|
||||
onOpenScriptModal,
|
||||
onOpenRebootModal,
|
||||
size,
|
||||
isCompact,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const toast = useToast();
|
||||
const deviceType = device?.deviceType ?? 'ap';
|
||||
const connectColor = useColorModeValue('blackAlpha', 'gray');
|
||||
const addEventListeners = useControllerStore((state) => state.addEventListeners);
|
||||
const { refetch: getRtty, isInitialLoading: isRtty } = useGetDeviceRtty({
|
||||
const { refetch: getRtty, isFetching: isRtty } = useGetDeviceRtty({
|
||||
serialNumber: device.serialNumber,
|
||||
extraId: 'inventory-modal',
|
||||
});
|
||||
@@ -145,50 +158,95 @@ const DeviceActionDropdown = ({
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleConnectClick = () => getRtty();
|
||||
const handleRebootClick = () => onOpenRebootModal(device.serialNumber);
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<Tooltip label={t('commands.other')}>
|
||||
{size === undefined || isCompact ? (
|
||||
<>
|
||||
<Tooltip label={t('commands.connect')}>
|
||||
<IconButton
|
||||
aria-label="Connect"
|
||||
icon={<TerminalWindow size={20} />}
|
||||
size={size ?? 'sm'}
|
||||
isDisabled={isDisabled}
|
||||
isLoading={isRtty}
|
||||
onClick={handleConnectClick}
|
||||
colorScheme={connectColor}
|
||||
hidden={isCompact}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label={t('controller.configure.title')}>
|
||||
<IconButton
|
||||
aria-label={t('controller.configure.title')}
|
||||
icon={<Barcode size={20} />}
|
||||
size={size ?? 'sm'}
|
||||
isDisabled={isDisabled}
|
||||
onClick={handleOpenConfigure}
|
||||
colorScheme="purple"
|
||||
hidden={isCompact}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label={t('commands.reboot')}>
|
||||
<IconButton
|
||||
aria-label={t('commands.reboot')}
|
||||
icon={<Power size={20} />}
|
||||
size={size ?? 'sm'}
|
||||
isDisabled={isDisabled}
|
||||
onClick={handleRebootClick}
|
||||
colorScheme="green"
|
||||
hidden={isCompact}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label={t('commands.wifiscan')}>
|
||||
<IconButton
|
||||
aria-label={t('commands.wifiscan')}
|
||||
icon={<WifiHigh size={20} />}
|
||||
size={size ?? 'sm'}
|
||||
isDisabled={isDisabled}
|
||||
onClick={handleOpenScan}
|
||||
colorScheme="teal"
|
||||
hidden={isCompact || deviceType !== 'ap'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Menu>
|
||||
<Tooltip label={t('common.actions')}>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label="Commands"
|
||||
icon={isRtty ? <Spinner /> : <Wrench size={20} />}
|
||||
icon={<Wrench size={20} />}
|
||||
size={size ?? 'sm'}
|
||||
isDisabled={isDisabled}
|
||||
ml={2}
|
||||
/>
|
||||
) : (
|
||||
<MenuButton
|
||||
as={Button}
|
||||
aria-label="Commands"
|
||||
rightIcon={isRtty ? <Spinner /> : <Wrench size={20} />}
|
||||
size={size ?? 'sm'}
|
||||
isDisabled={isDisabled}
|
||||
ml={2}
|
||||
>
|
||||
{t('commands.other')}
|
||||
</MenuButton>
|
||||
)}
|
||||
</Tooltip>
|
||||
<MenuList>
|
||||
<MenuItem onClick={handleBlinkClick}>{t('commands.blink')}</MenuItem>
|
||||
<MenuItem onClick={handleOpenConfigure}>{t('controller.configure.title')}</MenuItem>
|
||||
<MenuItem onClick={handleConnectClick}>{t('commands.connect')}</MenuItem>
|
||||
<MenuItem onClick={handleOpenQueue}>{t('controller.queue.title')}</MenuItem>
|
||||
<MenuItem onClick={handleOpenFactoryReset}>{t('commands.factory_reset')}</MenuItem>
|
||||
<MenuItem onClick={handleOpenUpgrade}>{t('commands.firmware_upgrade')}</MenuItem>
|
||||
<RebootMenuItem device={device} refresh={refresh} />
|
||||
<MenuItem onClick={handleOpenTelemetry}>{t('controller.telemetry.title')}</MenuItem>
|
||||
<MenuItem onClick={handleOpenScript}>{t('script.one')}</MenuItem>
|
||||
<MenuItem onClick={handleOpenTrace}>{t('controller.devices.trace')}</MenuItem>
|
||||
<MenuItem onClick={handleUpdateToLatest} hidden>
|
||||
{t('premium.toolbox.upgrade_to_latest')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleOpenScan}>{t('commands.wifiscan')}</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Tooltip>
|
||||
<Portal>
|
||||
<MenuList maxH="315px" overflowY="auto">
|
||||
<MenuItem onClick={handleBlinkClick}>{t('commands.blink')}</MenuItem>
|
||||
<MenuItem onClick={handleOpenConfigure} hidden={!isCompact || deviceType !== 'ap'}>
|
||||
{t('controller.configure.title')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleConnectClick} hidden={!isCompact}>
|
||||
{t('commands.connect')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleOpenQueue}>{t('controller.queue.title')}</MenuItem>
|
||||
<MenuItem onClick={handleOpenFactoryReset}>{t('commands.factory_reset')}</MenuItem>
|
||||
<MenuItem onClick={handleOpenUpgrade}>{t('commands.firmware_upgrade')}</MenuItem>
|
||||
<MenuItem onClick={handleRebootClick} hidden={!isCompact}>
|
||||
{t('commands.reboot')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleOpenTelemetry}>{t('controller.telemetry.title')}</MenuItem>
|
||||
<MenuItem onClick={handleOpenScript}>{t('script.one')}</MenuItem>
|
||||
<MenuItem onClick={handleOpenTrace}>{t('controller.devices.trace')}</MenuItem>
|
||||
<MenuItem onClick={handleUpdateToLatest} hidden>
|
||||
{t('premium.toolbox.upgrade_to_latest')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleOpenScan} hidden={!isCompact || deviceType !== 'ap'}>
|
||||
{t('commands.wifiscan')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Portal>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { IconButton, Button, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||
import { Pen } from 'phosphor-react';
|
||||
import { Pen } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface EditButtonProps {
|
||||
onClick: () => void;
|
||||
@@ -11,7 +12,15 @@ export interface EditButtonProps {
|
||||
ml?: string | number;
|
||||
}
|
||||
|
||||
const _EditButton: React.FC<EditButtonProps> = ({ onClick, label, isDisabled, isLoading, isCompact, ...props }) => {
|
||||
const _EditButton: React.FC<EditButtonProps> = ({
|
||||
onClick,
|
||||
label,
|
||||
isDisabled,
|
||||
isLoading,
|
||||
isCompact = true,
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const breakpoint = useBreakpoint();
|
||||
|
||||
if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') {
|
||||
@@ -24,12 +33,12 @@ const _EditButton: React.FC<EditButtonProps> = ({ onClick, label, isDisabled, is
|
||||
isDisabled={isDisabled}
|
||||
{...props}
|
||||
>
|
||||
{label}
|
||||
{label ?? t('common.edit')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Tooltip label={label}>
|
||||
<Tooltip label={label ?? t('common.edit')} hasArrow>
|
||||
<IconButton
|
||||
aria-label="edit"
|
||||
colorScheme="gray"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Button, IconButton, ThemeTypings, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||
import { ArrowsClockwise } from 'phosphor-react';
|
||||
import { ArrowsClockwise } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface RefreshButtonProps {
|
||||
@@ -17,7 +17,7 @@ const _RefreshButton: React.FC<RefreshButtonProps> = ({
|
||||
onClick,
|
||||
isDisabled,
|
||||
isFetching,
|
||||
isCompact,
|
||||
isCompact = true,
|
||||
ml,
|
||||
size,
|
||||
...props
|
||||
|
||||
@@ -15,7 +15,7 @@ const _ResponsiveButton: React.FC<ResponsiveButtonProps> = ({
|
||||
onClick,
|
||||
isDisabled,
|
||||
isLoading,
|
||||
isCompact,
|
||||
isCompact = true,
|
||||
color,
|
||||
label,
|
||||
icon,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||
import { FloppyDisk } from 'phosphor-react';
|
||||
import { FloppyDisk } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface SaveButtonProps
|
||||
@@ -18,7 +18,7 @@ const _SaveButton: React.FC<SaveButtonProps> = ({
|
||||
onClick,
|
||||
isDisabled,
|
||||
isLoading,
|
||||
isCompact,
|
||||
isCompact = true,
|
||||
isDirty,
|
||||
dirtyCheck,
|
||||
...props
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||
import { ArrowRight, FloppyDisk } from 'phosphor-react';
|
||||
import { ArrowRight, FloppyDisk } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface StepButtonProps {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Button, IconButton, Tooltip, useBreakpoint, useDisclosure } from '@chakra-ui/react';
|
||||
import { Pencil, X } from 'phosphor-react';
|
||||
import { Pencil, Stop } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ConfirmCloseAlertModal } from '../../Modals/ConfirmCloseAlert';
|
||||
|
||||
@@ -20,7 +20,7 @@ const _ToggleEditButton: React.FC<ToggleEditButtonProps> = ({
|
||||
isDirty,
|
||||
isDisabled,
|
||||
isLoading,
|
||||
isCompact,
|
||||
isCompact = true,
|
||||
ml,
|
||||
...props
|
||||
}) => {
|
||||
@@ -48,7 +48,7 @@ const _ToggleEditButton: React.FC<ToggleEditButtonProps> = ({
|
||||
colorScheme="gray"
|
||||
type="button"
|
||||
onClick={toggle}
|
||||
rightIcon={isEditing ? <X size={20} /> : <Pencil size={20} />}
|
||||
rightIcon={isEditing ? <Stop size={20} /> : <Pencil size={20} />}
|
||||
isLoading={isLoading}
|
||||
isDisabled={isDisabled}
|
||||
ml={ml}
|
||||
@@ -68,7 +68,7 @@ const _ToggleEditButton: React.FC<ToggleEditButtonProps> = ({
|
||||
colorScheme="gray"
|
||||
type="button"
|
||||
onClick={toggle}
|
||||
icon={isEditing ? <X size={20} /> : <Pencil size={20} />}
|
||||
icon={isEditing ? <Stop size={20} /> : <Pencil size={20} />}
|
||||
isLoading={isLoading}
|
||||
isDisabled={isDisabled}
|
||||
ml={ml}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||
import { Warning } from 'phosphor-react';
|
||||
import { Warning } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ThemeProps } from 'models/Theme';
|
||||
|
||||
@@ -16,7 +16,7 @@ const _WarningButton: React.FC<WarningButtonProps> = ({
|
||||
onClick,
|
||||
isDisabled,
|
||||
isLoading,
|
||||
isCompact,
|
||||
isCompact = true,
|
||||
label,
|
||||
...props
|
||||
}) => {
|
||||
|
||||
@@ -1,17 +1,57 @@
|
||||
import React from 'react';
|
||||
import { Box, LayoutProps, SpaceProps, useStyleConfig } from '@chakra-ui/react';
|
||||
import React, { DOMAttributes } from 'react';
|
||||
import {
|
||||
BackgroundProps,
|
||||
Box,
|
||||
EffectProps,
|
||||
InteractivityProps,
|
||||
LayoutProps,
|
||||
PositionProps,
|
||||
SpaceProps,
|
||||
useColorModeValue,
|
||||
useStyleConfig,
|
||||
useToken,
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
export interface CardHeaderProps extends LayoutProps, SpaceProps {
|
||||
variant?: string;
|
||||
export interface CardHeaderProps
|
||||
extends LayoutProps,
|
||||
SpaceProps,
|
||||
BackgroundProps,
|
||||
InteractivityProps,
|
||||
PositionProps,
|
||||
EffectProps,
|
||||
DOMAttributes<HTMLDivElement> {
|
||||
variant?: 'panel' | 'unstyled';
|
||||
children: React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
headerStyle?: {
|
||||
color: string;
|
||||
};
|
||||
}
|
||||
|
||||
const _CardHeader: React.FC<CardHeaderProps> = ({ variant, children, ...rest }) => {
|
||||
// @ts-ignore
|
||||
const _CardHeader: React.FC<CardHeaderProps> = ({
|
||||
variant,
|
||||
children,
|
||||
icon,
|
||||
headerStyle = {
|
||||
color: 'blue',
|
||||
},
|
||||
...rest
|
||||
}) => {
|
||||
const iconBgcolor = useToken('colors', [`${headerStyle?.color}.500`, `${headerStyle?.color}.300`]);
|
||||
const bgColor = useToken('colors', [`${headerStyle?.color}.50`, `${headerStyle?.color}.700`]);
|
||||
const iconColor = useColorModeValue(iconBgcolor[0], iconBgcolor[1]);
|
||||
const headerBgColor = useColorModeValue(bgColor[0], bgColor[1]);
|
||||
|
||||
const styles = useStyleConfig('CardHeader', { variant });
|
||||
|
||||
// Pass the computed styles into the `__css` prop
|
||||
return (
|
||||
<Box __css={styles} {...rest}>
|
||||
<Box __css={styles} bgColor={variant === 'unstyled' ? undefined : headerBgColor} {...rest}>
|
||||
{icon ? (
|
||||
<Box mr={2} color={headerStyle ? iconColor : undefined} bgColor="unset">
|
||||
{icon}
|
||||
</Box>
|
||||
) : null}
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
BackgroundProps,
|
||||
Box,
|
||||
EffectProps,
|
||||
InteractivityProps,
|
||||
LayoutProps,
|
||||
PositionProps,
|
||||
SpaceProps,
|
||||
useStyleConfig,
|
||||
} from '@chakra-ui/react';
|
||||
import { BackgroundProps, Box, InteractivityProps, LayoutProps, SpaceProps, useStyleConfig } from '@chakra-ui/react';
|
||||
|
||||
export interface CardProps
|
||||
extends LayoutProps,
|
||||
SpaceProps,
|
||||
BackgroundProps,
|
||||
InteractivityProps,
|
||||
PositionProps,
|
||||
EffectProps {
|
||||
export interface CardProps extends LayoutProps, SpaceProps, BackgroundProps, InteractivityProps {
|
||||
variant?: string;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Box, Heading, IconButton, Spacer, Tooltip, useDisclosure } from '@chakra-ui/react';
|
||||
import { ArrowsOut, Info } from 'phosphor-react';
|
||||
import { ArrowsOut, Info } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card } from '../Card';
|
||||
import { CardBody } from '../Card/CardBody';
|
||||
@@ -18,7 +18,7 @@ const GraphStatDisplay = ({ chart, title, explanation }: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card variant="widget" w="100%">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Heading mr={2} my="auto" size="md">
|
||||
{title}
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Flex, LayoutProps, ModalHeader as Header, SpaceProps, Spacer } from '@chakra-ui/react';
|
||||
import { HStack, ModalHeader as Header, Spacer, useColorModeValue } from '@chakra-ui/react';
|
||||
|
||||
export interface ModalHeaderProps extends LayoutProps, SpaceProps {
|
||||
export interface ModalHeaderProps {
|
||||
title: string;
|
||||
right?: React.ReactNode;
|
||||
left?: React.ReactNode;
|
||||
right: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ModalHeader = ({ title, right }: ModalHeaderProps) => (
|
||||
<Header>
|
||||
<Flex justifyContent="center" alignItems="center" maxW="100%" px={1}>
|
||||
const _ModalHeader: React.FC<ModalHeaderProps> = ({ title, left, right }) => {
|
||||
const bg = useColorModeValue('blue.50', 'blue.700');
|
||||
|
||||
return (
|
||||
<Header bg={bg}>
|
||||
{title}
|
||||
{left ? (
|
||||
<HStack spacing={2} ml={2}>
|
||||
{left}
|
||||
</HStack>
|
||||
) : null}
|
||||
<Spacer />
|
||||
{right}
|
||||
</Flex>
|
||||
</Header>
|
||||
);
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
export const ModalHeader = React.memo(_ModalHeader);
|
||||
|
||||
34
src/components/Containers/ResponsiveTag/index.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import { As, Icon, Tag, TagLabel, TagLeftIcon, TagProps, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||
|
||||
export interface ResponsiveTagProps extends TagProps {
|
||||
label: string;
|
||||
icon: As<any>;
|
||||
tooltip?: string;
|
||||
isCompact?: boolean;
|
||||
}
|
||||
|
||||
export const ResponsiveTag = React.memo(({ label, icon, tooltip, isCompact, ...props }: ResponsiveTagProps) => {
|
||||
const breakpoint = useBreakpoint();
|
||||
|
||||
const isCompactVersion = isCompact || breakpoint === 'base' || breakpoint === 'sm';
|
||||
|
||||
if (isCompactVersion) {
|
||||
return (
|
||||
<Tooltip label={tooltip ?? label}>
|
||||
<Tag size="lg" colorScheme="blue" {...props}>
|
||||
<Icon as={icon} boxSize="18px" />
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip label={tooltip ?? label}>
|
||||
<Tag size="lg" colorScheme="blue" {...props}>
|
||||
<TagLeftIcon boxSize="18px" as={icon} mt={-0.5} />
|
||||
<TagLabel>{label}</TagLabel>
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { As, Flex, Heading, Icon, Spacer, Text, Tooltip, useColorModeValue } from '@chakra-ui/react';
|
||||
import { Info } from 'phosphor-react';
|
||||
import { Info } from '@phosphor-icons/react';
|
||||
import { Card } from '../Card';
|
||||
|
||||
type Props = {
|
||||
@@ -15,19 +15,19 @@ const SimpleIconStatDisplay = ({ title, description, icon, value, color }: Props
|
||||
const bgColor = useColorModeValue(color[0], color[1]);
|
||||
|
||||
return (
|
||||
<Card variant="widget" w="100%" p={3}>
|
||||
<Flex h="70px" w="100%">
|
||||
<Card p={3} bgColor={bgColor}>
|
||||
<Flex h="70px" w="100%" color="white">
|
||||
<Flex direction="column" justifyContent="center">
|
||||
<Heading size="lg">{value}</Heading>
|
||||
<Heading size="sm" display="flex">
|
||||
<Text opacity={0.8}>{title}</Text>
|
||||
<Text>{title}</Text>
|
||||
<Tooltip label={description} hasArrow>
|
||||
<Info style={{ marginLeft: '4px', marginTop: '2px' }} />
|
||||
</Tooltip>
|
||||
</Heading>
|
||||
</Flex>
|
||||
<Spacer />
|
||||
<Icon borderRadius="15px" my="auto" as={icon} boxSize="70px" bgColor={bgColor} color="white" />
|
||||
<Icon borderRadius="15px" my="auto" as={icon} boxSize="70px" color="white" />
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Button, Checkbox, IconButton, Menu, MenuButton, MenuItem, MenuList, useBreakpoint } from '@chakra-ui/react';
|
||||
import { FunnelSimple } from 'phosphor-react';
|
||||
import { FunnelSimple } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
@@ -21,7 +21,7 @@ export const ColumnPicker = ({
|
||||
hiddenColumns,
|
||||
setHiddenColumns,
|
||||
size,
|
||||
isCompact,
|
||||
isCompact = true,
|
||||
}: ColumnPickerProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { getPref, setPref } = useAuth();
|
||||
|
||||
64
src/components/DataTables/DataGrid/CellRow.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as React from 'react';
|
||||
import { Td, Tr } from '@chakra-ui/react';
|
||||
import { Row, flexRender } from '@tanstack/react-table';
|
||||
|
||||
export type DataGridCellRowProps<TValue extends object> = {
|
||||
row: Row<TValue>;
|
||||
onRowClick: ((row: TValue) => (() => void) | undefined) | undefined;
|
||||
rowStyle: {
|
||||
hoveredRowBg: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const DataGridCellRow = <TValue extends object>({
|
||||
row,
|
||||
rowStyle: { hoveredRowBg },
|
||||
onRowClick,
|
||||
}: DataGridCellRowProps<TValue>) => {
|
||||
const onClick = onRowClick ? onRowClick(row.original) : undefined;
|
||||
|
||||
return (
|
||||
<Tr
|
||||
key={row.id}
|
||||
_hover={{
|
||||
backgroundColor: hoveredRowBg,
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<Td
|
||||
px={1}
|
||||
key={cell.id}
|
||||
textOverflow="ellipsis"
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
minWidth={cell.column.columnDef.meta?.customMinWidth ?? undefined}
|
||||
maxWidth={cell.column.columnDef.meta?.customMaxWidth ?? undefined}
|
||||
width={cell.column.columnDef.meta?.customWidth}
|
||||
textAlign={cell.column.columnDef.meta?.isCentered ? 'center' : undefined}
|
||||
fontFamily={
|
||||
cell.column.columnDef.meta?.isMonospace
|
||||
? 'Inter, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
|
||||
: undefined
|
||||
}
|
||||
onClick={
|
||||
cell.column.columnDef.meta?.stopPropagation || (cell.column.id === 'actions' && onClick)
|
||||
? (e) => {
|
||||
e.stopPropagation();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
cursor={
|
||||
!cell.column.columnDef.meta?.stopPropagation && cell.column.id !== 'actions' && onClick
|
||||
? 'pointer'
|
||||
: undefined
|
||||
}
|
||||
border="0.5px solid gray"
|
||||
style={cell.column.columnDef.meta?.rowContentOptions?.style}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</Td>
|
||||
))}
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
54
src/components/DataTables/DataGrid/DataGridColumnPicker.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { Box, Checkbox, IconButton, Menu, MenuButton, MenuItem, MenuList, Tooltip } from '@chakra-ui/react';
|
||||
import { FunnelSimple } from '@phosphor-icons/react';
|
||||
import { VisibilityState } from '@tanstack/react-table';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { DataGridColumn } from './useDataGrid';
|
||||
|
||||
export type DataGridColumnPickerProps<TValue extends object> = {
|
||||
columns: DataGridColumn<TValue>[];
|
||||
columnVisibility: VisibilityState;
|
||||
toggleVisibility: (id: string) => void;
|
||||
};
|
||||
|
||||
export const DataGridColumnPicker = <TValue extends object>({
|
||||
columns,
|
||||
columnVisibility,
|
||||
toggleVisibility,
|
||||
}: DataGridColumnPickerProps<TValue>) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Menu closeOnSelect={false} isLazy>
|
||||
<Tooltip label={t('common.columns')} hasArrow>
|
||||
<MenuButton as={IconButton} icon={<FunnelSimple />} />
|
||||
</Tooltip>
|
||||
<MenuList maxH="200px" overflowY="auto">
|
||||
{columns
|
||||
.filter((col) => col.id && col.header)
|
||||
.map((column) => {
|
||||
const handleClick =
|
||||
column.id !== undefined ? () => toggleVisibility(column.id as unknown as string) : undefined;
|
||||
const id = column.id ?? uuid();
|
||||
let label = column.header?.toString() ?? 'Unrecognized column';
|
||||
if (column.meta?.columnSelectorOptions?.label) label = column.meta.columnSelectorOptions.label;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={id}
|
||||
as={Checkbox}
|
||||
isChecked={columnVisibility[id] === undefined || columnVisibility[id]}
|
||||
onChange={column.meta?.alwaysShow ? undefined : handleClick}
|
||||
isDisabled={column.meta?.alwaysShow}
|
||||
>
|
||||
{label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
45
src/components/DataTables/DataGrid/HeaderRow.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import * as React from 'react';
|
||||
import { Box, Flex, Th, Tooltip, Tr } from '@chakra-ui/react';
|
||||
import { HeaderGroup, flexRender } from '@tanstack/react-table';
|
||||
import { DataGridSortIcon } from './SortIcon';
|
||||
|
||||
export type DataGridHeaderRowProps<TValue extends object> = {
|
||||
headerGroup: HeaderGroup<TValue>;
|
||||
};
|
||||
|
||||
export const DataGridHeaderRow = <TValue extends object>({ headerGroup }: DataGridHeaderRowProps<TValue>) => (
|
||||
<Tr p={0}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<Th
|
||||
color="gray.400"
|
||||
key={header.id}
|
||||
colSpan={header.colSpan}
|
||||
minWidth={header.column.columnDef.meta?.customMinWidth ?? undefined}
|
||||
maxWidth={header.column.columnDef.meta?.customMaxWidth ?? undefined}
|
||||
width={header.column.columnDef.meta?.customWidth}
|
||||
fontSize="sm"
|
||||
onClick={header.column.getCanSort() ? header.column.getToggleSortingHandler() : undefined}
|
||||
cursor={header.column.getCanSort() ? 'pointer' : undefined}
|
||||
border="0.5px solid gray"
|
||||
px={1}
|
||||
>
|
||||
<Flex display="flex" alignItems="center">
|
||||
{header.isPlaceholder ? null : (
|
||||
<Tooltip label={header.column.columnDef.meta?.headerOptions?.tooltip}>
|
||||
<Box
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
alignContent="center"
|
||||
width="100%"
|
||||
{...header.column.columnDef.meta?.headerStyleProps}
|
||||
>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
<DataGridSortIcon sortInfo={header.column.getIsSorted()} canSort={header.column.getCanSort()} />
|
||||
</Flex>
|
||||
</Th>
|
||||
))}
|
||||
</Tr>
|
||||
);
|
||||
124
src/components/DataTables/DataGrid/Input.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import * as React from 'react';
|
||||
import { ArrowLeftIcon, ArrowRightIcon, ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
Tooltip,
|
||||
Flex,
|
||||
IconButton,
|
||||
Text,
|
||||
Select,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper,
|
||||
} from '@chakra-ui/react';
|
||||
import { Table } from '@tanstack/react-table';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { useContainerDimensions } from 'hooks/useContainerDimensions';
|
||||
|
||||
type Props<T extends object> = {
|
||||
table: Table<T>;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
const DataGridControls = <T extends object>({ table, isDisabled }: Props<T>) => {
|
||||
const { t } = useTranslation();
|
||||
const { ref, dimensions } = useContainerDimensions({ precision: 100 });
|
||||
const isCompact = dimensions.width !== 0 && dimensions.width <= 800;
|
||||
|
||||
return (
|
||||
<Flex ref={ref} justifyContent="space-between" m={4} alignItems="center">
|
||||
<Flex>
|
||||
<Tooltip label={t('table.first_page')}>
|
||||
<IconButton
|
||||
aria-label="Go to first page"
|
||||
onClick={() => table.setPageIndex(0)}
|
||||
isDisabled={isDisabled || !table.getCanPreviousPage()}
|
||||
icon={<ArrowLeftIcon h={3} w={3} />}
|
||||
mr={4}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label={t('table.previous_page')}>
|
||||
<IconButton
|
||||
aria-label="Previous page"
|
||||
onClick={() => table.previousPage()}
|
||||
isDisabled={isDisabled || !table.getCanPreviousPage()}
|
||||
icon={<ChevronLeftIcon h={6} w={6} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
|
||||
<Flex alignItems="center">
|
||||
{isCompact ? null : (
|
||||
<>
|
||||
<Text flexShrink={0} mr={8}>
|
||||
{t('table.page')}{' '}
|
||||
<Text fontWeight="bold" as="span">
|
||||
{table.getState().pagination.pageIndex + 1}
|
||||
</Text>{' '}
|
||||
{t('common.of')}{' '}
|
||||
<Text fontWeight="bold" as="span">
|
||||
{table.getPageCount()}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text flexShrink={0}>{t('table.go_to_page')}</Text>{' '}
|
||||
<NumberInput
|
||||
ml={2}
|
||||
mr={8}
|
||||
w={28}
|
||||
min={1}
|
||||
max={table.getPageCount()}
|
||||
onChange={(_, numberValue) => {
|
||||
const newPage = numberValue ? numberValue - 1 : 0;
|
||||
table.setPageIndex(newPage);
|
||||
}}
|
||||
value={table.getState().pagination.pageIndex + 1}
|
||||
>
|
||||
<NumberInputField />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
</>
|
||||
)}
|
||||
<Select
|
||||
w={32}
|
||||
value={table.getState().pagination.pageSize}
|
||||
onChange={(e) => {
|
||||
table.setPageSize(Number(e.target.value));
|
||||
}}
|
||||
>
|
||||
{[10, 20, 30, 40, 50].map((opt) => (
|
||||
<option key={uuid()} value={opt}>
|
||||
{t('common.show')} {opt}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Flex>
|
||||
|
||||
<Flex>
|
||||
<Tooltip label={t('table.next_page')}>
|
||||
<IconButton
|
||||
aria-label="Go to next page"
|
||||
onClick={() => table.nextPage()}
|
||||
isDisabled={isDisabled || !table.getCanNextPage()}
|
||||
icon={<ChevronRightIcon h={6} w={6} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label={t('table.last_page')}>
|
||||
<IconButton
|
||||
aria-label="Go to last page"
|
||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
isDisabled={isDisabled || !table.getCanNextPage()}
|
||||
icon={<ArrowRightIcon h={3} w={3} />}
|
||||
ml={4}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataGridControls;
|
||||
23
src/components/DataTables/DataGrid/SortIcon.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@chakra-ui/react';
|
||||
import { ArrowDown, ArrowUp, Circle } from '@phosphor-icons/react';
|
||||
import { SortDirection } from '@tanstack/react-table';
|
||||
|
||||
export type DataGridSortIconProps = {
|
||||
sortInfo: false | SortDirection;
|
||||
canSort: boolean;
|
||||
};
|
||||
|
||||
export const DataGridSortIcon = ({ sortInfo, canSort }: DataGridSortIconProps) => {
|
||||
if (canSort) {
|
||||
if (sortInfo) {
|
||||
return sortInfo === 'desc' ? (
|
||||
<Icon ml={1} boxSize={3} as={ArrowDown} />
|
||||
) : (
|
||||
<Icon ml={1} boxSize={3} as={ArrowUp} />
|
||||
);
|
||||
}
|
||||
return <Icon ml={1} boxSize={3} as={Circle} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import * as React from 'react';
|
||||
import { Box, Icon, Text, Tooltip, useColorModeValue } from '@chakra-ui/react';
|
||||
import { Draggable } from '@hello-pangea/dnd';
|
||||
import { ArrowsDownUp, Lock } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DataGridColumn } from '../../useDataGrid';
|
||||
|
||||
type Props<TValue> = {
|
||||
draggableId: string;
|
||||
index: number;
|
||||
column: DataGridColumn<TValue>;
|
||||
};
|
||||
|
||||
const DraggableColumn = <TValue extends object>({ draggableId, index, column }: Props<TValue>) => {
|
||||
const { t } = useTranslation();
|
||||
const isDraggingBackground = useColorModeValue('blue.100', 'blue.600');
|
||||
const notDraggingBackground = useColorModeValue('gray.50', 'gray.700');
|
||||
|
||||
let label = column.header?.toString() ?? 'Unrecognized column';
|
||||
if (column.meta?.columnSelectorOptions?.label) label = column.meta.columnSelectorOptions.label;
|
||||
|
||||
const tooltipLabel = () => {
|
||||
if (column.meta?.anchored) return t('table.drag_locked');
|
||||
if (column.meta?.alwaysShow) return t('table.drag_always_show');
|
||||
|
||||
return t('table.drag_explanation');
|
||||
};
|
||||
|
||||
return (
|
||||
<Draggable draggableId={draggableId} index={index} isDragDisabled={column.meta?.anchored}>
|
||||
{(itemProvided, itemSnapshot) => (
|
||||
<Tooltip label={tooltipLabel()}>
|
||||
<Box
|
||||
ref={itemProvided.innerRef}
|
||||
{...itemProvided.draggableProps}
|
||||
{...itemProvided.dragHandleProps}
|
||||
display="flex"
|
||||
backgroundColor={itemSnapshot.isDragging ? isDraggingBackground : notDraggingBackground}
|
||||
px={6}
|
||||
py={2}
|
||||
my={2}
|
||||
borderRadius={15}
|
||||
cursor={column.meta?.anchored ? 'not-allowed' : undefined}
|
||||
>
|
||||
<Icon as={column.meta?.anchored ? Lock : ArrowsDownUp} boxSize={5} ml={0.5} mr={2} my="auto" />
|
||||
<Text my="auto">{label}</Text>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
export default DraggableColumn;
|
||||
@@ -0,0 +1,37 @@
|
||||
import * as React from 'react';
|
||||
import { Box, useColorModeValue } from '@chakra-ui/react';
|
||||
import { Droppable } from '@hello-pangea/dnd';
|
||||
import { DataGridColumn } from '../../useDataGrid';
|
||||
import DraggableColumn from './DraggableColumn';
|
||||
|
||||
type Props<TValue> = {
|
||||
items: string[];
|
||||
columns: DataGridColumn<TValue>[];
|
||||
droppableId: string;
|
||||
isDropDisabled?: boolean;
|
||||
};
|
||||
const DroppableBox = <TValue extends object>({ items, columns, droppableId, isDropDisabled }: Props<TValue>) => {
|
||||
const notDraggingBackground = useColorModeValue('gray.200', 'gray.600');
|
||||
const isDraggingOverBackground = useColorModeValue('blue.300', 'blue.500');
|
||||
|
||||
return (
|
||||
<Droppable droppableId={droppableId} direction="vertical" isCombineEnabled={false} isDropDisabled={isDropDisabled}>
|
||||
{(provided, snapshot) => (
|
||||
<Box
|
||||
ref={provided.innerRef}
|
||||
backgroundColor={snapshot.isDraggingOver ? isDraggingOverBackground : notDraggingBackground}
|
||||
padding={2}
|
||||
borderRadius={15}
|
||||
>
|
||||
{items.map((item, index) => {
|
||||
const found = columns.find((col) => col.id === item);
|
||||
return found ? <DraggableColumn key={item} draggableId={item} index={index} column={found} /> : null;
|
||||
})}
|
||||
{provided.placeholder}
|
||||
</Box>
|
||||
)}
|
||||
</Droppable>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(DroppableBox);
|
||||
@@ -0,0 +1,129 @@
|
||||
import * as React from 'react';
|
||||
import { Box, Flex, Heading } from '@chakra-ui/react';
|
||||
import { DragDropContext, DragStart, DropResult } from '@hello-pangea/dnd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DataGridColumn, UseDataGridReturn } from '../../useDataGrid';
|
||||
import DroppableBox from './DroppableBox';
|
||||
|
||||
const reorder = (list: string[], startIndex: number, endIndex: number) => {
|
||||
const result = Array.from(list);
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
if (removed) {
|
||||
result.splice(endIndex, 0, removed);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const getShownColumns = <TValue extends object>(columns: DataGridColumn<TValue>[], columnOrder: string[]) => {
|
||||
const order = [...columnOrder];
|
||||
|
||||
for (const col of columns) {
|
||||
if (!order.includes(col.id)) {
|
||||
order.push(col.id);
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
|
||||
type Props<TValue extends object> = {
|
||||
controller: UseDataGridReturn;
|
||||
shownColumns: DataGridColumn<TValue>[];
|
||||
hiddenColumns: DataGridColumn<TValue>[];
|
||||
};
|
||||
|
||||
const TableDragDrop = <TValue extends object>({ controller, shownColumns, hiddenColumns }: Props<TValue>) => {
|
||||
const { t } = useTranslation();
|
||||
const [shownOrder, setShowOrder] = React.useState(getShownColumns(shownColumns, controller.columnOrder));
|
||||
const [hiddenOrder, setHiddenOrder] = React.useState(hiddenColumns.map((col) => col.id));
|
||||
const [currentDraggingColumn, setCurrentDraggingColumn] = React.useState<DataGridColumn<TValue>>();
|
||||
|
||||
const handleDragStart = React.useCallback(
|
||||
(start: DragStart) => {
|
||||
const foundColumn =
|
||||
shownColumns.find(({ id }) => id === start.draggableId) ??
|
||||
hiddenColumns.find(({ id }) => id === start.draggableId);
|
||||
setCurrentDraggingColumn(foundColumn);
|
||||
},
|
||||
[shownColumns, hiddenColumns],
|
||||
);
|
||||
|
||||
const minimumIndex = React.useMemo(() => {
|
||||
let index = 0;
|
||||
for (const [i, col] of shownColumns.entries()) {
|
||||
if (col.meta?.anchored) {
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
return index + 1;
|
||||
}, [shownColumns]);
|
||||
|
||||
const handleDragEnd = React.useCallback(
|
||||
(result: DropResult) => {
|
||||
const { source, destination, draggableId } = result;
|
||||
|
||||
if (destination === null) return;
|
||||
|
||||
if (source.droppableId === destination.droppableId) {
|
||||
const newOrder = reorder(shownOrder, source.index, Math.max(destination.index, minimumIndex));
|
||||
if (destination.droppableId === 'displayed-columns') {
|
||||
controller.setColumnOrder(newOrder);
|
||||
setShowOrder(newOrder);
|
||||
} else setHiddenOrder(newOrder);
|
||||
}
|
||||
// This means we are moving from displayed to hidden
|
||||
else if (source.droppableId === 'displayed-columns') {
|
||||
// Toggle the column visibility in user preferences
|
||||
const results = controller.hideColumn(draggableId);
|
||||
if (results) {
|
||||
setHiddenOrder([...results.hiddenColumns]);
|
||||
setShowOrder([...results.columnOrder]);
|
||||
}
|
||||
}
|
||||
// This means we are moving from hidden to displayed
|
||||
else if (source.droppableId === 'hidden-columns') {
|
||||
const newOrder = Array.from(shownOrder);
|
||||
newOrder.splice(Math.max(destination.index, minimumIndex), 0, draggableId);
|
||||
const results = controller.unhideColumn(draggableId, newOrder);
|
||||
if (results) {
|
||||
setHiddenOrder(results.hiddenColumns);
|
||||
setShowOrder([...results.columnOrder]);
|
||||
setHiddenOrder([...results.hiddenColumns]);
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentDraggingColumn(undefined);
|
||||
},
|
||||
[shownColumns, hiddenColumns, controller.hideColumn, controller.unhideColumn, minimumIndex],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading size="md">{t('table.columns')}</Heading>
|
||||
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||
<Flex mt={4}>
|
||||
<Box w="50%" mr={2}>
|
||||
<Heading size="sm" mb={4}>
|
||||
Visible ({shownOrder.length})
|
||||
</Heading>
|
||||
<DroppableBox droppableId="displayed-columns" items={shownOrder} columns={shownColumns} />
|
||||
</Box>
|
||||
<Box ml={2} w="50%">
|
||||
<Heading size="sm" mb={4}>
|
||||
Hidden ({hiddenColumns.length})
|
||||
</Heading>
|
||||
<DroppableBox
|
||||
droppableId="hidden-columns"
|
||||
items={hiddenOrder}
|
||||
columns={hiddenColumns}
|
||||
isDropDisabled={currentDraggingColumn?.meta?.alwaysShow}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</DragDropContext>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableDragDrop;
|
||||
@@ -0,0 +1,57 @@
|
||||
import * as React from 'react';
|
||||
import { SettingsIcon } from '@chakra-ui/icons';
|
||||
import { Box, IconButton, Tooltip, useDisclosure } from '@chakra-ui/react';
|
||||
import { ClockCounterClockwise } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Modal } from '../../../Modals/Modal';
|
||||
import { DataGridColumn, UseDataGridReturn } from '../useDataGrid';
|
||||
import TableDragDrop from './DragDrop';
|
||||
|
||||
type Props<TValue extends object> = {
|
||||
controller: UseDataGridReturn;
|
||||
columns: DataGridColumn<TValue>[];
|
||||
};
|
||||
|
||||
const TableSettingsModal = <TValue extends object>({ controller, columns }: Props<TValue>) => {
|
||||
const { t } = useTranslation();
|
||||
const modalProps = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip label={t('table.preferences')}>
|
||||
<IconButton
|
||||
aria-label={t('table.preferences')}
|
||||
icon={<SettingsIcon weight="bold" />}
|
||||
onClick={modalProps.onOpen}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Modal
|
||||
title={t('table.preferences')}
|
||||
topRightButtons={
|
||||
<Tooltip label={t('table.reset')}>
|
||||
<IconButton
|
||||
aria-label={t('table.reset')}
|
||||
icon={<ClockCounterClockwise size={20} />}
|
||||
onClick={controller.resetPreferences}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
options={{
|
||||
modalSize: 'md',
|
||||
maxWidth: { sm: '600px', md: '600px', lg: '600px', xl: '600px' },
|
||||
}}
|
||||
{...modalProps}
|
||||
>
|
||||
<Box w="100%">
|
||||
<TableDragDrop<TValue>
|
||||
shownColumns={columns.filter((col) => controller.columnVisibility[col.id] !== false)}
|
||||
hiddenColumns={columns.filter((col) => controller.columnVisibility[col.id] === false)}
|
||||
controller={controller}
|
||||
/>
|
||||
</Box>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TableSettingsModal);
|
||||
273
src/components/DataTables/DataGrid/index.tsx
Normal file
@@ -0,0 +1,273 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Flex,
|
||||
HStack,
|
||||
Heading,
|
||||
LayoutProps,
|
||||
Spacer,
|
||||
Spinner,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Thead,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
import { getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RefreshButton } from '../../Buttons/RefreshButton';
|
||||
import { DataGridCellRow } from './CellRow';
|
||||
import { DataGridHeaderRow } from './HeaderRow';
|
||||
import DataGridControls from './Input';
|
||||
import TableSettingsModal from './TableSettingsModal';
|
||||
import { DataGridColumn, UseDataGridReturn } from './useDataGrid';
|
||||
import { Card } from 'components/Containers/Card';
|
||||
import { CardBody } from 'components/Containers/Card/CardBody';
|
||||
import { CardHeader } from 'components/Containers/Card/CardHeader';
|
||||
import { LoadingOverlay } from 'components/LoadingOverlay';
|
||||
|
||||
export type ColumnOptions = {
|
||||
isSortable?: boolean;
|
||||
};
|
||||
|
||||
export type DataGridOptions<TValue extends object> = {
|
||||
count?: number;
|
||||
isFullScreen?: boolean;
|
||||
isHidingControls?: boolean;
|
||||
isManual?: boolean;
|
||||
minimumHeight?: LayoutProps['minH'];
|
||||
onRowClick?: (row: TValue) => (() => void) | undefined;
|
||||
refetch?: () => void;
|
||||
showAsCard?: boolean;
|
||||
hideTablePreferences?: boolean;
|
||||
hideTableTitleRow?: boolean;
|
||||
};
|
||||
|
||||
export type DataGridProps<TValue extends object> = {
|
||||
innerTableKey?: string | number;
|
||||
controller: UseDataGridReturn;
|
||||
columns: DataGridColumn<TValue>[];
|
||||
header: {
|
||||
title: string | React.ReactNode;
|
||||
objectListed: string;
|
||||
leftContent?: React.ReactNode;
|
||||
addButton?: React.ReactNode;
|
||||
otherButtons?: React.ReactNode;
|
||||
};
|
||||
data?: TValue[];
|
||||
isLoading?: boolean;
|
||||
options?: DataGridOptions<TValue>;
|
||||
};
|
||||
|
||||
export const DataGrid = <TValue extends object>({
|
||||
innerTableKey,
|
||||
controller,
|
||||
columns,
|
||||
header,
|
||||
data = [],
|
||||
options = {},
|
||||
isLoading = false,
|
||||
}: DataGridProps<TValue>) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
/*
|
||||
Table Styling
|
||||
*/
|
||||
const textColor = useColorModeValue('gray.700', 'white');
|
||||
const hoveredRowBg = useColorModeValue('gray.100', 'gray.600');
|
||||
|
||||
const minimumHeight: LayoutProps['minH'] = React.useMemo(() => {
|
||||
if (options.isFullScreen) {
|
||||
return { base: 'calc(100vh - 360px)', md: 'calc(100vh - 288px)' };
|
||||
}
|
||||
return options.minimumHeight ?? '300px';
|
||||
}, [options.isFullScreen, options.minimumHeight]);
|
||||
|
||||
/*
|
||||
Table Options
|
||||
*/
|
||||
const onRowClick = React.useMemo(() => options.onRowClick, [options.onRowClick]);
|
||||
|
||||
const pagination = React.useMemo(
|
||||
() => ({
|
||||
pageIndex: controller.pageInfo.pageIndex,
|
||||
pageSize: controller.pageInfo.pageSize,
|
||||
}),
|
||||
[controller.pageInfo.pageIndex, controller.pageInfo.pageSize],
|
||||
);
|
||||
|
||||
const pageCount = React.useMemo(() => {
|
||||
if (options.isManual && options.count) {
|
||||
return Math.ceil(options.count / pagination.pageSize);
|
||||
}
|
||||
return Math.ceil((data?.length ?? 0) / pagination.pageSize);
|
||||
}, [options.count, options.isManual, data?.length, pagination.pageSize]);
|
||||
|
||||
const tableOptions = React.useMemo(
|
||||
() => ({
|
||||
pageCount: pageCount > 0 ? pageCount : 1,
|
||||
initialState: { sorting: controller.sortBy, pagination },
|
||||
manualPagination: options.isManual,
|
||||
manualSorting: options.isManual,
|
||||
autoResetPageIndex: false,
|
||||
}),
|
||||
[options.isManual, controller.sortBy, pageCount],
|
||||
);
|
||||
|
||||
const orderedColumns = React.useMemo(() => {
|
||||
const order = controller.columnOrder.filter((id) => columns.find((col) => col.id === id));
|
||||
if (order.length !== columns.length) {
|
||||
for (const col of columns) {
|
||||
if (!order.includes(col.id)) {
|
||||
order.push(col.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return columns.slice().sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));
|
||||
}, [columns, controller.columnOrder]);
|
||||
|
||||
const table = useReactTable<TValue>({
|
||||
// react-table base functions
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
|
||||
// Table State
|
||||
data,
|
||||
columns: orderedColumns,
|
||||
state: {
|
||||
sorting: controller.sortBy,
|
||||
columnVisibility: controller.columnVisibility,
|
||||
pagination,
|
||||
},
|
||||
|
||||
// Change Handlers
|
||||
onSortingChange: controller.setSortBy,
|
||||
onPaginationChange: controller.onPaginationChange,
|
||||
|
||||
// debugTable: true,
|
||||
|
||||
// Table Options
|
||||
...tableOptions,
|
||||
});
|
||||
|
||||
// If this is a manual DataTable, with a page index that is higher than 0 and higher than the max possible page, we send to index 0
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
options.isManual &&
|
||||
!isLoading &&
|
||||
data &&
|
||||
pagination.pageIndex > 0 &&
|
||||
options.count !== undefined &&
|
||||
Math.ceil(options.count / pagination.pageSize) - 1 < pagination.pageIndex
|
||||
) {
|
||||
controller.onPaginationChange({ pageIndex: 0, pageSize: pagination.pageSize });
|
||||
}
|
||||
}, [options.count, isLoading, pagination, data]);
|
||||
|
||||
if (isLoading && !options.showAsCard && data.length === 0) {
|
||||
return (
|
||||
<Center>
|
||||
<Spinner size="xl" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return options.showAsCard ? (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
{typeof header.title === 'string' ? (
|
||||
<Heading size="md" my="auto" mr={2}>
|
||||
{header.title}
|
||||
</Heading>
|
||||
) : (
|
||||
header.title
|
||||
)}
|
||||
{header.leftContent}
|
||||
<Spacer />
|
||||
<HStack spacing={2}>
|
||||
{header.otherButtons}
|
||||
{header.addButton}
|
||||
{options.hideTablePreferences ? null : (
|
||||
// @ts-ignore
|
||||
<TableSettingsModal<TValue> controller={controller} columns={columns} />
|
||||
)}
|
||||
{options.refetch ? <RefreshButton onClick={options.refetch} isCompact isFetching={isLoading} /> : null}
|
||||
</HStack>
|
||||
</CardHeader>
|
||||
<CardBody display="flex" flexDirection="column">
|
||||
<LoadingOverlay isLoading={isLoading}>
|
||||
<TableContainer minH={minimumHeight}>
|
||||
<Table size="small" variant="simple" textColor={textColor} w="100%" fontSize="14px" key={innerTableKey}>
|
||||
<Thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<DataGridHeaderRow<TValue> key={headerGroup.id} headerGroup={headerGroup} />
|
||||
))}
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<DataGridCellRow<TValue> key={row.id} row={row} onRowClick={onRowClick} rowStyle={{ hoveredRowBg }} />
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{data?.length === 0 ? (
|
||||
<Center mt={8}>
|
||||
<Heading size="md">
|
||||
{header.objectListed
|
||||
? t('common.no_obj_found', { obj: header.objectListed })
|
||||
: t('common.empty_list')}
|
||||
</Heading>
|
||||
</Center>
|
||||
) : null}
|
||||
</TableContainer>
|
||||
</LoadingOverlay>
|
||||
{!options.isHidingControls ? <DataGridControls table={table} isDisabled={isLoading} /> : null}
|
||||
</CardBody>
|
||||
</Card>
|
||||
) : (
|
||||
<Box w="100%">
|
||||
<Flex mb={2} hidden={options.hideTableTitleRow}>
|
||||
<Heading size="md" my="auto" mr={2}>
|
||||
{header.title}
|
||||
</Heading>
|
||||
{header.leftContent}
|
||||
<Spacer />
|
||||
<HStack spacing={2}>
|
||||
{header.otherButtons}
|
||||
{header.addButton}
|
||||
{options.hideTablePreferences ? null : (
|
||||
// @ts-ignore
|
||||
<TableSettingsModal<TValue> controller={controller} columns={columns} />
|
||||
)}
|
||||
{options.refetch ? <RefreshButton onClick={options.refetch} isCompact isFetching={isLoading} /> : null}
|
||||
</HStack>
|
||||
</Flex>
|
||||
<LoadingOverlay isLoading={isLoading}>
|
||||
<TableContainer minH={minimumHeight}>
|
||||
<Table size="small" variant="simple" textColor={textColor} w="100%" fontSize="14px" key={innerTableKey}>
|
||||
<Thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<DataGridHeaderRow<TValue> key={headerGroup.id} headerGroup={headerGroup} />
|
||||
))}
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<DataGridCellRow<TValue> key={row.id} row={row} onRowClick={onRowClick} rowStyle={{ hoveredRowBg }} />
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{data?.length === 0 ? (
|
||||
<Center mt={8}>
|
||||
<Heading size="md">
|
||||
{header.objectListed ? t('common.no_obj_found', { obj: header.objectListed }) : t('common.empty_list')}
|
||||
</Heading>
|
||||
</Center>
|
||||
) : null}
|
||||
</TableContainer>
|
||||
</LoadingOverlay>
|
||||
{!options.isHidingControls ? <DataGridControls table={table} isDisabled={isLoading} /> : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
198
src/components/DataTables/DataGrid/useDataGrid.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import * as React from 'react';
|
||||
import { ColumnDef, PaginationState, SortingColumnDef, SortingState, VisibilityState } from '@tanstack/react-table';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
|
||||
const getDefaultSettings = ({ settings, showAllRows }: { settings?: string; showAllRows?: boolean }) => {
|
||||
if (showAllRows) return { pageSize: 1000, pageIndex: 0 };
|
||||
let limit = 10;
|
||||
let index = 0;
|
||||
|
||||
if (settings) {
|
||||
const savedSizeSetting = localStorage.getItem(settings);
|
||||
if (savedSizeSetting) {
|
||||
try {
|
||||
limit = parseInt(savedSizeSetting, 10);
|
||||
} catch (e) {
|
||||
limit = 10;
|
||||
}
|
||||
}
|
||||
|
||||
const savedPageSetting = localStorage.getItem(`${settings}.page`);
|
||||
if (savedPageSetting) {
|
||||
try {
|
||||
index = parseInt(savedPageSetting, 10);
|
||||
} catch (e) {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pageSize: limit,
|
||||
pageIndex: index,
|
||||
};
|
||||
};
|
||||
|
||||
const getSavedColumnOrder = (defaultValue: string[], settings?: string) => {
|
||||
if (settings) {
|
||||
const savedOrderSetting = localStorage.getItem(`${settings}.order`);
|
||||
if (savedOrderSetting) {
|
||||
try {
|
||||
const savedOrder = JSON.parse(savedOrderSetting);
|
||||
return savedOrder.length > 0 ? savedOrder : defaultValue;
|
||||
} catch (e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
export type DataGridColumn<T> = ColumnDef<T> & SortingColumnDef<T> & { id: string };
|
||||
|
||||
export type UseDataGridProps = {
|
||||
tableSettingsId: string;
|
||||
defaultOrder: string[];
|
||||
defaultSortBy?: SortingState;
|
||||
showAllRows?: boolean;
|
||||
};
|
||||
|
||||
export const useDataGrid = ({ tableSettingsId, defaultSortBy, defaultOrder, showAllRows }: UseDataGridProps) => {
|
||||
const orderSetting = `${tableSettingsId}.order`;
|
||||
const hiddenColumnSetting = `${tableSettingsId}.hiddenColumns`;
|
||||
const pageSetting = `${tableSettingsId}.page`;
|
||||
const { getPref, setPref, setPrefs, deletePref } = useAuth();
|
||||
const [sortBy, setSortBy] = React.useState<SortingState>(defaultSortBy ?? []);
|
||||
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
||||
const [columnOrder, setColumnOrder] = React.useState<string[]>(
|
||||
getSavedColumnOrder(defaultOrder ?? [], tableSettingsId),
|
||||
);
|
||||
const [pageInfo, setPageInfo] = React.useState<PaginationState>(
|
||||
getDefaultSettings({ settings: tableSettingsId, showAllRows }),
|
||||
);
|
||||
const setNewColumnOrder = React.useCallback(
|
||||
(newOrder: string[]) => {
|
||||
setColumnOrder(newOrder);
|
||||
if (tableSettingsId) {
|
||||
localStorage.setItem(orderSetting, JSON.stringify(newOrder));
|
||||
setPref({ preference: orderSetting, value: newOrder.join(',') });
|
||||
}
|
||||
},
|
||||
[setPref],
|
||||
);
|
||||
|
||||
const resetPreferences = React.useCallback(async () => {
|
||||
if (tableSettingsId) {
|
||||
localStorage.removeItem(orderSetting);
|
||||
localStorage.removeItem(hiddenColumnSetting);
|
||||
await deletePref([orderSetting, hiddenColumnSetting]);
|
||||
}
|
||||
|
||||
setColumnOrder(defaultOrder ?? []);
|
||||
setColumnVisibility({});
|
||||
}, [deletePref]);
|
||||
|
||||
const hideColumn = React.useCallback(
|
||||
(id: string) => {
|
||||
const newVisibility = { ...columnVisibility };
|
||||
newVisibility[id] = false;
|
||||
let hiddenColumnsArray = Object.entries(newVisibility)
|
||||
.filter(([, value]) => !value)
|
||||
.map(([key]) => key);
|
||||
hiddenColumnsArray = [...new Set(hiddenColumnsArray)]; // Remove duplicates
|
||||
|
||||
// New column order without hidden columns
|
||||
let filteredColumnOrder = columnOrder.filter((columnId) => !hiddenColumnsArray.includes(columnId));
|
||||
filteredColumnOrder = [...new Set(filteredColumnOrder)]; // Remove duplicates
|
||||
|
||||
setPrefs([
|
||||
{ tag: hiddenColumnSetting, value: hiddenColumnsArray.join(',') },
|
||||
{ tag: orderSetting, value: filteredColumnOrder.join(',') },
|
||||
]);
|
||||
setColumnVisibility({ ...newVisibility });
|
||||
setColumnOrder(filteredColumnOrder);
|
||||
localStorage.setItem(orderSetting, JSON.stringify(filteredColumnOrder));
|
||||
localStorage.setItem(hiddenColumnSetting, hiddenColumnsArray.join(','));
|
||||
|
||||
return {
|
||||
hiddenColumns: hiddenColumnsArray,
|
||||
columnOrder: filteredColumnOrder,
|
||||
};
|
||||
},
|
||||
[columnOrder, columnVisibility, setPrefs],
|
||||
);
|
||||
|
||||
const unhideColumn = React.useCallback(
|
||||
(id: string, newOrder: string[]) => {
|
||||
const newVisibility = { ...columnVisibility };
|
||||
newVisibility[id] = true;
|
||||
let hiddenColumnsArray = Object.entries(newVisibility)
|
||||
.filter(([, value]) => !value)
|
||||
.map(([key]) => key);
|
||||
hiddenColumnsArray = [...new Set(hiddenColumnsArray)]; // Remove duplicates
|
||||
|
||||
const newColumnOrder = [...new Set(newOrder)]; // Remove duplicates
|
||||
|
||||
setPrefs([
|
||||
{ tag: hiddenColumnSetting, value: hiddenColumnsArray.join(',') },
|
||||
{ tag: orderSetting, value: newColumnOrder.join(',') },
|
||||
]);
|
||||
setColumnVisibility({ ...newVisibility });
|
||||
setColumnOrder(newColumnOrder);
|
||||
localStorage.setItem(orderSetting, JSON.stringify(newColumnOrder));
|
||||
localStorage.setItem(hiddenColumnSetting, hiddenColumnsArray.join(','));
|
||||
|
||||
return {
|
||||
hiddenColumns: hiddenColumnsArray,
|
||||
columnOrder: newColumnOrder,
|
||||
};
|
||||
},
|
||||
[columnOrder, columnVisibility, setPrefs],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const savedPrefs = getPref(hiddenColumnSetting);
|
||||
|
||||
if (savedPrefs) {
|
||||
const savedHiddenColumns = savedPrefs.split(',');
|
||||
setColumnVisibility(savedHiddenColumns.reduce((acc, curr) => ({ ...acc, [curr]: false }), {}));
|
||||
} else {
|
||||
setColumnVisibility({});
|
||||
}
|
||||
|
||||
const savedOrderSetting = getPref(orderSetting);
|
||||
|
||||
if (savedOrderSetting) {
|
||||
const savedHiddenColumns = savedOrderSetting.split(',');
|
||||
setColumnOrder(savedHiddenColumns);
|
||||
}
|
||||
}, [tableSettingsId]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (tableSettingsId) {
|
||||
localStorage.setItem(pageSetting, String(pageInfo.pageIndex));
|
||||
if (tableSettingsId) localStorage.setItem(`${tableSettingsId}`, String(pageInfo.pageSize));
|
||||
}
|
||||
}, [pageInfo.pageIndex, pageInfo.pageSize]);
|
||||
|
||||
return React.useMemo(
|
||||
() => ({
|
||||
tableSettingsId,
|
||||
pageInfo,
|
||||
sortBy,
|
||||
setSortBy,
|
||||
columnOrder,
|
||||
setColumnOrder: setNewColumnOrder,
|
||||
hideColumn,
|
||||
unhideColumn,
|
||||
columnVisibility,
|
||||
setColumnVisibility,
|
||||
onPaginationChange: setPageInfo,
|
||||
resetPreferences,
|
||||
}),
|
||||
[pageInfo, hideColumn, unhideColumn, columnVisibility, sortBy, columnOrder, setNewColumnOrder],
|
||||
);
|
||||
};
|
||||
|
||||
export type UseDataGridReturn = ReturnType<typeof useDataGrid>;
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@chakra-ui/react';
|
||||
import { ArrowDown, ArrowUp, Circle } from 'phosphor-react';
|
||||
import { ArrowDown, ArrowUp, Circle } from '@phosphor-icons/react';
|
||||
|
||||
interface Props {
|
||||
isSorted: boolean;
|
||||
|
||||
@@ -44,15 +44,18 @@ const defaultProps = {
|
||||
sortBy: [],
|
||||
};
|
||||
|
||||
export type DataTableProps = {
|
||||
columns: readonly Column<object>[];
|
||||
data: object[];
|
||||
export type DataTableProps<TValue> = {
|
||||
columns: Column<TValue>[];
|
||||
data: TValue[];
|
||||
count?: number;
|
||||
setPageInfo?: React.Dispatch<React.SetStateAction<PageInfo | undefined>>;
|
||||
isLoading?: boolean;
|
||||
onRowClick?: (row: TValue) => void;
|
||||
isRowClickable?: (row: TValue) => boolean;
|
||||
obj?: string;
|
||||
sortBy?: { id: string; desc: boolean }[];
|
||||
hiddenColumns?: string[];
|
||||
hideEmptyListText?: boolean;
|
||||
hideControls?: boolean;
|
||||
minHeight?: string | number;
|
||||
fullScreen?: boolean;
|
||||
@@ -67,7 +70,7 @@ type TableInstanceWithHooks<T extends object> = TableInstance<T> &
|
||||
state: UsePaginationState<T>;
|
||||
};
|
||||
|
||||
const _DataTable = ({
|
||||
const _DataTable = <TValue extends object>({
|
||||
columns,
|
||||
data,
|
||||
isLoading,
|
||||
@@ -77,15 +80,19 @@ const _DataTable = ({
|
||||
sortBy,
|
||||
hiddenColumns,
|
||||
hideControls,
|
||||
hideEmptyListText,
|
||||
count,
|
||||
setPageInfo,
|
||||
isManual,
|
||||
saveSettingsId,
|
||||
showAllRows,
|
||||
}: DataTableProps) => {
|
||||
onRowClick,
|
||||
isRowClickable,
|
||||
}: DataTableProps<TValue>) => {
|
||||
const { t } = useTranslation();
|
||||
const breakpoint = useBreakpoint();
|
||||
const textColor = useColorModeValue('gray.700', 'white');
|
||||
const hoveredRowBg = useColorModeValue('gray.100', 'gray.600');
|
||||
const getPageSize = () => {
|
||||
try {
|
||||
if (showAllRows) return 1000000;
|
||||
@@ -140,8 +147,12 @@ const _DataTable = ({
|
||||
},
|
||||
useSortBy,
|
||||
usePagination,
|
||||
) as TableInstanceWithHooks<object>;
|
||||
) as TableInstanceWithHooks<TValue>;
|
||||
|
||||
const handleGoToPage = (newPage: number) => {
|
||||
if (saveSettingsId) localStorage.setItem(`${saveSettingsId}.page`, String(newPage));
|
||||
gotoPage(newPage);
|
||||
};
|
||||
const handleNextPage = () => {
|
||||
nextPage();
|
||||
if (saveSettingsId) localStorage.setItem(`${saveSettingsId}.page`, String(pageIndex + 1));
|
||||
@@ -253,10 +264,19 @@ const _DataTable = ({
|
||||
</Thead>
|
||||
{data.length > 0 && (
|
||||
<Tbody {...getTableBodyProps()}>
|
||||
{page.map((row: Row) => {
|
||||
{page.map((row: Row<TValue>) => {
|
||||
prepareRow(row);
|
||||
const rowIsClickable = isRowClickable ? isRowClickable(row.original) : true;
|
||||
const onClick = rowIsClickable && onRowClick ? () => onRowClick(row.original) : undefined;
|
||||
return (
|
||||
<Tr {...row.getRowProps()} key={uuid()}>
|
||||
<Tr
|
||||
{...row.getRowProps()}
|
||||
key={uuid()}
|
||||
_hover={{
|
||||
backgroundColor: hoveredRowBg,
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
{
|
||||
// @ts-ignore
|
||||
row.cells.map((cell) => (
|
||||
@@ -275,8 +295,26 @@ const _DataTable = ({
|
||||
fontSize="14px"
|
||||
// @ts-ignore
|
||||
textAlign={cell.column.isCentered ? 'center' : undefined}
|
||||
// @ts-ignore
|
||||
fontFamily={cell.column.isMonospace ? 'monospace' : undefined}
|
||||
fontFamily={
|
||||
// @ts-ignore
|
||||
cell.column.isMonospace
|
||||
? 'Inter, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
|
||||
: undefined
|
||||
}
|
||||
onClick={
|
||||
// @ts-ignore
|
||||
cell.column.stopPropagation || (cell.column.id === 'actions' && onRowClick)
|
||||
? (e) => {
|
||||
e.stopPropagation();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
cursor={
|
||||
// @ts-ignore
|
||||
!cell.column.stopPropagation && cell.column.id !== 'actions' && onRowClick
|
||||
? 'pointer'
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{cell.render('Cell')}
|
||||
</Td>
|
||||
@@ -288,7 +326,7 @@ const _DataTable = ({
|
||||
</Tbody>
|
||||
)}
|
||||
</Table>
|
||||
{!isLoading && data.length === 0 && (
|
||||
{!isLoading && data.length === 0 && !hideEmptyListText && (
|
||||
<Center>
|
||||
{obj ? (
|
||||
<Heading size="md" pt={12}>
|
||||
@@ -309,7 +347,7 @@ const _DataTable = ({
|
||||
<Tooltip label={t('table.first_page')}>
|
||||
<IconButton
|
||||
aria-label="Go to first page"
|
||||
onClick={() => gotoPage(0)}
|
||||
onClick={() => handleGoToPage(0)}
|
||||
isDisabled={!canPreviousPage}
|
||||
icon={<ArrowLeftIcon h={3} w={3} />}
|
||||
mr={4}
|
||||
@@ -347,7 +385,7 @@ const _DataTable = ({
|
||||
max={pageOptions.length}
|
||||
onChange={(_: unknown, numberValue: number) => {
|
||||
const newPage = numberValue ? numberValue - 1 : 0;
|
||||
gotoPage(newPage);
|
||||
handleGoToPage(newPage);
|
||||
}}
|
||||
defaultValue={pageIndex + 1}
|
||||
>
|
||||
@@ -386,7 +424,7 @@ const _DataTable = ({
|
||||
<Tooltip label={t('table.last_page')}>
|
||||
<IconButton
|
||||
aria-label="Go to last page"
|
||||
onClick={() => gotoPage(pageCount - 1)}
|
||||
onClick={() => handleGoToPage(pageCount - 1)}
|
||||
isDisabled={!canNextPage}
|
||||
icon={<ArrowRightIcon h={3} w={3} />}
|
||||
ml={4}
|
||||
@@ -401,4 +439,4 @@ const _DataTable = ({
|
||||
|
||||
_DataTable.defaultProps = defaultProps;
|
||||
|
||||
export const DataTable = React.memo(_DataTable);
|
||||
export const DataTable = React.memo(_DataTable) as unknown as typeof _DataTable;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@chakra-ui/react';
|
||||
import { ArrowDown, ArrowUp, Circle } from 'phosphor-react';
|
||||
import { ArrowDown, ArrowUp, Circle } from '@phosphor-icons/react';
|
||||
|
||||
interface Props {
|
||||
isSorted: boolean;
|
||||
|
||||
@@ -100,6 +100,7 @@ const SortableDataTable: React.FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const breakpoint = useBreakpoint();
|
||||
const hoveredRowBg = useColorModeValue('gray.100', 'gray.600');
|
||||
const textColor = useColorModeValue('gray.700', 'white');
|
||||
const getPageSize = () => {
|
||||
const saved = saveSettingsId ? localStorage.getItem(saveSettingsId) : undefined;
|
||||
@@ -223,7 +224,13 @@ const SortableDataTable: React.FC<Props> = ({
|
||||
{page.map((row: Row) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<Tr {...row.getRowProps()} key={uuid()}>
|
||||
<Tr
|
||||
{...row.getRowProps()}
|
||||
key={uuid()}
|
||||
_hover={{
|
||||
backgroundColor: hoveredRowBg,
|
||||
}}
|
||||
>
|
||||
{
|
||||
// @ts-ignore
|
||||
row.cells.map((cell) => (
|
||||
@@ -242,8 +249,12 @@ const SortableDataTable: React.FC<Props> = ({
|
||||
fontSize="14px"
|
||||
// @ts-ignore
|
||||
textAlign={cell.column.isCentered ? 'center' : undefined}
|
||||
// @ts-ignore
|
||||
fontFamily={cell.column.isMonospace ? 'monospace' : undefined}
|
||||
fontFamily={
|
||||
// @ts-ignore
|
||||
cell.column.isMonospace
|
||||
? 'Inter, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{cell.render('Cell')}
|
||||
</Td>
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { Heading } from '@chakra-ui/react';
|
||||
import { Select } from 'chakra-react-select';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useControllerDeviceSearch } from 'contexts/ControllerSocketProvider/hooks/Commands/useDeviceSearch';
|
||||
import { useControllerStore } from 'contexts/ControllerSocketProvider/useStore';
|
||||
|
||||
const DeviceSearchBar = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { token } = useAuth();
|
||||
const { startWebSocket, isWebSocketOpen } = useControllerStore((state) => ({
|
||||
startWebSocket: state.startWebSocket,
|
||||
isWebSocketOpen: state.isWebSocketOpen,
|
||||
}));
|
||||
const { inputValue, results, onInputChange } = useControllerDeviceSearch({
|
||||
minLength: 2,
|
||||
});
|
||||
|
||||
const NoOptionsMessage = React.useCallback(
|
||||
() => (
|
||||
<Heading size="sm" textAlign="center">
|
||||
{isWebSocketOpen ? t('common.no_devices_found') : `${t('controller.devices.connecting')}...`}
|
||||
</Heading>
|
||||
),
|
||||
[t, isWebSocketOpen],
|
||||
);
|
||||
const onClick = React.useCallback((v: { value: string }) => {
|
||||
navigate(`/devices/${v.value}`);
|
||||
}, []);
|
||||
const onChange = React.useCallback((v: string) => {
|
||||
if ((v.length === 0 || v.match('^[a-fA-F0-9-*]+$')) && v.length <= 13) onInputChange(v);
|
||||
}, []);
|
||||
|
||||
const onFocus = () => {
|
||||
if (!isWebSocketOpen && token && token.length > 0) {
|
||||
startWebSocket(token, 0);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Select
|
||||
chakraStyles={{
|
||||
control: (provided) => ({
|
||||
...provided,
|
||||
borderRadius: '15px',
|
||||
color: 'unset',
|
||||
}),
|
||||
input: (provided) => ({
|
||||
...provided,
|
||||
width: '140px',
|
||||
}),
|
||||
dropdownIndicator: (provided) => ({
|
||||
...provided,
|
||||
backgroundColor: 'unset',
|
||||
border: 'unset',
|
||||
}),
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
color: 'black',
|
||||
}),
|
||||
}}
|
||||
components={{ NoOptionsMessage }}
|
||||
// @ts-ignore
|
||||
options={results.map((v: string) => ({ label: v, value: v }))}
|
||||
filterOption={() => true}
|
||||
inputValue={inputValue}
|
||||
value={inputValue}
|
||||
placeholder={t('common.search')}
|
||||
onInputChange={onChange}
|
||||
onFocus={onFocus}
|
||||
// @ts-ignore
|
||||
onChange={onClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeviceSearchBar;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import { IconButton, Input, InputGroup, InputRightElement, Tooltip } from '@chakra-ui/react';
|
||||
import { Trash } from 'phosphor-react';
|
||||
import { Trash } from '@phosphor-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { DataTable } from '../../../DataTables/DataTable';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { FormControl, FormErrorMessage, FormLabel } from '@chakra-ui/react';
|
||||
import { Select } from 'chakra-react-select';
|
||||
import { CreatableSelect, Select } from 'chakra-react-select';
|
||||
import PropTypes from 'prop-types';
|
||||
import isEqual from 'react-fast-compare';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -25,6 +25,7 @@ const propTypes = {
|
||||
isHidden: PropTypes.bool,
|
||||
isPortal: PropTypes.bool.isRequired,
|
||||
definitionKey: PropTypes.string,
|
||||
isCreatable: PropTypes.bool,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
@@ -36,6 +37,7 @@ const defaultProps = {
|
||||
isDisabled: false,
|
||||
isHidden: false,
|
||||
definitionKey: null,
|
||||
isCreatable: false,
|
||||
};
|
||||
|
||||
const FastMultiSelectInput = ({
|
||||
@@ -50,6 +52,7 @@ const FastMultiSelectInput = ({
|
||||
isRequired,
|
||||
isDisabled,
|
||||
isHidden,
|
||||
isCreatable,
|
||||
isPortal,
|
||||
definitionKey,
|
||||
}) => {
|
||||
@@ -61,35 +64,62 @@ const FastMultiSelectInput = ({
|
||||
{label}
|
||||
<ConfigurationFieldExplanation definitionKey={definitionKey} />
|
||||
</FormLabel>
|
||||
<Select
|
||||
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={canSelectAll ? [{ value: '*', label: t('common.all') }, ...options] : options}
|
||||
value={
|
||||
value?.map((val) => {
|
||||
if (val === '*') return { value: val, label: t('common.all') };
|
||||
return options.find((opt) => opt.value === val);
|
||||
}) ?? []
|
||||
}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
{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
|
||||
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={canSelectAll ? [{ value: '*', label: t('common.all') }, ...options] : options}
|
||||
value={
|
||||
value?.map((val) => {
|
||||
if (val === '*') return { value: val, label: t('common.all') };
|
||||
return options.find((opt) => opt.value === val);
|
||||
}) ?? []
|
||||
}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
)}
|
||||
<FormErrorMessage>{error}</FormErrorMessage>
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ const propTypes = {
|
||||
canSelectAll: PropTypes.bool,
|
||||
isPortal: PropTypes.bool,
|
||||
definitionKey: PropTypes.string,
|
||||
isCreatable: PropTypes.bool,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
@@ -31,6 +32,7 @@ const defaultProps = {
|
||||
canSelectAll: false,
|
||||
isPortal: false,
|
||||
definitionKey: null,
|
||||
isCreatable: false,
|
||||
};
|
||||
|
||||
const MultiSelectField = ({
|
||||
@@ -43,25 +45,39 @@ const MultiSelectField = ({
|
||||
emptyIsUndefined,
|
||||
canSelectAll,
|
||||
hasVirtualAll,
|
||||
isCreatable,
|
||||
isPortal,
|
||||
definitionKey,
|
||||
}) => {
|
||||
const [{ value }, { touched, error }, { setValue, setTouched }] = useField(name);
|
||||
|
||||
const onChange = useCallback((option) => {
|
||||
const allIndex = option.findIndex((opt) => opt.value === '*');
|
||||
if (option.length === 0 && emptyIsUndefined) {
|
||||
setValue(undefined);
|
||||
} else if (allIndex === 0 && option.length > 1) {
|
||||
const newValues = option.slice(1);
|
||||
setValue(newValues.map((val) => val.value));
|
||||
} else if (allIndex >= 0) {
|
||||
if (!hasVirtualAll) setValue(['*']);
|
||||
else setValue(options.map(({ value: v }) => v));
|
||||
} else if (option.length > 0) setValue(option.map((val) => val.value));
|
||||
else setValue([]);
|
||||
setTouched(true);
|
||||
}, []);
|
||||
const onChange = useCallback(
|
||||
(option) => {
|
||||
if (isCreatable) {
|
||||
if (typeof option === 'string') {
|
||||
setValue([...value, option]);
|
||||
} else {
|
||||
setValue(option);
|
||||
}
|
||||
|
||||
// setValue([...value, option]);
|
||||
} else {
|
||||
const allIndex = option.findIndex((opt) => opt.value === '*');
|
||||
if (option.length === 0 && emptyIsUndefined) {
|
||||
setValue(undefined);
|
||||
} else if (allIndex === 0 && option.length > 1) {
|
||||
const newValues = option.slice(1);
|
||||
setValue(newValues.map((val) => val.value));
|
||||
} else if (allIndex >= 0) {
|
||||
if (!hasVirtualAll) setValue(['*']);
|
||||
else setValue(options.map(({ value: v }) => v));
|
||||
} else if (option.length > 0) setValue(option.map((val) => val.value));
|
||||
else setValue([]);
|
||||
setTouched(true);
|
||||
}
|
||||
},
|
||||
[value],
|
||||
);
|
||||
|
||||
const onFieldBlur = useCallback(() => {
|
||||
setTouched(true);
|
||||
@@ -82,6 +98,7 @@ const MultiSelectField = ({
|
||||
isHidden={isHidden}
|
||||
isPortal={isPortal}
|
||||
definitionKey={definitionKey}
|
||||
isCreatable={isCreatable}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
196
src/components/GlobalSearchBar/index.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
import * as React from 'react';
|
||||
import { Tooltip, useColorMode, useColorModeValue } from '@chakra-ui/react';
|
||||
import {
|
||||
AsyncSelect,
|
||||
ChakraStylesConfig,
|
||||
GroupBase,
|
||||
LoadingIndicatorProps,
|
||||
OptionBase,
|
||||
OptionsOrGroups,
|
||||
chakraComponents,
|
||||
} from 'chakra-react-select';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useControllerStore } from 'contexts/ControllerSocketProvider/useStore';
|
||||
import debounce from 'helpers/debounce';
|
||||
import { getUsernameRadiusSessions } from 'hooks/Network/Radius';
|
||||
|
||||
const chakraStyles: (
|
||||
colorMode: 'light' | 'dark',
|
||||
) => ChakraStylesConfig<SearchOption, false, GroupBase<SearchOption>> = (colorMode) => ({
|
||||
dropdownIndicator: (provided) => ({
|
||||
...provided,
|
||||
width: '32px',
|
||||
}),
|
||||
placeholder: (provided) => ({
|
||||
...provided,
|
||||
lineHeight: '1',
|
||||
pointerEvents: 'none',
|
||||
userSelect: 'none',
|
||||
MozUserSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
msUserSelect: 'none',
|
||||
}),
|
||||
container: (provided) => ({
|
||||
...provided,
|
||||
width: '320px',
|
||||
backgroundColor: colorMode === 'light' ? 'white' : 'gray.600',
|
||||
borderRadius: '15px',
|
||||
}),
|
||||
input: (provided) => ({
|
||||
...provided,
|
||||
gridArea: '1 / 2 / 4 / 4 !important',
|
||||
}),
|
||||
});
|
||||
|
||||
interface SearchOption extends OptionBase {
|
||||
label: string;
|
||||
value: string;
|
||||
type: 'serial' | 'radius-username' | 'radius-mac';
|
||||
}
|
||||
|
||||
const asyncComponents = {
|
||||
LoadingIndicator: (props: LoadingIndicatorProps<SearchOption, false, GroupBase<SearchOption>>) => {
|
||||
const { color, emptyColor } = useColorModeValue(
|
||||
{
|
||||
color: 'blue.500',
|
||||
emptyColor: 'blue.100',
|
||||
},
|
||||
{
|
||||
color: 'blue.300',
|
||||
emptyColor: 'blue.900',
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<chakraComponents.LoadingIndicator
|
||||
color={color}
|
||||
emptyColor={emptyColor}
|
||||
speed="750ms"
|
||||
spinnerSize="md"
|
||||
thickness="3px"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const GlobalSearchBar = () => {
|
||||
const { colorMode } = useColorMode();
|
||||
const navigate = useNavigate();
|
||||
const store = useControllerStore((state) => ({
|
||||
searchSerialNumber: state.searchSerialNumber,
|
||||
}));
|
||||
|
||||
const onNewSearch = React.useCallback(
|
||||
async (v: string, callback: (options: OptionsOrGroups<SearchOption, GroupBase<SearchOption>>) => void) => {
|
||||
if (v.length < 3) return callback([]);
|
||||
|
||||
if (v.includes('rad:')) {
|
||||
const trimmed = v.replace('rad:', '').trim();
|
||||
if (trimmed.length < 3) return callback([]);
|
||||
const cleaned = trimmed.toLowerCase();
|
||||
return getUsernameRadiusSessions(cleaned)
|
||||
.then((res) =>
|
||||
callback(
|
||||
res
|
||||
.map((r) => ({
|
||||
label: r.serialNumber,
|
||||
value: r.serialNumber,
|
||||
type: 'radius-username',
|
||||
}))
|
||||
.filter(({ value }, i, a) => a.findIndex((t) => t.value === value) === i) as SearchOption[],
|
||||
),
|
||||
)
|
||||
.then(() => callback([]));
|
||||
}
|
||||
if (v.match('^[a-fA-F0-9-*]+$')) {
|
||||
let result: { label: string; value: string; type: 'serial' }[] = [];
|
||||
let tryAgain = true;
|
||||
|
||||
await store
|
||||
.searchSerialNumber(v)
|
||||
.then((res) => {
|
||||
result = res.map((r) => ({
|
||||
label: r,
|
||||
value: r,
|
||||
type: 'serial',
|
||||
}));
|
||||
tryAgain = false;
|
||||
})
|
||||
.catch(() => {
|
||||
result = [];
|
||||
});
|
||||
|
||||
if (tryAgain) {
|
||||
// Wait 1 second and try again
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
await store
|
||||
.searchSerialNumber(v)
|
||||
.then((res) => {
|
||||
result = res.map((r) => ({
|
||||
label: r,
|
||||
value: r,
|
||||
type: 'serial',
|
||||
}));
|
||||
tryAgain = false;
|
||||
})
|
||||
.catch(() => {
|
||||
result = [];
|
||||
});
|
||||
}
|
||||
|
||||
callback(result);
|
||||
}
|
||||
return callback([]);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const debouncedNewSearch = React.useCallback(
|
||||
debounce(
|
||||
// @ts-ignore
|
||||
({
|
||||
v,
|
||||
callback,
|
||||
}: {
|
||||
v: string;
|
||||
callback: (options: OptionsOrGroups<SearchOption, GroupBase<SearchOption>>) => void;
|
||||
}) => {
|
||||
onNewSearch(v as string, callback);
|
||||
},
|
||||
300,
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
const styles = React.useMemo(() => chakraStyles(colorMode), [colorMode]);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
label={`Search serial numbers and radius clients. For radius clients you can either use the client's username (rad:client@client.com)
|
||||
or use the client's station ID (rad:11:22:33:44:55:66)`}
|
||||
shouldWrapChildren
|
||||
placement="left"
|
||||
>
|
||||
<AsyncSelect<SearchOption, false, GroupBase<SearchOption>>
|
||||
name="global_search"
|
||||
chakraStyles={styles}
|
||||
closeMenuOnSelect
|
||||
placeholder="Search MACs or radius clients"
|
||||
components={asyncComponents}
|
||||
loadOptions={(inputValue, callback) => {
|
||||
debouncedNewSearch({ v: inputValue, callback });
|
||||
}}
|
||||
value={null}
|
||||
onChange={(newValue) => {
|
||||
if (newValue) {
|
||||
navigate(`/devices/${newValue.value}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearchBar;
|
||||