Merge pull request #58 from stephb9959/dev

Version 2.3.1
This commit is contained in:
Charles
2021-10-26 17:16:05 -04:00
committed by GitHub
25 changed files with 738 additions and 285 deletions

98
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "ucentral-client",
"version": "2.2.12",
"version": "2.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "ucentral-client",
"version": "2.2.12",
"version": "2.3.2",
"dependencies": {
"@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1",
@@ -32,7 +32,7 @@
"react-tooltip": "^4.2.21",
"react-widgets": "^5.1.1",
"sass": "^1.35.1",
"ucentral-libs": "^0.9.71",
"ucentral-libs": "^0.9.89",
"uuid": "^8.3.2"
},
"devDependencies": {
@@ -76,7 +76,7 @@
"terser-webpack-plugin": "^5.1.4",
"webpack": "^5.40.0",
"webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.7.2",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.8.0"
}
@@ -2955,9 +2955,9 @@
}
},
"node_modules/@webpack-cli/configtest": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.4.tgz",
"integrity": "sha512-cs3XLy+UcxiP6bj0A6u7MLLuwdXJ1c3Dtc0RkKg+wiI1g/Ti1om8+/2hc2A2B60NbBNAbMgyBMHvyymWm/j4wQ==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.0.tgz",
"integrity": "sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg==",
"dev": true,
"peerDependencies": {
"webpack": "4.x.x || 5.x.x",
@@ -2965,9 +2965,9 @@
}
},
"node_modules/@webpack-cli/info": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.3.0.tgz",
"integrity": "sha512-ASiVB3t9LOKHs5DyVUcxpraBXDOKubYu/ihHhU+t1UPpxsivg6Od2E2qU4gJCekfEddzRBzHhzA/Acyw/mlK/w==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.0.tgz",
"integrity": "sha512-F6b+Man0rwE4n0409FyAJHStYA5OIZERxmnUfLVwv0mc0V1wLad3V7jqRlMkgKBeAq07jUvglacNaa6g9lOpuw==",
"dev": true,
"dependencies": {
"envinfo": "^7.7.3"
@@ -2977,9 +2977,9 @@
}
},
"node_modules/@webpack-cli/serve": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.5.2.tgz",
"integrity": "sha512-vgJ5OLWadI8aKjDlOH3rb+dYyPd2GTZuQC/Tihjct6F9GpXGZINo3Y/IVuZVTM1eDQB+/AOsjPUWH/WySDaXvw==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.0.tgz",
"integrity": "sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA==",
"dev": true,
"peerDependencies": {
"webpack-cli": "4.x.x"
@@ -14842,9 +14842,9 @@
}
},
"node_modules/ucentral-libs": {
"version": "0.9.71",
"resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.71.tgz",
"integrity": "sha512-5TqSqbya2WO9wzfTnpVemVhG2sOoB8PwdXlU/XvVGKCUFuGBYbeDIcW2O5VRwSZsuwQkaB3r6SdAeOMdIZ9Obw==",
"version": "0.9.89",
"resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.89.tgz",
"integrity": "sha512-KH1RSQzN7AJTsryibxr3kfoYgr1Y80lQP5cB8mOcbqYamv3PS9F2QJHTQNoNOwQGASFd6D+WdnAj/viqV1wM0A==",
"dependencies": {
"@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1",
@@ -15425,23 +15425,22 @@
}
},
"node_modules/webpack-cli": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.8.0.tgz",
"integrity": "sha512-+iBSWsX16uVna5aAYN6/wjhJy1q/GKk4KjKvfg90/6hykCTSgozbfz5iRgDTSJt/LgSbYxdBX3KBHeobIs+ZEw==",
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.1.tgz",
"integrity": "sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ==",
"dev": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^1.0.4",
"@webpack-cli/info": "^1.3.0",
"@webpack-cli/serve": "^1.5.2",
"colorette": "^1.2.1",
"@webpack-cli/configtest": "^1.1.0",
"@webpack-cli/info": "^1.4.0",
"@webpack-cli/serve": "^1.6.0",
"colorette": "^2.0.14",
"commander": "^7.0.0",
"execa": "^5.0.0",
"fastest-levenshtein": "^1.0.12",
"import-local": "^3.0.2",
"interpret": "^2.2.0",
"rechoir": "^0.7.0",
"v8-compile-cache": "^2.2.0",
"webpack-merge": "^5.7.3"
},
"bin": {
@@ -15468,6 +15467,12 @@
}
}
},
"node_modules/webpack-cli/node_modules/colorette": {
"version": "2.0.16",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
"dev": true
},
"node_modules/webpack-cli/node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
@@ -18590,25 +18595,25 @@
}
},
"@webpack-cli/configtest": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.4.tgz",
"integrity": "sha512-cs3XLy+UcxiP6bj0A6u7MLLuwdXJ1c3Dtc0RkKg+wiI1g/Ti1om8+/2hc2A2B60NbBNAbMgyBMHvyymWm/j4wQ==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.0.tgz",
"integrity": "sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg==",
"dev": true,
"requires": {}
},
"@webpack-cli/info": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.3.0.tgz",
"integrity": "sha512-ASiVB3t9LOKHs5DyVUcxpraBXDOKubYu/ihHhU+t1UPpxsivg6Od2E2qU4gJCekfEddzRBzHhzA/Acyw/mlK/w==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.0.tgz",
"integrity": "sha512-F6b+Man0rwE4n0409FyAJHStYA5OIZERxmnUfLVwv0mc0V1wLad3V7jqRlMkgKBeAq07jUvglacNaa6g9lOpuw==",
"dev": true,
"requires": {
"envinfo": "^7.7.3"
}
},
"@webpack-cli/serve": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.5.2.tgz",
"integrity": "sha512-vgJ5OLWadI8aKjDlOH3rb+dYyPd2GTZuQC/Tihjct6F9GpXGZINo3Y/IVuZVTM1eDQB+/AOsjPUWH/WySDaXvw==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.0.tgz",
"integrity": "sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA==",
"dev": true,
"requires": {}
},
@@ -27711,9 +27716,9 @@
}
},
"ucentral-libs": {
"version": "0.9.71",
"resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.71.tgz",
"integrity": "sha512-5TqSqbya2WO9wzfTnpVemVhG2sOoB8PwdXlU/XvVGKCUFuGBYbeDIcW2O5VRwSZsuwQkaB3r6SdAeOMdIZ9Obw==",
"version": "0.9.89",
"resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.89.tgz",
"integrity": "sha512-KH1RSQzN7AJTsryibxr3kfoYgr1Y80lQP5cB8mOcbqYamv3PS9F2QJHTQNoNOwQGASFd6D+WdnAj/viqV1wM0A==",
"requires": {
"@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1",
@@ -28200,26 +28205,31 @@
}
},
"webpack-cli": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.8.0.tgz",
"integrity": "sha512-+iBSWsX16uVna5aAYN6/wjhJy1q/GKk4KjKvfg90/6hykCTSgozbfz5iRgDTSJt/LgSbYxdBX3KBHeobIs+ZEw==",
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.1.tgz",
"integrity": "sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ==",
"dev": true,
"requires": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^1.0.4",
"@webpack-cli/info": "^1.3.0",
"@webpack-cli/serve": "^1.5.2",
"colorette": "^1.2.1",
"@webpack-cli/configtest": "^1.1.0",
"@webpack-cli/info": "^1.4.0",
"@webpack-cli/serve": "^1.6.0",
"colorette": "^2.0.14",
"commander": "^7.0.0",
"execa": "^5.0.0",
"fastest-levenshtein": "^1.0.12",
"import-local": "^3.0.2",
"interpret": "^2.2.0",
"rechoir": "^0.7.0",
"v8-compile-cache": "^2.2.0",
"webpack-merge": "^5.7.3"
},
"dependencies": {
"colorette": {
"version": "2.0.16",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
"dev": true
},
"commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "ucentral-client",
"version": "2.2.12",
"version": "2.3.2",
"dependencies": {
"@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1",
@@ -26,7 +26,7 @@
"react-tooltip": "^4.2.21",
"react-widgets": "^5.1.1",
"sass": "^1.35.1",
"ucentral-libs": "^0.9.71",
"ucentral-libs": "^0.9.89",
"uuid": "^8.3.2"
},
"main": "index.js",
@@ -91,7 +91,7 @@
"terser-webpack-plugin": "^5.1.4",
"webpack": "^5.40.0",
"webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.7.2",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.8.0"
},

