From dd19ed8bcdbf4922956be0a95460ca92237ae482 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 12 Oct 2021 11:44:30 -0400 Subject: [PATCH 1/6] New labels --- public/locales/de/translation.json | 1 + public/locales/en/translation.json | 1 + public/locales/es/translation.json | 1 + public/locales/fr/translation.json | 1 + public/locales/pt/translation.json | 1 + 5 files changed, 5 insertions(+) diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json index c4151fe..fc259d2 100644 --- a/public/locales/de/translation.json +++ b/public/locales/de/translation.json @@ -524,6 +524,7 @@ "description": "Beschreibung", "edit": "Benutzer bearbeiten", "email_address": "E-Mail-Addresse", + "enter_new_phone": "Geben Sie Ihre neue Telefonnummer ein:", "error_fetching_users": "Fehler beim Abrufen der Nutzer: {{error}}", "force_password_change": "Passwortänderung bei der Anmeldung erzwingen", "id": "Benutzeridentifikation.", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 08c80a5..97b56be 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -524,6 +524,7 @@ "description": "Description", "edit": "Edit User", "email_address": "Email Address", + "enter_new_phone": "Enter your new phone number: ", "error_fetching_users": "Error fetching users: {{error}}", "force_password_change": "Force Password Change on Login", "id": "User Id.", diff --git a/public/locales/es/translation.json b/public/locales/es/translation.json index 3e327a2..d16e677 100644 --- a/public/locales/es/translation.json +++ b/public/locales/es/translation.json @@ -524,6 +524,7 @@ "description": "Descripción", "edit": "editar usuario", "email_address": "Dirección de correo electrónico", + "enter_new_phone": "Ingrese su nuevo número de teléfono:", "error_fetching_users": "Error al obtener usuarios: {{error}}", "force_password_change": "Forzar cambio de contraseña al iniciar sesión", "id": "Id. De usuario", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index cf4fc02..000cfac 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -524,6 +524,7 @@ "description": "La description", "edit": "Modifier l'utilisateur", "email_address": "Adresse électronique", + "enter_new_phone": "Saisissez votre nouveau numéro de téléphone :", "error_fetching_users": "Erreur lors de la récupération des utilisateurs : {{error}}", "force_password_change": "Forcer le changement de mot de passe lors de la connexion", "id": "Identifiant d'utilisateur.", diff --git a/public/locales/pt/translation.json b/public/locales/pt/translation.json index 3f2879f..ee482d4 100644 --- a/public/locales/pt/translation.json +++ b/public/locales/pt/translation.json @@ -524,6 +524,7 @@ "description": "Descrição", "edit": "Editar usuário", "email_address": "Endereço de e-mail", + "enter_new_phone": "Insira seu novo número de telefone:", "error_fetching_users": "Erro ao buscar usuários: {{error}}", "force_password_change": "Forçar mudança de senha no login", "id": "ID do usuário.", From a44932d4f8720610bbce6ef030f22cc02810c243 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 13 Oct 2021 15:54:44 -0400 Subject: [PATCH 2/6] V1 of MFA --- package-lock.json | 94 ++++++++++++--- package.json | 4 +- public/locales/de/translation.json | 19 ++- public/locales/en/translation.json | 19 ++- public/locales/es/translation.json | 19 ++- public/locales/fr/translation.json | 19 ++- public/locales/pt/translation.json | 19 ++- src/App.js | 7 +- src/pages/LoginPage/index.js | 147 ++++++++++++++++++++--- src/pages/ProfilePage/index.js | 179 +++++++++++++++++++---------- src/router/index.js | 6 +- src/utils/axiosInstance.js | 2 + 12 files changed, 424 insertions(+), 110 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c0ab7f..f1cd822 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ucentral-client", - "version": "2.2.5", + "version": "2.2.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ucentral-client", - "version": "2.2.5", + "version": "2.2.6", "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.58", + "ucentral-libs": "^0.9.61", "uuid": "^8.3.2" }, "devDependencies": { @@ -9279,6 +9279,11 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.9.37", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.37.tgz", + "integrity": "sha512-RnUR4XwiVhMLnT7uFSdnmLeprspquuDtaShAgKTA+g/ms9/S4hQU3/QpFdh3iXPHtxD52QscXLm2W2+QBmvYAg==" + }, "node_modules/lilconfig": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.3.tgz", @@ -9518,14 +9523,12 @@ "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -9533,6 +9536,16 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + }, + "node_modules/lodash.startswith": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", + "integrity": "sha1-xZjErc4YiiflMUVzHNxsDnF3YAw=" + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -12189,6 +12202,23 @@ "react": "^16.0.0 || ^17.0.0" } }, + "node_modules/react-phone-input-2": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.14.0.tgz", + "integrity": "sha512-gOY3jUpwO7ulryXPEdqzH7L6DPqI9RQxKfBxZbgqAwXyALGsmwLWFyi2RQwXlBLWN/EPPT4Nv6I9TESVY2YBcg==", + "dependencies": { + "classnames": "^2.2.6", + "lodash.debounce": "^4.0.8", + "lodash.memoize": "^4.1.2", + "lodash.reduce": "^4.6.0", + "lodash.startswith": "^4.2.1", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^16.12.0 || ^17.0.0", + "react-dom": "^16.12.0 || ^17.0.0" + } + }, "node_modules/react-redux": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", @@ -14812,18 +14842,20 @@ } }, "node_modules/ucentral-libs": { - "version": "0.9.58", - "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.58.tgz", - "integrity": "sha512-K0bacVDGzjK//pddaMNVh2WDOep0/22tV1EhEfMKBAzaXgYEx2Ir3fg/GQzswCkfwg9i0kDWKiAXqRQ8pWLblg==", + "version": "0.9.61", + "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.61.tgz", + "integrity": "sha512-6Z6dogHOcTDHp3sPiz5lLSKOpylBt9BLcOiw5SfLXvcSp2wqRMHjGmdo2kgZyHl/TSwV5uOqTMF1n6pdIBusTg==", "dependencies": { "@coreui/coreui": "^3.4.0", "@coreui/icons": "^2.0.1", "@coreui/icons-react": "^1.1.0", "@coreui/react": "^3.4.6", "@coreui/react-chartjs": "^1.1.0", + "libphonenumber-js": "^1.9.37", "lodash": "^4.17.21", "react-flow-renderer": "^9.6.6", "react-paginate": "^7.1.3", + "react-phone-input-2": "^2.14.0", "react-router-dom": "^5.2.0", "react-select": "^4.3.1", "react-tooltip": "^4.2.21", @@ -23416,6 +23448,11 @@ "type-check": "~0.4.0" } }, + "libphonenumber-js": { + "version": "1.9.37", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.37.tgz", + "integrity": "sha512-RnUR4XwiVhMLnT7uFSdnmLeprspquuDtaShAgKTA+g/ms9/S4hQU3/QpFdh3iXPHtxD52QscXLm2W2+QBmvYAg==" + }, "lilconfig": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.3.tgz", @@ -23598,14 +23635,12 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, "lodash.merge": { "version": "4.6.2", @@ -23613,6 +23648,16 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + }, + "lodash.startswith": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", + "integrity": "sha1-xZjErc4YiiflMUVzHNxsDnF3YAw=" + }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -25559,6 +25604,19 @@ "prop-types": "^15.6.1" } }, + "react-phone-input-2": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.14.0.tgz", + "integrity": "sha512-gOY3jUpwO7ulryXPEdqzH7L6DPqI9RQxKfBxZbgqAwXyALGsmwLWFyi2RQwXlBLWN/EPPT4Nv6I9TESVY2YBcg==", + "requires": { + "classnames": "^2.2.6", + "lodash.debounce": "^4.0.8", + "lodash.memoize": "^4.1.2", + "lodash.reduce": "^4.6.0", + "lodash.startswith": "^4.2.1", + "prop-types": "^15.7.2" + } + }, "react-redux": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", @@ -27653,18 +27711,20 @@ } }, "ucentral-libs": { - "version": "0.9.58", - "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.58.tgz", - "integrity": "sha512-K0bacVDGzjK//pddaMNVh2WDOep0/22tV1EhEfMKBAzaXgYEx2Ir3fg/GQzswCkfwg9i0kDWKiAXqRQ8pWLblg==", + "version": "0.9.61", + "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.61.tgz", + "integrity": "sha512-6Z6dogHOcTDHp3sPiz5lLSKOpylBt9BLcOiw5SfLXvcSp2wqRMHjGmdo2kgZyHl/TSwV5uOqTMF1n6pdIBusTg==", "requires": { "@coreui/coreui": "^3.4.0", "@coreui/icons": "^2.0.1", "@coreui/icons-react": "^1.1.0", "@coreui/react": "^3.4.6", "@coreui/react-chartjs": "^1.1.0", + "libphonenumber-js": "^1.9.37", "lodash": "^4.17.21", "react-flow-renderer": "^9.6.6", "react-paginate": "^7.1.3", + "react-phone-input-2": "^2.14.0", "react-router-dom": "^5.2.0", "react-select": "^4.3.1", "react-tooltip": "^4.2.21", diff --git a/package.json b/package.json index 9f40d0c..262795a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ucentral-client", - "version": "2.2.5", + "version": "2.2.6", "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.58", + "ucentral-libs": "^0.9.61", "uuid": "^8.3.2" }, "main": "index.js", diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json index fc259d2..78bb058 100644 --- a/public/locales/de/translation.json +++ b/public/locales/de/translation.json @@ -350,6 +350,7 @@ "error_create_venue": "Fehler beim Erstellen des Veranstaltungsortes", "error_delete_tag": "Fehler beim Löschen des Inventar-Tags", "error_get_venue": "Fehler beim Abrufen von Veranstaltungsorten", + "error_pushing_config": "Fehler beim Versuch, die Konfiguration auf das Gerät zu übertragen: {{error}}", "error_retrieving": "Beim Abrufen von Inventar-Tags ist ein Fehler aufgetreten", "error_unassign": "Fehler beim Aufheben der Zuweisung", "error_update_venue": "Fehler beim Aktualisieren des Veranstaltungsorts", @@ -398,12 +399,15 @@ "venue": "Tagungsort" }, "login": { + "account_verification": "Bestätigung des Kontos", + "authentication_expired": "Authentifizierung abgelaufen, bitte starten Sie den Anmeldevorgang erneut", "change_password": "Ändere das Passwort", "change_password_error": "Fehler beim Ändern des Passworts. Stellen Sie sicher, dass das neue Passwort gültig ist, indem Sie die Seite \"Passwortrichtlinie\" besuchen", "change_password_instructions": "Geben Sie Ihr neues Passwort ein und bestätigen Sie es", "changing_password": "Passwort ändern...", "confirm_new_password": "Bestätige neues Passwort", "different_passwords": "Sie müssen das gleiche Passwort zweimal eingeben", + "email_code_validation": "Bitte überprüfen Sie Ihr E-Mail-Postfach und geben Sie den Bestätigungscode, den wir Ihnen gerade gesendet haben, in das Feld unten ein", "forgot_password_error": "Fehler beim Versuch, eine E-Mail mit vergessenem Passwort zu senden. Stellen Sie sicher, dass diese Benutzer-ID mit einem Konto verknüpft ist.", "forgot_password_explanation": "Geben Sie Ihren Benutzernamen ein, um eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts zu erhalten", "forgot_password_success": "Sie sollten in Kürze eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts erhalten. Bitte überprüfen Sie Ihren Spam, wenn Sie die E-Mail nicht finden können", @@ -412,6 +416,7 @@ "login_error": "Anmeldefehler, stellen Sie sicher, dass die von Ihnen angegebenen Informationen gültig sind", "new_password": "Neues Kennwort", "password": "Passwort", + "phone_validation_explanation": "Bitte überprüfen Sie Ihr Mobilgerät und geben Sie den Bestätigungscode, den wir Ihnen gerade gesendet haben, in das Feld unten ein", "please_enter_gateway": "Bitte geben Sie eine uCentralSec-URL ein", "please_enter_password": "Bitte geben Sie Ihr Passwort ein", "please_enter_username": "Bitte geben Sie Ihren Benutzernamen ein", @@ -420,7 +425,9 @@ "sending_ellipsis": "Senden…", "sign_in_to_account": "Melden Sie sich bei Ihrem Konto an", "url": "uCentralSec-URL", - "username": "Benutzername" + "username": "Benutzername", + "verification_code": "Geben Sie hier Ihre Bestätigung ein", + "wrong_code": "Der eingegebene Bestätigungscode ist ungültig." }, "reboot": { "directions": "Wann möchten Sie dieses Gerät neu starten?", @@ -511,6 +518,7 @@ "add_phone_number": "Telefonnummer hinzufügen", "avatar": "Dein Avatar", "avatar_file": "Dein Avatar (max. 2 MB)", + "check_phone": "Bitte überprüfen Sie Ihr Telefon auf Ihren Validierungscode", "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", @@ -526,12 +534,14 @@ "email_address": "E-Mail-Addresse", "enter_new_phone": "Geben Sie Ihre neue Telefonnummer ein:", "error_fetching_users": "Fehler beim Abrufen der Nutzer: {{error}}", + "error_sending_code": "Fehler beim Versuch, den Validierungscode zu senden. Bitte bestätigen Sie, dass Ihre Telefonnummer gültig ist.", "force_password_change": "Passwortänderung bei der Anmeldung erzwingen", "id": "Benutzeridentifikation.", "last_login": "Letzte Anmeldung", "login_id": "Anmelde-ID.", "my_profile": "Mein Profil", "name": "Name", + "new_code_sent": "Neuer Code gesendet!", "nickname": "Spitzname", "nickname_explanation": "Spitzname (optional)", "not_validated": "Nicht validiert", @@ -541,14 +551,19 @@ "provide_email": "Bitte geben Sie eine gültige E-Mail Adresse an", "provide_password": "Bitte geben Sie ein gültiges Passwort ein", "save_avatar": "Avatar speichern", + "send_code": "Code senden", + "send_code_again": "Code nochmal senden", "show_hide_password": "Passwort anzeigen/verbergen", + "successful_validation": "Telefonnummer bestätigt! Klicken Sie auf die Schaltfläche Speichern, um es mit Ihrem Profil zu verknüpfen", "update_failure": "Fehler beim Aktualisieren: {{error}}", "update_failure_title": "Update fehlgeschlagen", "update_success": "Benutzer erfolgreich aktualisiert", "update_success_title": "Erfolg", "user_role": "Rolle", "users": "Benutzer", - "validated": "Bestätigt" + "validate_phone": "Bestätigen", + "validated": "Bestätigt", + "wrong_validation_code": "Sie haben keinen gültigen Code eingegeben. Bitte versuchen Sie es erneut und vergewissern Sie sich, dass Sie die richtige Telefonnummer eingegeben haben" }, "wifi_analysis": { "association": "Verband", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 97b56be..34c8b82 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -350,6 +350,7 @@ "error_create_venue": "Error while creating venue", "error_delete_tag": "Error while deleting inventory tag", "error_get_venue": "Error while retrieving venues", + "error_pushing_config": "Error while trying to push configuration to device: {{error}}", "error_retrieving": "Error occurred while retrieving inventory tags", "error_unassign": "Error during unassign operation", "error_update_venue": "Error while updating venue", @@ -398,12 +399,15 @@ "venue": "Venue" }, "login": { + "account_verification": "Account Verification", + "authentication_expired": "Authentication expired, please start the login process again", "change_password": "Change Password", "change_password_error": "Error while changing password. Make sure the new password is valid by visiting the 'Password Policy' page", "change_password_instructions": "Enter and confirm your new password", "changing_password": "Changing Password... ", "confirm_new_password": "Confirm New Password", "different_passwords": "You need to enter the same password twice", + "email_code_validation": "Please check your email box and enter the verification code we have just sent you in the box below", "forgot_password_error": "Error while trying to send Forgot Password email. Please make sure this userId is associated to an account.", "forgot_password_explanation": "Enter your username to receive an email containing the instructions to reset your password", "forgot_password_success": "You should soon receive an email containing the instructions to reset your password. Please make sure to check your spam if you can't find the email", @@ -412,6 +416,7 @@ "login_error": "Login error, make sure the information you are providing is valid", "new_password": "New Password", "password": "Password", + "phone_validation_explanation": "Please check your mobile device and enter the verification code we have just sent you in the box below", "please_enter_gateway": "Please enter a uCentralSec URL", "please_enter_password": "Please enter your password", "please_enter_username": "Please enter your username", @@ -420,7 +425,9 @@ "sending_ellipsis": "Sending... ", "sign_in_to_account": "Sign in to your account", "url": "uCentralSec URL", - "username": "Username" + "username": "Username", + "verification_code": "Enter your verification here", + "wrong_code": "The verification code that was entered is not valid. " }, "reboot": { "directions": "When would you like to reboot this device?", @@ -511,6 +518,7 @@ "add_phone_number": "Add Phone Number", "avatar": "Your Avatar", "avatar_file": "Your Avatar (max. of 2 MB)", + "check_phone": "Please check your phone for your validation code", "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", @@ -526,12 +534,14 @@ "email_address": "Email Address", "enter_new_phone": "Enter your new phone number: ", "error_fetching_users": "Error fetching users: {{error}}", + "error_sending_code": "Error while trying to send validation code. Please confirm that your phone number is valid.", "force_password_change": "Force Password Change on Login", "id": "User Id.", "last_login": "Last Login", "login_id": "Login Id.", "my_profile": "My Profile", "name": "Name", + "new_code_sent": "New Code Sent!", "nickname": "Nickname", "nickname_explanation": "Nickname (optional)", "not_validated": "Not Validated", @@ -541,14 +551,19 @@ "provide_email": "Please provide a valid email address", "provide_password": "Please provide a valid password", "save_avatar": "Save Avatar", + "send_code": "Send Code", + "send_code_again": "Send Code Again", "show_hide_password": "Show/Hide Password", + "successful_validation": "Phone Number Validated! Click the save button to link it to your profile", "update_failure": "Error while trying to update: {{error}}", "update_failure_title": "Update Failed", "update_success": "User Updated Successfully", "update_success_title": "Success", "user_role": "Role", "users": "Users", - "validated": "Validated" + "validate_phone": "Validate", + "validated": "Validated", + "wrong_validation_code": "You have not entered a valid code. Please try again and make sure you have entered the right phone number" }, "wifi_analysis": { "association": "Association", diff --git a/public/locales/es/translation.json b/public/locales/es/translation.json index d16e677..007a205 100644 --- a/public/locales/es/translation.json +++ b/public/locales/es/translation.json @@ -350,6 +350,7 @@ "error_create_venue": "Error al crear el lugar", "error_delete_tag": "Error al eliminar la etiqueta de inventario", "error_get_venue": "Error al recuperar lugares", + "error_pushing_config": "Error al intentar enviar la configuración al dispositivo: {{error}}", "error_retrieving": "Se produjo un error al recuperar las etiquetas de inventario", "error_unassign": "Error durante la operación de anulación de asignación", "error_update_venue": "Error al actualizar el lugar", @@ -398,12 +399,15 @@ "venue": "Lugar de encuentro" }, "login": { + "account_verification": "Verificación de la cuenta", + "authentication_expired": "Autenticación caducada, vuelva a iniciar el proceso de inicio de sesión", "change_password": "Cambia la contraseña", "change_password_error": "Error al cambiar la contraseña. Asegúrese de que la nueva contraseña sea válida visitando la página 'Política de contraseñas'", "change_password_instructions": "Ingrese y confirme su nueva contraseña", "changing_password": "Cambio de contraseña ...", "confirm_new_password": "confirmar nueva contraseña", "different_passwords": "Debes ingresar la misma contraseña dos veces", + "email_code_validation": "Por favor, marque su casilla de correo electrónico e ingrese el código de verificación que le acabamos de enviar en la casilla a continuación.", "forgot_password_error": "Error al intentar enviar el correo electrónico de Olvidé mi contraseña. Asegúrese de que este ID de usuario esté asociado a una cuenta.", "forgot_password_explanation": "Ingrese su nombre de usuario para recibir un correo electrónico con las instrucciones para restablecer su contraseña", "forgot_password_success": "Pronto debería recibir un correo electrónico con las instrucciones para restablecer su contraseña. Asegúrese de verificar su correo no deseado si no puede encontrar el correo electrónico", @@ -412,6 +416,7 @@ "login_error": "Error de inicio de sesión, asegúrese de que la información que proporciona sea válida", "new_password": "Nueva contraseña", "password": "Contraseña", + "phone_validation_explanation": "Verifique su dispositivo móvil e ingrese el código de verificación que le acabamos de enviar en el cuadro a continuación", "please_enter_gateway": "Ingrese una URL de uCentralSec", "please_enter_password": "Por favor, introduzca su contraseña", "please_enter_username": "Por favor, ingrese su nombre de usuario", @@ -420,7 +425,9 @@ "sending_ellipsis": "Enviando...", "sign_in_to_account": "Iniciar sesión en su cuenta", "url": "URL de uCentralSec", - "username": "Nombre de usuario" + "username": "Nombre de usuario", + "verification_code": "Ingrese su verificación aquí", + "wrong_code": "El código de verificación que se ingresó no es válido." }, "reboot": { "directions": "¿Cuándo le gustaría reiniciar este dispositivo?", @@ -511,6 +518,7 @@ "add_phone_number": "Agregar el número de teléfono", "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", "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", @@ -526,12 +534,14 @@ "email_address": "Dirección de correo electrónico", "enter_new_phone": "Ingrese su nuevo número de teléfono:", "error_fetching_users": "Error al obtener usuarios: {{error}}", + "error_sending_code": "Error al intentar enviar el código de validación. Confirma que tu número de teléfono es válido.", "force_password_change": "Forzar cambio de contraseña al iniciar sesión", "id": "Id. De usuario", "last_login": "Último acceso", "login_id": "Ingresar identificación.", "my_profile": "Mi perfil", "name": "Nombre", + "new_code_sent": "¡Nuevo código enviado!", "nickname": "Apodo", "nickname_explanation": "Apodo (opcional)", "not_validated": "No validado", @@ -541,14 +551,19 @@ "provide_email": "Por favor ingrese su dirección de correo electrónico válida", "provide_password": "Proporcione una contraseña válida", "save_avatar": "Guardar avatar", + "send_code": "Enviar código", + "send_code_again": "Enviar Código De nuevo", "show_hide_password": "Mostrar / Ocultar contraseña", + "successful_validation": "¡Número de teléfono validado! Haga clic en el botón guardar para vincularlo a su perfil", "update_failure": "Error al intentar actualizar: {{error}}", "update_failure_title": "Actualización fallida", "update_success": "Usuario actualizado con éxito", "update_success_title": "Éxito", "user_role": "papel", "users": "Usuarios", - "validated": "Validado" + "validate_phone": "validar", + "validated": "Validado", + "wrong_validation_code": "No ha introducido un código válido. Vuelve a intentarlo y asegúrate de haber ingresado el número de teléfono correcto." }, "wifi_analysis": { "association": "Asociación", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 000cfac..38e6d51 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -350,6 +350,7 @@ "error_create_venue": "Erreur lors de la création du lieu", "error_delete_tag": "Erreur lors de la suppression de la balise d'inventaire", "error_get_venue": "Erreur lors de la récupération des lieux", + "error_pushing_config": "Erreur lors de la tentative d'envoi de la configuration sur l'appareil : {{error}}", "error_retrieving": "Une erreur s'est produite lors de la récupération des balises d'inventaire", "error_unassign": "Erreur lors de l'opération de désaffectation", "error_update_venue": "Erreur lors de la mise à jour du lieu", @@ -398,12 +399,15 @@ "venue": "Lieu" }, "login": { + "account_verification": "Vérification de compte", + "authentication_expired": "Authentification expirée, veuillez recommencer le processus de connexion", "change_password": "Changer le mot de passe", "change_password_error": "Erreur lors du changement de mot de passe. Assurez-vous que le nouveau mot de passe est valide en visitant la page « Politique de mot de passe »", "change_password_instructions": "Saisissez et confirmez votre nouveau mot de passe", "changing_password": "Modification du mot de passe...", "confirm_new_password": "Confirmer le nouveau mot de passe", "different_passwords": "Vous devez saisir deux fois le même mot de passe", + "email_code_validation": "Veuillez vérifier votre boîte e-mail et entrer le code de vérification que nous venons de vous envoyer dans la case ci-dessous", "forgot_password_error": "Erreur lors de la tentative d'envoi de l'e-mail Mot de passe oublié. Veuillez vous assurer que cet identifiant est associé à un compte.", "forgot_password_explanation": "Entrez votre nom d'utilisateur pour recevoir un e-mail contenant les instructions pour réinitialiser votre mot de passe", "forgot_password_success": "Vous devriez bientôt recevoir un e-mail contenant les instructions pour réinitialiser votre mot de passe. S'il vous plaît assurez-vous de vérifier vos spams si vous ne trouvez pas l'e-mail", @@ -412,6 +416,7 @@ "login_error": "Erreur de connexion, assurez-vous que les informations que vous fournissez sont valides", "new_password": "Nouveau mot de passe", "password": "Mot de passe", + "phone_validation_explanation": "Veuillez vérifier votre appareil mobile et entrer le code de vérification que nous venons de vous envoyer dans la case ci-dessous", "please_enter_gateway": "Veuillez saisir une URL uCentralSec", "please_enter_password": "s'il vous plait entrez votre mot de passe", "please_enter_username": "s'il vous plaît entrez votre nom d'utilisateur", @@ -420,7 +425,9 @@ "sending_ellipsis": "Envoi...", "sign_in_to_account": "Connectez-vous à votre compte", "url": "URL uCentralSec", - "username": "Nom d'utilisateur" + "username": "Nom d'utilisateur", + "verification_code": "Entrez votre vérification ici", + "wrong_code": "Le code de vérification saisi n'est pas valide." }, "reboot": { "directions": "Quand souhaitez-vous redémarrer cet appareil ?", @@ -511,6 +518,7 @@ "add_phone_number": "Ajouter un numéro de téléphone", "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", "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", @@ -526,12 +534,14 @@ "email_address": "Adresse électronique", "enter_new_phone": "Saisissez votre nouveau numéro de téléphone :", "error_fetching_users": "Erreur lors de la récupération des utilisateurs : {{error}}", + "error_sending_code": "Erreur lors de la tentative d'envoi du code de validation. Veuillez confirmer que votre numéro de téléphone est valide.", "force_password_change": "Forcer le changement de mot de passe lors de la connexion", "id": "Identifiant d'utilisateur.", "last_login": "Dernière connexion", "login_id": "Identifiant de connexion.", "my_profile": "Mon profil", "name": "Prénom", + "new_code_sent": "Nouveau code envoyé !", "nickname": "Surnom", "nickname_explanation": "Surnom (optionnel)", "not_validated": "Pas valide", @@ -541,14 +551,19 @@ "provide_email": "Veuillez fournir une adresse email valide", "provide_password": "Veuillez fournir un mot de passe valide", "save_avatar": "Enregistrer l'avatar", + "send_code": "Envoyer le code", + "send_code_again": "Envoyer code à nouveau", "show_hide_password": "Afficher/Masquer le mot de passe", + "successful_validation": "Numéro de téléphone validé ! Cliquez sur le bouton Enregistrer pour le lier à votre profil", "update_failure": "Erreur lors de la tentative de mise à jour : {{error}}", "update_failure_title": "mise à jour a échoué", "update_success": "L'utilisateur a bien été mis à jour", "update_success_title": "Succès", "user_role": "Rôle", "users": "Utilisateurs", - "validated": "Validé" + "validate_phone": "Valider", + "validated": "Validé", + "wrong_validation_code": "Vous n'avez pas entré de code valide. Veuillez réessayer et assurez-vous d'avoir entré le bon numéro de téléphone" }, "wifi_analysis": { "association": "Association", diff --git a/public/locales/pt/translation.json b/public/locales/pt/translation.json index ee482d4..f32a1f9 100644 --- a/public/locales/pt/translation.json +++ b/public/locales/pt/translation.json @@ -350,6 +350,7 @@ "error_create_venue": "Erro ao criar local", "error_delete_tag": "Erro ao excluir tag de inventário", "error_get_venue": "Erro ao recuperar locais", + "error_pushing_config": "Erro ao tentar enviar a configuração para o dispositivo: {{error}}", "error_retrieving": "Ocorreu um erro ao recuperar as tags de inventário", "error_unassign": "Erro durante operação de cancelamento de atribuição", "error_update_venue": "Erro ao atualizar o local", @@ -398,12 +399,15 @@ "venue": "Local" }, "login": { + "account_verification": "Verificação de conta", + "authentication_expired": "Autenticação expirada, reinicie o processo de login", "change_password": "Mudar senha", "change_password_error": "Erro ao alterar a senha. Certifique-se de que a nova senha é válida visitando a página 'Política de senha'", "change_password_instructions": "Digite e confirme sua nova senha", "changing_password": "Alterando senha ...", "confirm_new_password": "confirme a nova senha", "different_passwords": "Você precisa inserir a mesma senha duas vezes", + "email_code_validation": "Verifique sua caixa de e-mail e insira o código de verificação que acabamos de enviar na caixa abaixo", "forgot_password_error": "Erro ao tentar enviar e-mail Esqueci a senha. Certifique-se de que este userId esteja associado a uma conta.", "forgot_password_explanation": "Digite seu nome de usuário para receber um e-mail contendo as instruções para redefinir sua senha", "forgot_password_success": "Em breve, você receberá um e-mail com as instruções para redefinir sua senha. Certifique-se de verificar o seu spam se você não conseguir encontrar o e-mail", @@ -412,6 +416,7 @@ "login_error": "Erro de login, certifique-se de que as informações que você está fornecendo são válidas", "new_password": "Nova senha", "password": "Senha", + "phone_validation_explanation": "Verifique seu dispositivo móvel e insira o código de verificação que acabamos de enviar na caixa abaixo", "please_enter_gateway": "Insira um URL uCentralSec", "please_enter_password": "Por favor, insira sua senha", "please_enter_username": "Por favor insira seu nome de usuário", @@ -420,7 +425,9 @@ "sending_ellipsis": "Enviando ...", "sign_in_to_account": "Faça login em sua conta", "url": "URL uCentralSec", - "username": "Nome de usuário" + "username": "Nome de usuário", + "verification_code": "Insira sua verificação aqui", + "wrong_code": "O código de verificação inserido não é válido." }, "reboot": { "directions": "Quando você gostaria de reinicializar este dispositivo?", @@ -511,6 +518,7 @@ "add_phone_number": "ADICIONAR NÚMERO DE TELEFONE", "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", "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", @@ -526,12 +534,14 @@ "email_address": "Endereço de e-mail", "enter_new_phone": "Insira seu novo número de telefone:", "error_fetching_users": "Erro ao buscar usuários: {{error}}", + "error_sending_code": "Erro ao tentar enviar o código de validação. Por favor, confirme se o seu número de telefone é válido.", "force_password_change": "Forçar mudança de senha no login", "id": "ID do usuário.", "last_login": "Último login", "login_id": "Identificação de usuário.", "my_profile": "Meu perfil", "name": "Nome", + "new_code_sent": "Novo código enviado!", "nickname": "Apelido", "nickname_explanation": "Apelido (opcional)", "not_validated": "Não validado", @@ -541,14 +551,19 @@ "provide_email": "Por favor, forneça um endereço de e-mail válido", "provide_password": "Forneça uma senha válida", "save_avatar": "Salvar Avatar", + "send_code": "Enviar código", + "send_code_again": "Envie o Código Novamente", "show_hide_password": "Mostrar / ocultar senha", + "successful_validation": "Número de telefone validado! Clique no botão Salvar para vinculá-lo ao seu perfil", "update_failure": "Erro ao tentar atualizar: {{error}}", "update_failure_title": "Atualização falhou", "update_success": "Usuário atualizado com sucesso", "update_success_title": "Sucesso", "user_role": "Função", "users": "Comercial", - "validated": "Validado" + "validate_phone": "validar", + "validated": "Validado", + "wrong_validation_code": "Você não digitou um código válido. Por favor, tente novamente e certifique-se de ter inserido o número de telefone correto" }, "wifi_analysis": { "association": "Associação", diff --git a/src/App.js b/src/App.js index 9cb09d0..bafaa71 100644 --- a/src/App.js +++ b/src/App.js @@ -5,6 +5,7 @@ import Router from 'router'; import { AuthProvider } from 'ucentral-libs'; import { checkIfJson } from 'utils/helper'; import axiosInstance from 'utils/axiosInstance'; +import { getItem } from 'utils/localStorageHelper'; const loading = (
@@ -13,9 +14,9 @@ const loading = ( ); const App = () => { - const storageToken = sessionStorage.getItem('access_token'); - const apiEndpoints = checkIfJson(sessionStorage.getItem('gateway_endpoints')) - ? JSON.parse(sessionStorage.getItem('gateway_endpoints')) + const storageToken = getItem('access_token'); + const apiEndpoints = checkIfJson(getItem('gateway_endpoints')) + ? JSON.parse(getItem('gateway_endpoints')) : {}; return ( diff --git a/src/pages/LoginPage/index.js b/src/pages/LoginPage/index.js index 0634807..af85990 100644 --- a/src/pages/LoginPage/index.js +++ b/src/pages/LoginPage/index.js @@ -1,7 +1,8 @@ import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import * as axios from 'axios'; -import { LoginPage, useFormFields, useAuth } from 'ucentral-libs'; +import { LoginPage, useFormFields, useAuth, useToast } from 'ucentral-libs'; +import { setItem } from 'utils/localStorageHelper'; const initialFormState = { username: { @@ -46,6 +47,7 @@ const initialResponseState = { const Login = () => { const { t, i18n } = useTranslation(); const { setCurrentToken, setEndpoints } = useAuth(); + const { addToast } = useToast(); const [defaultConfig, setDefaultConfig] = useState({ value: '', error: false, @@ -61,8 +63,7 @@ const Login = () => { passwordPattern: '', accessPolicy: '', }); - const [isLogin, setIsLogin] = useState(true); - const [isPasswordChange, setIsChangePassword] = useState(false); + const [formType, setFormType] = useState('login'); const [fields, updateFieldWithId, updateField, setFormFields] = useFormFields(initialFormState); const axiosInstance = axios.create(); axiosInstance.defaults.timeout = 5000; @@ -76,7 +77,8 @@ const Login = () => { }); setLoginResponse(initialResponseState); setForgotResponse(initialResponseState); - setIsLogin(!isLogin); + if (formType === 'login') setFormType('forgot-password'); + else setFormType('login'); }; const cancelPasswordChange = () => { @@ -88,8 +90,7 @@ const Login = () => { }); setLoginResponse(initialResponseState); setForgotResponse(initialResponseState); - setIsLogin(true); - setIsChangePassword(false); + setFormType('login'); }; const signInValidation = () => { @@ -106,7 +107,10 @@ const Login = () => { updateField('username', { error: true }); valid = false; } - if (isPasswordChange && fields.newpassword.value !== fields.confirmpassword.value) { + if ( + formType === 'change-password' && + fields.newpassword.value !== fields.confirmpassword.value + ) { updateField('confirmpassword', { error: true }); valid = false; } @@ -172,6 +176,20 @@ const Login = () => { .catch(); }; + const getGatewayUIUrl = (token, gwUrl) => { + axiosInstance + .get(`${gwUrl}/api/v1/system?command=info`, { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${token}`, + }, + }) + .then((response) => { + if (response.data.UI) setItem('owgw-ui', response.data.UI); + }) + .catch(() => {}); + }; + const SignIn = () => { setLoginResponse(initialResponseState); if (signInValidation()) { @@ -183,18 +201,23 @@ const Login = () => { password: fields.password.value, }; - if (isPasswordChange) { + if (formType === 'change-password') { parameters.newPassword = fields.newpassword.value; } axiosInstance .post(`${fields.ucentralsecurl.value}/api/v1/oauth2`, parameters) .then((response) => { - if (response.data.userMustChangePassword) { - setIsChangePassword(true); + // If there's MFA to do + if (response.data.method && response.data.created) { + setFormType(`validation-${response.data.method}-${response.data.uuid}`); return null; } - sessionStorage.setItem('access_token', response.data.access_token); + if (response.data.userMustChangePassword) { + setFormType('change-password'); + return null; + } + setItem('access_token', response.data.access_token); token = response.data.access_token; return axiosInstance.get(`${fields.ucentralsecurl.value}/api/v1/systemEndpoints`, { headers: { @@ -211,18 +234,19 @@ const Login = () => { for (const endpoint of response.data.endpoints) { endpoints[endpoint.type] = endpoint.uri; } - sessionStorage.setItem('gateway_endpoints', JSON.stringify(endpoints)); + getGatewayUIUrl(token, endpoints.owgw); + setItem('gateway_endpoints', JSON.stringify(endpoints)); setEndpoints(endpoints); setCurrentToken(token); } }) .catch((error) => { - if (!isPasswordChange) { + if (formType !== 'change-password') { if ( error.response.status === 403 && error.response?.data?.ErrorDescription === 'Password change expected.' ) { - setIsChangePassword(true); + setFormType('change-password'); } setLoginResponse({ text: t('login.login_error'), @@ -279,6 +303,94 @@ const Login = () => { } }; + const validateCode = (code) => { + const options = { + headers: { + Accept: 'application/json', + }, + }; + + const parameters = { + uuid: formType.split('-').slice(2).join('-'), + answer: code, + }; + + let token = ''; + + return axiosInstance + .post( + `${fields.ucentralsecurl.value}/api/v1/oauth2?completeMFAChallenge=true`, + parameters, + options, + ) + .then((response) => { + if (response.data.userMustChangePassword) { + setFormType('change-password'); + return null; + } + setItem('access_token', response.data.access_token); + token = response.data.access_token; + return axiosInstance.get(`${fields.ucentralsecurl.value}/api/v1/systemEndpoints`, { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${response.data.access_token}`, + }, + }); + }) + .then((response) => { + if (response) { + const endpoints = { + owsec: fields.ucentralsecurl.value, + }; + for (const endpoint of response.data.endpoints) { + endpoints[endpoint.type] = endpoint.uri; + } + setItem('gateway_endpoints', JSON.stringify(endpoints)); + setEndpoints(endpoints); + setCurrentToken(token); + } + }) + .catch(() => false) + .finally(() => { + setLoading(false); + return true; + }); + }; + + const resendValidationCode = () => { + const options = { + headers: { + Accept: 'application/json', + }, + }; + + const parameters = { + uuid: formType.split('-').slice(2).join('-'), + }; + + return axiosInstance + .post(`${fields.ucentralsecurl.value}/api/v1/oauth2?resendMFACode=true`, parameters, options) + .then(() => { + addToast({ + title: t('common.success'), + body: t('user.new_code_sent'), + color: 'success', + autohide: true, + }); + return true; + }) + .catch((e) => { + addToast({ + title: t('common.error'), + body: t('login.authentication_expired'), + color: 'danger', + autohide: true, + }); + if (e.response?.data?.ErrorCode === 403) setFormType('login'); + return false; + }); + }; + useEffect(() => { getDefaultConfig(); }, []); @@ -287,21 +399,22 @@ const Login = () => { ); }; diff --git a/src/pages/ProfilePage/index.js b/src/pages/ProfilePage/index.js index 4409f74..0a23750 100644 --- a/src/pages/ProfilePage/index.js +++ b/src/pages/ProfilePage/index.js @@ -1,8 +1,8 @@ import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { CCard, CCardBody, CCardHeader, CButton, CPopover } from '@coreui/react'; -import CIcon from '@coreui/icons-react'; import { cilSave } from '@coreui/icons'; +import CIcon from '@coreui/icons-react'; import axiosInstance from 'utils/axiosInstance'; import { testRegex } from 'utils/helper'; import { useUser, EditMyProfile, useAuth, useToast } from 'ucentral-libs'; @@ -42,6 +42,14 @@ const initialState = { value: [], editable: false, }, + userTypeProprietaryInfo: { + value: {}, + error: false, + }, + mfaMethod: { + value: '', + error: false, + }, }; const ProfilePage = () => { @@ -49,7 +57,6 @@ const ProfilePage = () => { const { currentToken, endpoints, user, getAvatar, avatar } = useAuth(); const { addToast } = useToast(); const [loading, setLoading] = useState(false); - const [initialUser, setInitialUser] = useState({}); const [userForm, updateWithId, updateWithKey, setUser] = useUser(initialState); const [newAvatar, setNewAvatar] = useState(''); const [newAvatarFile, setNewAvatarFile] = useState(null); @@ -93,10 +100,24 @@ const ProfilePage = () => { }; } } - setInitialUser({ ...initialState, ...newUser }); + + newUser.mfaMethod = { + value: response.data.userTypeProprietaryInfo.mfa.enabled + ? response.data.userTypeProprietaryInfo.mfa.method + : '', + error: false, + }; + setUser({ ...initialState, ...newUser }); }) - .catch(() => {}); + .catch((e) => { + addToast({ + title: t('common.error'), + body: t('user.error_fetching_users', { error: e }), + color: 'danger', + autohide: true, + }); + }); }; const uploadAvatar = () => { @@ -124,10 +145,10 @@ const ProfilePage = () => { setNewAvatarFile(null); setFileInputKey(fileInputKey + 1); }) - .catch((e) => { + .catch(() => { addToast({ title: t('user.update_failure_title'), - body: t('user.update_failure', { error: e.response?.data?.ErrorDescription }), + body: t('user.update_failure'), color: 'danger', autohide: true, }); @@ -137,34 +158,38 @@ const ProfilePage = () => { const updateUser = () => { setLoading(true); - const parameters = { - id: user.Id, - }; - - let newData = true; - - for (const key of Object.keys(userForm)) { - if (userForm[key].editable && userForm[key].value !== initialUser[key].value) { - if ( - key === 'currentPassword' && - !testRegex(userForm[key].value, policies.passwordPattern) - ) { - updateWithKey('currentPassword', { - error: true, - }); - newData = false; - break; - } else { - parameters[key] = userForm[key].value; - } - } - } - if (newAvatarFile !== null) { uploadAvatar(); } - if (newData) { + if ( + userForm.currentPassword.value !== '' && + !testRegex(userForm.currentPassword.value, policies.passwordPattern) + ) { + updateWithKey('currentPassword', { + error: true, + }); + setLoading(false); + } else { + const newNotes = []; + + for (let i = 0; i < userForm.notes.value.length; i += 1) { + if (userForm.notes.value[i].new) newNotes.push({ note: userForm.notes.value[i].note }); + } + + const propInfo = { ...userForm.userTypeProprietaryInfo.value }; + propInfo.mfa.method = userForm.mfaMethod.value === '' ? undefined : userForm.mfaMethod.value; + propInfo.mfa.enabled = userForm.mfaMethod.value !== ''; + + const parameters = { + id: user.Id, + description: userForm.description.value, + name: userForm.name.value, + userRole: userForm.userRole.value, + notes: newNotes, + userTypeProprietaryInfo: propInfo, + }; + const options = { headers: { Accept: 'application/json', @@ -194,35 +219,18 @@ const ProfilePage = () => { getUser(); setLoading(false); }); - } else { - setLoading(false); } }; const addNote = (currentNote) => { - setLoading(true); - - const parameters = { - id: user.Id, - notes: [{ note: currentNote }], - }; - - const options = { - headers: { - Accept: 'application/json', - Authorization: `Bearer ${currentToken}`, - }, - }; - - axiosInstance - .put(`${endpoints.owsec}/api/v1/user/${user.Id}`, parameters, options) - .then(() => { - getUser(); - }) - .catch(() => {}) - .finally(() => { - setLoading(false); - }); + const newNotes = [...userForm.notes.value]; + newNotes.unshift({ + note: currentNote, + new: true, + created: new Date().getTime() / 1000, + createdBy: '', + }); + updateWithKey('notes', { value: newNotes }); }; const showPreview = (e) => { @@ -239,6 +247,7 @@ const ProfilePage = () => { Authorization: `Bearer ${currentToken}`, }, }; + return axiosInstance .delete(`${endpoints.owsec}/api/v1/avatar/${user.Id}`, options) .then(() => { @@ -250,6 +259,54 @@ const ProfilePage = () => { }); }; + const sendPhoneNumberTest = async (phoneNumber) => { + const options = { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${currentToken}`, + }, + }; + + return axiosInstance + .post(`${endpoints.owsec}/api/v1/sms?validateNumber=true`, { to: phoneNumber }, options) + .then(() => true) + .catch(() => { + addToast({ + title: t('common.error'), + body: t('user.error_sending_code'), + color: 'danger', + autohide: true, + }); + return false; + }); + }; + + const testVerificationCode = async (phoneNumber, code) => { + const options = { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${currentToken}`, + }, + }; + + return axiosInstance + .post( + `${endpoints.owsec}/api/v1/sms?completeValidation=true&validationCode=${code}`, + { to: phoneNumber }, + options, + ) + .then(() => true) + .catch(() => { + addToast({ + title: t('common.error'), + body: t('user.wrong_validation_code'), + color: 'danger', + autohide: true, + }); + return false; + }); + }; + useEffect(() => { if (user.Id) { getAvatar(); @@ -262,11 +319,12 @@ const ProfilePage = () => { return ( - -
+ +
{t('user.my_profile')}
+
- - + +
@@ -276,6 +334,7 @@ const ProfilePage = () => { t={t} user={userForm} updateUserWithId={updateWithId} + updateWithKey={updateWithKey} loading={loading} policies={policies} addNote={addNote} @@ -284,6 +343,8 @@ const ProfilePage = () => { showPreview={showPreview} deleteAvatar={deleteAvatar} fileInputKey={fileInputKey} + sendPhoneNumberTest={sendPhoneNumberTest} + testVerificationCode={testVerificationCode} /> diff --git a/src/router/index.js b/src/router/index.js index 475dca1..3136cbe 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,4 +1,4 @@ -import { useAuth } from 'ucentral-libs'; +import { useAuth, ToastProvider } from 'ucentral-libs'; import { Route } from 'react-router-dom'; import React from 'react'; @@ -16,7 +16,9 @@ const Routes = () => { currentToken !== '' && Object.keys(endpoints).length !== 0 ? ( ) : ( - + + + ) } /> diff --git a/src/utils/axiosInstance.js b/src/utils/axiosInstance.js index e179807..060383a 100644 --- a/src/utils/axiosInstance.js +++ b/src/utils/axiosInstance.js @@ -20,6 +20,8 @@ axiosInstance.interceptors.response.use( case 401: break; case 403: + localStorage.removeItem('access_token'); + localStorage.removeItem('gateway_endpoints'); sessionStorage.clear(); window.location.href = '/'; break; From 0faa9f63d25b64c49b443f0894ac3558f79db7cd Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 13 Oct 2021 16:02:40 -0400 Subject: [PATCH 3/6] Fix for getting gw ui during login --- package-lock.json | 4 ++-- package.json | 2 +- src/pages/LoginPage/index.js | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index f1cd822..a24789d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ucentral-client", - "version": "2.2.6", + "version": "2.2.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ucentral-client", - "version": "2.2.6", + "version": "2.2.7", "dependencies": { "@coreui/coreui": "^3.4.0", "@coreui/icons": "^2.0.1", diff --git a/package.json b/package.json index 262795a..ceb8177 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ucentral-client", - "version": "2.2.6", + "version": "2.2.7", "dependencies": { "@coreui/coreui": "^3.4.0", "@coreui/icons": "^2.0.1", diff --git a/src/pages/LoginPage/index.js b/src/pages/LoginPage/index.js index af85990..aa9f93e 100644 --- a/src/pages/LoginPage/index.js +++ b/src/pages/LoginPage/index.js @@ -234,7 +234,7 @@ const Login = () => { for (const endpoint of response.data.endpoints) { endpoints[endpoint.type] = endpoint.uri; } - getGatewayUIUrl(token, endpoints.owgw); + if (endpoints.owgw) getGatewayUIUrl(token, endpoints.owgw); setItem('gateway_endpoints', JSON.stringify(endpoints)); setEndpoints(endpoints); setCurrentToken(token); @@ -345,6 +345,7 @@ const Login = () => { for (const endpoint of response.data.endpoints) { endpoints[endpoint.type] = endpoint.uri; } + if (endpoints.owgw) getGatewayUIUrl(token, endpoints.owgw); setItem('gateway_endpoints', JSON.stringify(endpoints)); setEndpoints(endpoints); setCurrentToken(token); From c9c3e003ebf6440df75dfc6a0cdbbaade71d7eae Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 13 Oct 2021 16:09:06 -0400 Subject: [PATCH 4/6] Upgrading ucentral-libs --- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index a24789d..cf722fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ucentral-client", - "version": "2.2.7", + "version": "2.2.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ucentral-client", - "version": "2.2.7", + "version": "2.2.8", "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.61", + "ucentral-libs": "^0.9.62", "uuid": "^8.3.2" }, "devDependencies": { @@ -14842,9 +14842,9 @@ } }, "node_modules/ucentral-libs": { - "version": "0.9.61", - "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.61.tgz", - "integrity": "sha512-6Z6dogHOcTDHp3sPiz5lLSKOpylBt9BLcOiw5SfLXvcSp2wqRMHjGmdo2kgZyHl/TSwV5uOqTMF1n6pdIBusTg==", + "version": "0.9.62", + "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.62.tgz", + "integrity": "sha512-teqIoFOkALEFzqRwdEuLWoDgIOiLhUt+ZDiS2ygYmqVsbCQZWUk/iOEpfLr1ZkRcEUtOPdrjqIRF5HrMopAPHA==", "dependencies": { "@coreui/coreui": "^3.4.0", "@coreui/icons": "^2.0.1", @@ -27711,9 +27711,9 @@ } }, "ucentral-libs": { - "version": "0.9.61", - "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.61.tgz", - "integrity": "sha512-6Z6dogHOcTDHp3sPiz5lLSKOpylBt9BLcOiw5SfLXvcSp2wqRMHjGmdo2kgZyHl/TSwV5uOqTMF1n6pdIBusTg==", + "version": "0.9.62", + "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.62.tgz", + "integrity": "sha512-teqIoFOkALEFzqRwdEuLWoDgIOiLhUt+ZDiS2ygYmqVsbCQZWUk/iOEpfLr1ZkRcEUtOPdrjqIRF5HrMopAPHA==", "requires": { "@coreui/coreui": "^3.4.0", "@coreui/icons": "^2.0.1", diff --git a/package.json b/package.json index ceb8177..f01a229 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ucentral-client", - "version": "2.2.7", + "version": "2.2.8", "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.61", + "ucentral-libs": "^0.9.62", "uuid": "^8.3.2" }, "main": "index.js", From e4883bf588ff369debf4f5c4bcb4fca72aa32739 Mon Sep 17 00:00:00 2001 From: Charles Date: Thu, 14 Oct 2021 10:58:53 -0400 Subject: [PATCH 5/6] Good favicon --- public/favicon.ico | Bin 105342 -> 206910 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/favicon.ico b/public/favicon.ico index 8e76896fede3e4f1131bc011fb7f554f66638d6a..d3f1ee8f9bd0a7a1b86f163684166dedc8c84ed2 100644 GIT binary patch literal 206910 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�yUy$-iB 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 adETZl2CX2c{U7Jd>b@*i`P)L3#ri)Ao!P_y From 4422d54b748d76512b2b32896a9eb039b075dd62 Mon Sep 17 00:00:00 2001 From: Charles Date: Thu, 14 Oct 2021 11:00:10 -0400 Subject: [PATCH 6/6] New labels --- public/locales/de/translation.json | 19 +++++++++++++++++++ public/locales/en/translation.json | 19 +++++++++++++++++++ public/locales/es/translation.json | 19 +++++++++++++++++++ public/locales/fr/translation.json | 19 +++++++++++++++++++ public/locales/pt/translation.json | 19 +++++++++++++++++++ 5 files changed, 95 insertions(+) diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json index 78bb058..d34a10b 100644 --- a/public/locales/de/translation.json +++ b/public/locales/de/translation.json @@ -33,6 +33,7 @@ "common": { "access_policy": "Zugangsrichtlinien", "add": "Hinzufügen", + "add_items": "Füge Artikel hinzu", "adding_ellipsis": "Hinzufügen ...", "are_you_sure": "Bist du sicher?", "back_to_login": "Zurück zur Anmeldung", @@ -101,7 +102,12 @@ "hour": "stunde", "hours": "std", "id": "ID", + "invalid_file": "Die ausgewählte Datei war ungültig, bitte lesen Sie die Anweisungen und passen Sie Ihre Datei entsprechend an", + "invalid_pem": "Ihre PEM-Datei ist ungültig. Es sollte mit '-----BEGIN CERTIFICATE-----' ODER '-----BEGIN PRIVATE KEY-----' beginnen und mit '-----END CERTIFICATE--- enden. --' ODER '-----END PRIVATSCHLÜSSEL-----'", "ip_address": "IP Adresse", + "ips": "IPs", + "item": "Artikel", + "items": "Artikel", "items_per_page": "Objekte pro Seite:", "last_dashboard_refresh": "Letzte Dashboard-Aktualisierung", "later_tonight": "Später am Abend", @@ -130,6 +136,7 @@ "overall_health": "Allgemeine Gesundheit", "password_policy": "Kennwortrichtlinie", "preview": "Vorschau", + "program": "Programm", "recorded": "Verzeichnet", "refresh": "Aktualisierung", "refresh_device": "Gerät aktualisieren", @@ -163,6 +170,7 @@ "unknown": "unbekannte", "up_to_date": "Aktuelle Geräte", "uptimes": "Betriebszeiten", + "use_file": "Datei verwenden", "uuid": "UUID", "vendors": "Anbieter", "view_more": "Mehr anzeigen", @@ -172,7 +180,10 @@ "add_configuration": "Konfiguration hinzufügen", "add_new_block": "Neuen Konfigurationsblock hinzufügen", "add_or_link": "Verlinken oder hinzufügen", + "add_radio": "Radio hinzufügen", + "ca_cert_explanation": "Bitte verwenden Sie eine .pem-Datei, die mit \"-----BEGIN CERTIFICATE-----\" beginnt und mit \"-----END CERTIFICATE-----\" endet. Das Ergebnis wird im Feld darunter angezeigt. Sie können das Zertifikat, das Sie verwenden möchten, auch direkt kopieren und einfügen.", "cannot_delete": "Diese Konfiguration kann nicht gelöscht werden, da sie von mindestens einer Entität, einem Veranstaltungsort oder einem Gerät verwendet wird", + "choose_radio_band": "Welche Radioband möchten Sie gründen?", "choose_section": "Welchen Abschnitt soll dieser Block enthalten?", "configuration_browser": "Konfigurationsbrowser", "configurations": "Konfigurationen", @@ -194,6 +205,7 @@ "error_trying_delete": "Fehler beim Versuch zu löschen: {{error}}", "error_update": "Fehler: {{error}}", "explanation": "Erläuterung", + "key_pem_explanation": "Bitte .pem-Datei auswählen", "last_configuration_change": "Konfigurationsänderung", "last_configuration_download": "Letzter Konfigurations-Download", "location": "Ort", @@ -249,6 +261,7 @@ "entity": { "add_child": "Untergeordnete Entität zu {{entityName}}hinzufügen", "add_failure": "Fehler, der Server hat zurückgegeben: {{error}}", + "add_ips": "IPs verwalten", "add_root": "Root-Entität hinzufügen", "add_success": "Entität erfolgreich erstellt!", "assigned_inventory": "Zugewiesenes Inventar", @@ -258,13 +271,18 @@ "delete_success": "Entität erfolgreich gelöscht", "delete_warning": "Achtung: Dieser Vorgang kann nicht rückgängig gemacht werden", "edit_failure": "Aktualisierung fehlgeschlagen : {{error}}", + "enter_here": "Geben Sie hier die IP(s) ein, die Sie hinzufügen möchten", "entities": "Entitäten", "entity": "Entität", "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", + "ip_detection": "IP-Erkennung", + "ip_formats": "Sie können IPv4- oder IPv6-Adressen in den folgenden Formaten hinzufügen:", + "no_ips": "Keine IPs ausgewählt", "not_assigned": "Nicht zugeordnet", "only_unassigned": "Nur nicht zugewiesen", + "update_failure_error": "Fehler beim Versuch, die Entität zu aktualisieren: {{error}}", "valid_serial": "Muss eine gültige Seriennummer sein (12 HEX-Zeichen)", "venues": "Veranstaltungsorte" }, @@ -319,6 +337,7 @@ "add_tag": "Tag erstellen", "add_tag_to": "Neues Gerät zu {{name}}hinzufügen", "add_venue": "Veranstaltungsort hinzufügen", + "assign_ent_ven": "Zu Entität oder Veranstaltungsort zuweisen", "assign_entity_instructions": "Sie können die Entität, der dieses Tag zugewiesen werden soll, entweder über das Menü unten finden oder die UUID der Entität manuell in das Feld oben einfügen.", "assign_error": "Fehler beim Versuch, Tag zuzuweisen", "assign_to_entity": "Zu Entität zuweisen", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 34c8b82..51b03f4 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -33,6 +33,7 @@ "common": { "access_policy": "Access Policy", "add": "Add", + "add_items": "Add Items", "adding_ellipsis": "Adding...", "are_you_sure": "Are you sure?", "back_to_login": "Back to Login", @@ -101,7 +102,12 @@ "hour": "hour", "hours": "hours", "id": "Id", + "invalid_file": "The chosen file was invalid, please read the instructions and adjust your file accordingly", + "invalid_pem": "Your .pem file is invalid. It should start with '-----BEGIN CERTIFICATE-----' OR '-----BEGIN PRIVATE KEY-----' and it should end with '-----END CERTIFICATE-----' OR '-----END PRIVATE KEY-----'", "ip_address": "IP Address", + "ips": "IPs", + "item": "Item", + "items": "Items", "items_per_page": "Items per page: ", "last_dashboard_refresh": "Last Dashboard Refresh", "later_tonight": "Later tonight", @@ -130,6 +136,7 @@ "overall_health": "Overall Health", "password_policy": "Password Policy", "preview": "Preview", + "program": "Program", "recorded": "Recorded", "refresh": "Refresh", "refresh_device": "Refresh Device", @@ -163,6 +170,7 @@ "unknown": "Unknown", "up_to_date": "Up to Date Devices", "uptimes": "Uptimes", + "use_file": "Use File", "uuid": "UUID", "vendors": "Vendors", "view_more": "View more", @@ -172,7 +180,10 @@ "add_configuration": "Add Configuration", "add_new_block": "Add new Configuration Block", "add_or_link": "Link or Add", + "add_radio": "Add Radio", + "ca_cert_explanation": "Please use a .pem file that starts with \"-----BEGIN CERTIFICATE-----\" and ends with \"-----END CERTIFICATE-----\". The result will be shown in the field below. You can also copy and paste the certificate you would like to use directly.", "cannot_delete": "This configuration cannot be deleted because it is being used by at least one entity, venue or device", + "choose_radio_band": "What radio band you would like to create?", "choose_section": "Which section you would like this block to contain?", "configuration_browser": "Configuration Browser", "configurations": "Configurations", @@ -194,6 +205,7 @@ "error_trying_delete": "Error while trying to delete: {{error}}", "error_update": "Error: {{error}}", "explanation": "Explanation", + "key_pem_explanation": "Please select .pem file", "last_configuration_change": "Config Change", "last_configuration_download": "Last Configuration Download", "location": "Location", @@ -249,6 +261,7 @@ "entity": { "add_child": "Add Child Entity to {{entityName}}", "add_failure": "Error, the server returned : {{error}}", + "add_ips": "Manage IPs", "add_root": "Add Root Entity", "add_success": "Entity Successfully Created!", "assigned_inventory": "Assigned Inventory", @@ -258,13 +271,18 @@ "delete_success": "Entity Successfully Deleted", "delete_warning": "Warning: this operation cannot be reverted", "edit_failure": "Update unsuccessful : {{error}}", + "enter_here": "Enter the IP(s) you'd like to add here", "entities": "Entities", "entity": "Entity", "error_fetch_entity": "Error while fetching entity information", "error_fetching": "Error while fetching entities", "error_saving": "Error while saving entity", + "ip_detection": "IP Detection", + "ip_formats": "You can add IPv4 or IPv6 addresses in the following formats:", + "no_ips": "No IPs selected", "not_assigned": "Not Assigned", "only_unassigned": "Only Unassigned", + "update_failure_error": "Error while trying to update entity: {{error}}", "valid_serial": "Needs to be a valid serial number (12 HEX characters)", "venues": "Venues" }, @@ -319,6 +337,7 @@ "add_tag": "Create Tag", "add_tag_to": "Add New Device to {{name}}", "add_venue": "Add Venue", + "assign_ent_ven": "Assign to Entity or Venue", "assign_entity_instructions": "You can either find the entity you want this tag to be assigned to by using the menu below, or you can manually paste the entity's UUID in the field above.", "assign_error": "Error while trying to assign tag", "assign_to_entity": "Assign to Entity", diff --git a/public/locales/es/translation.json b/public/locales/es/translation.json index 007a205..f3ea93f 100644 --- a/public/locales/es/translation.json +++ b/public/locales/es/translation.json @@ -33,6 +33,7 @@ "common": { "access_policy": "Política de acceso", "add": "Añadir", + "add_items": "Agregar articulos", "adding_ellipsis": "Añadiendo ...", "are_you_sure": "¿Estás seguro?", "back_to_login": "Atrás para iniciar sesión", @@ -101,7 +102,12 @@ "hour": "hora", "hours": "horas", "id": "Carné de identidad", + "invalid_file": "El archivo elegido no es válido, lea las instrucciones y ajuste su archivo en consecuencia", + "invalid_pem": "Su archivo .pem no es válido. Debe comenzar con '----- BEGIN CERTIFICATE -----' O '----- BEGIN PRIVATE KEY -----' y debe terminar con '----- END CERTIFICATE --- - 'O' ----- FIN DE CLAVE PRIVADA ----- '", "ip_address": "Dirección IP", + "ips": "IPs", + "item": "ít", + "items": "artículos", "items_per_page": "Artículos por página:", "last_dashboard_refresh": "Última actualización del panel", "later_tonight": "Más tarde esta noche", @@ -130,6 +136,7 @@ "overall_health": "Salud en general", "password_policy": "Política de contraseñas", "preview": "Avance", + "program": "Programa", "recorded": "Grabado", "refresh": "Refrescar", "refresh_device": "Actualizar dispositivo", @@ -163,6 +170,7 @@ "unknown": "Desconocido", "up_to_date": "Dispositivos actualizados", "uptimes": "Tiempos de actividad", + "use_file": "Usar archivo", "uuid": "UUID", "vendors": "Vendedores", "view_more": "Ver más", @@ -172,7 +180,10 @@ "add_configuration": "Agregar configuración", "add_new_block": "Agregar nuevo bloque de configuración", "add_or_link": "Vincular o agregar", + "add_radio": "Agregar radio", + "ca_cert_explanation": "Utilice un archivo .pem que comience con \"----- BEGIN CERTIFICATE -----\" y termine con \"----- END CERTIFICATE -----\". El resultado se mostrará en el campo siguiente. También puede copiar y pegar el certificado que le gustaría usar directamente.", "cannot_delete": "Esta configuración no se puede eliminar porque está siendo utilizada por al menos una entidad, lugar o dispositivo", + "choose_radio_band": "¿Qué banda de radio te gustaría crear?", "choose_section": "Qué sección le gustaría que contenga este bloque?", "configuration_browser": "Navegador de configuración", "configurations": "Configuraciones", @@ -194,6 +205,7 @@ "error_trying_delete": "Error al intentar eliminar: {{error}}", "error_update": "Error: {{error}}", "explanation": "Explicación", + "key_pem_explanation": "Seleccione el archivo .pem", "last_configuration_change": "CAMBIO DE CONFIGURACIÓN", "last_configuration_download": "Descarga de la última configuración", "location": "Ubicación", @@ -249,6 +261,7 @@ "entity": { "add_child": "Agregar entidad secundaria a {{entityName}}", "add_failure": "Error, el servidor devolvió: {{error}}", + "add_ips": "Administrar direcciones IP", "add_root": "Agregar entidad raíz", "add_success": "¡Entidad creada con éxito!", "assigned_inventory": "Inventario asignado", @@ -258,13 +271,18 @@ "delete_success": "Entidad eliminada correctamente", "delete_warning": "Advertencia: esta operación no se puede revertir", "edit_failure": "Actualización fallida: {{error}}", + "enter_here": "Ingrese las IP que desea agregar aquí", "entities": "entidades", "entity": "Entidad", "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", + "ip_detection": "Detección de IP", + "ip_formats": "Puede agregar direcciones IPv4 o IPv6 en los siguientes formatos:", + "no_ips": "No se seleccionaron direcciones IP", "not_assigned": "No asignado", "only_unassigned": "Solo sin asignar", + "update_failure_error": "Error al intentar actualizar la entidad: {{error}}", "valid_serial": "Debe ser un número de serie válido (12 caracteres HEX)", "venues": "Sedes" }, @@ -319,6 +337,7 @@ "add_tag": "Crear etiqueta", "add_tag_to": "Agregar nuevo dispositivo a {{name}}", "add_venue": "Agregar lugar", + "assign_ent_ven": "Asignar a entidad o lugar", "assign_entity_instructions": "Puede encontrar la entidad a la que desea que se asigne esta etiqueta utilizando el menú a continuación, o puede pegar manualmente el UUID de la entidad en el campo de arriba.", "assign_error": "Error al intentar asignar la etiqueta", "assign_to_entity": "Asignar a entidad", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 38e6d51..2cab7a8 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -33,6 +33,7 @@ "common": { "access_policy": "Politique d'accès", "add": "Ajouter", + "add_items": "Ajouter des articles", "adding_ellipsis": "Ajouter...", "are_you_sure": "Êtes-vous sûr?", "back_to_login": "Retour connexion", @@ -101,7 +102,12 @@ "hour": "heure", "hours": "heures", "id": "Id", + "invalid_file": "Le fichier choisi n'était pas valide, veuillez lire les instructions et ajuster votre fichier en conséquence", + "invalid_pem": "Votre fichier .pem n'est pas valide. Il doit commencer par '-----BEGIN CERTIFICATE-----' OU '-----BEGIN PRIVATE KEY-----' et il doit se terminer par '-----END CERTIFICATE--- --' OU '-----FIN CLÉ PRIVÉE-----'", "ip_address": "Adresse IP", + "ips": "IPS", + "item": "article", + "items": "Articles", "items_per_page": "Objets par page:", "last_dashboard_refresh": "Dernière actualisation du tableau de bord", "later_tonight": "Plus tard ce soir", @@ -130,6 +136,7 @@ "overall_health": "Santé globale", "password_policy": "Politique de mot de passe", "preview": "Aperçu", + "program": "Programme", "recorded": "Enregistré", "refresh": "Rafraîchir", "refresh_device": "Actualiser l'appareil", @@ -163,6 +170,7 @@ "unknown": "Inconnu", "up_to_date": "Appareils à jour", "uptimes": "Disponibilités", + "use_file": "Utiliser le fichier", "uuid": "UUID", "vendors": "Vendeurs", "view_more": "Afficher plus", @@ -172,7 +180,10 @@ "add_configuration": "Ajouter une configuration", "add_new_block": "Ajouter un nouveau bloc de configuration", "add_or_link": "Lier ou ajouter", + "add_radio": "Ajouter une radio", + "ca_cert_explanation": "Veuillez utiliser un fichier .pem qui commence par \"-----BEGIN CERTIFICATE-----\" et se termine par \"-----END CERTIFICATE-----\". Le résultat sera affiché dans le champ ci-dessous. Vous pouvez également copier et coller le certificat que vous souhaitez utiliser directement.", "cannot_delete": "Cette configuration ne peut pas être supprimée car elle est utilisée par au moins une entité, un lieu ou un appareil", + "choose_radio_band": "Quelle bande de radio voudriez-vous créer ?", "choose_section": "Quelle section souhaitez-vous que ce bloc contienne?", "configuration_browser": "Navigateur de configuration", "configurations": "Les configurations", @@ -194,6 +205,7 @@ "error_trying_delete": "Erreur lors de la tentative de suppression : {{error}}", "error_update": "Erreur: {{error}}", "explanation": "Explication", + "key_pem_explanation": "Veuillez sélectionner le fichier .pem", "last_configuration_change": "Changement de configuration", "last_configuration_download": "Téléchargement de la dernière configuration", "location": "Emplacement", @@ -249,6 +261,7 @@ "entity": { "add_child": "Ajouter une entité enfant à {{entityName}}", "add_failure": "Erreur, le serveur a renvoyé : {{error}}", + "add_ips": "Gérer les IP", "add_root": "Ajouter une entité racine", "add_success": "Entité créée avec succès !", "assigned_inventory": "Inventaire assigné", @@ -258,13 +271,18 @@ "delete_success": "Entité supprimée avec succès", "delete_warning": "Attention : cette opération ne peut pas être annulée", "edit_failure": "Échec de la mise à jour : {{error}}", + "enter_here": "Entrez les IP que vous souhaitez ajouter ici", "entities": "Entités", "entity": "Entité", "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é", + "ip_detection": "Détection IP", + "ip_formats": "Vous pouvez ajouter des adresses IPv4 ou IPv6 aux formats suivants :", + "no_ips": "Aucune adresse IP sélectionnée", "not_assigned": "Non attribué", "only_unassigned": "Uniquement non attribué", + "update_failure_error": "Erreur lors de la tentative de mise à jour de l'entité : {{error}}", "valid_serial": "Doit être un numéro de série valide (12 caractères HEX)", "venues": "Les lieux" }, @@ -319,6 +337,7 @@ "add_tag": "Créer un tag", "add_tag_to": "Ajouter un nouvel appareil à {{name}}", "add_venue": "Ajouter un lieu", + "assign_ent_ven": "Attribuer à une entité ou à un lieu", "assign_entity_instructions": "Vous pouvez soit trouver l'entité à laquelle vous souhaitez que cette balise soit attribuée en utilisant le menu ci-dessous, soit coller manuellement l'UUID de l'entité dans le champ ci-dessus.", "assign_error": "Erreur lors de la tentative d'attribution de balise", "assign_to_entity": "Affecter à l'entité", diff --git a/public/locales/pt/translation.json b/public/locales/pt/translation.json index f32a1f9..1bc77b8 100644 --- a/public/locales/pt/translation.json +++ b/public/locales/pt/translation.json @@ -33,6 +33,7 @@ "common": { "access_policy": "Política de Acesso", "add": "Adicionar", + "add_items": "Adicionar itens", "adding_ellipsis": "Adicionando ...", "are_you_sure": "Você tem certeza?", "back_to_login": "Volte ao login", @@ -101,7 +102,12 @@ "hour": "hora", "hours": "horas", "id": "identidade", + "invalid_file": "O arquivo escolhido era inválido, por favor, leia as instruções e ajuste seu arquivo de acordo", + "invalid_pem": "Seu arquivo .pem é inválido. Deve começar com '----- BEGIN CERTIFICATE -----' OU '----- BEGIN PRIVATE KEY -----' e deve terminar com '----- END CERTIFICATE --- - 'OU' ----- END PRIVATE KEY ----- '", "ip_address": "Endereço de IP", + "ips": "IPs", + "item": "Item", + "items": "Unid", "items_per_page": "Itens por página:", "last_dashboard_refresh": "Última atualização do painel", "later_tonight": "Logo à noite", @@ -130,6 +136,7 @@ "overall_health": "Saúde geral", "password_policy": "Política de Senha", "preview": "Visualizar", + "program": "Programa", "recorded": "Gravado", "refresh": "REFRESH", "refresh_device": "Atualizar dispositivo", @@ -163,6 +170,7 @@ "unknown": "Desconhecido", "up_to_date": "Dispositivos atualizados", "uptimes": "Uptimes", + "use_file": "Usar arquivo", "uuid": "UUID", "vendors": "Vendedores", "view_more": "Veja mais", @@ -172,7 +180,10 @@ "add_configuration": "Adicionar configuração", "add_new_block": "Adicionar novo bloco de configuração", "add_or_link": "Link ou adicionar", + "add_radio": "Adicionar rádio", + "ca_cert_explanation": "Use um arquivo .pem que comece com \"----- BEGIN CERTIFICATE -----\" e termine com \"----- END CERTIFICATE -----\". O resultado será mostrado no campo abaixo. Você também pode copiar e colar o certificado que deseja usar diretamente.", "cannot_delete": "Esta configuração não pode ser excluída porque está sendo usada por pelo menos uma entidade, local ou dispositivo", + "choose_radio_band": "Que banda de rádio você gostaria de criar?", "choose_section": "Qual seção você gostaria que este bloco contivesse?", "configuration_browser": "Navegador de configuração", "configurations": "configurações", @@ -194,6 +205,7 @@ "error_trying_delete": "Erro ao tentar excluir: {{error}}", "error_update": "Erro: {{error}}", "explanation": "Explicação", + "key_pem_explanation": "Selecione o arquivo .pem", "last_configuration_change": "Mudança de configuração", "last_configuration_download": "Último download da configuração", "location": "Localização", @@ -249,6 +261,7 @@ "entity": { "add_child": "Adicionar Entidade Filha a {{entityName}}", "add_failure": "Erro, o servidor retornou: {{error}}", + "add_ips": "Gerenciar IPs", "add_root": "Adicionar Entidade Raiz", "add_success": "Entidade criada com sucesso!", "assigned_inventory": "Estoque Atribuído", @@ -258,13 +271,18 @@ "delete_success": "Entidade excluída com sucesso", "delete_warning": "Aviso: esta operação não pode ser revertida", "edit_failure": "Atualização malsucedida: {{error}}", + "enter_here": "Digite o (s) IP (s) que deseja adicionar aqui", "entities": "Entidades", "entity": "Entidade", "error_fetch_entity": "Erro ao buscar informações da entidade", "error_fetching": "Erro ao buscar entidades", "error_saving": "Erro ao salvar entidade", + "ip_detection": "Detecção de IP", + "ip_formats": "Você pode adicionar endereços IPv4 ou IPv6 nos seguintes formatos:", + "no_ips": "Nenhum IP selecionado", "not_assigned": "Não atribuído", "only_unassigned": "Apenas não atribuídos", + "update_failure_error": "Erro ao tentar atualizar a entidade: {{error}}", "valid_serial": "Precisa ser um número de série válido (12 caracteres HEX)", "venues": "Locais" }, @@ -319,6 +337,7 @@ "add_tag": "Criar tag", "add_tag_to": "Adicionar novo dispositivo a {{name}}", "add_venue": "Adicionar Local", + "assign_ent_ven": "Atribuir à Entidade ou Local", "assign_entity_instructions": "Você pode encontrar a entidade à qual deseja que esta tag seja atribuída usando o menu abaixo ou pode colar manualmente o UUID da entidade no campo acima.", "assign_error": "Erro ao tentar atribuir tag", "assign_to_entity": "Atribuir à Entidade",