mirror of
https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
synced 2025-11-02 11:17:46 +00:00
[WIFI-11543] Added API keys to profile page
Signed-off-by: Charles <charles.bourque96@gmail.com>
This commit is contained in:
29
package-lock.json
generated
29
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.8.0(10)",
|
"version": "2.8.0(11)",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.8.0(10)",
|
"version": "2.8.0(11)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/icons": "^2.0.11",
|
"@chakra-ui/icons": "^2.0.11",
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
"@types/node": "^18.11.2",
|
"@types/node": "^18.11.2",
|
||||||
"@types/react": "^18.0.21",
|
"@types/react": "^18.0.21",
|
||||||
"@types/react-csv": "^1.1.3",
|
"@types/react-csv": "^1.1.3",
|
||||||
|
"@types/react-datepicker": "4.8.0",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/react-table": "^7.7.12",
|
"@types/react-table": "^7.7.12",
|
||||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||||
@@ -3551,6 +3552,18 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-datepicker": {
|
||||||
|
"version": "4.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.8.0.tgz",
|
||||||
|
"integrity": "sha512-20uzZsIf4moPAjjHDfPvH8UaOHZBxrkiQZoLS3wgKq8Xhp+95gdercLEdoA7/I8nR9R5Jz2qQkdMIM+Lq4AS1A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.9.2",
|
||||||
|
"@types/react": "*",
|
||||||
|
"date-fns": "^2.0.1",
|
||||||
|
"react-popper": "^2.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react-dom": {
|
"node_modules/@types/react-dom": {
|
||||||
"version": "18.0.6",
|
"version": "18.0.6",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -11815,6 +11828,18 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-datepicker": {
|
||||||
|
"version": "4.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.8.0.tgz",
|
||||||
|
"integrity": "sha512-20uzZsIf4moPAjjHDfPvH8UaOHZBxrkiQZoLS3wgKq8Xhp+95gdercLEdoA7/I8nR9R5Jz2qQkdMIM+Lq4AS1A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@popperjs/core": "^2.9.2",
|
||||||
|
"@types/react": "*",
|
||||||
|
"date-fns": "^2.0.1",
|
||||||
|
"react-popper": "^2.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/react-dom": {
|
"@types/react-dom": {
|
||||||
"version": "18.0.6",
|
"version": "18.0.6",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.8.0(10)",
|
"version": "2.8.0(11)",
|
||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
"@types/react-csv": "^1.1.3",
|
"@types/react-csv": "^1.1.3",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/react-table": "^7.7.12",
|
"@types/react-table": "^7.7.12",
|
||||||
|
"@types/react-datepicker": "4.8.0",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||||
"@types/react-window": "^1.8.5",
|
"@types/react-window": "^1.8.5",
|
||||||
|
|||||||
@@ -245,6 +245,7 @@
|
|||||||
"identification": "Identifizierung",
|
"identification": "Identifizierung",
|
||||||
"inherit": "Erben",
|
"inherit": "Erben",
|
||||||
"language": "Sprache",
|
"language": "Sprache",
|
||||||
|
"last_use": "Zuletzt verwendeten",
|
||||||
"lifetime": "Lebenszeit",
|
"lifetime": "Lebenszeit",
|
||||||
"locale": "Gebietsschema",
|
"locale": "Gebietsschema",
|
||||||
"logout": "Ausloggen",
|
"logout": "Ausloggen",
|
||||||
@@ -261,6 +262,7 @@
|
|||||||
"model": "Modell",
|
"model": "Modell",
|
||||||
"modified": "Geändert",
|
"modified": "Geändert",
|
||||||
"monthly": "Monatlich",
|
"monthly": "Monatlich",
|
||||||
|
"months": "Monate",
|
||||||
"my_account": "Mein Konto",
|
"my_account": "Mein Konto",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"name_error": "Der Name muss weniger als 50 Zeichen lang sein",
|
"name_error": "Der Name muss weniger als 50 Zeichen lang sein",
|
||||||
@@ -314,6 +316,7 @@
|
|||||||
"use_file": "Datei verwenden",
|
"use_file": "Datei verwenden",
|
||||||
"value": "Wert",
|
"value": "Wert",
|
||||||
"variable": "Variable",
|
"variable": "Variable",
|
||||||
|
"view": "Aussicht",
|
||||||
"view_details": "Details anzeigen",
|
"view_details": "Details anzeigen",
|
||||||
"view_in_gateway": "Im Controller anzeigen",
|
"view_in_gateway": "Im Controller anzeigen",
|
||||||
"view_json": "JSON anzeigen",
|
"view_json": "JSON anzeigen",
|
||||||
@@ -742,6 +745,15 @@
|
|||||||
"successful_macs": "Erfolgreiche MACs",
|
"successful_macs": "Erfolgreiche MACs",
|
||||||
"title": "Arbeitsplätze"
|
"title": "Arbeitsplätze"
|
||||||
},
|
},
|
||||||
|
"keys": {
|
||||||
|
"description_error": "Die Beschreibung muss weniger als 64 Zeichen lang sein",
|
||||||
|
"expire_error": "Der Ablauf darf nicht mehr als ein Jahr in der Zukunft liegen",
|
||||||
|
"expires": "Läuft ab",
|
||||||
|
"max_keys": "Max. Schlüssel erreicht (10)",
|
||||||
|
"name_error": "Der Name sollte eindeutig sein und aus 6 bis 20 alphanumerischen Zeichen bestehen",
|
||||||
|
"one": "API-Schlüssel",
|
||||||
|
"other": "API-Schlüssel"
|
||||||
|
},
|
||||||
"locations": {
|
"locations": {
|
||||||
"address_line_one": "Adresszeile eins",
|
"address_line_one": "Adresszeile eins",
|
||||||
"address_line_two": "Adresszeile zwei",
|
"address_line_two": "Adresszeile zwei",
|
||||||
@@ -802,7 +814,10 @@
|
|||||||
"receiving_types": "Typen empfangen",
|
"receiving_types": "Typen empfangen",
|
||||||
"security": "Sicherheit",
|
"security": "Sicherheit",
|
||||||
"source": "Quelle",
|
"source": "Quelle",
|
||||||
"thread": "Faden"
|
"thread": "Faden",
|
||||||
|
"venue_config": "Aufbau",
|
||||||
|
"venue_reboot": "Starten Sie neu",
|
||||||
|
"venue_upgrade": "Aktualisierung"
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
"auto_align": "Automatisch ausrichten",
|
"auto_align": "Automatisch ausrichten",
|
||||||
@@ -838,6 +853,22 @@
|
|||||||
"my_organization": "Meine Organisation",
|
"my_organization": "Meine Organisation",
|
||||||
"title": "Organisation"
|
"title": "Organisation"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"delete_source": "Alle Überschreibungen von {{source}}löschen",
|
||||||
|
"ignore_overrides": "Konfigurationsüberschreibungen ignorieren",
|
||||||
|
"name_error": "Der Parameter ist bereits von Ihrer Quelle definiert",
|
||||||
|
"one": "Konfigurationsüberschreibung",
|
||||||
|
"other": "Konfigurationsüberschreibungen",
|
||||||
|
"param_name": "Parameter",
|
||||||
|
"param_value": "Wert",
|
||||||
|
"parameter": "Parameter",
|
||||||
|
"reason": "Grund",
|
||||||
|
"reason_error": "Ihr Grund muss weniger als 64 Zeichen lang sein. lang",
|
||||||
|
"source": "Quelle",
|
||||||
|
"tx_power_error": "Die Sendeleistung muss zwischen 1 und 32 liegen",
|
||||||
|
"update_success": "Aktualisierte Konfigurationsüberschreibungen!",
|
||||||
|
"value": "Wert"
|
||||||
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"about_me": "Über mich",
|
"about_me": "Über mich",
|
||||||
"activate": "",
|
"activate": "",
|
||||||
|
|||||||
@@ -245,6 +245,7 @@
|
|||||||
"identification": "Identification",
|
"identification": "Identification",
|
||||||
"inherit": "Inherit",
|
"inherit": "Inherit",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
|
"last_use": "Last Use",
|
||||||
"lifetime": "Lifetime",
|
"lifetime": "Lifetime",
|
||||||
"locale": "Locale",
|
"locale": "Locale",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
@@ -261,6 +262,7 @@
|
|||||||
"model": "Model",
|
"model": "Model",
|
||||||
"modified": "Modified",
|
"modified": "Modified",
|
||||||
"monthly": "Monthly",
|
"monthly": "Monthly",
|
||||||
|
"months": "Months",
|
||||||
"my_account": "My Account",
|
"my_account": "My Account",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"name_error": "Name must be less than 50 characters long",
|
"name_error": "Name must be less than 50 characters long",
|
||||||
@@ -314,6 +316,7 @@
|
|||||||
"use_file": "Use File",
|
"use_file": "Use File",
|
||||||
"value": "Value",
|
"value": "Value",
|
||||||
"variable": "Variable",
|
"variable": "Variable",
|
||||||
|
"view": "View",
|
||||||
"view_details": "View Details",
|
"view_details": "View Details",
|
||||||
"view_in_gateway": "View In Controller",
|
"view_in_gateway": "View In Controller",
|
||||||
"view_json": "View JSON",
|
"view_json": "View JSON",
|
||||||
@@ -742,6 +745,15 @@
|
|||||||
"successful_macs": "Successful MACs",
|
"successful_macs": "Successful MACs",
|
||||||
"title": "Jobs"
|
"title": "Jobs"
|
||||||
},
|
},
|
||||||
|
"keys": {
|
||||||
|
"description_error": "Description needs to be less than 64 characters long",
|
||||||
|
"expire_error": "The expiry cannot be more than one year in the future",
|
||||||
|
"expires": "Expires",
|
||||||
|
"max_keys": "Max keys reached (10)",
|
||||||
|
"name_error": "Name should be unique and be between 6 and 20 alphanumeric characters",
|
||||||
|
"one": "API Key",
|
||||||
|
"other": "API Keys"
|
||||||
|
},
|
||||||
"locations": {
|
"locations": {
|
||||||
"address_line_one": "Address Line One",
|
"address_line_one": "Address Line One",
|
||||||
"address_line_two": "Address Line Two",
|
"address_line_two": "Address Line Two",
|
||||||
@@ -802,7 +814,10 @@
|
|||||||
"receiving_types": "Receiving Types",
|
"receiving_types": "Receiving Types",
|
||||||
"security": "Security",
|
"security": "Security",
|
||||||
"source": "Source",
|
"source": "Source",
|
||||||
"thread": "Thread"
|
"thread": "Thread",
|
||||||
|
"venue_config": "Configuration",
|
||||||
|
"venue_reboot": "Reboot",
|
||||||
|
"venue_upgrade": "Upgrade"
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
"auto_align": "Auto Align",
|
"auto_align": "Auto Align",
|
||||||
@@ -838,6 +853,22 @@
|
|||||||
"my_organization": "My Organization",
|
"my_organization": "My Organization",
|
||||||
"title": "Organization"
|
"title": "Organization"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"delete_source": "Delete all overrides from {{source}}",
|
||||||
|
"ignore_overrides": "Ignore Configuration Overrides",
|
||||||
|
"name_error": "Parameter is already defined by your source",
|
||||||
|
"one": "Configuration Override",
|
||||||
|
"other": "Configuration Overrides",
|
||||||
|
"param_name": "Parameter",
|
||||||
|
"param_value": "Value",
|
||||||
|
"parameter": "Parameter",
|
||||||
|
"reason": "Reason",
|
||||||
|
"reason_error": "Your reason needs to be less than 64 chars. long",
|
||||||
|
"source": "Source",
|
||||||
|
"tx_power_error": "Tx power needs to be between 1 and 32",
|
||||||
|
"update_success": "Updated Configuration Overrides!",
|
||||||
|
"value": "Value"
|
||||||
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"about_me": "About Me",
|
"about_me": "About Me",
|
||||||
"activate": "Activate",
|
"activate": "Activate",
|
||||||
|
|||||||
@@ -245,6 +245,7 @@
|
|||||||
"identification": "identificación",
|
"identification": "identificación",
|
||||||
"inherit": "Heredar",
|
"inherit": "Heredar",
|
||||||
"language": "idioma",
|
"language": "idioma",
|
||||||
|
"last_use": "Utilizado por última vez",
|
||||||
"lifetime": "Toda la vida",
|
"lifetime": "Toda la vida",
|
||||||
"locale": "lugar",
|
"locale": "lugar",
|
||||||
"logout": "Cerrar sesión",
|
"logout": "Cerrar sesión",
|
||||||
@@ -261,6 +262,7 @@
|
|||||||
"model": "Modelo",
|
"model": "Modelo",
|
||||||
"modified": "Modificado",
|
"modified": "Modificado",
|
||||||
"monthly": "Mensual",
|
"monthly": "Mensual",
|
||||||
|
"months": "Meses",
|
||||||
"my_account": "Mi cuenta",
|
"my_account": "Mi cuenta",
|
||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
"name_error": "El nombre debe tener menos de 50 caracteres",
|
"name_error": "El nombre debe tener menos de 50 caracteres",
|
||||||
@@ -314,6 +316,7 @@
|
|||||||
"use_file": "Usar archivo",
|
"use_file": "Usar archivo",
|
||||||
"value": "Valor",
|
"value": "Valor",
|
||||||
"variable": "Variable",
|
"variable": "Variable",
|
||||||
|
"view": "Ver",
|
||||||
"view_details": "Ver detalles",
|
"view_details": "Ver detalles",
|
||||||
"view_in_gateway": "Ver en controlador",
|
"view_in_gateway": "Ver en controlador",
|
||||||
"view_json": "Ver JSON",
|
"view_json": "Ver JSON",
|
||||||
@@ -742,6 +745,15 @@
|
|||||||
"successful_macs": "MAC exitosos",
|
"successful_macs": "MAC exitosos",
|
||||||
"title": "Trabajos"
|
"title": "Trabajos"
|
||||||
},
|
},
|
||||||
|
"keys": {
|
||||||
|
"description_error": "La descripción debe tener menos de 64 caracteres",
|
||||||
|
"expire_error": "El vencimiento no puede ser más de un año en el futuro",
|
||||||
|
"expires": "Vence",
|
||||||
|
"max_keys": "Número máximo de claves alcanzado (10)",
|
||||||
|
"name_error": "El nombre debe ser único y tener entre 6 y 20 caracteres alfanuméricos",
|
||||||
|
"one": "Clave API",
|
||||||
|
"other": "Claves de api"
|
||||||
|
},
|
||||||
"locations": {
|
"locations": {
|
||||||
"address_line_one": "Dirección Línea Uno",
|
"address_line_one": "Dirección Línea Uno",
|
||||||
"address_line_two": "Dirección línea dos",
|
"address_line_two": "Dirección línea dos",
|
||||||
@@ -802,7 +814,10 @@
|
|||||||
"receiving_types": "Tipos de recepción",
|
"receiving_types": "Tipos de recepción",
|
||||||
"security": "SEGURIDAD",
|
"security": "SEGURIDAD",
|
||||||
"source": "Fuente",
|
"source": "Fuente",
|
||||||
"thread": "Hilo"
|
"thread": "Hilo",
|
||||||
|
"venue_config": "Configuración",
|
||||||
|
"venue_reboot": "Reiniciar",
|
||||||
|
"venue_upgrade": "Mejorar"
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
"auto_align": "Alineación automática",
|
"auto_align": "Alineación automática",
|
||||||
@@ -838,6 +853,22 @@
|
|||||||
"my_organization": "MI ORGANIZACION",
|
"my_organization": "MI ORGANIZACION",
|
||||||
"title": "Organización"
|
"title": "Organización"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"delete_source": "Eliminar todas las anulaciones de {{source}}",
|
||||||
|
"ignore_overrides": "Ignorar anulaciones de configuración",
|
||||||
|
"name_error": "El parámetro ya está definido por su fuente",
|
||||||
|
"one": "Anulación de configuración",
|
||||||
|
"other": "Anulaciones de configuración",
|
||||||
|
"param_name": "parámetro",
|
||||||
|
"param_value": "Valor",
|
||||||
|
"parameter": "parámetro",
|
||||||
|
"reason": "Razón",
|
||||||
|
"reason_error": "Su motivo debe tener menos de 64 caracteres. largo",
|
||||||
|
"source": "Fuente",
|
||||||
|
"tx_power_error": "La potencia Tx debe estar entre 1 y 32",
|
||||||
|
"update_success": "¡Anulaciones de configuración actualizadas!",
|
||||||
|
"value": "Valor"
|
||||||
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"about_me": "Sobre mí",
|
"about_me": "Sobre mí",
|
||||||
"activate": "",
|
"activate": "",
|
||||||
|
|||||||
@@ -245,6 +245,7 @@
|
|||||||
"identification": "Identification",
|
"identification": "Identification",
|
||||||
"inherit": "Hériter",
|
"inherit": "Hériter",
|
||||||
"language": "La langue",
|
"language": "La langue",
|
||||||
|
"last_use": "Dernière utilisation",
|
||||||
"lifetime": "durée de vie",
|
"lifetime": "durée de vie",
|
||||||
"locale": "lieu",
|
"locale": "lieu",
|
||||||
"logout": "Connectez - Out",
|
"logout": "Connectez - Out",
|
||||||
@@ -261,6 +262,7 @@
|
|||||||
"model": "Modèle",
|
"model": "Modèle",
|
||||||
"modified": "Modifié",
|
"modified": "Modifié",
|
||||||
"monthly": "Mensuel",
|
"monthly": "Mensuel",
|
||||||
|
"months": "mois",
|
||||||
"my_account": "Mon compte",
|
"my_account": "Mon compte",
|
||||||
"name": "Prénom",
|
"name": "Prénom",
|
||||||
"name_error": "Le nom doit comporter moins de 50 caractères",
|
"name_error": "Le nom doit comporter moins de 50 caractères",
|
||||||
@@ -314,6 +316,7 @@
|
|||||||
"use_file": "Utiliser le fichier",
|
"use_file": "Utiliser le fichier",
|
||||||
"value": "Valeur",
|
"value": "Valeur",
|
||||||
"variable": "Variable",
|
"variable": "Variable",
|
||||||
|
"view": "Vue",
|
||||||
"view_details": "Voir les détails",
|
"view_details": "Voir les détails",
|
||||||
"view_in_gateway": "Afficher dans le contrôleur",
|
"view_in_gateway": "Afficher dans le contrôleur",
|
||||||
"view_json": "Afficher JSON",
|
"view_json": "Afficher JSON",
|
||||||
@@ -742,6 +745,15 @@
|
|||||||
"successful_macs": "MAC réussis",
|
"successful_macs": "MAC réussis",
|
||||||
"title": "Emplois"
|
"title": "Emplois"
|
||||||
},
|
},
|
||||||
|
"keys": {
|
||||||
|
"description_error": "La description doit comporter moins de 64 caractères",
|
||||||
|
"expire_error": "L'expiration ne peut pas être supérieure à un an dans le futur",
|
||||||
|
"expires": "EXPIRÉ",
|
||||||
|
"max_keys": "Max de clés atteint (10)",
|
||||||
|
"name_error": "Le nom doit être unique et comporter entre 6 et 20 caractères alphanumériques",
|
||||||
|
"one": "Clé API",
|
||||||
|
"other": "Clés API"
|
||||||
|
},
|
||||||
"locations": {
|
"locations": {
|
||||||
"address_line_one": "Adresse Ligne 1",
|
"address_line_one": "Adresse Ligne 1",
|
||||||
"address_line_two": "Adresse ligne deux",
|
"address_line_two": "Adresse ligne deux",
|
||||||
@@ -802,7 +814,10 @@
|
|||||||
"receiving_types": "Types de réception",
|
"receiving_types": "Types de réception",
|
||||||
"security": "SÉCURITÉ",
|
"security": "SÉCURITÉ",
|
||||||
"source": "La source",
|
"source": "La source",
|
||||||
"thread": "Fil de discussion"
|
"thread": "Fil de discussion",
|
||||||
|
"venue_config": "Configuration",
|
||||||
|
"venue_reboot": "Redémarrer",
|
||||||
|
"venue_upgrade": "Améliorer"
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
"auto_align": "Alignement automatique",
|
"auto_align": "Alignement automatique",
|
||||||
@@ -838,6 +853,22 @@
|
|||||||
"my_organization": "Mon organisation",
|
"my_organization": "Mon organisation",
|
||||||
"title": "Organisation"
|
"title": "Organisation"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"delete_source": "Supprimer tous les remplacements de {{source}}",
|
||||||
|
"ignore_overrides": "Ignorer les remplacements de configuration",
|
||||||
|
"name_error": "Le paramètre est déjà défini par votre source",
|
||||||
|
"one": "Remplacement de la configuration",
|
||||||
|
"other": "Remplacements de configuration",
|
||||||
|
"param_name": "paramètre",
|
||||||
|
"param_value": "Valeur",
|
||||||
|
"parameter": "paramètre",
|
||||||
|
"reason": "raison",
|
||||||
|
"reason_error": "Votre raison doit être inférieure à 64 caractères. long",
|
||||||
|
"source": "La source",
|
||||||
|
"tx_power_error": "La puissance de transmission doit être comprise entre 1 et 32",
|
||||||
|
"update_success": "Remplacements de configuration mis à jour !",
|
||||||
|
"value": "Valeur"
|
||||||
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"about_me": "À propos de moi",
|
"about_me": "À propos de moi",
|
||||||
"activate": "",
|
"activate": "",
|
||||||
|
|||||||
@@ -245,6 +245,7 @@
|
|||||||
"identification": "Identificação",
|
"identification": "Identificação",
|
||||||
"inherit": "Herdar",
|
"inherit": "Herdar",
|
||||||
"language": "Língua",
|
"language": "Língua",
|
||||||
|
"last_use": "Usado por último",
|
||||||
"lifetime": "Tempo de vida",
|
"lifetime": "Tempo de vida",
|
||||||
"locale": "Localidade",
|
"locale": "Localidade",
|
||||||
"logout": "Sair",
|
"logout": "Sair",
|
||||||
@@ -261,6 +262,7 @@
|
|||||||
"model": "Modelo",
|
"model": "Modelo",
|
||||||
"modified": "Modificado",
|
"modified": "Modificado",
|
||||||
"monthly": "Por mês",
|
"monthly": "Por mês",
|
||||||
|
"months": "Meses",
|
||||||
"my_account": "Minha conta",
|
"my_account": "Minha conta",
|
||||||
"name": "Nome",
|
"name": "Nome",
|
||||||
"name_error": "O nome deve ter menos de 50 caracteres",
|
"name_error": "O nome deve ter menos de 50 caracteres",
|
||||||
@@ -314,6 +316,7 @@
|
|||||||
"use_file": "Usar arquivo",
|
"use_file": "Usar arquivo",
|
||||||
"value": "Valor",
|
"value": "Valor",
|
||||||
"variable": "Variável",
|
"variable": "Variável",
|
||||||
|
"view": "Visão",
|
||||||
"view_details": "VER DETALHES",
|
"view_details": "VER DETALHES",
|
||||||
"view_in_gateway": "Ver no controlador",
|
"view_in_gateway": "Ver no controlador",
|
||||||
"view_json": "Ver JSON",
|
"view_json": "Ver JSON",
|
||||||
@@ -742,6 +745,15 @@
|
|||||||
"successful_macs": "MACs de sucesso",
|
"successful_macs": "MACs de sucesso",
|
||||||
"title": "Empregos"
|
"title": "Empregos"
|
||||||
},
|
},
|
||||||
|
"keys": {
|
||||||
|
"description_error": "A descrição precisa ter menos de 64 caracteres",
|
||||||
|
"expire_error": "A expiração não pode ser superior a um ano no futuro",
|
||||||
|
"expires": "expira",
|
||||||
|
"max_keys": "Teclas máximas alcançadas (10)",
|
||||||
|
"name_error": "O nome deve ser único e ter entre 6 e 20 caracteres alfanuméricos",
|
||||||
|
"one": "Chave API",
|
||||||
|
"other": "Chaves de Api"
|
||||||
|
},
|
||||||
"locations": {
|
"locations": {
|
||||||
"address_line_one": "Linha de endereço um",
|
"address_line_one": "Linha de endereço um",
|
||||||
"address_line_two": "Linha de endereço dois",
|
"address_line_two": "Linha de endereço dois",
|
||||||
@@ -802,7 +814,10 @@
|
|||||||
"receiving_types": "Tipos de recebimento",
|
"receiving_types": "Tipos de recebimento",
|
||||||
"security": "SEGURANÇA",
|
"security": "SEGURANÇA",
|
||||||
"source": "Fonte",
|
"source": "Fonte",
|
||||||
"thread": "FIO"
|
"thread": "FIO",
|
||||||
|
"venue_config": "Configuração",
|
||||||
|
"venue_reboot": "Reiniciar",
|
||||||
|
"venue_upgrade": "Melhorar"
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
"auto_align": "Alinhamento Automático",
|
"auto_align": "Alinhamento Automático",
|
||||||
@@ -838,6 +853,22 @@
|
|||||||
"my_organization": "Minha organização",
|
"my_organization": "Minha organização",
|
||||||
"title": "Organização"
|
"title": "Organização"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"delete_source": "Excluir todas as substituições de {{source}}",
|
||||||
|
"ignore_overrides": "Ignorar substituições de configuração",
|
||||||
|
"name_error": "O parâmetro já está definido pela sua fonte",
|
||||||
|
"one": "Substituição de configuração",
|
||||||
|
"other": "Substituições de configuração",
|
||||||
|
"param_name": "parâmetro",
|
||||||
|
"param_value": "Valor",
|
||||||
|
"parameter": "parâmetro",
|
||||||
|
"reason": "RAZÃO",
|
||||||
|
"reason_error": "Seu motivo precisa ter menos de 64 caracteres. grandes",
|
||||||
|
"source": "Fonte",
|
||||||
|
"tx_power_error": "A potência Tx precisa estar entre 1 e 32",
|
||||||
|
"update_success": "Substituições de configuração atualizadas!",
|
||||||
|
"value": "Valor"
|
||||||
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"about_me": "Sobre mim",
|
"about_me": "Sobre mim",
|
||||||
"activate": "",
|
"activate": "",
|
||||||
|
|||||||
15
src/components/DatePickers/DatePickerInput/index.tsx
Normal file
15
src/components/DatePickers/DatePickerInput/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React, { forwardRef } from 'react';
|
||||||
|
import { Button } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
};
|
||||||
|
const DatePickerInput = forwardRef(({ value, onClick, isDisabled }: Props, ref: React.Ref<HTMLButtonElement>) => (
|
||||||
|
<Button colorScheme="gray" onClick={onClick} ref={ref} isDisabled={isDisabled}>
|
||||||
|
{value}
|
||||||
|
</Button>
|
||||||
|
));
|
||||||
|
|
||||||
|
export default DatePickerInput;
|
||||||
37
src/components/DatePickers/DateTimePicker/index.tsx
Normal file
37
src/components/DatePickers/DateTimePicker/index.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import DatePicker from 'react-datepicker';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
|
import DatePickerInput from '../DatePickerInput';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
date: Date;
|
||||||
|
onChange: (v: Date | null) => void;
|
||||||
|
isStart?: boolean;
|
||||||
|
isEnd?: boolean;
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DateTimePicker = ({ date, onChange, isStart, isEnd, startDate, endDate, isDisabled }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
selected={date}
|
||||||
|
onChange={onChange}
|
||||||
|
selectsStart={isStart}
|
||||||
|
selectsEnd={isEnd}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
timeInputLabel={`${t('common.time')}: `}
|
||||||
|
dateFormat="dd/MM/yyyy hh:mm aa"
|
||||||
|
timeFormat="p"
|
||||||
|
showTimeSelect
|
||||||
|
customInput={<DatePickerInput isDisabled={isDisabled} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(DateTimePicker);
|
||||||
84
src/hooks/Network/ApiKeys.ts
Normal file
84
src/hooks/Network/ApiKeys.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { axiosSec } from 'constants/axiosInstances';
|
||||||
|
import { AxiosError } from 'models/Axios';
|
||||||
|
|
||||||
|
export type ApiKey = {
|
||||||
|
id: string;
|
||||||
|
userUuid: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
apiKey: string;
|
||||||
|
expiresOn: number;
|
||||||
|
lastUse: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiKeyResponse = {
|
||||||
|
apiKeys: ApiKey[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getKeys = async (uuid?: string) =>
|
||||||
|
axiosSec
|
||||||
|
.get(`apiKey/${uuid}`)
|
||||||
|
.then(({ data }: { data: ApiKeyResponse }) => data)
|
||||||
|
.catch((e: AxiosError) => {
|
||||||
|
if (e.response?.status === 404) {
|
||||||
|
return {
|
||||||
|
apiKeys: [],
|
||||||
|
} as ApiKeyResponse;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useGetUserApiKeys = ({ userId }: { userId?: string }) =>
|
||||||
|
useQuery(['apiKeys', userId], () => getKeys(userId), {
|
||||||
|
enabled: userId !== undefined && userId !== '',
|
||||||
|
staleTime: 1000 * 60 * 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteKey = async ({ userId, keyId }: { userId: string; keyId: string }) =>
|
||||||
|
axiosSec.delete(`apiKey/${userId}?keyUuid=${keyId}`, {});
|
||||||
|
export const useDeleteApiKey = ({ userId }: { userId?: string }) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation(deleteKey, {
|
||||||
|
onSuccess: () => {
|
||||||
|
if (userId !== undefined && userId.length > 0) {
|
||||||
|
queryClient.invalidateQueries(['apiKeys', userId]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateKey = async ({ description, userId, keyId }: { description: string; userId: string; keyId: string }) =>
|
||||||
|
axiosSec.put(`apiKey/${userId}?keyUuid=${keyId}`, { id: keyId, userUuid: userId, description });
|
||||||
|
export const useUpdateApiKey = ({ userId }: { userId?: string }) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation(updateKey, {
|
||||||
|
onSuccess: () => {
|
||||||
|
if (userId !== undefined && userId.length > 0) {
|
||||||
|
queryClient.invalidateQueries(['apiKeys', userId]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createKey = async ({
|
||||||
|
data,
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
data: { userUuid: string; name: string; description: string; expiresOn: number };
|
||||||
|
}) => axiosSec.post(`apiKey/${userId}`, data);
|
||||||
|
|
||||||
|
export const useCreateApiKey = ({ userId }: { userId?: string }) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation(createKey, {
|
||||||
|
onSuccess: () => {
|
||||||
|
if (userId !== undefined && userId.length > 0) {
|
||||||
|
queryClient.invalidateQueries(['apiKeys', userId]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
140
src/pages/Profile/ApiKeys/Table/Actions.tsx
Normal file
140
src/pages/Profile/ApiKeys/Table/Actions.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CopyIcon } from '@chakra-ui/icons';
|
||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
Popover,
|
||||||
|
PopoverArrow,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverCloseButton,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverFooter,
|
||||||
|
PopoverHeader,
|
||||||
|
PopoverTrigger,
|
||||||
|
Center,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
useDisclosure,
|
||||||
|
HStack,
|
||||||
|
Text,
|
||||||
|
useClipboard,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { Eye, Trash } from 'phosphor-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ApiKey, useDeleteApiKey } from 'hooks/Network/ApiKeys';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
apiKey: ApiKey;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApiKeyActions = ({ apiKey, isDisabled }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const deleteKey = useDeleteApiKey({ userId: apiKey.userUuid });
|
||||||
|
const { hasCopied, onCopy } = useClipboard(apiKey.apiKey);
|
||||||
|
|
||||||
|
const handleDeleteClick = React.useCallback(() => {
|
||||||
|
deleteKey.mutate(
|
||||||
|
{ userId: apiKey.userUuid, keyId: apiKey.id },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack mx="auto">
|
||||||
|
<Popover isOpen={isOpen} onOpen={onOpen} onClose={onClose}>
|
||||||
|
<Tooltip hasArrow label={t('crud.delete')} placement="top" isDisabled={isOpen}>
|
||||||
|
<Box>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<IconButton
|
||||||
|
aria-label="delete-device"
|
||||||
|
colorScheme="red"
|
||||||
|
icon={<Trash size={20} />}
|
||||||
|
size="sm"
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
</PopoverTrigger>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<PopoverContent w="340px">
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverCloseButton />
|
||||||
|
<PopoverHeader>
|
||||||
|
{t('crud.delete')} {apiKey.name}
|
||||||
|
</PopoverHeader>
|
||||||
|
<PopoverBody>
|
||||||
|
<Text whiteSpace="break-spaces">{t('crud.delete_confirm', { obj: t('keys.one') })}</Text>
|
||||||
|
</PopoverBody>
|
||||||
|
<PopoverFooter>
|
||||||
|
<Center>
|
||||||
|
<Button colorScheme="gray" mr="1" onClick={onClose}>
|
||||||
|
{t('common.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button colorScheme="red" ml="1" onClick={handleDeleteClick} isLoading={deleteKey.isLoading}>
|
||||||
|
{t('common.yes')}
|
||||||
|
</Button>
|
||||||
|
</Center>
|
||||||
|
</PopoverFooter>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<Tooltip
|
||||||
|
label={hasCopied ? `${t('common.copied')}!` : `${t('common.copy')} ${t('keys.one')}`}
|
||||||
|
hasArrow
|
||||||
|
closeOnClick={false}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('common.copy')}
|
||||||
|
icon={<CopyIcon h={5} w={5} />}
|
||||||
|
onClick={onCopy}
|
||||||
|
size="sm"
|
||||||
|
colorScheme="teal"
|
||||||
|
mr={2}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Popover>
|
||||||
|
<Tooltip label={`${t('common.view')} ${t('keys.one')}`} hasArrow closeOnClick={false}>
|
||||||
|
<Box>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<IconButton aria-label={t('common.view')} icon={<Eye size={20} />} size="sm" colorScheme="purple" />
|
||||||
|
</PopoverTrigger>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<PopoverContent w="560px">
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverCloseButton />
|
||||||
|
<PopoverHeader>
|
||||||
|
{t('common.view')} {apiKey.name} {t('keys.one')}
|
||||||
|
<Tooltip
|
||||||
|
label={hasCopied ? `${t('common.copied')}!` : `${t('common.copy')} ${t('keys.one')}`}
|
||||||
|
hasArrow
|
||||||
|
closeOnClick={false}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('common.copy')}
|
||||||
|
icon={<CopyIcon h={4} w={4} />}
|
||||||
|
onClick={onCopy}
|
||||||
|
size="xs"
|
||||||
|
colorScheme="teal"
|
||||||
|
ml={2}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</PopoverHeader>
|
||||||
|
<PopoverBody>
|
||||||
|
<Text whiteSpace="break-spaces">
|
||||||
|
<Center>
|
||||||
|
<pre style={{ fontFamily: 'monospace' }}>{apiKey.apiKey}</pre>
|
||||||
|
</Center>
|
||||||
|
</Text>
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiKeyActions;
|
||||||
131
src/pages/Profile/ApiKeys/Table/AddButton.tsx
Normal file
131
src/pages/Profile/ApiKeys/Table/AddButton.tsx
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
AlertDescription,
|
||||||
|
AlertIcon,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
FormErrorMessage,
|
||||||
|
FormLabel,
|
||||||
|
Input,
|
||||||
|
Textarea,
|
||||||
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import ApiKeyExpiresOnField from './ExpiresOnField';
|
||||||
|
import { CreateButton } from 'components/Buttons/CreateButton';
|
||||||
|
import { Modal } from 'components/Modals/Modal';
|
||||||
|
import { ApiKey, useCreateApiKey } from 'hooks/Network/ApiKeys';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
apiKeys: ApiKey[];
|
||||||
|
userId: string;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const thirtyDaysFromNow = () => Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30;
|
||||||
|
|
||||||
|
const CreateApiKeyButton = ({ apiKeys, userId, isDisabled }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [name, setName] = React.useState('');
|
||||||
|
const [description, setDescription] = React.useState('');
|
||||||
|
const [expiresOn, setExpiresOn] = React.useState(thirtyDaysFromNow());
|
||||||
|
const createKey = useCreateApiKey({ userId });
|
||||||
|
|
||||||
|
const onNameChange = (e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value);
|
||||||
|
const onDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => setDescription(e.target.value);
|
||||||
|
|
||||||
|
const onCreate = React.useCallback(() => {
|
||||||
|
createKey.mutate(
|
||||||
|
{ data: { name, description, userUuid: userId, expiresOn }, userId },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
if (axios.isAxiosError(e)) {
|
||||||
|
toast({
|
||||||
|
id: 'create-api-key-error',
|
||||||
|
title: t('common.error'),
|
||||||
|
description: e?.response?.data?.ErrorDescription,
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
position: 'top-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}, [name, description, expiresOn]);
|
||||||
|
|
||||||
|
const nameError = React.useMemo(() => {
|
||||||
|
if (
|
||||||
|
name.length === 0 ||
|
||||||
|
name.length > 20 ||
|
||||||
|
apiKeys.map(({ name: n }) => n).includes(name) ||
|
||||||
|
!/^[a-z0-9]+$/i.test(name)
|
||||||
|
) {
|
||||||
|
return t('keys.name_error');
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [name, apiKeys]);
|
||||||
|
|
||||||
|
const handleOpenClick = () => {
|
||||||
|
setName('');
|
||||||
|
setDescription('');
|
||||||
|
setExpiresOn(thirtyDaysFromNow());
|
||||||
|
onOpen();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{apiKeys.length >= 10 && (
|
||||||
|
<Alert status="error">
|
||||||
|
<AlertIcon />
|
||||||
|
<AlertDescription>{t('keys.max_keys')}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<CreateButton onClick={handleOpenClick} isDisabled={isDisabled || apiKeys.length >= 10} isCompact />
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
title={` ${t('crud.create')} ${t('keys.one')}`}
|
||||||
|
topRightButtons={
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
ml="1"
|
||||||
|
onClick={onCreate}
|
||||||
|
isDisabled={nameError !== undefined || description.length > 64}
|
||||||
|
>
|
||||||
|
{t('common.save')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
options={{
|
||||||
|
modalSize: 'sm',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<FormControl mb={2} isInvalid={nameError !== undefined}>
|
||||||
|
<FormLabel>{t('common.name')}</FormLabel>
|
||||||
|
<Input value={name} onChange={onNameChange} />
|
||||||
|
<FormErrorMessage>{nameError}</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl mb={2} isInvalid={description.length > 64}>
|
||||||
|
<FormLabel>{t('common.description')}</FormLabel>
|
||||||
|
<Textarea value={description} onChange={onDescriptionChange} noOfLines={2} />
|
||||||
|
<FormErrorMessage>{t('keys.description_error')}</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
<ApiKeyExpiresOnField value={expiresOn} setValue={setExpiresOn} />
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateApiKeyButton;
|
||||||
123
src/pages/Profile/ApiKeys/Table/DescriptionCell.tsx
Normal file
123
src/pages/Profile/ApiKeys/Table/DescriptionCell.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
FormControl,
|
||||||
|
FormErrorMessage,
|
||||||
|
FormLabel,
|
||||||
|
IconButton,
|
||||||
|
Popover,
|
||||||
|
PopoverArrow,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverCloseButton,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverFooter,
|
||||||
|
PopoverHeader,
|
||||||
|
PopoverTrigger,
|
||||||
|
Text,
|
||||||
|
Textarea,
|
||||||
|
Tooltip,
|
||||||
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { Pen } from 'phosphor-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ApiKey, useUpdateApiKey } from 'hooks/Network/ApiKeys';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
apiKey: ApiKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ApiKeyDescriptionCell = ({ apiKey }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const toast = useToast();
|
||||||
|
const [newDescription, setNewDescription] = React.useState(apiKey.description);
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const updateApiKey = useUpdateApiKey({ userId: apiKey.userUuid });
|
||||||
|
|
||||||
|
const onDescriptionChange = React.useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setNewDescription(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSaveClick = () => {
|
||||||
|
updateApiKey.mutate(
|
||||||
|
{
|
||||||
|
userId: apiKey.userUuid,
|
||||||
|
keyId: apiKey.id,
|
||||||
|
description: newDescription,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
if (axios.isAxiosError(e)) {
|
||||||
|
toast({
|
||||||
|
id: 'error-save-api-key-description',
|
||||||
|
title: t('common.error'),
|
||||||
|
description: e?.response?.data?.ErrorDescription,
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
position: 'top-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
setNewDescription(apiKey.description);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text w="100%" overflowWrap="break-word" whiteSpace="pre-wrap">
|
||||||
|
<Popover isOpen={isOpen} onOpen={onOpen} onClose={onClose}>
|
||||||
|
<Tooltip label={t('common.edit')} hasArrow closeOnClick={false} isDisabled={isOpen}>
|
||||||
|
<Box display="unset">
|
||||||
|
<PopoverTrigger>
|
||||||
|
<IconButton aria-label={t('common.edit')} icon={<Pen size={20} />} size="xs" colorScheme="teal" mr={2} />
|
||||||
|
</PopoverTrigger>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<PopoverContent w="340px">
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverCloseButton />
|
||||||
|
<PopoverHeader>
|
||||||
|
{t('common.edit')} {apiKey.name} {t('common.description')}
|
||||||
|
</PopoverHeader>
|
||||||
|
<PopoverBody>
|
||||||
|
<FormControl mb={2} isInvalid={newDescription.length > 64}>
|
||||||
|
<FormLabel>{t('common.description')}</FormLabel>
|
||||||
|
<Textarea value={newDescription} onChange={onDescriptionChange} noOfLines={2} />
|
||||||
|
<FormErrorMessage>{t('keys.description_error')}</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverBody>
|
||||||
|
<PopoverFooter>
|
||||||
|
<Center>
|
||||||
|
<Button colorScheme="gray" mr="1" onClick={onCancel}>
|
||||||
|
{t('common.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
ml="1"
|
||||||
|
onClick={handleSaveClick}
|
||||||
|
isDisabled={newDescription.length > 64}
|
||||||
|
isLoading={updateApiKey.isLoading}
|
||||||
|
>
|
||||||
|
{t('common.save')}
|
||||||
|
</Button>
|
||||||
|
</Center>
|
||||||
|
</PopoverFooter>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
{apiKey.description}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiKeyDescriptionCell;
|
||||||
81
src/pages/Profile/ApiKeys/Table/ExpiresOnField.tsx
Normal file
81
src/pages/Profile/ApiKeys/Table/ExpiresOnField.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Box, Flex, FormControl, FormErrorMessage, FormLabel, Radio, RadioGroup, Stack, Text } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import DateTimePicker from 'components/DatePickers/DateTimePicker';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: number;
|
||||||
|
setValue: (value: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const aYearFromNow = () => Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365;
|
||||||
|
|
||||||
|
const ApiKeyExpiresOnField = ({ value, setValue }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [radio, setRadioValue] = React.useState('30');
|
||||||
|
|
||||||
|
const onRadioChange = (v: string) => {
|
||||||
|
setRadioValue(v);
|
||||||
|
const days = parseInt(v, 10);
|
||||||
|
const now = new Date();
|
||||||
|
if (days > 0) {
|
||||||
|
now.setDate(now.getDate() + days);
|
||||||
|
setValue(Math.floor(now.getTime() / 1000));
|
||||||
|
} else {
|
||||||
|
now.setDate(now.getDate() + 30);
|
||||||
|
setValue(Math.floor(now.getTime() / 1000));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDateChange = (v: Date | null) => {
|
||||||
|
if (v) setValue(Math.floor(v.getTime() / 1000));
|
||||||
|
};
|
||||||
|
|
||||||
|
const tempDate = () => {
|
||||||
|
if (!value || value === 0) return new Date();
|
||||||
|
|
||||||
|
return new Date(value * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl isInvalid={value > aYearFromNow()} isRequired>
|
||||||
|
<FormLabel ms={0} mb={2} fontSize="md" fontWeight="normal" _disabled={{ opacity: 0.8 }}>
|
||||||
|
{t('certificates.expires_on')}
|
||||||
|
</FormLabel>
|
||||||
|
<Box>
|
||||||
|
<RadioGroup onChange={onRadioChange} defaultValue="30">
|
||||||
|
<Stack spacing={5} direction="column">
|
||||||
|
<Radio colorScheme="blue" value="30">
|
||||||
|
30 {t('common.days')}
|
||||||
|
</Radio>
|
||||||
|
<Radio colorScheme="blue" value="60">
|
||||||
|
60 {t('common.days')}
|
||||||
|
</Radio>
|
||||||
|
<Radio colorScheme="blue" value="90">
|
||||||
|
90 {t('common.days')}
|
||||||
|
</Radio>
|
||||||
|
<Radio colorScheme="blue" value="180">
|
||||||
|
6 {t('common.months')}
|
||||||
|
</Radio>
|
||||||
|
<Radio colorScheme="green" value="0">
|
||||||
|
<Flex>
|
||||||
|
<Text my="auto" mr={2} w="180px">
|
||||||
|
{t('common.custom')}
|
||||||
|
</Text>
|
||||||
|
<DateTimePicker
|
||||||
|
date={tempDate()}
|
||||||
|
isStart
|
||||||
|
onChange={onDateChange}
|
||||||
|
isDisabled={!value || value === 0 || radio !== '0'}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Radio>
|
||||||
|
</Stack>
|
||||||
|
</RadioGroup>
|
||||||
|
</Box>
|
||||||
|
<FormErrorMessage>{t('keys.expire_error')}</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiKeyExpiresOnField;
|
||||||
54
src/pages/Profile/ApiKeys/Table/index.tsx
Normal file
54
src/pages/Profile/ApiKeys/Table/index.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Box, Flex, Heading, HStack, Spacer } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import CreateApiKeyButton from './AddButton';
|
||||||
|
import useApiKeyTable from './useApiKeyTable';
|
||||||
|
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
||||||
|
import { ColumnPicker } from 'components/DataTables/ColumnPicker';
|
||||||
|
import { DataTable } from 'components/DataTables/DataTable';
|
||||||
|
import { Column } from 'models/Table';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ApiKeyTable = ({ userId }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { query, columns, hiddenColumns } = useApiKeyTable({ userId });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Flex mb={2}>
|
||||||
|
<Heading size="md" my="auto">
|
||||||
|
{t('keys.other')} ({query.data?.apiKeys.length})
|
||||||
|
</Heading>
|
||||||
|
<Spacer />
|
||||||
|
<HStack spacing={2}>
|
||||||
|
<CreateApiKeyButton userId={userId} apiKeys={query.data?.apiKeys ?? []} />
|
||||||
|
<ColumnPicker
|
||||||
|
columns={columns as Column<unknown>[]}
|
||||||
|
hiddenColumns={hiddenColumns[0]}
|
||||||
|
setHiddenColumns={hiddenColumns[1]}
|
||||||
|
preference="apiKeys.profile.table.hiddenColumns"
|
||||||
|
/>
|
||||||
|
<RefreshButton onClick={query.refetch} isFetching={query.isFetching} isCompact />
|
||||||
|
</HStack>
|
||||||
|
</Flex>
|
||||||
|
<Box>
|
||||||
|
<DataTable
|
||||||
|
columns={columns as Column<object>[]}
|
||||||
|
saveSettingsId="apiKeys.profile.table"
|
||||||
|
data={query.data?.apiKeys ?? []}
|
||||||
|
obj={t('keys.other')}
|
||||||
|
sortBy={[{ id: 'expiresOn', desc: false }]}
|
||||||
|
minHeight="400px"
|
||||||
|
hiddenColumns={hiddenColumns[0]}
|
||||||
|
showAllRows
|
||||||
|
hideControls
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiKeyTable;
|
||||||
78
src/pages/Profile/ApiKeys/Table/useApiKeyTable.tsx
Normal file
78
src/pages/Profile/ApiKeys/Table/useApiKeyTable.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import ApiKeyActions from './Actions';
|
||||||
|
import ApiKeyDescriptionCell from './DescriptionCell';
|
||||||
|
import FormattedDate from 'components/InformationDisplays/FormattedDate';
|
||||||
|
import { ApiKey, useGetUserApiKeys } from 'hooks/Network/ApiKeys';
|
||||||
|
import { Column } from 'models/Table';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useApiKeyTable = ({ userId }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const hiddenColumns = React.useState<string[]>([]);
|
||||||
|
const query = useGetUserApiKeys({ userId });
|
||||||
|
|
||||||
|
const dateCell = React.useCallback((date: number) => <FormattedDate date={date} />, []);
|
||||||
|
const actionCell = React.useCallback((apiKey: ApiKey) => <ApiKeyActions apiKey={apiKey} />, []);
|
||||||
|
const descriptionCell = React.useCallback((apiKey: ApiKey) => <ApiKeyDescriptionCell apiKey={apiKey} />, []);
|
||||||
|
|
||||||
|
const columns = React.useMemo(
|
||||||
|
(): Column<ApiKey>[] => [
|
||||||
|
{
|
||||||
|
id: 'name',
|
||||||
|
Header: t('common.name'),
|
||||||
|
Footer: '',
|
||||||
|
accessor: 'name',
|
||||||
|
alwaysShow: true,
|
||||||
|
customWidth: '120px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'expiresOn',
|
||||||
|
Header: t('keys.expires'),
|
||||||
|
Footer: '',
|
||||||
|
Cell: ({ cell }) => dateCell(cell.row.original.expiresOn),
|
||||||
|
accessor: 'expiresOn',
|
||||||
|
customWidth: '120px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'lastUse',
|
||||||
|
Header: t('common.last_use'),
|
||||||
|
Footer: '',
|
||||||
|
Cell: ({ cell }) => dateCell(cell.row.original.lastUse),
|
||||||
|
accessor: 'lastUse',
|
||||||
|
customWidth: '120px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'description',
|
||||||
|
Header: t('common.description'),
|
||||||
|
Footer: '',
|
||||||
|
Cell: ({ cell }) => descriptionCell(cell.row.original),
|
||||||
|
accessor: 'description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
Header: t('common.actions'),
|
||||||
|
Footer: '',
|
||||||
|
Cell: (v) => actionCell(v.cell.row.original),
|
||||||
|
disableSortBy: true,
|
||||||
|
customWidth: '120px',
|
||||||
|
alwaysShow: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
|
return React.useMemo(
|
||||||
|
() => ({
|
||||||
|
query,
|
||||||
|
columns,
|
||||||
|
hiddenColumns,
|
||||||
|
}),
|
||||||
|
[query, columns, hiddenColumns],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useApiKeyTable;
|
||||||
19
src/pages/Profile/ApiKeys/index.tsx
Normal file
19
src/pages/Profile/ApiKeys/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import ApiKeyTable from './Table';
|
||||||
|
import { Card } from 'components/Containers/Card';
|
||||||
|
import { CardBody } from 'components/Containers/Card/CardBody';
|
||||||
|
import { useAuth } from 'contexts/AuthProvider';
|
||||||
|
|
||||||
|
const ApiKeysCard = () => {
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card p={4}>
|
||||||
|
<CardBody>
|
||||||
|
<ApiKeyTable userId={user?.id ?? ''} />
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiKeysCard;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import Masonry from 'react-masonry-css';
|
import Masonry from 'react-masonry-css';
|
||||||
|
import ApiKeysCard from './ApiKeys';
|
||||||
import GeneralInformationProfile from './GeneralInformation';
|
import GeneralInformationProfile from './GeneralInformation';
|
||||||
import MultiFactorAuthProfile from './MultiFactorAuth';
|
import MultiFactorAuthProfile from './MultiFactorAuth';
|
||||||
import ProfileNotes from './Notes';
|
import ProfileNotes from './Notes';
|
||||||
@@ -10,7 +11,7 @@ const ProfileLayout = () => (
|
|||||||
breakpointCols={{
|
breakpointCols={{
|
||||||
default: 3,
|
default: 3,
|
||||||
1800: 2,
|
1800: 2,
|
||||||
1100: 1,
|
1200: 1,
|
||||||
}}
|
}}
|
||||||
className="my-masonry-grid"
|
className="my-masonry-grid"
|
||||||
columnClassName="my-masonry-grid_column"
|
columnClassName="my-masonry-grid_column"
|
||||||
@@ -19,6 +20,7 @@ const ProfileLayout = () => (
|
|||||||
<MultiFactorAuthProfile />
|
<MultiFactorAuthProfile />
|
||||||
<GeneralInformationProfile />
|
<GeneralInformationProfile />
|
||||||
<ProfileNotes />
|
<ProfileNotes />
|
||||||
|
<ApiKeysCard />
|
||||||
</Masonry>
|
</Masonry>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user