View File

@@ -44,6 +44,7 @@
"certificates": "Zertifikate",
"clear": "Löschen",
"close": "Schließen",
"code": "Code",
"command": "Befehl",
"commands": "Befehle",
"commands_executed": "Ausgeführte Befehle",
@@ -125,6 +126,7 @@
"na": "(unbekannt)",
"need_date": "Du brauchst ein Datum...",
"no": "Nein",
"no_addresses_found": "Keine Adressen gefunden",
"no_devices_found": "Keine Geräte gefunden",
"no_items": "Keine Gegenstände",
"none": "Keiner",
@@ -150,6 +152,7 @@
"second": "zweite",
"seconds": "sekunden",
"seconds_elapsed": "Sekunden verstrichen",
"select": "wählen",
"serial_number": "Seriennummer",
"show_all": "Zeige alles",
"socket_connection_closed": "Verbindung geschlossen!",
@@ -226,6 +229,7 @@
"used_by": "Benutzt von",
"used_by_details": "{{entities}} Entitäten, {{venues}} Veranstaltungsorte und {{devices}} Geräte",
"uuid": "Konfigurations-ID",
"view_config": "Konfiguration anzeigen",
"view_in_use": "In Verwendung anzeigen",
"view_json": "Rohe Konfiguration anzeigen"
},
@@ -243,13 +247,15 @@
"access_pin": "Zugangs-PIN",
"add_contact": "Kontakt hinzufügen",
"create_contact": "Kontakt erstellen",
"delete": "Kontakt löschen?",
"error_assign": "Fehler beim Versuch, Kontakt zuzuweisen: {{error}}",
"error_creation": "Fehler beim Versuch, einen Kontakt zu erstellen: {{error}}",
"error_delete": "Fehler beim Löschen",
"error_delete": "Fehler beim Versuch, den Kontakt zu löschen: {{error}}",
"error_fetching_list": "Fehler beim Abrufen der Kontakte",
"error_fetching_single": "Fehler beim Abrufen des Kontakts: {{error}}",
"error_unassign": "Fehler beim Versuch, die Zuweisung des Kontakts aufzuheben: {{error}}",
"first_name": "Vorname",
"identifier": "Identifikator",
"initials": "Initialen",
"last_name": "Nachname",
"no_associated_contact": "Kein zugehöriger Kontakt",
@@ -305,8 +311,10 @@
"error_fetch_entity": "Fehler beim Abrufen von Entitätsinformationen",
"error_fetching": "Fehler beim Abrufen von Entitäten",
"error_saving": "Fehler beim Speichern der Entität",
"higher_priority": "Stellen Sie eine höhere Priorität ein",
"ip_detection": "IP-Erkennung",
"ip_formats": "Sie können IPv4- oder IPv6-Adressen in den folgenden Formaten hinzufügen:",
"lower_priority": "Niedrigere Priorität setzen",
"need_select_entity": "sSie müssen eine Entität aus der folgenden Tabelle auswählen",
"no_ips": "Keine IPs ausgewählt",
"not_assigned": "Nicht zugeordnet",
@@ -449,7 +457,28 @@
"venue": "Tagungsort"
},
"location": {
"title": "Standorte"
"add": "Ort hinzufügen",
"building_name": "Gebäudename",
"city": "Stadt",
"country": "Land",
"create": "Standort erstellen",
"delete": "Ort löschen?",
"error_assign": "Fehler beim Versuch, den Standort zuzuweisen: {{error}}",
"error_creation": "Fehler beim Versuch, Standorte zu erstellen: {{error}}",
"error_delete": "Fehler beim Löschen des Standorts: {{error}}",
"error_fetching_single": "Fehler beim Versuch, den Standort abzurufen: {{error}}",
"geocode": "GeoCode",
"no_associated": "Kein zugeordneter Standort",
"postal": "Postleitzahl",
"search": "Suchen Sie nach Standorten, um die Felder unten automatisch auszufüllen",
"state": "Zustand",
"street_address": "Adresse",
"successful_creation": "Standort erfolgreich erstellt!",
"successful_delete": "Standort erfolgreich gelöscht!",
"successful_update": "Standort erfolgreich aktualisiert!",
"successfully_assigned": "Standort erfolgreich zugewiesen!",
"title": "Standorte",
"update_error": "Fehler beim Aktualisieren des Standorts: {{error}}"
},
"login": {
"account_verification": "Bestätigung des Kontos",
@@ -572,6 +601,7 @@
"avatar": "Dein Avatar",
"avatar_file": "Dein Avatar (max. 2 MB)",
"check_phone": "Bitte überprüfen Sie Ihr Telefon auf Ihren Validierungscode",
"confirm_new_password": "Bestätige neues Passwort",
"create": "Benutzer erstellen",
"create_failure": "Fehler beim Erstellen des Benutzers. Bitte stellen Sie sicher, dass diese E-Mail-Adresse nicht bereits mit einem Konto verknüpft ist.",
"create_success": "Benutzer erfolgreich erstellt",
@@ -592,6 +622,7 @@
"id": "Benutzeridentifikation.",
"last_login": "Letzte Anmeldung",
"login_id": "Anmelde-ID.",
"make_sure_same_password": "Stellen Sie sicher, dass beide Passwörter gleich und gültig sind",
"my_profile": "Mein Profil",
"name": "Name",
"new_code_sent": "Neuer Code gesendet!",

View File

@@ -44,6 +44,7 @@
"certificates": "Certificates",
"clear": "Clear",
"close": "Close",
"code": "Code",
"command": "Command",
"commands": "Commands",
"commands_executed": "Commands Executed",
@@ -125,6 +126,7 @@
"na": "N/A",
"need_date": "You need a date...",
"no": "No",
"no_addresses_found": "No Addresses Found",
"no_devices_found": "No Devices Found",
"no_items": "No Items",
"none": "None",
@@ -150,6 +152,7 @@
"second": "second",
"seconds": "seconds",
"seconds_elapsed": "Seconds elapsed",
"select": "Select",
"serial_number": "Serial Number",
"show_all": "Show All",
"socket_connection_closed": "Connection closed!",
@@ -206,7 +209,7 @@
"error_update": "Error: {{error}}",
"explanation": "Explanation",
"key_pem_explanation": "Please select .pem file",
"last_configuration_change": "Config Change",
"last_configuration_change": "Configuration Change",
"last_configuration_download": "Last Configuration Download",
"location": "Location",
"need_device_type": "Every configuration needs to support at least one device type",
@@ -226,6 +229,7 @@
"used_by": "Used By",
"used_by_details": "{{entities}} Entities, {{venues}} Venues and {{devices}} Devices",
"uuid": "Config ID",
"view_config": "View Configuration",
"view_in_use": "View In Use",
"view_json": "View raw JSON"
},
@@ -243,13 +247,15 @@
"access_pin": "Access PIN",
"add_contact": "Add Contact",
"create_contact": "Create Contact",
"delete": "Delete Contact?",
"error_assign": "Error while trying to assign contact: {{error}}",
"error_creation": "Error while trying to create contact: {{error}}",
"error_delete": "Error trying to delete",
"error_delete": "Error trying to delete contact: {{error}}",
"error_fetching_list": "Error fetching contacts",
"error_fetching_single": "Error fetching contact: {{error}}",
"error_unassign": "Error while trying to unassign contact: {{error}}",
"first_name": "First Name",
"identifier": "Identifier",
"initials": "Initials",
"last_name": "Last Name",
"no_associated_contact": "No Associated Contact",
@@ -305,8 +311,10 @@
"error_fetch_entity": "Error while fetching entity information",
"error_fetching": "Error while fetching entities",
"error_saving": "Error while saving entity",
"higher_priority": "Make Higher Priority",
"ip_detection": "IP Detection",
"ip_formats": "You can add IPv4 or IPv6 addresses in the following formats:",
"lower_priority": "Make Lower Priority",
"need_select_entity": "You need to select an entity from the table below",
"no_ips": "No IPs selected",
"not_assigned": "Not Assigned",
@@ -449,7 +457,28 @@
"venue": "Venue"
},
"location": {
"title": "Locations"
"add": "Add Location",
"building_name": "Building Name",
"city": "City",
"country": "Country",
"create": "Create Location",
"delete": "Delete Location?",
"error_assign": "Error while trying to assign location: {{error}}",
"error_creation": "Error while trying to create locations: {{error}}",
"error_delete": "Error while deleting location: {{error}}",
"error_fetching_single": "Error while trying to fetch location: {{error}}",
"geocode": "GeoCode",
"no_associated": "No Associated Location",
"postal": "ZIP/Postal Code",
"search": "Search locations to auto fill the fields below",
"state": "State",
"street_address": "Street Address",
"successful_creation": "Location Successfully Created!",
"successful_delete": "Successfully Deleted Location!",
"successful_update": "Successfully updated location!",
"successfully_assigned": "Location Successfully Assigned!",
"title": "Locations",
"update_error": "Error updating location: {{error}}"
},
"login": {
"account_verification": "Account Verification",
@@ -572,6 +601,7 @@
"avatar": "Your Avatar",
"avatar_file": "Your Avatar (max. of 2 MB)",
"check_phone": "Please check your phone for your validation code",
"confirm_new_password": "Confirm New Password",
"create": "Create User",
"create_failure": "Error while creating user. Please make sure this email address is not already linked to an account.",
"create_success": "User Created Successfully",
@@ -592,6 +622,7 @@
"id": "User Id.",
"last_login": "Last Login",
"login_id": "Login Id.",
"make_sure_same_password": "Make sure both passwords are the same and are valid",
"my_profile": "My Profile",
"name": "Name",
"new_code_sent": "New Code Sent!",

View File

@@ -44,6 +44,7 @@
"certificates": "Certificados",
"clear": "Claro",
"close": "Cerrar",
"code": "Código",
"command": "Mando",
"commands": "comandos",
"commands_executed": "Comandos ejecutados",
@@ -125,6 +126,7 @@
"na": "N / A",
"need_date": "Necesitas una cita ...",
"no": "No",
"no_addresses_found": "No se encontraron direcciones",
"no_devices_found": "No se encontraron dispositivos",
"no_items": "No hay articulos",
"none": "Ninguna",
@@ -150,6 +152,7 @@
"second": "segundo",
"seconds": "segundos",
"seconds_elapsed": "Segundos transcurridos",
"select": "Seleccionar",
"serial_number": "Número de serie",
"show_all": "Mostrar todo",
"socket_connection_closed": "¡Conexión cerrada!",
@@ -226,6 +229,7 @@
"used_by": "Usado por",
"used_by_details": "{{entities}} Entidades, {{venues}} lugares y {{devices}} dispositivos",
"uuid": "ID de configuración",
"view_config": "Ver configuración",
"view_in_use": "Ver en uso",
"view_json": "Ver JSON sin procesar"
},
@@ -243,13 +247,15 @@
"access_pin": "PIN de acceso",
"add_contact": "Agregar contacto",
"create_contact": "Crear contacto",
"delete": "¿Borrar contacto?",
"error_assign": "Error al intentar asignar el contacto: {{error}}",
"error_creation": "Error al intentar crear contacto: {{error}}",
"error_delete": "Error al intentar eliminar",
"error_delete": "Error al intentar eliminar el contacto: {{error}}",
"error_fetching_list": "Error al obtener los contactos",
"error_fetching_single": "Error al obtener el contacto: {{error}}",
"error_unassign": "Error al intentar anular la asignación del contacto: {{error}}",
"first_name": "Nombre de pila",
"identifier": "Identificador",
"initials": "Iniciales",
"last_name": "Apellido",
"no_associated_contact": "Sin contacto asociado",
@@ -305,8 +311,10 @@
"error_fetch_entity": "Error al obtener la información de la entidad",
"error_fetching": "Error al recuperar entidades",
"error_saving": "Error al guardar la entidad",
"higher_priority": "Dar mayor prioridad",
"ip_detection": "Detección de IP",
"ip_formats": "Puede agregar direcciones IPv4 o IPv6 en los siguientes formatos:",
"lower_priority": "Hacer una prioridad más baja",
"need_select_entity": "Debe seleccionar una entidad de la siguiente tabla",
"no_ips": "No se seleccionaron direcciones IP",
"not_assigned": "No asignado",
@@ -449,7 +457,28 @@
"venue": "Lugar de encuentro"
},
"location": {
"title": "Ubicaciones"
"add": "Añade una ubicación",
"building_name": "Nombre del edificio",
"city": "ciudad",
"country": "País",
"create": "Crear ubicación",
"delete": "¿Eliminar ubicación?",
"error_assign": "Error al intentar asignar la ubicación: {{error}}",
"error_creation": "Error al intentar crear ubicaciones: {{error}}",
"error_delete": "Error al eliminar la ubicación: {{error}}",
"error_fetching_single": "Error al intentar obtener la ubicación: {{error}}",
"geocode": "Geocode",
"no_associated": "Sin ubicación asociada",
"postal": "código postal",
"search": "Busque ubicaciones para completar automáticamente los campos a continuación",
"state": "Estado",
"street_address": "Dirección",
"successful_creation": "¡Ubicación creada con éxito!",
"successful_delete": "¡Ubicación eliminada con éxito!",
"successful_update": "¡Ubicación actualizada con éxito!",
"successfully_assigned": "¡Ubicación asignada correctamente!",
"title": "Ubicaciones",
"update_error": "Error al actualizar la ubicación: {{error}}"
},
"login": {
"account_verification": "Verificación de la cuenta",
@@ -572,6 +601,7 @@
"avatar": "Tu avatar",
"avatar_file": "Tu avatar (máx. De 2 MB)",
"check_phone": "Por favor revise su teléfono para su código de validación",
"confirm_new_password": "confirmar nueva contraseña",
"create": "Crear usuario",
"create_failure": "Error al crear usuario. Asegúrese de que esta dirección de correo electrónico no esté vinculada a una cuenta.",
"create_success": "Usuario creado con éxito",
@@ -592,6 +622,7 @@
"id": "Id. De usuario",
"last_login": "Último acceso",
"login_id": "Ingresar identificación.",
"make_sure_same_password": "Asegúrese de que ambas contraseñas sean iguales y válidas",
"my_profile": "Mi perfil",
"name": "Nombre",
"new_code_sent": "¡Nuevo código enviado!",

View File

@@ -44,6 +44,7 @@
"certificates": "Certificats",
"clear": "Clair",
"close": "Fermer",
"code": "Code",
"command": "Commander",
"commands": "Les commandes",
"commands_executed": "commandes exécutées",
@@ -125,6 +126,7 @@
"na": "N / A",
"need_date": "Vous avez besoin d'un rendez-vous...",
"no": "Non",
"no_addresses_found": "Aucune adresse trouvée",
"no_devices_found": "Aucun périphérique trouvé",
"no_items": "Pas d'objet",
"none": "Aucun",
@@ -150,6 +152,7 @@
"second": "seconde",
"seconds": "secondes",
"seconds_elapsed": "Secondes écoulées",
"select": "sélectionner",
"serial_number": "Numéro de série",
"show_all": "Montre tout",
"socket_connection_closed": "Connexion fermée !",
@@ -226,6 +229,7 @@
"used_by": "Utilisé par",
"used_by_details": "{{entities}} Entités, {{venues}} Lieux et {{devices}} Appareils",
"uuid": "Identifiant de configuration",
"view_config": "Afficher la configuration",
"view_in_use": "Afficher en cours d'utilisation",
"view_json": "Afficher le JSON brut"
},
@@ -243,13 +247,15 @@
"access_pin": "NIP d'accès",
"add_contact": "Ajouter le contact",
"create_contact": "Créer un contact",
"delete": "Effacer le contact?",
"error_assign": "Erreur lors de la tentative d'attribution du contact : {{error}}",
"error_creation": "Erreur lors de la tentative de création du contact : {{error}}",
"error_delete": "Erreur lors de la tentative de suppression",
"error_delete": "Erreur lors de la tentative de suppression du contact : {{error}}",
"error_fetching_list": "Erreur lors de la récupération des contacts",
"error_fetching_single": "Erreur lors de la récupération du contact : {{error}}",
"error_unassign": "Erreur lors de la tentative de désattribution du contact : {{error}}",
"first_name": "Prénom",
"identifier": "Identifiant",
"initials": "Initiales",
"last_name": "Nom de famille",
"no_associated_contact": "Aucun contact associé",
@@ -305,8 +311,10 @@
"error_fetch_entity": "Erreur lors de la récupération des informations sur l'entité",
"error_fetching": "Erreur lors de la récupération des entités",
"error_saving": "Erreur lors de l'enregistrement de l'entité",
"higher_priority": "Faire une priorité plus élevée",
"ip_detection": "Détection IP",
"ip_formats": "Vous pouvez ajouter des adresses IPv4 ou IPv6 aux formats suivants :",
"lower_priority": "Faire une priorité inférieure",
"need_select_entity": "Vous devez sélectionner une entité dans le tableau ci-dessous",
"no_ips": "Aucune adresse IP sélectionnée",
"not_assigned": "Non attribué",
@@ -449,7 +457,28 @@
"venue": "Lieu"
},
"location": {
"title": "Emplacements"
"add": "Ajouter un emplacement",
"building_name": "Nom du bâtiment",
"city": "Ville",
"country": "Pays",
"create": "Créer un lieu",
"delete": "Supprimer le lieu?",
"error_assign": "Erreur lors de la tentative d'attribution de l'emplacement : {{error}}",
"error_creation": "Erreur lors de la tentative de création d'établissements : {{error}}",
"error_delete": "Erreur lors de la suppression de l'emplacement : {{error}}",
"error_fetching_single": "Erreur lors de la tentative de récupération de l'emplacement : {{error}}",
"geocode": "Géocode",
"no_associated": "Aucun emplacement associé",
"postal": "Zip / code postal",
"search": "Recherchez des emplacements pour remplir automatiquement les champs ci-dessous",
"state": "Etat",
"street_address": "Adresse de rue",
"successful_creation": "Emplacement créé avec succès !",
"successful_delete": "Emplacement supprimé avec succès !",
"successful_update": "Emplacement mis à jour avec succès !",
"successfully_assigned": "Emplacement attribué avec succès !",
"title": "Emplacements",
"update_error": "Erreur lors de la mise à jour de l'emplacement : {{error}}"
},
"login": {
"account_verification": "Vérification de compte",
@@ -572,6 +601,7 @@
"avatar": "Votre avatar",
"avatar_file": "Votre Avatar (max. de 2 Mo)",
"check_phone": "Veuillez vérifier votre téléphone pour votre code de validation",
"confirm_new_password": "Confirmer le nouveau mot de passe",
"create": "Créer un utilisateur",
"create_failure": "Erreur lors de la création de l'utilisateur. Veuillez vous assurer que cette adresse e-mail n'est pas déjà liée à un compte.",
"create_success": "L'utilisateur a été créé avec succès",
@@ -592,6 +622,7 @@
"id": "Identifiant d'utilisateur.",
"last_login": "Dernière connexion",
"login_id": "Identifiant de connexion.",
"make_sure_same_password": "Assurez-vous que les deux mots de passe sont identiques et valides",
"my_profile": "Mon profil",
"name": "Prénom",
"new_code_sent": "Nouveau code envoyé !",

View File

@@ -44,6 +44,7 @@
"certificates": "Certificados",
"clear": "Claro",
"close": "Perto",
"code": "Código",
"command": "Comando",
"commands": "comandos",
"commands_executed": "Comandos Executados",
@@ -125,6 +126,7 @@
"na": "N / D",
"need_date": "Você precisa de um encontro ...",
"no": "Não",
"no_addresses_found": "Nenhum endereço encontrado",
"no_devices_found": "Nenhum dispositivo encontrado",
"no_items": "Nenhum item",
"none": "Nenhum",
@@ -150,6 +152,7 @@
"second": "segundo",
"seconds": "segundos",
"seconds_elapsed": "Segundos decorridos",
"select": "Selecione",
"serial_number": "Número de série",
"show_all": "mostre tudo",
"socket_connection_closed": "Conexão fechada!",
@@ -226,6 +229,7 @@
"used_by": "Usado por",
"used_by_details": "{{entities}} Entidades, {{venues}} Locais e {{devices}} Dispositivos",
"uuid": "ID de configuração",
"view_config": "Ver configuração",
"view_in_use": "Visualização em uso",
"view_json": "Exibir JSON bruto"
},
@@ -243,13 +247,15 @@
"access_pin": "PIN de acesso",
"add_contact": "Adicionar contato",
"create_contact": "Criar Contato",
"delete": "Excluir contato?",
"error_assign": "Erro ao tentar atribuir contato: {{error}}",
"error_creation": "Erro ao tentar criar contato: {{error}}",
"error_delete": "Erro ao tentar excluir",
"error_delete": "Erro ao tentar excluir contato: {{error}}",
"error_fetching_list": "Erro ao buscar contatos",
"error_fetching_single": "Erro ao buscar contato: {{error}}",
"error_unassign": "Erro ao tentar cancelar a atribuição do contato: {{error}}",
"first_name": "Primeiro nome",
"identifier": "Identificador",
"initials": "Iniciais",
"last_name": "Último nome",
"no_associated_contact": "Nenhum contato associado",
@@ -305,8 +311,10 @@
"error_fetch_entity": "Erro ao buscar informações da entidade",
"error_fetching": "Erro ao buscar entidades",
"error_saving": "Erro ao salvar entidade",
"higher_priority": "Dê maior prioridade",
"ip_detection": "Detecção de IP",
"ip_formats": "Você pode adicionar endereços IPv4 ou IPv6 nos seguintes formatos:",
"lower_priority": "Faça menor prioridade",
"need_select_entity": "Você precisa selecionar uma entidade da tabela abaixo",
"no_ips": "Nenhum IP selecionado",
"not_assigned": "Não atribuído",
@@ -449,7 +457,28 @@
"venue": "Local"
},
"location": {
"title": "Localizações"
"add": "Adicionar Local",
"building_name": "nome do edifício",
"city": "Cidade",
"country": "País",
"create": "Criar local",
"delete": "Excluir localização?",
"error_assign": "Erro ao tentar atribuir local: {{error}}",
"error_creation": "Erro ao tentar criar locais: {{error}}",
"error_delete": "Erro ao excluir localização: {{error}}",
"error_fetching_single": "Erro ao tentar buscar localização: {{error}}",
"geocode": "GeoCode",
"no_associated": "Sem localização associada",
"postal": "CEP / Código Postal",
"search": "Pesquise locais para preencher automaticamente os campos abaixo",
"state": "Estado",
"street_address": "Endereço",
"successful_creation": "Local criado com sucesso!",
"successful_delete": "Local excluído com sucesso!",
"successful_update": "Local atualizado com sucesso!",
"successfully_assigned": "Local atribuído com sucesso!",
"title": "Localizações",
"update_error": "Erro ao atualizar local: {{error}}"
},
"login": {
"account_verification": "Verificação de conta",
@@ -572,6 +601,7 @@
"avatar": "Seu avatar",
"avatar_file": "Seu avatar (máx. De 2 MB)",
"check_phone": "Por favor, verifique o seu telefone para o seu código de validação",
"confirm_new_password": "confirme a nova senha",
"create": "Criar usuário",
"create_failure": "Erro ao criar usuário. Certifique-se de que este endereço de e-mail ainda não esteja vinculado a uma conta.",
"create_success": "Usuário criado com sucesso",
@@ -592,6 +622,7 @@
"id": "ID do usuário.",
"last_login": "Último login",
"login_id": "Identificação de usuário.",
"make_sure_same_password": "Certifique-se de que ambas as senhas são iguais e válidas",
"my_profile": "Meu perfil",
"name": "Nome",
"new_code_sent": "Novo código enviado!",

View File

@@ -188,9 +188,11 @@ const DeviceCommands = () => {
};
const columns = [
{ key: 'command', label: t('common.command'), _style: { width: '15%' } },
{ key: 'completed', label: t('common.completed'), filter: false, _style: { width: '20%' } },
{ key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } },
{ key: 'command', label: t('common.command'), _style: { width: '15%' } },
{ key: 'executed', label: t('common.executed'), filter: false, _style: { width: '20%' } },
{ key: 'completed', label: t('common.completed'), filter: false, _style: { width: '20%' } },
{ key: 'errorCode', label: t('common.code'), filter: false, _style: { width: '5%' } },
{
key: 'show_buttons',
label: '',
@@ -244,6 +246,7 @@ const DeviceCommands = () => {
return (
<div>
<CWidgetDropdown
className="m-0"
inverse="true"
color="gradient-primary"
header={t('commands.title')}
@@ -270,21 +273,29 @@ const DeviceCommands = () => {
sorterValue={{ column: 'created', desc: 'true' }}
scopedSlots={{
completed: (item) => (
<td>
<td className="align-middle">
{item.completed && item.completed !== 0
? prettyDate(item.completed)
: 'Pending'}
</td>
),
executed: (item) => (
<td className="align-middle">
{item.executed && item.executed !== 0
? prettyDate(item.executed)
: 'Pending'}
</td>
),
submitted: (item) => (
<td>
<td className="align-middle">
{item.submitted && item.submitted !== ''
? prettyDate(item.submitted)
: 'Pending'}
</td>
),
errorCode: (item) => <td className="align-middle">{item.errorCode}</td>,
show_buttons: (item, index) => (
<td>
<td className="align-middle">
<CButtonToolbar
role="group"
className="justify-content-flex-end"

View File

@@ -0,0 +1,64 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
CRow,
CCol,
CCard,
CCardBody,
CCardHeader,
CLabel,
CPopover,
CButton,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilSync } from '@coreui/icons';
import { prettyDate } from 'utils/helper';
import { useTranslation } from 'react-i18next';
import { CopyToClipboardButton } from 'ucentral-libs';
const ConfigurationDisplay = ({ getData, deviceConfig }) => {
const { t } = useTranslation();
return (
<CCard className="m-0">
<CCardHeader className="p-1">
<div className="d-flex flex-row-reverse align-items-center">
<div className="text-right">
<CPopover content={t('common.refresh')}>
<CButton size="sm" color="primary" variant="outline" onClick={getData}>
<CIcon content={cilSync} />
</CButton>
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody>
<h5>
{t('configuration.title')}
<CopyToClipboardButton
t={t}
size="sm"
content={JSON.stringify(deviceConfig?.configuration ?? {})}
/>
</h5>
<CRow>
<CCol md="2" xl="2" xxl="1">
<CLabel>{t('configuration.last_configuration_change')}: </CLabel>
</CCol>
<CCol>{prettyDate(deviceConfig?.lastConfigurationChange)}</CCol>
</CRow>
<pre className="ignore">{JSON.stringify(deviceConfig?.configuration ?? {}, null, 4)}</pre>
</CCardBody>
</CCard>
);
};
ConfigurationDisplay.propTypes = {
getData: PropTypes.func.isRequired,
deviceConfig: PropTypes.instanceOf(Object),
};
ConfigurationDisplay.defaultProps = {
deviceConfig: null,
};
export default ConfigurationDisplay;

View File

@@ -122,7 +122,7 @@ const DeviceActions = () => {
return (
<CCard>
<CCardHeader>
<CCardHeader className="p-1">
<div className="text-value-lg">{t('actions.title')}</div>
</CCardHeader>
<CCardBody>
@@ -138,7 +138,7 @@ const DeviceActions = () => {
</CButton>
</CCol>
</CRow>
<CRow className="mt-4">
<CRow className="my-1">
<CCol>
<CButton block color="primary" onClick={toggleUpgradeModal}>
{t('actions.firmware_upgrade')}
@@ -150,7 +150,7 @@ const DeviceActions = () => {
</CButton>
</CCol>
</CRow>
<CRow className="mt-4">
<CRow className="my-1">
<CCol>
<CButton block color="primary" onClick={toggleScanModal}>
{t('actions.wifi_scan')}
@@ -162,7 +162,7 @@ const DeviceActions = () => {
</CButton>
</CCol>
</CRow>
<CRow className="mt-4">
<CRow className="my-1">
<CCol>
<LoadingButton
isLoading={connectLoading}
@@ -177,7 +177,7 @@ const DeviceActions = () => {
</CButton>
</CCol>
</CRow>
<CRow className="mt-4">
<CRow className="my-1">
<CCol>
<CButton block color="primary" onClick={toggleQueueModal}>
{t('commands.event_queue')}

View File

@@ -2,11 +2,9 @@
import React, { useState, useEffect } from 'react';
import {
CWidgetDropdown,
CCollapse,
CButton,
CDataTable,
CCard,
CCardBody,
CRow,
CCol,
CProgress,
@@ -26,7 +24,6 @@ const DeviceHealth = () => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const [details, setDetails] = useState([]);
const [loading, setLoading] = useState(false);
const [healthChecks, setHealthChecks] = useState([]);
const [start, setStart] = useState('');
@@ -95,35 +92,11 @@ const DeviceHealth = () => {
});
};
const toggleDetails = (index) => {
const position = details.indexOf(index);
let newDetails = details.slice();
if (position !== -1) {
newDetails.splice(position, 1);
} else {
newDetails = [...details, index];
}
setDetails(newDetails);
};
const getDetails = (index, healthCheckDetails) => {
if (details.includes(index))
return <pre className="ignore">{JSON.stringify(healthCheckDetails, null, 4)}</pre>;
return <pre className="ignore" />;
};
const columns = [
{ key: 'UUID', label: t('common.config_id') },
{ key: 'recorded', label: t('common.recorded') },
{ key: 'sanity', label: t('health.sanity') },
{
key: 'show_details',
label: '',
_style: { width: '1%' },
sorter: false,
filter: false,
},
{ key: 'recorded', label: t('common.recorded'), _style: { width: '15%' } },
{ key: 'UUID', label: t('common.config_id'), _style: { width: '10%' } },
{ key: 'sanity', label: t('health.sanity'), _style: { width: '5%' } },
{ key: 'checkDetails', label: t('common.details'), _style: { width: '65%' } },
];
useEffect(() => {
@@ -184,6 +157,7 @@ const DeviceHealth = () => {
return (
<CWidgetDropdown
className="m-0"
header={t('health.title')}
text={sanityLevel ? `${sanityLevel}%` : t('common.unknown')}
value={sanityLevel ?? 100}
@@ -217,29 +191,11 @@ const DeviceHealth = () => {
UUID: (item) => <td className="align-middle">{item.UUID}</td>,
recorded: (item) => <td className="align-middle">{prettyDate(item.recorded)}</td>,
sanity: (item) => <td className="align-middle">{`${item.sanity}%`}</td>,
show_details: (item, index) => (
<td className="align-middle">
<CButton
color="primary"
variant={details.includes(index) ? '' : 'outline'}
shape="square"
size="sm"
onClick={() => {
toggleDetails(index);
}}
>
<CIcon name="cilList" size="lg" />
</CButton>
checkDetails: (item) => (
<td>
<pre className="my-0">{JSON.stringify(item.values)}</pre>
</td>
),
details: (item, index) => (
<CCollapse show={details.includes(index)}>
<CCardBody>
<h5>{t('common.details')}</h5>
<div>{getDetails(index, item.values)}</div>
</CCardBody>
</CCollapse>
),
}}
/>
{showLoadingMore && (

View File

@@ -111,9 +111,10 @@ const DeviceLogs = () => {
};
const columns = [
{ key: 'recorded', label: t('common.recorded'), _style: { width: '15%' } },
{ key: 'UUID', label: t('common.config_id'), _style: { width: '10%' } },
{ key: 'severity', label: t('device_logs.severity'), _style: { width: '5%' } },
{ key: 'log', label: t('device_logs.log') },
{ key: 'severity', label: t('device_logs.severity') },
{ key: 'recorded', label: t('common.recorded') },
{
key: 'show_details',
label: '',
@@ -167,6 +168,7 @@ const DeviceLogs = () => {
return (
<div>
<CWidgetDropdown
className="m-0"
inverse="true"
color="gradient-info"
header={t('device_logs.title')}
@@ -185,6 +187,7 @@ const DeviceLogs = () => {
<CCard>
<div className="overflow-auto" style={{ height: '250px' }}>
<CDataTable
border
items={logs ?? []}
fields={columns}
loading={loading}

View File

@@ -125,6 +125,13 @@ const EditUserModal = ({ show, toggle, userId, getUsers }) => {
}
}
const newNotes = [];
for (let i = 0; i < user.notes.value.length; i += 1) {
if (user.notes.value[i].new) newNotes.push({ note: user.notes.value[i].note });
}
parameters.notes = newNotes;
if (newData) {
const options = {
headers: {
@@ -171,29 +178,14 @@ const EditUserModal = ({ show, toggle, userId, getUsers }) => {
};
const addNote = (currentNote) => {
setLoading(true);
const parameters = {
id: userId,
notes: [{ note: currentNote }],
};
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.put(`${endpoints.owsec}/api/v1/user/${userId}`, parameters, options)
.then(() => {
getUser();
})
.catch(() => {})
.finally(() => {
setLoading(false);
});
const newNotes = [...user.notes.value];
newNotes.unshift({
note: currentNote,
new: true,
created: new Date().getTime() / 1000,
createdBy: '',
});
updateWithKey('notes', { value: newNotes });
};
useEffect(() => {

View File

@@ -1,6 +1,5 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import { CCard, CCardHeader, CCardBody, CPopover, CButton } from '@coreui/react';
import { cilSync } from '@coreui/icons';
import CIcon from '@coreui/icons-react';
@@ -10,8 +9,6 @@ import StatisticsChartList from './StatisticsChartList';
import LatestStatisticsmodal from './LatestStatisticsModal';
const DeviceStatisticsCard = () => {
const history = useHistory();
const { deviceId } = useParams();
const { t } = useTranslation();
const [showLatestModal, setShowLatestModal] = useState(false);
const [showLifetimeModal, setShowLifetimeModal] = useState(false);
@@ -24,45 +21,35 @@ const DeviceStatisticsCard = () => {
setShowLifetimeModal(!showLifetimeModal);
};
const goToAnalysis = () => {
history.push(`/devices/${deviceId}/wifianalysis`);
};
const refresh = () => {
eventBus.dispatch('refreshInterfaceStatistics', { message: 'Refresh interface statistics' });
};
return (
<div>
<CCard>
<CCardHeader>
<CCard className="m-0">
<CCardHeader className="p-1">
<div className="d-flex flex-row-reverse align-items-center">
<div className="pl-2">
<CPopover content={t('common.refresh')}>
<CButton color="primary" variant="outline" onClick={refresh}>
<CButton size="sm" color="primary" variant="outline" onClick={refresh}>
<CIcon content={cilSync} />
</CButton>
</CPopover>
</div>
<div className="pl-2">
<CButton color="primary" variant="outline" onClick={toggleLifetimeModal}>
<CButton size="sm" color="primary" variant="outline" onClick={toggleLifetimeModal}>
Lifetime Statistics
</CButton>
</div>
<div className="pl-2">
<CButton color="primary" variant="outline" onClick={toggleLatestModal}>
<CButton size="sm" color="primary" variant="outline" onClick={toggleLatestModal}>
{t('statistics.show_latest')}
</CButton>
</div>
<div>
<CButton color="primary" variant="outline" onClick={goToAnalysis}>
{t('wifi_analysis.title')}
</CButton>
</div>
<div className="text-value-lg mr-auto">{t('statistics.title')}</div>
</div>
</CCardHeader>
<CCardBody className="p-5">
<CCardBody className="p-1">
<StatisticsChartList />
</CCardBody>
</CCard>

View File

@@ -27,7 +27,7 @@ const parseDbm = (value) => {
return (4294967295 - value) * -1;
};
const WifiAnalysisPage = () => {
const WifiAnalysis = () => {
const { t } = useTranslation();
const { deviceId } = useParams();
const { currentToken, endpoints } = useAuth();
@@ -213,7 +213,7 @@ const WifiAnalysisPage = () => {
</CCol>
</CRow>
</CCardHeader>
<CCardBody className="overflow-auto" style={{ height: 'calc(100vh - 300px)' }}>
<CCardBody>
<CRow className="mb-4">
<CCol className="text-center">
<input
@@ -232,15 +232,17 @@ const WifiAnalysisPage = () => {
</h5>
</CCol>
</CRow>
<h5 className="pb-3 text-center">{t('wifi_analysis.radios')}</h5>
<RadioAnalysisTable data={selectedRadioStats ?? []} loading={loading} range={range} />
<h5 className="pt-5 pb-3 text-center">{t('wifi_analysis.associations')}</h5>
<WifiAnalysisTable
t={t}
data={selectedAssociationStats ?? []}
loading={loading}
range={range}
/>
<div className="overflow-auto" style={{ height: 'calc(100vh - 300px)' }}>
<h5 className="pb-3 text-center">{t('wifi_analysis.radios')}</h5>
<RadioAnalysisTable data={selectedRadioStats ?? []} loading={loading} range={range} />
<h5 className="pt-5 pb-3 text-center">{t('wifi_analysis.associations')}</h5>
<WifiAnalysisTable
t={t}
data={selectedAssociationStats ?? []}
loading={loading}
range={range}
/>
</div>
</CCardBody>
</CCard>
<CModal size="xl" show={showModal} onClose={toggleModal}>
@@ -270,4 +272,4 @@ const WifiAnalysisPage = () => {
);
};
export default WifiAnalysisPage;
export default WifiAnalysis;

View File

@@ -10,42 +10,16 @@ const TheLayout = () => {
const navigation = [
{
_tag: 'CSidebarNavDropdown',
_tag: 'CSidebarNavItem',
name: t('common.devices'),
icon: 'cilRouter',
_children: [
{
addLinkClass: 'c-sidebar-nav-link ml-2',
_tag: 'CSidebarNavItem',
name: t('common.dashboard'),
to: '/devicedashboard',
},
{
addLinkClass: 'c-sidebar-nav-link ml-2',
_tag: 'CSidebarNavItem',
name: t('common.table'),
to: '/devices',
},
],
to: '/devices',
},
{
_tag: 'CSidebarNavDropdown',
_tag: 'CSidebarNavItem',
name: t('firmware.title'),
icon: 'cilSave',
_children: [
{
addLinkClass: 'c-sidebar-nav-link ml-2',
_tag: 'CSidebarNavItem',
name: t('common.dashboard'),
to: '/firmwaredashboard',
},
{
addLinkClass: 'c-sidebar-nav-link ml-2',
_tag: 'CSidebarNavItem',
name: t('common.table'),
to: '/firmware',
},
],
to: '/firmware',
},
{
_tag: 'CSidebarNavItem',

View File

@@ -1,10 +1,45 @@
import React from 'react';
import React, { useState } from 'react';
import DeviceList from 'components/DeviceListTable';
import DeviceDashboard from 'components/DeviceDashboard';
import { CCard, CCardBody, CNav, CNavLink, CTabPane, CTabContent } from '@coreui/react';
import { useTranslation } from 'react-i18next';
const DeviceListPage = () => (
<div className="App">
<DeviceList />
</div>
);
const DeviceListPage = () => {
const { t } = useTranslation();
const [index, setIndex] = useState(0);
return (
<CCard>
<CCardBody className="p-0">
<CNav variant="tabs">
<CNavLink
className="font-weight-bold"
href="#"
active={index === 0}
onClick={() => setIndex(0)}
>
{t('common.table')}
</CNavLink>
<CNavLink
className="font-weight-bold"
href="#"
active={index === 1}
onClick={() => setIndex(1)}
>
{t('common.dashboard')}
</CNavLink>
</CNav>
<CTabContent>
<CTabPane active={index === 0}>
<DeviceList />
</CTabPane>
<CTabPane active={index === 1}>
<DeviceDashboard />
</CTabPane>
</CTabContent>
</CCardBody>
</CCard>
);
};
export default DeviceListPage;

View File

@@ -1,41 +1,225 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { CRow, CCol } from '@coreui/react';
import { CRow, CCol, CCard, CCardBody, CNav, CNavLink, CTabPane, CTabContent } from '@coreui/react';
import DeviceHealth from 'components/DeviceHealth';
import DeviceStatusCard from 'components/DeviceStatusCard';
import CommandHistory from 'components/CommandHistory';
import DeviceLogs from 'components/DeviceLogs';
import DeviceStatisticsCard from 'components/InterfaceStatistics';
import DeviceActionCard from 'components/DeviceActionCard';
import axiosInstance from 'utils/axiosInstance';
import { DeviceProvider } from 'ucentral-libs';
import { DeviceProvider, DeviceStatusCard, DeviceDetails, useAuth, useToast } from 'ucentral-libs';
import { useTranslation } from 'react-i18next';
import ConfigurationDisplay from 'components/ConfigurationDisplay';
import WifiAnalysis from 'components/WifiAnalysis';
const DevicePage = () => {
const { t } = useTranslation();
const { deviceId } = useParams();
const [index, setIndex] = useState(0);
const { currentToken, endpoints } = useAuth();
const { addToast } = useToast();
const [lastStats, setLastStats] = useState(null);
const [status, setStatus] = useState(null);
const [deviceConfig, setDeviceConfig] = useState(null);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const getDevice = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
let deviceInfo = null;
axiosInstance
.get(`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceId)}`, options)
.then((response) => {
deviceInfo = response.data;
if (response.data.venue !== '' || (response.data.owner !== '' && endpoints.owprov)) {
return axiosInstance.get(
`${endpoints.owprov}/api/v1/inventory/${encodeURIComponent(
deviceId,
)}?withExtendedInfo=true`,
options,
);
}
setDeviceConfig(deviceInfo);
return null;
})
.then((response) => {
if (response) setDeviceConfig({ ...deviceInfo, extendedInfo: response.data.extendedInfo });
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('device.error_fetching_device', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
});
};
const getData = () => {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
const lastStatsRequest = axiosInstance.get(
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceId)}/statistics?lastOnly=true`,
options,
);
const statusRequest = axiosInstance.get(
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceId)}/status`,
options,
);
Promise.all([lastStatsRequest, statusRequest])
.then(([newStats, newStatus]) => {
setLastStats(newStats.data);
setStatus(newStatus.data);
})
.catch(() => {
setError(true);
})
.finally(() => {
setLoading(false);
});
};
const refresh = () => {
getData();
getDevice();
};
useEffect(() => {
setError(false);
if (deviceId) {
getDevice();
getData();
}
}, [deviceId]);
return (
<div className="App">
<DeviceProvider axiosInstance={axiosInstance} serialNumber={deviceId}>
<CRow>
<CCol>
<DeviceStatusCard />
</CCol>
</CRow>
<CRow>
<CCol lg="12" xl="6">
<CommandHistory />
<DeviceStatusCard
t={t}
loading={loading}
error={error}
deviceSerialNumber={deviceId}
getData={refresh}
deviceConfig={deviceConfig}
status={status}
lastStats={lastStats}
/>
</CCol>
<CCol lg="12" xl="6">
<DeviceActionCard />
</CCol>
</CRow>
<CRow>
<CCol lg="12" xl="6">
<DeviceStatisticsCard />
</CCol>
<CCol lg="12" xl="6">
<DeviceHealth />
<DeviceLogs />
<CCol>
<CCard>
<CCardBody className="p-0">
<CNav variant="tabs">
<CNavLink
className="font-weight-bold"
href="#"
active={index === 0}
onClick={() => setIndex(0)}
>
{t('statistics.title')}
</CNavLink>
<CNavLink
className="font-weight-bold"
href="#"
active={index === 1}
onClick={() => setIndex(1)}
>
{t('common.details')}
</CNavLink>
<CNavLink
className="font-weight-bold"
href="#"
active={index === 5}
onClick={() => setIndex(5)}
>
{t('configuration.title')}
</CNavLink>
<CNavLink
className="font-weight-bold"
href="#"
active={index === 6}
onClick={() => setIndex(6)}
>
{t('wifi_analysis.title')}
</CNavLink>
<CNavLink
className="font-weight-bold"
href="#"
active={index === 2}
onClick={() => setIndex(2)}
>
{t('commands.title')}
</CNavLink>
<CNavLink
className="font-weight-bold"
href="#"
active={index === 3}
onClick={() => setIndex(3)}
>
{t('health.title')}
</CNavLink>
<CNavLink
className="font-weight-bold"
href="#"
active={index === 4}
onClick={() => setIndex(4)}
>
{t('device_logs.title')}
</CNavLink>
</CNav>
<CTabContent>
<CTabPane active={index === 0}>
{index === 0 ? <DeviceStatisticsCard /> : null}
</CTabPane>
<CTabPane active={index === 1}>
{index === 1 ? (
<DeviceDetails
t={t}
loading={loading}
getData={refresh}
deviceConfig={deviceConfig}
status={status}
lastStats={lastStats}
/>
) : null}
</CTabPane>
<CTabPane active={index === 5}>
{index === 5 ? (
<ConfigurationDisplay deviceConfig={deviceConfig} getData={refresh} />
) : null}
</CTabPane>
<CTabPane active={index === 6}>{index === 6 ? <WifiAnalysis /> : null}</CTabPane>
<CTabPane active={index === 2}>
{index === 2 ? <CommandHistory /> : null}
</CTabPane>
<CTabPane active={index === 3}>{index === 3 ? <DeviceHealth /> : null}</CTabPane>
<CTabPane active={index === 4}>{index === 4 ? <DeviceLogs /> : null}</CTabPane>
</CTabContent>
</CCardBody>
</CCard>
</CCol>
</CRow>
</DeviceProvider>

View File

@@ -2,12 +2,15 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import axiosInstance from 'utils/axiosInstance';
import { CCard, CCardBody, CNav, CNavLink, CTabPane, CTabContent } from '@coreui/react';
import { FirmwareList, useAuth, useToast } from 'ucentral-libs';
import FirmwareDashboard from 'components/FirmwareDashboard';
const FirmwareListPage = () => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { addToast } = useToast();
const [index, setIndex] = useState(0);
const [page, setPage] = useState({ selected: 0 });
const [pageCount, setPageCount] = useState(0);
const [firmwarePerPage, setFirmwarePerPage] = useState('10');
@@ -215,25 +218,54 @@ const FirmwareListPage = () => {
}, []);
return (
<FirmwareList
t={t}
loading={loading}
page={page}
pageCount={pageCount}
setPage={updatePage}
data={displayedFirmware}
firmwarePerPage={firmwarePerPage}
setFirmwarePerPage={updateFirmwarePerPage}
selectedDeviceType={selectedDeviceType}
deviceTypes={deviceTypes}
setSelectedDeviceType={updateSelectedType}
addNote={addNote}
addNoteLoading={addNoteLoading}
updateDescription={updateDescription}
updateDescriptionLoading={updateDescriptionLoading}
displayDev={displayDev}
toggleDevDisplay={toggleDevDisplay}
/>
<CCard>
<CCardBody className="p-0">
<CNav variant="tabs">
<CNavLink
className="font-weight-bold"
href="#"
active={index === 0}
onClick={() => setIndex(0)}
>
{t('common.table')}
</CNavLink>
<CNavLink
className="font-weight-bold"
href="#"
active={index === 1}
onClick={() => setIndex(1)}
>
{t('common.dashboard')}
</CNavLink>
</CNav>
<CTabContent>
<CTabPane active={index === 0}>
<FirmwareList
t={t}
loading={loading}
page={page}
pageCount={pageCount}
setPage={updatePage}
data={displayedFirmware}
firmwarePerPage={firmwarePerPage}
setFirmwarePerPage={updateFirmwarePerPage}
selectedDeviceType={selectedDeviceType}
deviceTypes={deviceTypes}
setSelectedDeviceType={updateSelectedType}
addNote={addNote}
addNoteLoading={addNoteLoading}
updateDescription={updateDescription}
updateDescriptionLoading={updateDescriptionLoading}
displayDev={displayDev}
toggleDevDisplay={toggleDevDisplay}
/>
</CTabPane>
<CTabPane active={index === 1}>
<FirmwareDashboard />
</CTabPane>
</CTabContent>
</CCardBody>
</CCard>
);
};

View File

@@ -190,6 +190,20 @@ const Login = () => {
.catch(() => {});
};
const getProvUIUrl = (token, provUrl) => {
axiosInstance
.get(`${provUrl}/api/v1/system?command=info`, {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
},
})
.then((response) => {
if (response.data.UI) setItem('owprov-ui', response.data.UI);
})
.catch(() => {});
};
const SignIn = () => {
setLoginResponse(initialResponseState);
if (signInValidation()) {
@@ -235,6 +249,7 @@ const Login = () => {
endpoints[endpoint.type] = endpoint.uri;
}
if (endpoints.owgw) getGatewayUIUrl(token, endpoints.owgw);
if (endpoints.owprov) getProvUIUrl(token, endpoints.owprov);
setItem('gateway_endpoints', JSON.stringify(endpoints));
setEndpoints(endpoints);
setCurrentToken(token);
@@ -346,6 +361,7 @@ const Login = () => {
endpoints[endpoint.type] = endpoint.uri;
}
if (endpoints.owgw) getGatewayUIUrl(token, endpoints.owgw);
if (endpoints.owprov) getProvUIUrl(token, endpoints.owprov);
setItem('gateway_endpoints', JSON.stringify(endpoints));
setEndpoints(endpoints);
setCurrentToken(token);

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { CCard, CCardBody, CCardHeader, CButton, CPopover } from '@coreui/react';
import { cilSave } from '@coreui/icons';
import { CCard, CCardBody, CCardHeader, CButton, CPopover, CButtonToolbar } from '@coreui/react';
import { cilPencil, cilSave, cilSync, cilX } from '@coreui/icons';
import CIcon from '@coreui/icons-react';
import axiosInstance from 'utils/axiosInstance';
import { testRegex } from 'utils/helper';
@@ -13,10 +13,17 @@ const initialState = {
error: false,
editable: false,
},
currentPassword: {
newPassword: {
value: '',
error: false,
editable: true,
ignore: true,
},
confirmNewPassword: {
value: '',
error: false,
editable: true,
ignore: true,
},
email: {
value: '',
@@ -33,11 +40,6 @@ const initialState = {
error: false,
editable: true,
},
userRole: {
value: '',
error: false,
editable: true,
},
notes: {
value: [],
editable: false,
@@ -56,6 +58,7 @@ const ProfilePage = () => {
const { t } = useTranslation();
const { currentToken, endpoints, user, getAvatar, avatar } = useAuth();
const { addToast } = useToast();
const [editing, setEditing] = useState(false);
const [loading, setLoading] = useState(false);
const [userForm, updateWithId, updateWithKey, setUser] = useUser(initialState);
const [newAvatar, setNewAvatar] = useState('');
@@ -163,10 +166,11 @@ const ProfilePage = () => {
}
if (
userForm.currentPassword.value !== '' &&
!testRegex(userForm.currentPassword.value, policies.passwordPattern)
userForm.newPassword.value !== '' &&
(!testRegex(userForm.newPassword.value, policies.passwordPattern) ||
userForm.newPassword.value !== userForm.confirmNewPassword.value)
) {
updateWithKey('currentPassword', {
updateWithKey('newPassword', {
error: true,
});
setLoading(false);
@@ -185,9 +189,9 @@ const ProfilePage = () => {
id: user.Id,
description: userForm.description.value,
name: userForm.name.value,
userRole: userForm.userRole.value,
notes: newNotes,
userTypeProprietaryInfo: propInfo,
currentPassword: userForm.newPassword.value !== '' ? userForm.newPassword.value : undefined,
};
const options = {
@@ -206,6 +210,8 @@ const ProfilePage = () => {
color: 'success',
autohide: true,
});
// eslint-disable-next-line no-use-before-define
toggleEditing();
})
.catch((e) => {
addToast({
@@ -307,6 +313,11 @@ const ProfilePage = () => {
});
};
const toggleEditing = () => {
if (editing) getUser();
setEditing(!editing);
};
useEffect(() => {
if (user.Id) {
getAvatar();
@@ -322,11 +333,52 @@ const ProfilePage = () => {
<CCardHeader className="p-1">
<div className="text-value-lg float-left">{t('user.my_profile')}</div>
<div className="text-right float-right">
<CPopover content={t('common.save')}>
<CButton color="primary" variant="outline" onClick={updateUser} className="mx-1">
<CIcon name="cil-save" content={cilSave} />
</CButton>
</CPopover>
<CButtonToolbar role="group" className="justify-content-end">
<CPopover content={t('common.save')}>
<CButton
disabled={!editing}
color="primary"
variant="outline"
onClick={updateUser}
className="mx-1"
>
<CIcon name="cil-save" content={cilSave} />
</CButton>
</CPopover>
<CPopover content={t('common.edit')}>
<CButton
disabled={editing}
color="primary"
variant="outline"
onClick={toggleEditing}
className="mx-1"
>
<CIcon name="cil-pencil" content={cilPencil} />
</CButton>
</CPopover>
<CPopover content={t('common.stop_editing')}>
<CButton
disabled={!editing}
color="primary"
variant="outline"
onClick={toggleEditing}
className="mx-1"
>
<CIcon name="cil-x" content={cilX} />
</CButton>
</CPopover>
<CPopover content={t('common.refresh')}>
<CButton
disabled={editing}
color="primary"
variant="outline"
onClick={getUser}
className="mx-1"
>
<CIcon content={cilSync} />
</CButton>
</CPopover>
</CButtonToolbar>
</div>
</CCardHeader>
<CCardBody>
@@ -345,6 +397,7 @@ const ProfilePage = () => {
fileInputKey={fileInputKey}
sendPhoneNumberTest={sendPhoneNumberTest}
testVerificationCode={testVerificationCode}
editing={editing}
/>
</CCardBody>
</CCard>

View File

@@ -21,7 +21,6 @@ const SystemPage = () => {
processors: t('common.unknown'),
uptime: t('common.unknown'),
version: t('common.unknown'),
start: t('common.unknown'),
certificates: [],
subsystems: [],
};

View File

@@ -1,36 +1,16 @@
import React from 'react';
const DevicePage = React.lazy(() => import('pages/DevicePage'));
const DeviceDashboard = React.lazy(() => import('pages/DeviceDashboard'));
const DeviceListPage = React.lazy(() => import('pages/DeviceListPage'));
const UserListPage = React.lazy(() => import('pages/UserListPage'));
const ProfilePage = React.lazy(() => import('pages/ProfilePage'));
const WifiAnalysisPage = React.lazy(() => import('pages/WifiAnalysisPage'));
const SystemPage = React.lazy(() => import('pages/SystemPage'));
const FirmwareListPage = React.lazy(() => import('pages/FirmwareListPage'));
const FirmwareDashboard = React.lazy(() => import('pages/FirmwareDashboard'));
export default [
{
path: '/devicedashboard',
exact: true,
name: 'common.device_dashboard',
component: DeviceDashboard,
},
{ path: '/devices', exact: true, name: 'common.devices', component: DeviceListPage },
{
path: '/devices/:deviceId/wifianalysis',
name: 'wifi_analysis.title',
component: WifiAnalysisPage,
},
{ path: '/devices/:deviceId', name: 'common.device_page', component: DevicePage },
{ path: '/firmware', name: 'firmware.title', component: FirmwareListPage },
{
path: '/firmwaredashboard',
exact: true,
name: 'common.firmware_dashboard',
component: FirmwareDashboard,
},
{ path: '/users', exact: true, name: 'user.users', component: UserListPage },
{ path: '/myprofile', exact: true, name: 'user.my_profile', component: ProfilePage },
{ path: '/system', exact: true, name: 'common.system', component: SystemPage },