From d27f79c25055274c5641ebb5c07d3fe0628fc9de Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 6 Jun 2022 11:41:58 +0800 Subject: [PATCH 01/10] Add get api keys UI --- .../networking/queries/useGetApiKeysQuery.tsx | 75 ++++++++++ packages/web/pages/settings/api-keys.tsx | 130 ++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 packages/web/lib/networking/queries/useGetApiKeysQuery.tsx create mode 100644 packages/web/pages/settings/api-keys.tsx diff --git a/packages/web/lib/networking/queries/useGetApiKeysQuery.tsx b/packages/web/lib/networking/queries/useGetApiKeysQuery.tsx new file mode 100644 index 000000000..b13422427 --- /dev/null +++ b/packages/web/lib/networking/queries/useGetApiKeysQuery.tsx @@ -0,0 +1,75 @@ +import { gql } from 'graphql-request' +import useSWR from 'swr' +import { publicGqlFetcher } from '../networkHelpers' + +export interface ApiKey { + id: string + name: string + key?: string + scopes: string[] + createdAt: Date + expiresAt: Date + usedAt?: Date +} + +interface ApiKeysQueryResponse { + isValidating: boolean + apiKeys: ApiKey[] + revalidate: () => void +} + +interface ApiKeysQueryResponseData { + apiKeys: ApiKeysData +} + +interface ApiKeysData { + apiKeys: unknown +} + +export function useGetApiKeysQuery(): ApiKeysQueryResponse { + const query = gql` + query GetApiKeys { + apiKeys { + ... on ApiKeysSuccess { + apiKeys { + id + name + key + scopes + createdAt + expiresAt + usedAt + } + } + ... on ApiKeysError { + errorCodes + } + } + } + ` + + const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher) + console.log('api keys data', data) + + try { + if (data) { + const result = data as ApiKeysQueryResponseData + const apiKeys = result.apiKeys.apiKeys as ApiKey[] + return { + isValidating, + apiKeys, + revalidate: () => { + mutate() + }, + } + } + } catch (error) { + console.log('error', error) + } + return { + isValidating: false, + apiKeys: [], + // eslint-disable-next-line @typescript-eslint/no-empty-function + revalidate: () => {}, + } +} diff --git a/packages/web/pages/settings/api-keys.tsx b/packages/web/pages/settings/api-keys.tsx new file mode 100644 index 000000000..a46ed0352 --- /dev/null +++ b/packages/web/pages/settings/api-keys.tsx @@ -0,0 +1,130 @@ +import { PrimaryLayout } from '../../components/templates/PrimaryLayout' +import { Toaster } from 'react-hot-toast' +import { Table } from '../../components/elements/Table' +import { applyStoredTheme } from '../../lib/themeUpdater' +import { useMemo } from 'react' +import { useGetApiKeysQuery } from '../../lib/networking/queries/useGetApiKeysQuery' + +interface ApiKey { + name: string + scopes: string + expiresAt: string + usedAt: string +} + +export default function ApiKeys(): JSX.Element { + const { apiKeys, revalidate } = useGetApiKeysQuery() + // const [onDeleteId, setOnDeleteId] = useState(null) + // const [addModelOpen, setAddModelOpen] = useState(false) + // const [url, setUrl] = useState('') + // const [formInputs, setFormInputs] = useState([]) + + const headers = ['Name', 'Scopes', 'Used at', 'Expires at'] + const rows = useMemo(() => { + const rows = new Map() + apiKeys.forEach((apiKey) => + rows.set(apiKey.id, { + name: apiKey.name, + scopes: apiKey.scopes.join(', '), + usedAt: apiKey.usedAt?.toISOString() || 'Never', + expiresAt: apiKey.expiresAt.toISOString(), + }) + ) + return rows + }, [apiKeys]) + + applyStoredTheme(false) + + // async function onDelete(id: string): Promise { + // const result = await deleteWebhookMutation(id) + // if (result) { + // showSuccessToast('Webhook deleted', { position: 'bottom-right' }) + // } else { + // showErrorToast('Failed to delete', { position: 'bottom-right' }) + // } + // revalidate() + // } + // + // async function onCreate(): Promise { + // const result = await setWebhookMutation({ url, eventTypes }) + // if (result) { + // showSuccessToast('Webhook created', { position: 'bottom-right' }) + // } else { + // showErrorToast('Failed to add', { position: 'bottom-right' }) + // } + // revalidate() + // } + + return ( + + + + {/*{addModelOpen && (*/} + {/* */} + {/*)}*/} + + {/*{onDeleteId && (*/} + {/* {*/} + {/* await onDelete(onDeleteId)*/} + {/* setOnDeleteId(null)*/} + {/* }}*/} + {/* onOpenChange={() => setOnDeleteId(null)}*/} + {/* />*/} + {/*)}*/} + { + // setFormInputs([ + // { + // label: 'URL', + // onChange: setUrl, + // name: 'url', + // placeholder: 'https://example.com/webhook', + // required: true, + // }, + // { + // label: 'Event Types', + // name: 'eventTypes', + // value: [true, true], + // onChange: setEventTypes, + // options: eventTypeOptions, + // type: 'checkbox', + // }, + // { + // label: 'Method', + // name: 'method', + // value: method, + // disabled: true, + // }, + // { + // label: 'Content Type', + // name: 'contentType', + // value: contentType, + // disabled: true, + // }, + // ]) + // setUrl('') + // setEventTypes(eventTypeOptions as WebhookEvent[]) + // setAddModelOpen(true) + // }} + /> + + ) +} From af4a2ed3be9e6790a46a22c88092d7321cbd3e0a Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 6 Jun 2022 11:46:00 +0800 Subject: [PATCH 02/10] Add generateApiKey mutation in Web --- .../mutations/generateApiKeyMutation.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 packages/web/lib/networking/mutations/generateApiKeyMutation.ts diff --git a/packages/web/lib/networking/mutations/generateApiKeyMutation.ts b/packages/web/lib/networking/mutations/generateApiKeyMutation.ts new file mode 100644 index 000000000..d8c531001 --- /dev/null +++ b/packages/web/lib/networking/mutations/generateApiKeyMutation.ts @@ -0,0 +1,47 @@ +import { gql } from 'graphql-request' +import { gqlFetcher } from '../networkHelpers' +import { ApiKey } from '../queries/useGetApiKeysQuery' + +export interface GenerateApiKeyInput { + name: string + scopes?: string[] + expiresAt: Date +} + +interface GenerateApiKeyResult { + generateApiKey: GenerateApiKey + errorCodes?: unknown[] +} + +type GenerateApiKey = { + apiKey: ApiKey +} + +export async function generateApiKeyMutation( + input: GenerateApiKeyInput +): Promise { + const mutation = gql` + mutation GenerateApiKey($input: GenerateApiKeyInput!) { + generateApiKey(input: $input) { + ... on GenerateApiKeySuccess { + apiKey { + key + } + } + ... on GenerateApiKeyError { + errorCodes + } + } + } + ` + + try { + const data = (await gqlFetcher(mutation, { + input, + })) as GenerateApiKeyResult + return data.errorCodes ? undefined : data.generateApiKey.apiKey.key + } catch (error) { + console.log('generateApiKeyMutation error', error) + return undefined + } +} From cdcb0d9da321cac7307d37f122c31c016508fe8e Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 6 Jun 2022 12:32:35 +0800 Subject: [PATCH 03/10] Add generate api key modal --- packages/web/pages/settings/api-keys.tsx | 114 +++++++++++------------ 1 file changed, 53 insertions(+), 61 deletions(-) diff --git a/packages/web/pages/settings/api-keys.tsx b/packages/web/pages/settings/api-keys.tsx index a46ed0352..f09ec61bf 100644 --- a/packages/web/pages/settings/api-keys.tsx +++ b/packages/web/pages/settings/api-keys.tsx @@ -2,8 +2,12 @@ import { PrimaryLayout } from '../../components/templates/PrimaryLayout' import { Toaster } from 'react-hot-toast' import { Table } from '../../components/elements/Table' import { applyStoredTheme } from '../../lib/themeUpdater' -import { useMemo } from 'react' +import { useMemo, useState } from 'react' import { useGetApiKeysQuery } from '../../lib/networking/queries/useGetApiKeysQuery' +import { FormInputProps } from '../../components/elements/FormElements' +import { generateApiKeyMutation } from '../../lib/networking/mutations/generateApiKeyMutation' +import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' +import { FormModal } from '../../components/patterns/FormModal' interface ApiKey { name: string @@ -15,19 +19,21 @@ interface ApiKey { export default function ApiKeys(): JSX.Element { const { apiKeys, revalidate } = useGetApiKeysQuery() // const [onDeleteId, setOnDeleteId] = useState(null) - // const [addModelOpen, setAddModelOpen] = useState(false) - // const [url, setUrl] = useState('') - // const [formInputs, setFormInputs] = useState([]) + const [addModelOpen, setAddModelOpen] = useState(false) + const [name, setName] = useState('') + // const [scopes, setScopes] = useState(undefined) + const [expiresAt, setExpiresAt] = useState(new Date()) + const [formInputs, setFormInputs] = useState([]) - const headers = ['Name', 'Scopes', 'Used at', 'Expires at'] + const headers = ['Name', 'Scopes', 'Used on', 'Expires on'] const rows = useMemo(() => { const rows = new Map() apiKeys.forEach((apiKey) => rows.set(apiKey.id, { name: apiKey.name, - scopes: apiKey.scopes.join(', '), - usedAt: apiKey.usedAt?.toISOString() || 'Never', - expiresAt: apiKey.expiresAt.toISOString(), + scopes: apiKey.scopes.join(', ') || 'All', + usedAt: apiKey.usedAt?.toISOString() || 'Never used', + expiresAt: new Date(apiKey.expiresAt).toDateString(), }) ) return rows @@ -44,16 +50,16 @@ export default function ApiKeys(): JSX.Element { // } // revalidate() // } - // - // async function onCreate(): Promise { - // const result = await setWebhookMutation({ url, eventTypes }) - // if (result) { - // showSuccessToast('Webhook created', { position: 'bottom-right' }) - // } else { - // showErrorToast('Failed to add', { position: 'bottom-right' }) - // } - // revalidate() - // } + + async function onCreate(): Promise { + const result = await generateApiKeyMutation({ name, expiresAt }) + if (result) { + showSuccessToast('Api key generated', { position: 'bottom-right' }) + } else { + showErrorToast('Failed to add', { position: 'bottom-right' }) + } + revalidate() + } return ( @@ -63,15 +69,15 @@ export default function ApiKeys(): JSX.Element { }} /> - {/*{addModelOpen && (*/} - {/* */} - {/*)}*/} + {addModelOpen && ( + + )} {/*{onDeleteId && (*/} {/* { - // setFormInputs([ - // { - // label: 'URL', - // onChange: setUrl, - // name: 'url', - // placeholder: 'https://example.com/webhook', - // required: true, - // }, - // { - // label: 'Event Types', - // name: 'eventTypes', - // value: [true, true], - // onChange: setEventTypes, - // options: eventTypeOptions, - // type: 'checkbox', - // }, - // { - // label: 'Method', - // name: 'method', - // value: method, - // disabled: true, - // }, - // { - // label: 'Content Type', - // name: 'contentType', - // value: contentType, - // disabled: true, - // }, - // ]) - // setUrl('') - // setEventTypes(eventTypeOptions as WebhookEvent[]) - // setAddModelOpen(true) - // }} + onAdd={() => { + setFormInputs([ + { + label: 'Name', + onChange: setName, + name: 'name', + required: true, + }, + { + label: 'Expired at', + name: 'expiredAt', + required: true, + onChange: setExpiresAt, + type: 'date', + }, + ]) + setName('') + setExpiresAt(new Date(Date.now() + 1000 * 60 * 60 * 24 * 365)) + setAddModelOpen(true) + }} /> ) From fba142eb3b05b65a896b6c91b1ddb8f27c5260f9 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 6 Jun 2022 12:51:25 +0800 Subject: [PATCH 04/10] Add copy key modal --- packages/web/pages/settings/api-keys.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/web/pages/settings/api-keys.tsx b/packages/web/pages/settings/api-keys.tsx index f09ec61bf..e5aeccf63 100644 --- a/packages/web/pages/settings/api-keys.tsx +++ b/packages/web/pages/settings/api-keys.tsx @@ -8,6 +8,7 @@ import { FormInputProps } from '../../components/elements/FormElements' import { generateApiKeyMutation } from '../../lib/networking/mutations/generateApiKeyMutation' import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' import { FormModal } from '../../components/patterns/FormModal' +import { ConfirmationModal } from '../../components/patterns/ConfirmationModal' interface ApiKey { name: string @@ -24,6 +25,7 @@ export default function ApiKeys(): JSX.Element { // const [scopes, setScopes] = useState(undefined) const [expiresAt, setExpiresAt] = useState(new Date()) const [formInputs, setFormInputs] = useState([]) + const [apiKeyGenerated, setApiKeyGenerated] = useState('') const headers = ['Name', 'Scopes', 'Used on', 'Expires on'] const rows = useMemo(() => { @@ -54,6 +56,7 @@ export default function ApiKeys(): JSX.Element { async function onCreate(): Promise { const result = await generateApiKeyMutation({ name, expiresAt }) if (result) { + setApiKeyGenerated(result) showSuccessToast('Api key generated', { position: 'bottom-right' }) } else { showErrorToast('Failed to add', { position: 'bottom-right' }) @@ -79,6 +82,20 @@ export default function ApiKeys(): JSX.Element { /> )} + {apiKeyGenerated && ( + { + await navigator.clipboard.writeText(apiKeyGenerated) + setApiKeyGenerated('') + }} + onOpenChange={() => setApiKeyGenerated('')} + /> + )} + {/*{onDeleteId && (*/} {/* Date: Mon, 6 Jun 2022 12:53:25 +0800 Subject: [PATCH 05/10] Add revoke api key mutation in Web --- .../mutations/revokeApiKeyMutation.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 packages/web/lib/networking/mutations/revokeApiKeyMutation.ts diff --git a/packages/web/lib/networking/mutations/revokeApiKeyMutation.ts b/packages/web/lib/networking/mutations/revokeApiKeyMutation.ts new file mode 100644 index 000000000..83f9447f5 --- /dev/null +++ b/packages/web/lib/networking/mutations/revokeApiKeyMutation.ts @@ -0,0 +1,39 @@ +import { gql } from 'graphql-request' +import { gqlFetcher } from '../networkHelpers' +import { ApiKey } from '../queries/useGetApiKeysQuery' + +interface RevokeApiKeyResult { + revokeApiKey: RevokeApiKey + errorCodes?: unknown[] +} + +type RevokeApiKey = { + apiKey: ApiKey +} + +export async function revokeApiKeyMutation( + id: string +): Promise { + const mutation = gql` + mutation { + revokeApiKey(id: "${id}") { + ... on RevokeApiKeySuccess { + apiKey { + id + } + } + ... on RevokeApiKeyError { + errorCodes + } + } + } + ` + + try { + const data = (await gqlFetcher(mutation)) as RevokeApiKeyResult + return data.errorCodes ? undefined : data.revokeApiKey.apiKey.id + } catch (error) { + console.log('revokeApiKeyMutation error', error) + return undefined + } +} From 07c71da88e3b8ed7cb9c803e268993ef9f729b0e Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 6 Jun 2022 12:57:00 +0800 Subject: [PATCH 06/10] Add revoke api key modal --- packages/web/pages/settings/api-keys.tsx | 53 ++++++++++++------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/packages/web/pages/settings/api-keys.tsx b/packages/web/pages/settings/api-keys.tsx index e5aeccf63..3a72e1c0d 100644 --- a/packages/web/pages/settings/api-keys.tsx +++ b/packages/web/pages/settings/api-keys.tsx @@ -9,6 +9,7 @@ import { generateApiKeyMutation } from '../../lib/networking/mutations/generateA import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' import { FormModal } from '../../components/patterns/FormModal' import { ConfirmationModal } from '../../components/patterns/ConfirmationModal' +import { revokeApiKeyMutation } from '../../lib/networking/mutations/revokeApiKeyMutation' interface ApiKey { name: string @@ -19,7 +20,7 @@ interface ApiKey { export default function ApiKeys(): JSX.Element { const { apiKeys, revalidate } = useGetApiKeysQuery() - // const [onDeleteId, setOnDeleteId] = useState(null) + const [onDeleteId, setOnDeleteId] = useState('') const [addModelOpen, setAddModelOpen] = useState(false) const [name, setName] = useState('') // const [scopes, setScopes] = useState(undefined) @@ -43,21 +44,21 @@ export default function ApiKeys(): JSX.Element { applyStoredTheme(false) - // async function onDelete(id: string): Promise { - // const result = await deleteWebhookMutation(id) - // if (result) { - // showSuccessToast('Webhook deleted', { position: 'bottom-right' }) - // } else { - // showErrorToast('Failed to delete', { position: 'bottom-right' }) - // } - // revalidate() - // } + async function onDelete(id: string): Promise { + const result = await revokeApiKeyMutation(id) + if (result) { + showSuccessToast('API Key deleted', { position: 'bottom-right' }) + } else { + showErrorToast('Failed to delete', { position: 'bottom-right' }) + } + revalidate() + } async function onCreate(): Promise { const result = await generateApiKeyMutation({ name, expiresAt }) if (result) { setApiKeyGenerated(result) - showSuccessToast('Api key generated', { position: 'bottom-right' }) + showSuccessToast('API key generated', { position: 'bottom-right' }) } else { showErrorToast('Failed to add', { position: 'bottom-right' }) } @@ -74,7 +75,7 @@ export default function ApiKeys(): JSX.Element { {addModelOpen && ( )} - {/*{onDeleteId && (*/} - {/* {*/} - {/* await onDelete(onDeleteId)*/} - {/* setOnDeleteId(null)*/} - {/* }}*/} - {/* onOpenChange={() => setOnDeleteId(null)}*/} - {/* />*/} - {/*)}*/} + {onDeleteId && ( + { + await onDelete(onDeleteId) + setOnDeleteId('') + }} + onOpenChange={() => setOnDeleteId('')} + /> + )}
{ setFormInputs([ { From c389a48209775f9d847b532ecf0f641f7c92b121 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 6 Jun 2022 13:10:46 +0800 Subject: [PATCH 07/10] Add default expires at and validation --- packages/web/components/elements/FormElements.tsx | 2 ++ packages/web/pages/settings/api-keys.tsx | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/web/components/elements/FormElements.tsx b/packages/web/components/elements/FormElements.tsx index f7896089f..03f501e7d 100644 --- a/packages/web/components/elements/FormElements.tsx +++ b/packages/web/components/elements/FormElements.tsx @@ -16,6 +16,7 @@ export interface FormInputProps { required?: boolean css?: any options?: string[] + min?: any } export const FormInput = styled('input', { @@ -96,6 +97,7 @@ export function GeneralFormInput(props: FormInputProps): JSX.Element { }, }} name={input.name} + min={input.min} /> ) } diff --git a/packages/web/pages/settings/api-keys.tsx b/packages/web/pages/settings/api-keys.tsx index 3a72e1c0d..012d1759f 100644 --- a/packages/web/pages/settings/api-keys.tsx +++ b/packages/web/pages/settings/api-keys.tsx @@ -42,6 +42,11 @@ export default function ApiKeys(): JSX.Element { return rows }, [apiKeys]) + // default expiry date is 1 year from now + const defaultExpiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 365) + .toISOString() + .split('T')[0] + applyStoredTheme(false) async function onDelete(id: string): Promise { @@ -121,15 +126,17 @@ export default function ApiKeys(): JSX.Element { required: true, }, { - label: 'Expired at', + label: 'Expired on', name: 'expiredAt', required: true, onChange: setExpiresAt, type: 'date', + min: new Date().toISOString().split('T')[0], // today + value: defaultExpiresAt, }, ]) setName('') - setExpiresAt(new Date(Date.now() + 1000 * 60 * 60 * 24 * 365)) + setExpiresAt(new Date(defaultExpiresAt)) setAddModelOpen(true) }} /> From aa15ef0282e887f4da1f1284150a935eae6b14dd Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 6 Jun 2022 13:34:23 +0800 Subject: [PATCH 08/10] Sort by expires at desc nulls last --- packages/api/src/resolvers/api_key/index.ts | 5 ++++- packages/web/pages/settings/api-keys.tsx | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/api/src/resolvers/api_key/index.ts b/packages/api/src/resolvers/api_key/index.ts index f6c067e75..5ef001fc4 100644 --- a/packages/api/src/resolvers/api_key/index.ts +++ b/packages/api/src/resolvers/api_key/index.ts @@ -34,7 +34,10 @@ export const apiKeysResolver = authorized( const apiKeys = await getRepository(ApiKey).find({ select: ['id', 'name', 'scopes', 'expiresAt', 'createdAt', 'usedAt'], where: { user: { id: uid } }, - order: { usedAt: 'DESC', createdAt: 'DESC' }, + order: { + usedAt: { direction: 'DESC', nulls: 'last' }, + createdAt: 'DESC', + }, }) return { diff --git a/packages/web/pages/settings/api-keys.tsx b/packages/web/pages/settings/api-keys.tsx index 012d1759f..c1617ca85 100644 --- a/packages/web/pages/settings/api-keys.tsx +++ b/packages/web/pages/settings/api-keys.tsx @@ -35,7 +35,9 @@ export default function ApiKeys(): JSX.Element { rows.set(apiKey.id, { name: apiKey.name, scopes: apiKey.scopes.join(', ') || 'All', - usedAt: apiKey.usedAt?.toISOString() || 'Never used', + usedAt: apiKey.usedAt + ? new Date(apiKey.usedAt).toISOString() + : 'Never used', expiresAt: new Date(apiKey.expiresAt).toDateString(), }) ) From 0586b8b97e8b2b8a69e3ebff7315e4c60b9b485d Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 6 Jun 2022 13:38:31 +0800 Subject: [PATCH 09/10] Rename to used at --- packages/web/pages/settings/api-keys.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/pages/settings/api-keys.tsx b/packages/web/pages/settings/api-keys.tsx index c1617ca85..59b75a395 100644 --- a/packages/web/pages/settings/api-keys.tsx +++ b/packages/web/pages/settings/api-keys.tsx @@ -28,7 +28,7 @@ export default function ApiKeys(): JSX.Element { const [formInputs, setFormInputs] = useState([]) const [apiKeyGenerated, setApiKeyGenerated] = useState('') - const headers = ['Name', 'Scopes', 'Used on', 'Expires on'] + const headers = ['Name', 'Scopes', 'Used at', 'Expires on'] const rows = useMemo(() => { const rows = new Map() apiKeys.forEach((apiKey) => From d0d22363da0b95602b7ad1fcfbbe8416ce08e438 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 7 Jun 2022 09:48:51 +0800 Subject: [PATCH 10/10] Rename api-key file to api --- packages/web/pages/settings/{api-keys.tsx => api.tsx} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/web/pages/settings/{api-keys.tsx => api.tsx} (98%) diff --git a/packages/web/pages/settings/api-keys.tsx b/packages/web/pages/settings/api.tsx similarity index 98% rename from packages/web/pages/settings/api-keys.tsx rename to packages/web/pages/settings/api.tsx index 59b75a395..38430bf83 100644 --- a/packages/web/pages/settings/api-keys.tsx +++ b/packages/web/pages/settings/api.tsx @@ -18,7 +18,7 @@ interface ApiKey { usedAt: string } -export default function ApiKeys(): JSX.Element { +export default function Api(): JSX.Element { const { apiKeys, revalidate } = useGetApiKeysQuery() const [onDeleteId, setOnDeleteId] = useState('') const [addModelOpen, setAddModelOpen] = useState(false)