mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
synced 2025-10-30 02:12:33 +00:00
Merge pull request #125 from stephb9959/main
[WIFI-11543] Added API keys to profile page
This commit is contained in:
29
package-lock.json
generated
29
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ucentral-client",
|
||||
"version": "2.8.0(10)",
|
||||
"version": "2.8.0(11)",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ucentral-client",
|
||||
"version": "2.8.0(10)",
|
||||
"version": "2.8.0(11)",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@chakra-ui/icons": "^2.0.11",
|
||||
@@ -57,6 +57,7 @@
|
||||
"@types/node": "^18.11.2",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-csv": "^1.1.3",
|
||||
"@types/react-datepicker": "4.8.0",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-table": "^7.7.12",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
@@ -3551,6 +3552,18 @@
|
||||
"@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": {
|
||||
"version": "18.0.6",
|
||||
"dev": true,
|
||||
@@ -11815,6 +11828,18 @@
|
||||
"@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": {
|
||||
"version": "18.0.6",
|
||||
"dev": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ucentral-client",
|
||||
"version": "2.8.0(10)",
|
||||
"version": "2.8.0(11)",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"main": "index.tsx",
|
||||
@@ -65,6 +65,7 @@
|
||||
"@types/react-csv": "^1.1.3",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-table": "^7.7.12",
|
||||
"@types/react-datepicker": "4.8.0",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
|
||||
@@ -245,6 +245,7 @@
|
||||
"identification": "Identifizierung",
|
||||
"inherit": "Erben",
|
||||
"language": "Sprache",
|
||||
"last_use": "Zuletzt verwendeten",
|
||||
"lifetime": "Lebenszeit",
|
||||
"locale": "Gebietsschema",
|
||||
"logout": "Ausloggen",
|
||||
@@ -261,6 +262,7 @@
|
||||
"model": "Modell",
|
||||
"modified": "Geändert",
|
||||
"monthly": "Monatlich",
|
||||
"months": "Monate",
|
||||
"my_account": "Mein Konto",
|
||||
"name": "Name",
|
||||
"name_error": "Der Name muss weniger als 50 Zeichen lang sein",
|
||||
@@ -314,6 +316,7 @@
|
||||
"use_file": "Datei verwenden",
|
||||
"value": "Wert",
|
||||
"variable": "Variable",
|
||||
"view": "Aussicht",
|
||||
"view_details": "Details anzeigen",
|
||||
"view_in_gateway": "Im Controller anzeigen",
|
||||
"view_json": "JSON anzeigen",
|
||||
@@ -742,6 +745,15 @@
|
||||
"successful_macs": "Erfolgreiche MACs",
|
||||
"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": {
|
||||
"address_line_one": "Adresszeile eins",
|
||||
"address_line_two": "Adresszeile zwei",
|
||||
@@ -802,7 +814,10 @@
|
||||
"receiving_types": "Typen empfangen",
|
||||
"security": "Sicherheit",
|
||||
"source": "Quelle",
|
||||
"thread": "Faden"
|
||||
"thread": "Faden",
|
||||
"venue_config": "Aufbau",
|
||||
"venue_reboot": "Starten Sie neu",
|
||||
"venue_upgrade": "Aktualisierung"
|
||||
},
|
||||
"map": {
|
||||
"auto_align": "Automatisch ausrichten",
|
||||
@@ -838,6 +853,22 @@
|
||||
"my_organization": "Meine 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": {
|
||||
"about_me": "Über mich",
|
||||
"activate": "",
|
||||
|
||||
@@ -245,6 +245,7 @@
|
||||
"identification": "Identification",
|
||||
"inherit": "Inherit",
|
||||
"language": "Language",
|
||||
"last_use": "Last Use",
|
||||
"lifetime": "Lifetime",
|
||||
"locale": "Locale",
|
||||
"logout": "Logout",
|
||||
@@ -261,6 +262,7 @@
|
||||
"model": "Model",
|
||||
"modified": "Modified",
|
||||
"monthly": "Monthly",
|
||||
"months": "Months",
|
||||
"my_account": "My Account",
|
||||
"name": "Name",
|
||||
"name_error": "Name must be less than 50 characters long",
|
||||
@@ -314,6 +316,7 @@
|
||||
"use_file": "Use File",
|
||||
"value": "Value",
|
||||
"variable": "Variable",
|
||||
"view": "View",
|
||||
"view_details": "View Details",
|
||||
"view_in_gateway": "View In Controller",
|
||||
"view_json": "View JSON",
|
||||
@@ -742,6 +745,15 @@
|
||||
"successful_macs": "Successful MACs",
|
||||
"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": {
|
||||
"address_line_one": "Address Line One",
|
||||
"address_line_two": "Address Line Two",
|
||||
@@ -802,7 +814,10 @@
|
||||
"receiving_types": "Receiving Types",
|
||||
"security": "Security",
|
||||
"source": "Source",
|
||||
"thread": "Thread"
|
||||
"thread": "Thread",
|
||||
"venue_config": "Configuration",
|
||||
"venue_reboot": "Reboot",
|
||||
"venue_upgrade": "Upgrade"
|
||||
},
|
||||
"map": {
|
||||
"auto_align": "Auto Align",
|
||||
@@ -838,6 +853,22 @@
|
||||
"my_organization": "My 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": {
|
||||
"about_me": "About Me",
|
||||
"activate": "Activate",
|
||||
|
||||
@@ -245,6 +245,7 @@
|
||||
"identification": "identificación",
|
||||
"inherit": "Heredar",
|
||||
"language": "idioma",
|
||||
"last_use": "Utilizado por última vez",
|
||||
"lifetime": "Toda la vida",
|
||||
"locale": "lugar",
|
||||
"logout": "Cerrar sesión",
|
||||
@@ -261,6 +262,7 @@
|
||||
"model": "Modelo",
|
||||
"modified": "Modificado",
|
||||
"monthly": "Mensual",
|
||||
"months": "Meses",
|
||||
"my_account": "Mi cuenta",
|
||||
"name": "Nombre",
|
||||
"name_error": "El nombre debe tener menos de 50 caracteres",
|
||||
@@ -314,6 +316,7 @@
|
||||
"use_file": "Usar archivo",
|
||||
"value": "Valor",
|
||||
"variable": "Variable",
|
||||
"view": "Ver",
|
||||
"view_details": "Ver detalles",
|
||||
"view_in_gateway": "Ver en controlador",
|
||||
"view_json": "Ver JSON",
|
||||
@@ -742,6 +745,15 @@
|
||||
"successful_macs": "MAC exitosos",
|
||||
"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": {
|
||||
"address_line_one": "Dirección Línea Uno",
|
||||
"address_line_two": "Dirección línea dos",
|
||||
@@ -802,7 +814,10 @@
|
||||
"receiving_types": "Tipos de recepción",
|
||||
"security": "SEGURIDAD",
|
||||
"source": "Fuente",
|
||||
"thread": "Hilo"
|
||||
"thread": "Hilo",
|
||||
"venue_config": "Configuración",
|
||||
"venue_reboot": "Reiniciar",
|
||||
"venue_upgrade": "Mejorar"
|
||||
},
|
||||
"map": {
|
||||
"auto_align": "Alineación automática",
|
||||
@@ -838,6 +853,22 @@
|
||||
"my_organization": "MI ORGANIZACION",
|
||||
"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": {
|
||||
"about_me": "Sobre mí",
|
||||
"activate": "",
|
||||
|
||||
@@ -245,6 +245,7 @@
|
||||
"identification": "Identification",
|
||||
"inherit": "Hériter",
|
||||
"language": "La langue",
|
||||
"last_use": "Dernière utilisation",
|
||||
"lifetime": "durée de vie",
|
||||
"locale": "lieu",
|
||||
"logout": "Connectez - Out",
|
||||
@@ -261,6 +262,7 @@
|
||||
"model": "Modèle",
|
||||
"modified": "Modifié",
|
||||
"monthly": "Mensuel",
|
||||
"months": "mois",
|
||||
"my_account": "Mon compte",
|
||||
"name": "Prénom",
|
||||
"name_error": "Le nom doit comporter moins de 50 caractères",
|
||||
@@ -314,6 +316,7 @@
|
||||
"use_file": "Utiliser le fichier",
|
||||
"value": "Valeur",
|
||||
"variable": "Variable",
|
||||
"view": "Vue",
|
||||
"view_details": "Voir les détails",
|
||||
"view_in_gateway": "Afficher dans le contrôleur",
|
||||
"view_json": "Afficher JSON",
|
||||
@@ -742,6 +745,15 @@
|
||||
"successful_macs": "MAC réussis",
|
||||
"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": {
|
||||
"address_line_one": "Adresse Ligne 1",
|
||||
"address_line_two": "Adresse ligne deux",
|
||||
@@ -802,7 +814,10 @@
|
||||
"receiving_types": "Types de réception",
|
||||
"security": "SÉCURITÉ",
|
||||
"source": "La source",
|
||||
"thread": "Fil de discussion"
|
||||
"thread": "Fil de discussion",
|
||||
"venue_config": "Configuration",
|
||||
"venue_reboot": "Redémarrer",
|
||||
"venue_upgrade": "Améliorer"
|
||||
},
|
||||
"map": {
|
||||
"auto_align": "Alignement automatique",
|
||||
@@ -838,6 +853,22 @@
|
||||
"my_organization": "Mon 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": {
|
||||
"about_me": "À propos de moi",
|
||||
"activate": "",
|
||||
|
||||
@@ -245,6 +245,7 @@
|
||||
"identification": "Identificação",
|
||||
"inherit": "Herdar",
|
||||
"language": "Língua",
|
||||
"last_use": "Usado por último",
|
||||
"lifetime": "Tempo de vida",
|
||||
"locale": "Localidade",
|
||||
"logout": "Sair",
|
||||
@@ -261,6 +262,7 @@
|
||||
"model": "Modelo",
|
||||
"modified": "Modificado",
|
||||
"monthly": "Por mês",
|
||||
"months": "Meses",
|
||||
"my_account": "Minha conta",
|
||||
"name": "Nome",
|
||||
"name_error": "O nome deve ter menos de 50 caracteres",
|
||||
@@ -314,6 +316,7 @@
|
||||
"use_file": "Usar arquivo",
|
||||
"value": "Valor",
|
||||
"variable": "Variável",
|
||||
"view": "Visão",
|
||||
"view_details": "VER DETALHES",
|
||||
"view_in_gateway": "Ver no controlador",
|
||||
"view_json": "Ver JSON",
|
||||
@@ -742,6 +745,15 @@
|
||||
"successful_macs": "MACs de sucesso",
|
||||
"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": {
|
||||
"address_line_one": "Linha de endereço um",
|
||||
"address_line_two": "Linha de endereço dois",
|
||||
@@ -802,7 +814,10 @@
|
||||
"receiving_types": "Tipos de recebimento",
|
||||
"security": "SEGURANÇA",
|
||||
"source": "Fonte",
|
||||
"thread": "FIO"
|
||||
"thread": "FIO",
|
||||
"venue_config": "Configuração",
|
||||
"venue_reboot": "Reiniciar",
|
||||
"venue_upgrade": "Melhorar"
|
||||
},
|
||||
"map": {
|
||||
"auto_align": "Alinhamento Automático",
|
||||
@@ -838,6 +853,22 @@
|
||||
"my_organization": "Minha 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": {
|
||||
"about_me": "Sobre mim",
|
||||
"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 Masonry from 'react-masonry-css';
|
||||
import ApiKeysCard from './ApiKeys';
|
||||
import GeneralInformationProfile from './GeneralInformation';
|
||||
import MultiFactorAuthProfile from './MultiFactorAuth';
|
||||
import ProfileNotes from './Notes';
|
||||
@@ -10,7 +11,7 @@ const ProfileLayout = () => (
|
||||
breakpointCols={{
|
||||
default: 3,
|
||||
1800: 2,
|
||||
1100: 1,
|
||||
1200: 1,
|
||||
}}
|
||||
className="my-masonry-grid"
|
||||
columnClassName="my-masonry-grid_column"
|
||||
@@ -19,6 +20,7 @@ const ProfileLayout = () => (
|
||||
<MultiFactorAuthProfile />
|
||||
<GeneralInformationProfile />
|
||||
<ProfileNotes />
|
||||
<ApiKeysCard />
|
||||
</Masonry>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user