[WIFI-12506] Added radius search and radius clients tile

Signed-off-by: Charles <charles.bourque96@gmail.com>
This commit is contained in:
Charles
2023-04-12 10:43:35 +02:00
parent df1686a2ae
commit f70992e9a1
19 changed files with 6291 additions and 5824 deletions

View File

@@ -3,3 +3,4 @@ build
dist
node_modules
.github
/helm

72
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "ucentral-client",
"version": "2.10.0(11)",
"version": "2.10.0(14)",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "ucentral-client",
"version": "2.10.0(11)",
"version": "2.10.0(14)",
"license": "ISC",
"dependencies": {
"@chakra-ui/icons": "^2.0.11",
@@ -24,7 +24,7 @@
"@textea/json-viewer": "^2.10.0",
"axios": "^1.1.3",
"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": "^4.0.3",
@@ -2839,14 +2839,16 @@
}
},
"node_modules/@floating-ui/core": {
"version": "1.0.1",
"license": "MIT"
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.6.tgz",
"integrity": "sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg=="
},
"node_modules/@floating-ui/dom": {
"version": "1.0.2",
"license": "MIT",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.6.tgz",
"integrity": "sha512-02vxFDuvuVPs22iJICacezYJyf7zwwOCWkPNkWNBr1U0Qt1cKFYzWvxts0AmqcOQGwt/3KJWcWIgtbUU38keyw==",
"dependencies": {
"@floating-ui/core": "^1.0.1"
"@floating-ui/core": "^1.2.6"
}
},
"node_modules/@fontsource/inter": {
@@ -4387,15 +4389,17 @@
"license": "CC-BY-4.0"
},
"node_modules/chakra-react-select": {
"version": "4.3.0",
"license": "MIT",
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/chakra-react-select/-/chakra-react-select-4.6.0.tgz",
"integrity": "sha512-Ckcs+ofX5LxCc0oOz4SorDIRqF/afd5tAQOa694JVJiIckYorUmZASEUSSDdXaZltsUAtJE11CUmEZgVVsk9Eg==",
"dependencies": {
"react-select": "^5.5.0"
"react-select": "5.7.0"
},
"peerDependencies": {
"@chakra-ui/form-control": "^2.0.0",
"@chakra-ui/icon": "^3.0.0",
"@chakra-ui/layout": "^2.0.0",
"@chakra-ui/media-query": "^3.0.0",
"@chakra-ui/menu": "^2.0.0",
"@chakra-ui/spinner": "^2.0.0",
"@chakra-ui/system": "^2.0.0",
@@ -7979,15 +7983,16 @@
}
},
"node_modules/react-select": {
"version": "5.5.2",
"license": "MIT",
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.0.tgz",
"integrity": "sha512-lJGiMxCa3cqnUr2Jjtg9YHsaytiZqeNOKeibv6WF5zbK/fPegZ1hg3y/9P1RZVLhqBTs0PfqQLKuAACednYGhQ==",
"dependencies": {
"@babel/runtime": "^7.12.0",
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.8.1",
"@floating-ui/dom": "^1.0.1",
"@types/react-transition-group": "^4.4.0",
"memoize-one": "^5.0.0",
"memoize-one": "^6.0.0",
"prop-types": "^15.6.0",
"react-transition-group": "^4.3.0",
"use-isomorphic-layout-effect": "^1.1.2"
@@ -7997,6 +8002,11 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-select/node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
},
"node_modules/react-style-singleton": {
"version": "2.2.1",
"license": "MIT",
@@ -9129,7 +9139,8 @@
},
"node_modules/use-isomorphic-layout-effect": {
"version": "1.1.2",
"license": "MIT",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
"integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
@@ -11486,12 +11497,16 @@
}
},
"@floating-ui/core": {
"version": "1.0.1"
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.6.tgz",
"integrity": "sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg=="
},
"@floating-ui/dom": {
"version": "1.0.2",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.6.tgz",
"integrity": "sha512-02vxFDuvuVPs22iJICacezYJyf7zwwOCWkPNkWNBr1U0Qt1cKFYzWvxts0AmqcOQGwt/3KJWcWIgtbUU38keyw==",
"requires": {
"@floating-ui/core": "^1.0.1"
"@floating-ui/core": "^1.2.6"
}
},
"@fontsource/inter": {
@@ -12379,9 +12394,11 @@
"version": "1.0.30001422"
},
"chakra-react-select": {
"version": "4.3.0",
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/chakra-react-select/-/chakra-react-select-4.6.0.tgz",
"integrity": "sha512-Ckcs+ofX5LxCc0oOz4SorDIRqF/afd5tAQOa694JVJiIckYorUmZASEUSSDdXaZltsUAtJE11CUmEZgVVsk9Eg==",
"requires": {
"react-select": "^5.5.0"
"react-select": "5.7.0"
}
},
"chalk": {
@@ -14532,17 +14549,26 @@
}
},
"react-select": {
"version": "5.5.2",
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.0.tgz",
"integrity": "sha512-lJGiMxCa3cqnUr2Jjtg9YHsaytiZqeNOKeibv6WF5zbK/fPegZ1hg3y/9P1RZVLhqBTs0PfqQLKuAACednYGhQ==",
"requires": {
"@babel/runtime": "^7.12.0",
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.8.1",
"@floating-ui/dom": "^1.0.1",
"@types/react-transition-group": "^4.4.0",
"memoize-one": "^5.0.0",
"memoize-one": "^6.0.0",
"prop-types": "^15.6.0",
"react-transition-group": "^4.3.0",
"use-isomorphic-layout-effect": "^1.1.2"
},
"dependencies": {
"memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
}
}
},
"react-style-singleton": {
@@ -15209,6 +15235,8 @@
},
"use-isomorphic-layout-effect": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
"integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
"requires": {}
},
"use-sidecar": {

View File

@@ -1,6 +1,6 @@
{
"name": "ucentral-client",
"version": "2.10.0(11)",
"version": "2.10.0(14)",
"description": "",
"private": true,
"main": "index.tsx",
@@ -27,7 +27,7 @@
"@react-spring/web": "^9.5.5",
"axios": "^1.1.3",
"buffer": "^6.0.3",
"chakra-react-select": "^4.3.0",
"chakra-react-select": "^4.6.0",
"dagre": "^0.8.5",
"formik": "^2.2.9",
"fast-equals": "^4.0.3",

View File

@@ -356,6 +356,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",
@@ -522,6 +523,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",
@@ -537,6 +539,14 @@
"queue": {
"title": "Ereigniswarteschlange"
},
"radius": {
"calling_station_id": "Stations",
"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",
@@ -712,7 +722,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",
@@ -1072,14 +1082,23 @@
"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",
"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",

View File

@@ -356,6 +356,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",
@@ -522,6 +523,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",
@@ -537,6 +539,14 @@
"queue": {
"title": "Event Queue"
},
"radius": {
"calling_station_id": "Station",
"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",
@@ -712,7 +722,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",
@@ -753,8 +763,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}}"
@@ -1072,14 +1082,23 @@
"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",
"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",

View File

@@ -356,6 +356,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",
@@ -522,6 +523,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",
@@ -537,6 +539,14 @@
"queue": {
"title": "Cola de eventos"
},
"radius": {
"calling_station_id": "Estación",
"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",
@@ -712,7 +722,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",
@@ -1072,14 +1082,23 @@
"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.",
"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",

View File

@@ -356,6 +356,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",
@@ -522,6 +523,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",
@@ -537,6 +539,14 @@
"queue": {
"title": "File d'attente d'événements"
},
"radius": {
"calling_station_id": "Station",
"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",
@@ -712,7 +722,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",
@@ -1072,14 +1082,23 @@
"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",
"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",

View File

@@ -356,6 +356,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",
@@ -522,6 +523,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",
@@ -537,6 +539,14 @@
"queue": {
"title": "Fila de Eventos"
},
"radius": {
"calling_station_id": "estação",
"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",
@@ -712,7 +722,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",
@@ -1072,14 +1082,23 @@
"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",
"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",

View File

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

View File

@@ -0,0 +1,155 @@
import * as React from 'react';
import { Tooltip, 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: ChakraStylesConfig<SearchOption, false, GroupBase<SearchOption>> = {
dropdownIndicator: (provided) => ({
...provided,
width: '32px',
}),
placeholder: (provided) => ({
...provided,
lineHeight: '1',
}),
container: (provided) => ({
...provided,
width: '320px',
}),
};
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 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-*]+$')) {
await store
.searchSerialNumber(v)
.then((res) => {
callback(
res.map((r) => ({
label: r,
value: r,
type: 'serial',
})),
);
})
.catch(() => []);
}
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,
),
[],
);
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={chakraStyles}
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;

View File

@@ -3,6 +3,7 @@ import { v4 as uuid } from 'uuid';
import create from 'zustand';
import { ControllerSocketRawMessage, SocketEventCallback, SocketWebSocketNotificationData } from './utils';
import { axiosGw } from 'constants/axiosInstances';
import { randomIntId } from 'helpers/stringHelper';
import { DevicesStats, DeviceWithStatus } from 'hooks/Network/Devices';
import { NotificationType } from 'models/Socket';
@@ -122,6 +123,7 @@ export type ControllerStoreState = {
lastSearchResults: string[];
setLastSearchResults: (result: string[]) => void;
errors: { str: string; timestamp: Date }[];
searchSerialNumber: (serialNumber: string, timeout?: number) => Promise<string[]>;
};
export const useControllerStore = create<ControllerStoreState>((set, get) => ({
@@ -169,14 +171,24 @@ export const useControllerStore = create<ControllerStoreState>((set, get) => ({
id: uuid(),
};
const eventsToFire = get().eventListeners.filter(
({ type, serialNumber }) => type === msg.type && serialNumber === msg.serialNumber,
);
const eventsToFire = get().eventListeners.filter((event) => {
if (event.type === 'DEVICE_CONNECTION' || event.type === 'DEVICE_DISCONNECTION') {
return event.serialNumber === msg.serialNumber;
}
if (msg.type === 'DEVICE_SEARCH_RESULTS' && event.type === 'DEVICE_SEARCH_RESULTS') {
return true;
}
return false;
});
if (eventsToFire.length > 0) {
for (const event of eventsToFire) {
if (event.type === 'DEVICE_SEARCH_RESULTS' && msg.type === 'DEVICE_SEARCH_RESULTS') {
event.callback(msg.serialNumbers);
} else if (event.type === 'DEVICE_CONNECTION' || event.type === 'DEVICE_DISCONNECTION') {
event.callback();
}
}
return set((state) => ({
allMessages:
@@ -211,6 +223,37 @@ export const useControllerStore = create<ControllerStoreState>((set, get) => ({
const ws = get().webSocket;
if (ws) ws.send(str);
},
searchSerialNumber: async (serialNumber: string, timeout = 1000 * 5): Promise<string[]> =>
new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Promise timed out after ${timeout} ms`));
}, timeout);
const ws = get().webSocket;
if (ws) {
const id = randomIntId();
get().addEventListeners([
{
id: uuid(),
type: 'DEVICE_SEARCH_RESULTS',
searchId: id,
callback: (serialNumbers: string[]) => {
clearTimeout(timer);
resolve(serialNumbers);
},
},
]);
ws.send(
JSON.stringify({
command: 'serial_number_search',
serial_prefix: serialNumber,
id: randomIntId(),
}),
);
} else {
clearTimeout(timer);
reject(new Error('No websocket connection'));
}
}),
startWebSocket: (token: string, tries = 0) => {
const newTries = tries + 1;
if (tries <= 10) {

View File

@@ -116,9 +116,16 @@ export type SocketWebSocketNotificationData =
log?: undefined;
message: InitialSocketMessage;
};
export type SocketEventCallback = {
export type SocketEventCallback =
| {
id: string;
type: 'DEVICE_CONNECTION' | 'DEVICE_DISCONNECTION';
serialNumber: string;
callback: () => void;
};
}
| {
id: string;
type: 'DEVICE_SEARCH_RESULTS';
searchId: number;
callback: (serialNumbers: string[]) => void;
};

View File

@@ -0,0 +1,48 @@
import { QueryFunctionContext, useQuery } from '@tanstack/react-query';
import { axiosGw } from 'constants/axiosInstances';
export type RadiusSession = {
started: number;
lastTransaction: number;
inputPackets: number;
outputPackets: number;
inputOctets: number;
outputOctets: number;
inputGigaWords: number;
outputGigaWords: number;
sessionTime: number;
serialNumber: string;
destination: string;
userName: string;
accountingSessionId: string;
accountingMultiSessionId: string;
callingStationId: string;
};
export const getDeviceRadiusSessions = async (mac: string) =>
axiosGw.get(`/radiusSessions/${mac}`).then((res) => res.data.sessions as RadiusSession[]);
const getDeviceSessions = async (context: QueryFunctionContext<[string, string]>) =>
getDeviceRadiusSessions(context.queryKey[1]);
export const useGetDeviceRadiusSessions = ({ serialNumber }: { serialNumber: string }) =>
useQuery(['radius-sessions', serialNumber], getDeviceSessions, {
keepPreviousData: true,
staleTime: 1000 * 60,
});
export const getUsernameRadiusSessions = async (username: string) =>
axiosGw.get(`/radiusSessions/0?userName=${username}`).then((res) => res.data.sessions as RadiusSession[]);
const getUserSessions = async (context: QueryFunctionContext<[string, string, string]>) =>
getUsernameRadiusSessions(context.queryKey[2]);
export const useGetUserRadiusSessions = ({ userName }: { userName: string }) =>
useQuery(['radius-sessions', 'username', userName], getUserSessions, {
staleTime: 1000 * 60,
});
export const getStationRadiusSessions = async (station: string) =>
axiosGw.get(`/radiusSessions/0?mac=${station}`).then((res) => res.data.sessions as RadiusSession[]);
const getStationSessions = async (context: QueryFunctionContext<[string, string, string]>) =>
getStationRadiusSessions(context.queryKey[2]);
export const useGetStationRadiusSessions = ({ station }: { station: string }) =>
useQuery(['radius-sessions', 'station', station], getStationSessions, {
staleTime: 1000 * 60,
});

View File

@@ -11,6 +11,8 @@ export interface GatewayDevice {
entity: string;
firmware: string;
fwUpdatePolicy: string;
hasGPS: boolean;
hasRADIUSSessions: number;
lastConfigurationChange: number;
lastConfigurationDownload: number;
lastFWUpdate: number;

View File

@@ -1,6 +1,5 @@
import { Note } from './Note';
export type UserRole =
| 'root'
| 'admin'

View File

@@ -0,0 +1,132 @@
/* eslint-disable react/no-unstable-nested-components */
import * as React from 'react';
import { Box } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { DataGrid } from 'components/DataTables/DataGrid';
import { DataGridColumn, useDataGrid } from 'components/DataTables/DataGrid/useDataGrid';
import DataCell from 'components/TableCells/DataCell';
import DurationCell from 'components/TableCells/DurationCell';
import { RadiusSession } from 'hooks/Network/Radius';
type Props = {
sessions: RadiusSession[];
refetch: () => void;
isFetching: boolean;
};
const DeviceRadiusClientsTable = ({ sessions, refetch, isFetching }: Props) => {
const { t } = useTranslation();
const tableController = useDataGrid({
tableSettingsId: 'gateway.device_radius_sessions.table',
defaultSortBy: [
{
id: 'callingStationId',
desc: false,
},
],
});
const columns: DataGridColumn<RadiusSession>[] = React.useMemo(
(): DataGridColumn<RadiusSession>[] => [
{
id: 'callingStationId',
header: t('controller.radius.calling_station_id'),
accessorKey: 'callingStationId',
cell: ({ cell }) => {
let { callingStationId } = cell.row.original;
callingStationId = callingStationId.replace(/-/g, ':').toLowerCase();
return (
<div className="flex items-center">
<div className="flex-1">{callingStationId}</div>
</div>
);
},
meta: {
customWidth: '150px',
isMonospace: true,
alwaysShow: true,
},
},
{
id: 'userName',
header: t('controller.radius.username'),
accessorKey: 'userName',
meta: {
alwaysShow: true,
},
},
{
id: 'sessionTime',
header: t('controller.radius.session_time'),
accessorKey: 'sessionTime',
cell: ({ cell }) => {
const { sessionTime } = cell.row.original;
return <DurationCell seconds={sessionTime} />;
},
meta: {
customWidth: '120px',
},
},
{
id: 'inputOctets',
header: t('controller.radius.input_octets'),
accessorKey: 'inputOctets',
cell: ({ cell }) => {
const { inputOctets } = cell.row.original;
return (
<Box textAlign="right">
<DataCell bytes={inputOctets} showZerosAs="-" />
</Box>
);
},
meta: {
customWidth: '40px',
customMinWidth: '40px',
headerStyleProps: {
textAlign: 'right',
},
},
},
{
id: 'outputOctets',
header: t('controller.radius.output_octets'),
accessorKey: 'outputOctets',
cell: ({ cell }) => {
const { outputOctets } = cell.row.original;
return (
<Box textAlign="right">
<DataCell bytes={outputOctets} showZerosAs="-" />
</Box>
);
},
meta: {
customWidth: '40px',
customMinWidth: '40px',
headerStyleProps: {
textAlign: 'right',
},
},
},
],
[t],
);
return (
<DataGrid<RadiusSession>
controller={tableController}
header={{
title: `${t('controller.radius.radius_clients')} (${sessions.length})`,
objectListed: t('controller.radius.radius_clients'),
}}
columns={columns}
data={sessions}
isLoading={isFetching}
options={{
refetch,
minimumHeight: '200px',
}}
/>
);
};
export default DeviceRadiusClientsTable;

View File

@@ -0,0 +1,32 @@
import * as React from 'react';
import { Box } from '@chakra-ui/react';
import DeviceRadiusClientsTable from './Table';
import { Card } from 'components/Containers/Card';
import { CardBody } from 'components/Containers/Card/CardBody';
import { useGetDeviceRadiusSessions } from 'hooks/Network/Radius';
type Props = {
serialNumber: string;
};
const RadiusClientsCard = ({ serialNumber }: Props) => {
const getRadiusClients = useGetDeviceRadiusSessions({ serialNumber });
if (!getRadiusClients.data || getRadiusClients.data.length === 0) return null;
return (
<Card mb={4}>
<CardBody>
<Box w="100%">
<DeviceRadiusClientsTable
sessions={getRadiusClients.data}
refetch={getRadiusClients.refetch}
isFetching={getRadiusClients.isFetching}
/>
</Box>
</CardBody>
</Card>
);
};
export default RadiusClientsCard;

View File

@@ -29,6 +29,7 @@ import { useNavigate } from 'react-router-dom';
import DeviceDetails from './Details';
import DeviceLogsCard from './LogsCard';
import DeviceNotes from './Notes';
import RadiusClientsCard from './RadiusClients';
import RestrictionsCard from './RestrictionsCard';
import DeviceStatisticsCard from './StatisticsCard';
import DeviceSummary from './Summary';
@@ -38,7 +39,7 @@ import DeviceActionDropdown from 'components/Buttons/DeviceActionDropdown';
import { RefreshButton } from 'components/Buttons/RefreshButton';
import { Card } from 'components/Containers/Card';
import { CardHeader } from 'components/Containers/Card/CardHeader';
import DeviceSearchBar from 'components/DeviceSearchBar';
import GlobalSearchBar from 'components/GlobalSearchBar';
import FormattedDate from 'components/InformationDisplays/FormattedDate';
import { ConfigureModal } from 'components/Modals/ConfigureModal';
import { EventQueueModal } from 'components/Modals/EventQueueModal';
@@ -194,7 +195,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
</HStack>
<Spacer />
<HStack spacing={2}>
<DeviceSearchBar />
<GlobalSearchBar />
<DeleteButton isCompact onClick={onDeleteOpen} />
{getDevice?.data && (
<DeviceActionDropdown
@@ -244,7 +245,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
</HStack>
<Spacer />
<HStack spacing={2}>
<DeviceSearchBar />
<GlobalSearchBar />
<DeleteButton isCompact onClick={onDeleteOpen} />
{getDevice?.data && (
<DeviceActionDropdown
@@ -319,6 +320,9 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
<DeviceStatisticsCard serialNumber={serialNumber} />
<WifiAnalysisCard serialNumber={serialNumber} />
<DeviceLogsCard serialNumber={serialNumber} />
{getDevice.data && getDevice.data?.hasRADIUSSessions > 0 ? (
<RadiusClientsCard serialNumber={serialNumber} />
) : null}
<RestrictionsCard serialNumber={serialNumber} />
<Box />
<DeviceNotes serialNumber={serialNumber} />

View File

@@ -24,7 +24,7 @@ import DeviceUptimeCell from './Uptime';
import { CardBody } from 'components/Containers/Card/CardBody';
import { DataGrid } from 'components/DataTables/DataGrid';
import { DataGridColumn, useDataGrid } from 'components/DataTables/DataGrid/useDataGrid';
import DeviceSearchBar from 'components/DeviceSearchBar';
import GlobalSearchBar from 'components/GlobalSearchBar';
import FormattedDate from 'components/InformationDisplays/FormattedDate';
import { ConfigureModal } from 'components/Modals/ConfigureModal';
import { EventQueueModal } from 'components/Modals/EventQueueModal';
@@ -644,7 +644,7 @@ const DeviceListCard = () => {
header={{
title: `${getCount.data?.count} ${t('devices.title')}`,
objectListed: t('devices.title'),
leftContent: <DeviceSearchBar />,
leftContent: <GlobalSearchBar />,
}}
columns={columns}
data={data}