From e38a7ce3acb0c598a78f17dc5589b5476cf14396 Mon Sep 17 00:00:00 2001 From: Sina Date: Fri, 12 Jul 2024 12:55:14 +0200 Subject: [PATCH] [feat] Add API token to user settings (#267) --- src/locales/de-DE/messages.po | 63 +++++++++- src/locales/en-US/messages.po | 63 +++++++++- .../user-settings/UserSettingsApiTokens.tsx | 46 +++++++ .../UserSettingsApiTokensAddToken.tsx | 116 ++++++++++++++++++ .../UserSettingsApiTokensRow.tsx | 103 ++++++++++++++++ .../panel/user-settings/UserSettingsPage.tsx | 8 ++ .../UserSettingsSocialNetworkDeleteButton.tsx | 4 +- .../user-settings/deleteApiToken.mutation.ts | 7 ++ ...ry.ts => deleteOAuthAssociate.mutation.ts} | 2 +- .../panel/user-settings/getApiToken.query.ts | 8 ++ .../user-settings/postApiToken.mutation.ts | 7 ++ src/shared/constants/endPoints.ts | 5 + .../types/server/requests/DeleteApiToken.ts | 4 + .../types/server/requests/PostApiToken.ts | 5 + src/shared/types/server/requests/index.ts | 2 + .../types/server/responses/GetApiToken.ts | 10 ++ .../types/server/responses/PostApiToken.ts | 3 + src/shared/types/server/responses/index.ts | 2 + 18 files changed, 449 insertions(+), 9 deletions(-) create mode 100644 src/pages/panel/user-settings/UserSettingsApiTokens.tsx create mode 100644 src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx create mode 100644 src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx create mode 100644 src/pages/panel/user-settings/deleteApiToken.mutation.ts rename src/pages/panel/user-settings/{deleteOAuthAssociate.query.ts => deleteOAuthAssociate.mutation.ts} (84%) create mode 100644 src/pages/panel/user-settings/getApiToken.query.ts create mode 100644 src/pages/panel/user-settings/postApiToken.mutation.ts create mode 100644 src/shared/types/server/requests/DeleteApiToken.ts create mode 100644 src/shared/types/server/requests/PostApiToken.ts create mode 100644 src/shared/types/server/responses/GetApiToken.ts create mode 100644 src/shared/types/server/responses/PostApiToken.ts diff --git a/src/locales/de-DE/messages.po b/src/locales/de-DE/messages.po index 1794cb47..0938a78b 100644 --- a/src/locales/de-DE/messages.po +++ b/src/locales/de-DE/messages.po @@ -63,6 +63,10 @@ msgstr "{0, plural, one {# Woche} other {# Wochen}}" msgid "{0, plural, one {# Year} other {# Years}}" msgstr "{0, plural, one {# Jahr} other {# Jahre}}" +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:31 +msgid "{0} ago" +msgstr "" + #: src/pages/panel/resource-detail/utils/nodeChange.tsx:68 #: src/pages/panel/resource-detail/utils/nodeChange.tsx:115 msgid "{0} checks failed." @@ -432,6 +436,11 @@ msgstr "API-Zugriff" msgid "API Key" msgstr "API-Schlüssel" +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:15 +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:20 +msgid "API Tokens" +msgstr "" + #: src/pages/panel/workspace-settings-accounts-setup-cloud-azure/getInstructions.tsx:79 #: src/pages/panel/workspace-settings-accounts-setup-cloud-azure/WorkspaceSettingsAccountsSetupCloudAzureSubmitCredentials.tsx:102 msgid "Application (client) ID" @@ -449,6 +458,7 @@ msgstr "Möchten Sie die Verbindung zu {name} wirklich trennen?" msgid "Are you sure you want to remove the {name} account {0}?" msgstr "Sind Sie sicher, dass Sie das {name}-Konto {0} entfernen möchten?" +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:52 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountRow.tsx:454 #: src/pages/panel/workspace-settings-users/WorkspaceSettingsUserInvitationRow.tsx:85 #: src/pages/panel/workspace-settings-users/WorkspaceSettingsUserRow.tsx:107 @@ -541,6 +551,7 @@ msgstr "" msgid "Business" msgstr "Business" +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:59 #: src/pages/panel/user-settings/UserSettingsSocialNetworkDeleteButton.tsx:68 #: src/pages/panel/user-settings/UserSettingsTotpActivationModal.tsx:121 #: src/pages/panel/user-settings/UserSettingsTotpDeactivationModal.tsx:49 @@ -659,6 +670,7 @@ msgstr "" msgid "Client secret" msgstr "" +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:78 #: src/pages/panel/user-settings/UserSettingsTotpRecoveryCodesModal.tsx:36 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountRow.tsx:391 msgid "Close" @@ -748,7 +760,7 @@ msgstr "Verbinden Sie Teams" msgid "Connect your AWS account" msgstr "Verbinden Sie Ihr AWS-Konto" -#: src/pages/panel/user-settings/UserSettingsPage.tsx:32 +#: src/pages/panel/user-settings/UserSettingsPage.tsx:34 msgid "Connected Accounts" msgstr "Verbundene Konten" @@ -781,14 +793,34 @@ msgstr "" msgid "Core CSPM scanning capabilities" msgstr "Kern-CSPM-Scanfunktionen" +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:90 +msgid "Create" +msgstr "" + #: src/pages/panel/workspace-settings-accounts-setup-cloud-azure/getInstructions.tsx:84 msgid "Create an App secret" msgstr "" +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:64 +msgid "Create new API token" +msgstr "" + +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:72 +msgid "Create new API Token" +msgstr "" + #: src/pages/panel/workspace-settings-accounts-setup-cloud-gcp/getInstructions.tsx:46 msgid "Create Service Account" msgstr "" +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:27 +msgid "Created" +msgstr "" + +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:95 +msgid "Created At" +msgstr "" + #: src/pages/panel/resource-detail/ResourceDetailView.tsx:242 msgid "Created Time" msgstr "Erstellte Zeit" @@ -862,6 +894,11 @@ msgstr "" msgid "Delayed Effect" msgstr "Verzögerte Wirkung" +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:33 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:40 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:44 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:45 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:86 #: src/pages/panel/user-settings/UserSettingsSocialNetworkDeleteButton.tsx:79 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountRow.tsx:372 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountRow.tsx:376 @@ -896,6 +933,8 @@ msgstr "Bereitstellung für ein einzelnes Konto" msgid "Deploy to organization" msgstr "Für die Organisation bereitstellen" +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:24 +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:97 #: src/pages/panel/workspace-settings-external-directory/WorkspaceSettingsExternalDirectoryPage.tsx:24 msgid "Description" msgstr "Beschreibung" @@ -938,6 +977,10 @@ msgstr "" msgid "Do you want to delete this account?" msgstr "Möchten Sie dieses Konto löschen?" +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:53 +msgid "Do you want to delete this API Token?" +msgstr "" + #: src/pages/panel/workspace-settings-users/WorkspaceSettingsUserInvitationRow.tsx:86 msgid "Do you want to delete this invitation?" msgstr "Möchten Sie diese Einladung löschen?" @@ -1359,6 +1402,11 @@ msgstr "Arten" msgid "Last login" msgstr "Letzte Anmeldung" +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:30 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:98 +msgid "Last used" +msgstr "" + #: src/pages/panel/benchmark-detail/BenchmarkDetailCheckDetail.tsx:105 msgid "Learn more about the associated risk" msgstr "" @@ -1453,6 +1501,7 @@ msgstr "Die meisten nicht konformen Konten" #: src/pages/panel/benchmark-detail/BenchmarkDetailCheckDetail.tsx:177 #: src/pages/panel/resource-detail/ResourceDetailView.tsx:233 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:92 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountRow.tsx:496 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountsTableItem.tsx:65 #: src/pages/panel/workspace-settings-external-directory/AddExternalDirectory.tsx:42 @@ -1575,7 +1624,7 @@ msgstr "" msgid "Nothing to show yet" msgstr "Noch nichts zu zeigen" -#: src/pages/panel/user-settings/UserSettingsPage.tsx:27 +#: src/pages/panel/user-settings/UserSettingsPage.tsx:29 msgid "Notification" msgstr "Benachrichtigung" @@ -1746,6 +1795,10 @@ msgstr "Wählen Sie eine der Empfehlungen rechts aus und verbessern Sie Ihre Sic msgid "Please add a payment method to switch your workspace's product tier" msgstr "" +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:105 +msgid "Please enter a brief description" +msgstr "" + #: src/pages/auth/login/LoginPage.tsx:184 msgid "Please enter your One-Time-Password or one of your Recovery code." msgstr "Bitte geben Sie Ihr One-Time-Passwort oder einen Ihrer Wiederherstellungscodes ein." @@ -2161,6 +2214,10 @@ msgstr "Das von Ihnen gesendete Token ist ungültig oder abgelaufen. Bitte versu msgid "The workspace you requested cannot be accessed. Please request access from the workspace administrator." msgstr "" +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:104 +msgid "There's already another api tokens with the same description" +msgstr "" + #: src/shared/layouts/panel-layout/AccountCheckGuard.tsx:48 msgid "There's no account configured for this workspace." msgstr "Für diesen Arbeitsbereich ist kein Konto konfiguriert." @@ -2303,7 +2360,7 @@ msgstr "" msgid "User Impersonation permission" msgstr "" -#: src/pages/panel/user-settings/UserSettingsPage.tsx:16 +#: src/pages/panel/user-settings/UserSettingsPage.tsx:18 #: src/shared/layouts/panel-layout/UserProfileButton.tsx:157 msgid "User Settings" msgstr "Benutzereinstellungen" diff --git a/src/locales/en-US/messages.po b/src/locales/en-US/messages.po index 8ec19152..00c8ee9d 100644 --- a/src/locales/en-US/messages.po +++ b/src/locales/en-US/messages.po @@ -63,6 +63,10 @@ msgstr "{0, plural, one {# Week} other {# Weeks}}" msgid "{0, plural, one {# Year} other {# Years}}" msgstr "{0, plural, one {# Year} other {# Years}}" +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:31 +msgid "{0} ago" +msgstr "{0} ago" + #: src/pages/panel/resource-detail/utils/nodeChange.tsx:68 #: src/pages/panel/resource-detail/utils/nodeChange.tsx:115 msgid "{0} checks failed." @@ -432,6 +436,11 @@ msgstr "API access" msgid "API Key" msgstr "API Key" +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:15 +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:20 +msgid "API Tokens" +msgstr "API Tokens" + #: src/pages/panel/workspace-settings-accounts-setup-cloud-azure/getInstructions.tsx:79 #: src/pages/panel/workspace-settings-accounts-setup-cloud-azure/WorkspaceSettingsAccountsSetupCloudAzureSubmitCredentials.tsx:102 msgid "Application (client) ID" @@ -449,6 +458,7 @@ msgstr "Are you sure you want to disconnect {name}?" msgid "Are you sure you want to remove the {name} account {0}?" msgstr "Are you sure you want to remove the {name} account {0}?" +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:52 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountRow.tsx:454 #: src/pages/panel/workspace-settings-users/WorkspaceSettingsUserInvitationRow.tsx:85 #: src/pages/panel/workspace-settings-users/WorkspaceSettingsUserRow.tsx:107 @@ -541,6 +551,7 @@ msgstr "Billing history" msgid "Business" msgstr "Business" +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:59 #: src/pages/panel/user-settings/UserSettingsSocialNetworkDeleteButton.tsx:68 #: src/pages/panel/user-settings/UserSettingsTotpActivationModal.tsx:121 #: src/pages/panel/user-settings/UserSettingsTotpDeactivationModal.tsx:49 @@ -659,6 +670,7 @@ msgstr "Click the service Account, go to the \"Keys\" Tab, and Click \"Add Key\" msgid "Client secret" msgstr "Client secret" +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:78 #: src/pages/panel/user-settings/UserSettingsTotpRecoveryCodesModal.tsx:36 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountRow.tsx:391 msgid "Close" @@ -748,7 +760,7 @@ msgstr "Connect Teams" msgid "Connect your AWS account" msgstr "Connect your AWS account" -#: src/pages/panel/user-settings/UserSettingsPage.tsx:32 +#: src/pages/panel/user-settings/UserSettingsPage.tsx:34 msgid "Connected Accounts" msgstr "Connected Accounts" @@ -781,14 +793,34 @@ msgstr "Copy the Secret Value of the created secret and paste it in the Secret V msgid "Core CSPM scanning capabilities" msgstr "Core CSPM scanning capabilities" +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:90 +msgid "Create" +msgstr "Create" + #: src/pages/panel/workspace-settings-accounts-setup-cloud-azure/getInstructions.tsx:84 msgid "Create an App secret" msgstr "Create an App secret" +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:64 +msgid "Create new API token" +msgstr "Create new API token" + +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:72 +msgid "Create new API Token" +msgstr "Create new API Token" + #: src/pages/panel/workspace-settings-accounts-setup-cloud-gcp/getInstructions.tsx:46 msgid "Create Service Account" msgstr "Create Service Account" +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:27 +msgid "Created" +msgstr "Created" + +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:95 +msgid "Created At" +msgstr "Created At" + #: src/pages/panel/resource-detail/ResourceDetailView.tsx:242 msgid "Created Time" msgstr "Created Time" @@ -862,6 +894,11 @@ msgstr "Define Roles" msgid "Delayed Effect" msgstr "Delayed Effect" +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:33 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:40 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:44 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:45 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:86 #: src/pages/panel/user-settings/UserSettingsSocialNetworkDeleteButton.tsx:79 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountRow.tsx:372 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountRow.tsx:376 @@ -896,6 +933,8 @@ msgstr "Deploy to a single account" msgid "Deploy to organization" msgstr "Deploy to organization" +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:24 +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:97 #: src/pages/panel/workspace-settings-external-directory/WorkspaceSettingsExternalDirectoryPage.tsx:24 msgid "Description" msgstr "Description" @@ -938,6 +977,10 @@ msgstr "Do not show this message again." msgid "Do you want to delete this account?" msgstr "Do you want to delete this account?" +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:53 +msgid "Do you want to delete this API Token?" +msgstr "Do you want to delete this API Token?" + #: src/pages/panel/workspace-settings-users/WorkspaceSettingsUserInvitationRow.tsx:86 msgid "Do you want to delete this invitation?" msgstr "Do you want to delete this invitation?" @@ -1359,6 +1402,11 @@ msgstr "Kinds" msgid "Last login" msgstr "Last login" +#: src/pages/panel/user-settings/UserSettingsApiTokens.tsx:30 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:98 +msgid "Last used" +msgstr "Last used" + #: src/pages/panel/benchmark-detail/BenchmarkDetailCheckDetail.tsx:105 msgid "Learn more about the associated risk" msgstr "Learn more about the associated risk" @@ -1453,6 +1501,7 @@ msgstr "Most Non-Compliant Accounts" #: src/pages/panel/benchmark-detail/BenchmarkDetailCheckDetail.tsx:177 #: src/pages/panel/resource-detail/ResourceDetailView.tsx:233 +#: src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx:92 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountRow.tsx:496 #: src/pages/panel/workspace-settings-accounts/WorkspaceSettingsAccountsTableItem.tsx:65 #: src/pages/panel/workspace-settings-external-directory/AddExternalDirectory.tsx:42 @@ -1575,7 +1624,7 @@ msgstr "Nothing" msgid "Nothing to show yet" msgstr "Nothing to show yet" -#: src/pages/panel/user-settings/UserSettingsPage.tsx:27 +#: src/pages/panel/user-settings/UserSettingsPage.tsx:29 msgid "Notification" msgstr "Notification" @@ -1746,6 +1795,10 @@ msgstr "Pick one of the recommendations to the right and improve your security" msgid "Please add a payment method to switch your workspace's product tier" msgstr "Please add a payment method to switch your workspace's product tier" +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:105 +msgid "Please enter a brief description" +msgstr "Please enter a brief description" + #: src/pages/auth/login/LoginPage.tsx:184 msgid "Please enter your One-Time-Password or one of your Recovery code." msgstr "Please enter your One-Time-Password or one of your Recovery code." @@ -2161,6 +2214,10 @@ msgstr "The token that you sent is invalid or have been expired please try again msgid "The workspace you requested cannot be accessed. Please request access from the workspace administrator." msgstr "The workspace you requested cannot be accessed. Please request access from the workspace administrator." +#: src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx:104 +msgid "There's already another api tokens with the same description" +msgstr "There's already another api tokens with the same description" + #: src/shared/layouts/panel-layout/AccountCheckGuard.tsx:48 msgid "There's no account configured for this workspace." msgstr "There's no account configured for this workspace." @@ -2303,7 +2360,7 @@ msgstr "Uploading your Google Cloud Service Account file..." msgid "User Impersonation permission" msgstr "User Impersonation permission" -#: src/pages/panel/user-settings/UserSettingsPage.tsx:16 +#: src/pages/panel/user-settings/UserSettingsPage.tsx:18 #: src/shared/layouts/panel-layout/UserProfileButton.tsx:157 msgid "User Settings" msgstr "User Settings" diff --git a/src/pages/panel/user-settings/UserSettingsApiTokens.tsx b/src/pages/panel/user-settings/UserSettingsApiTokens.tsx new file mode 100644 index 00000000..db9d40a2 --- /dev/null +++ b/src/pages/panel/user-settings/UserSettingsApiTokens.tsx @@ -0,0 +1,46 @@ +import { t, Trans } from '@lingui/macro' +import { Paper, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from '@mui/material' +import { useSuspenseQuery } from '@tanstack/react-query' +import { getApiTokenQuery } from './getApiToken.query' +import { UserSettingsApiTokensAddToken } from './UserSettingsApiTokensAddToken' +import { UserSettingsApiTokensRow } from './UserSettingsApiTokensRow' + +export const UserSettingsApiTokens = () => { + const { data } = useSuspenseQuery({ queryKey: ['api-token'], queryFn: getApiTokenQuery }) + + return ( + <> + + + API Tokens + + i.name)} /> + + + + + + + Description + + + Created + + + Last used + + + Delete + + + + + {data.map((item) => ( + + ))} + +
+
+ + ) +} diff --git a/src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx b/src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx new file mode 100644 index 00000000..8ef4f5df --- /dev/null +++ b/src/pages/panel/user-settings/UserSettingsApiTokensAddToken.tsx @@ -0,0 +1,116 @@ +import { t, Trans } from '@lingui/macro' +import AddIcon from '@mui/icons-material/Add' +import { LoadingButton } from '@mui/lab' +import { Button, Stack, TextField } from '@mui/material' +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { useRef, useState } from 'react' +import { useThemeMode } from 'src/core/theme' +import { Modal } from 'src/shared/modal' +import { ApiTokenItem } from 'src/shared/types/server' +import { postApiTokenMutation } from './postApiToken.mutation' + +interface UserSettingsApiTokensAddTokenProps { + forbiddenNames: string[] +} + +export const UserSettingsApiTokensAddToken = ({ forbiddenNames }: UserSettingsApiTokensAddTokenProps) => { + const [submitted, setSubmitted] = useState(false) + const [description, setDescription] = useState('') + const { mode } = useThemeMode() + + const modalRef = useRef<(show?: boolean | undefined) => void>() + const queryClient = useQueryClient() + const { mutate, isPending } = useMutation({ + mutationFn: postApiTokenMutation, + onSettled: () => { + modalRef.current?.(false) + setDescription('') + setSubmitted(false) + void queryClient.invalidateQueries({ queryKey: ['api-token'] }) + }, + }) + + const hasErrorBeforeSubmitted = !description || forbiddenNames.includes(description) + const hasError = submitted && hasErrorBeforeSubmitted + + const onSubmit = () => { + setSubmitted(true) + if (!hasErrorBeforeSubmitted) { + mutate( + { name: description, permission: null, workspace_id: null }, + { + onSuccess: () => { + const newDate = new Date().toISOString() + queryClient.setQueryData(['api-token'], (oldData: ApiTokenItem[]) => [ + { + created_at: newDate, + last_used_at: null, + name: description, + permission: null, + workspace_id: null, + updated_at: newDate, + }, + ...oldData, + ]) + }, + }, + ) + } + } + + return ( + <> + + { + setDescription('') + setSubmitted(false) + }} + openRef={modalRef} + title={Create new API Token} + onSubmit={onSubmit} + actions={ + <> + {isPending ? null : ( + + )} + } + loading={isPending} + color="success" + variant="outlined" + disabled={hasError} + onClick={onSubmit} + > + Create + + + } + > + + setDescription(e.currentTarget.value)} + /> + + + + ) +} diff --git a/src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx b/src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx new file mode 100644 index 00000000..0d81cf54 --- /dev/null +++ b/src/pages/panel/user-settings/UserSettingsApiTokensRow.tsx @@ -0,0 +1,103 @@ +import { t, Trans } from '@lingui/macro' +import { useLingui } from '@lingui/react' +import DeleteIcon from '@mui/icons-material/Delete' +import { LoadingButton } from '@mui/lab' +import { Button, IconButton, TableCell, TableRow, Tooltip, Typography } from '@mui/material' +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { useRef } from 'react' +import { Modal } from 'src/shared/modal' +import { ApiTokenItem } from 'src/shared/types/server' +import { diffDateTimeToDuration, iso8601DurationToString } from 'src/shared/utils/parseDuration' +import { deleteApiTokenMutation } from './deleteApiToken.mutation' + +interface UserSettingsApiTokensRowProps { + item: ApiTokenItem +} + +export const UserSettingsApiTokensRow = ({ item }: UserSettingsApiTokensRowProps) => { + const { + i18n: { locale }, + } = useLingui() + const deleteModalRef = useRef<(show?: boolean | undefined) => void>() + const queryClient = useQueryClient() + const { mutate, isPending } = useMutation({ + mutationFn: deleteApiTokenMutation, + onSettled: () => { + void queryClient.invalidateQueries({ queryKey: ['api-token'] }) + }, + }) + const createdAt = new Date(item.created_at) + const lastUsed = item.last_used_at + ? t`${iso8601DurationToString(diffDateTimeToDuration(new Date(), new Date(item.last_used_at)), 1)} ago` + : '-' + return ( + + {item.name} + {`${createdAt.toLocaleDateString(locale)} ${createdAt.toLocaleTimeString(locale)}`} + {lastUsed} + + {isPending ? ( + + + + ) : ( + Delete} arrow> + deleteModalRef.current?.(true)}> + + + + )} + + Are you sure?} + description={Do you want to delete this API Token?} + openRef={deleteModalRef} + actions={ + <> + {isPending ? null : ( + + )} + } + loading={isPending} + color="error" + variant="outlined" + onClick={() => { + mutate( + { name: item.name, token: null }, + { + onSuccess: () => { + queryClient.setQueryData(['api-token'], (oldData: ApiTokenItem[]) => { + const newData = [...oldData] + const index = oldData.findIndex((oldItem) => oldItem.name === item.name) + if (index > -1) { + newData.splice(index, 1) + } + return newData + }) + }, + }, + ) + }} + > + Delete + + + } + > + + Name: {item.name} + + + Created At: {createdAt.toLocaleDateString(locale)} {createdAt.toLocaleTimeString(locale)} + + + Last used: {lastUsed} + + + + ) +} diff --git a/src/pages/panel/user-settings/UserSettingsPage.tsx b/src/pages/panel/user-settings/UserSettingsPage.tsx index db002013..59c65203 100644 --- a/src/pages/panel/user-settings/UserSettingsPage.tsx +++ b/src/pages/panel/user-settings/UserSettingsPage.tsx @@ -2,6 +2,8 @@ import { Trans } from '@lingui/macro' import { Divider, Stack, Typography } from '@mui/material' import { Suspense } from 'react' import { ErrorBoundaryFallback, NetworkErrorBoundary } from 'src/shared/error-boundary-fallback' +import { LoadingSuspenseFallback } from 'src/shared/loading' +import { UserSettingsApiTokens } from './UserSettingsApiTokens' import { UserSettingsFormEmail } from './UserSettingsFormEmail' import { UserSettingsFormPassword } from './UserSettingsFormPassword' import { UserSettingsNotification } from './UserSettingsNotification' @@ -43,6 +45,12 @@ export default function UserSettingsPage() { + + + }> + + + ) } diff --git a/src/pages/panel/user-settings/UserSettingsSocialNetworkDeleteButton.tsx b/src/pages/panel/user-settings/UserSettingsSocialNetworkDeleteButton.tsx index f2b63231..0e4fa9e3 100644 --- a/src/pages/panel/user-settings/UserSettingsSocialNetworkDeleteButton.tsx +++ b/src/pages/panel/user-settings/UserSettingsSocialNetworkDeleteButton.tsx @@ -7,7 +7,7 @@ import { useRef } from 'react' import { useUserProfile } from 'src/core/auth' import { Modal } from 'src/shared/modal' import { GetOAuthAssociatesResponse } from 'src/shared/types/server' -import { deleteOAuthAssociateQuery } from './deleteOAuthAssociate.query' +import { deleteOAuthAssociateMutation } from './deleteOAuthAssociate.mutation' interface UserSettingsSocialNetworkDeleteButtonProps { providerId: string @@ -17,7 +17,7 @@ interface UserSettingsSocialNetworkDeleteButtonProps { export const UserSettingsSocialNetworkDeleteButton = ({ providerId, email, name }: UserSettingsSocialNetworkDeleteButtonProps) => { const { currentUser } = useUserProfile() - const { mutate, isPending } = useMutation({ mutationFn: deleteOAuthAssociateQuery }) + const { mutate, isPending } = useMutation({ mutationFn: deleteOAuthAssociateMutation }) const queryClient = useQueryClient() const openRef = useRef<(show?: boolean | undefined) => void>() diff --git a/src/pages/panel/user-settings/deleteApiToken.mutation.ts b/src/pages/panel/user-settings/deleteApiToken.mutation.ts new file mode 100644 index 00000000..4d224f10 --- /dev/null +++ b/src/pages/panel/user-settings/deleteApiToken.mutation.ts @@ -0,0 +1,7 @@ +import { endPoints } from 'src/shared/constants' +import { DeleteApiTokenRequest } from 'src/shared/types/server' +import { axiosWithAuth } from 'src/shared/utils/axios' + +export const deleteApiTokenMutation = async (data: DeleteApiTokenRequest) => { + return axiosWithAuth.delete(endPoints.token.self, { data }).then((res) => res.data) +} diff --git a/src/pages/panel/user-settings/deleteOAuthAssociate.query.ts b/src/pages/panel/user-settings/deleteOAuthAssociate.mutation.ts similarity index 84% rename from src/pages/panel/user-settings/deleteOAuthAssociate.query.ts rename to src/pages/panel/user-settings/deleteOAuthAssociate.mutation.ts index 1e78a4af..accb9997 100644 --- a/src/pages/panel/user-settings/deleteOAuthAssociate.query.ts +++ b/src/pages/panel/user-settings/deleteOAuthAssociate.mutation.ts @@ -2,7 +2,7 @@ import { AxiosError } from 'axios' import { endPoints } from 'src/shared/constants' import { axiosWithAuth } from 'src/shared/utils/axios' -export const deleteOAuthAssociateQuery = async (provider_id: string) => { +export const deleteOAuthAssociateMutation = async (provider_id: string) => { return axiosWithAuth .delete(endPoints.auth.oauthAccounts(provider_id)) .then(() => undefined) diff --git a/src/pages/panel/user-settings/getApiToken.query.ts b/src/pages/panel/user-settings/getApiToken.query.ts new file mode 100644 index 00000000..9722951c --- /dev/null +++ b/src/pages/panel/user-settings/getApiToken.query.ts @@ -0,0 +1,8 @@ +import { QueryFunctionContext } from '@tanstack/react-query' +import { endPoints } from 'src/shared/constants' +import { GetApiTokenResponse } from 'src/shared/types/server' +import { axiosWithAuth } from 'src/shared/utils/axios' + +export const getApiTokenQuery = async ({ signal }: QueryFunctionContext<['api-token']>) => { + return axiosWithAuth.get(endPoints.token.self, { signal }).then((res) => res.data) +} diff --git a/src/pages/panel/user-settings/postApiToken.mutation.ts b/src/pages/panel/user-settings/postApiToken.mutation.ts new file mode 100644 index 00000000..8a36049d --- /dev/null +++ b/src/pages/panel/user-settings/postApiToken.mutation.ts @@ -0,0 +1,7 @@ +import { endPoints } from 'src/shared/constants' +import { PostApiTokenRequest, PostApiTokenResponse } from 'src/shared/types/server' +import { axiosWithAuth } from 'src/shared/utils/axios' + +export const postApiTokenMutation = async (data: PostApiTokenRequest) => { + return axiosWithAuth.post(endPoints.token.self, data).then((res) => res.data) +} diff --git a/src/shared/constants/endPoints.ts b/src/shared/constants/endPoints.ts index 99d3dc9c..2f61f08f 100644 --- a/src/shared/constants/endPoints.ts +++ b/src/shared/constants/endPoints.ts @@ -1,6 +1,11 @@ import { NotificationChannel } from 'src/shared/types/server-shared' export const endPoints = { + token: { + self: '/api/token/', + access: '/api/token/access', + info: '/api/token/info', + }, auth: { forgotPassword: 'api/auth/forgot-password', jwt: { diff --git a/src/shared/types/server/requests/DeleteApiToken.ts b/src/shared/types/server/requests/DeleteApiToken.ts new file mode 100644 index 00000000..e99c3265 --- /dev/null +++ b/src/shared/types/server/requests/DeleteApiToken.ts @@ -0,0 +1,4 @@ +export type DeleteApiTokenRequest = { + name: string | null + token: string | null +} diff --git a/src/shared/types/server/requests/PostApiToken.ts b/src/shared/types/server/requests/PostApiToken.ts new file mode 100644 index 00000000..16abcfd0 --- /dev/null +++ b/src/shared/types/server/requests/PostApiToken.ts @@ -0,0 +1,5 @@ +export type PostApiTokenRequest = { + name: string + workspace_id: string | null + permission: number | null +} diff --git a/src/shared/types/server/requests/index.ts b/src/shared/types/server/requests/index.ts index 4f722bca..8e7098fc 100644 --- a/src/shared/types/server/requests/index.ts +++ b/src/shared/types/server/requests/index.ts @@ -1,5 +1,7 @@ +export type { DeleteApiTokenRequest } from './DeleteApiToken' export type { PatchWorkspaceInventoryNodeSecurityIgnoreRequest } from './PatchWorkspaceInventoryNodeSecurityIgnore' export type { PatchWorkspaceSettingsRequest } from './PatchWorkspaceSetting' +export type { PostApiTokenRequest } from './PostApiToken' export type { PostAuthForgotPasswordRequest } from './PostAuthForgotPassword' export type { PostAuthJWTLoginErrorResponse, PostAuthJWTLoginRequest, PostAuthJWTLoginResponse } from './PostAuthJWTLogin' export type { PostAuthMfaAddErrorResponse, PostAuthMfaAddRequest, PostAuthMfaAddResponse } from './PostAuthMfaAdd' diff --git a/src/shared/types/server/responses/GetApiToken.ts b/src/shared/types/server/responses/GetApiToken.ts new file mode 100644 index 00000000..64bacdea --- /dev/null +++ b/src/shared/types/server/responses/GetApiToken.ts @@ -0,0 +1,10 @@ +export interface ApiTokenItem { + name: string + workspace_id: string | null + permission: number | null + last_used_at: string | null + created_at: string + updated_at: string +} + +export type GetApiTokenResponse = ApiTokenItem[] diff --git a/src/shared/types/server/responses/PostApiToken.ts b/src/shared/types/server/responses/PostApiToken.ts new file mode 100644 index 00000000..2d535925 --- /dev/null +++ b/src/shared/types/server/responses/PostApiToken.ts @@ -0,0 +1,3 @@ +export type PostApiTokenResponse = { + token: string +} diff --git a/src/shared/types/server/responses/index.ts b/src/shared/types/server/responses/index.ts index 802bff61..06036e7d 100644 --- a/src/shared/types/server/responses/index.ts +++ b/src/shared/types/server/responses/index.ts @@ -1,3 +1,4 @@ +export type { ApiTokenItem, GetApiTokenResponse } from './GetApiToken' export type { GetCurrentUserResponse } from './GetCurrentUser' export type { GetInfoResponse } from './GetInfo' export type { GetNotificationUserResponse } from './GetNotificationUser' @@ -59,6 +60,7 @@ export type { GetWorkspaceProductTier, GetWorkspaceProductTiersResponse } from ' export type { GetWorkspaceSettingsResponse } from './GetWorkspaceSettings' export type { GetWorkspaceUsersResponse, WorkspaceUser } from './GetWorkspaceUsers' export type { GetWorkspaceResponse, GetWorkspacesResponse } from './GetWorkspaces' +export type { PostApiTokenResponse } from './PostApiToken' export type { PostWorkspaceInventoryPropertyAttributesResponse } from './PostWorkspaceInventoryPropertyAttributes' export type { PostWorkspaceInventoryPropertyPathCompleteResponse } from './PostWorkspaceInventoryPropertyPathComplete' export type { PostWorkspaceInventoryPropertyValuesResponse } from './PostWorkspaceInventoryPropertyValues'