mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-29 22:19:31 +02:00
Add translation keys in admin/api-keys
This commit is contained in:
parent
d994a8100d
commit
8242a66b97
6 changed files with 67 additions and 56 deletions
|
@ -32,6 +32,7 @@ import { Input } from "@app/components/ui/input";
|
|||
import { DataTablePagination } from "@app/components/DataTablePagination";
|
||||
import { Plus, Search } from "lucide-react";
|
||||
import { DataTable } from "@app/components/ui/data-table";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
|
@ -44,15 +45,17 @@ export function ApiKeysDataTable<TData, TValue>({
|
|||
columns,
|
||||
data
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
title="API Keys"
|
||||
searchPlaceholder="Search API keys..."
|
||||
title={t('apiKeys')}
|
||||
searchPlaceholder={t('searchApiKeys')}
|
||||
searchColumn="name"
|
||||
onAdd={addApiKey}
|
||||
addButtonText="Generate API Key"
|
||||
addButtonText={t('apiKeysAdd')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import { createApiClient } from "@app/lib/api";
|
|||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import moment from "moment";
|
||||
import { ApiKeysDataTable } from "./ApiKeysDataTable";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export type ApiKeyRow = {
|
||||
id: string;
|
||||
|
@ -45,14 +46,16 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
|
|||
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const deleteSite = (apiKeyId: string) => {
|
||||
api.delete(`/api-key/${apiKeyId}`)
|
||||
.catch((e) => {
|
||||
console.error("Error deleting API key", e);
|
||||
console.error(t('apiKeysErrorDelete'), e);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error deleting API key",
|
||||
description: formatAxiosError(e, "Error deleting API key")
|
||||
title: t('apiKeysErrorDelete'),
|
||||
description: formatAxiosError(e, t('apiKeysErrorDeleteMessage'))
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
|
@ -86,7 +89,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
|
|||
setSelected(apiKeyROw);
|
||||
}}
|
||||
>
|
||||
<span>View settings</span>
|
||||
<span>{t('viewSettings')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
|
@ -94,7 +97,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
|
|||
setIsDeleteModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="text-red-500">Delete</span>
|
||||
<span className="text-red-500">{t('delete')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
@ -111,7 +114,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
|
|||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Name
|
||||
{t('name')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
|
@ -141,7 +144,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
|
|||
<div className="flex items-center justify-end">
|
||||
<Link href={`/admin/api-keys/${r.id}`}>
|
||||
<Button variant={"outlinePrimary"} className="ml-2">
|
||||
Edit
|
||||
{t('edit')}
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
|
@ -163,27 +166,24 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
|
|||
dialog={
|
||||
<div className="space-y-4">
|
||||
<p>
|
||||
Are you sure you want to remove the API key{" "}
|
||||
<b>{selected?.name || selected?.id}</b>?
|
||||
{t('apiKeysQuestionRemove', {selectedApiKey: selected?.name || selected?.id})}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>
|
||||
Once removed, the API key will no longer be
|
||||
able to be used.
|
||||
{t('apiKeysMessageRemove')}
|
||||
</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To confirm, please type the name of the API key
|
||||
below.
|
||||
{t('apiKeysMessageConfirm')}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
buttonText="Confirm Delete API Key"
|
||||
buttonText={t('apiKeysDeleteConfirm')}
|
||||
onConfirm={async () => deleteSite(selected!.id)}
|
||||
string={selected.name}
|
||||
title="Delete API Key"
|
||||
title={t('apiKeysDelete')}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
import { GetApiKeyResponse } from "@server/routers/apiKeys";
|
||||
import ApiKeyProvider from "@app/providers/ApiKeyProvider";
|
||||
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface SettingsLayoutProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -29,6 +30,8 @@ interface SettingsLayoutProps {
|
|||
export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||
const params = await props.params;
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const { children } = props;
|
||||
|
||||
let apiKey = null;
|
||||
|
@ -45,14 +48,14 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
|||
|
||||
const navItems = [
|
||||
{
|
||||
title: "Permissions",
|
||||
title: t('apiKeysPermissionsTitle'),
|
||||
href: "/admin/api-keys/{apiKeyId}/permissions"
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSectionTitle title={`${apiKey?.name} Settings`} />
|
||||
<SettingsSectionTitle title={t('apiKeysSettings', {apiKeyName: apiKey?.name})} />
|
||||
|
||||
<ApiKeyProvider apiKey={apiKey}>
|
||||
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>
|
||||
|
|
|
@ -23,12 +23,15 @@ import { ListApiKeyActionsResponse } from "@server/routers/apiKeys";
|
|||
import { AxiosResponse } from "axios";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function Page() {
|
||||
const { env } = useEnvContext();
|
||||
const api = createApiClient({ env });
|
||||
const { apiKeyId } = useParams();
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const [loadingPage, setLoadingPage] = useState<boolean>(true);
|
||||
const [selectedPermissions, setSelectedPermissions] = useState<
|
||||
Record<string, boolean>
|
||||
|
@ -47,10 +50,10 @@ export default function Page() {
|
|||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error loading API key actions",
|
||||
title: t('apiKeysPermissionsErrorLoadingActions'),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"Error loading API key actions"
|
||||
t('apiKeysPermissionsErrorLoadingActions')
|
||||
)
|
||||
});
|
||||
});
|
||||
|
@ -81,18 +84,18 @@ export default function Page() {
|
|||
)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error("Error setting permissions", e);
|
||||
console.error(t('apiKeysPermissionsErrorUpdate'), e);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error setting permissions",
|
||||
title: t('apiKeysPermissionsErrorUpdate'),
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
});
|
||||
|
||||
if (actionsRes && actionsRes.status === 200) {
|
||||
toast({
|
||||
title: "Permissions updated",
|
||||
description: "The permissions have been updated."
|
||||
title: t('apiKeysPermissionsUpdated'),
|
||||
description: t('apiKeysPermissionsUpdatedDescription')
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -106,10 +109,10 @@ export default function Page() {
|
|||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Permissions
|
||||
{t('apiKeysPermissionsTitle')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Determine what this API key can do
|
||||
{t('apiKeysPermissionsGeneralSettingsDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
|
@ -127,7 +130,7 @@ export default function Page() {
|
|||
loading={loadingSavePermissions}
|
||||
disabled={loadingSavePermissions}
|
||||
>
|
||||
Save Permissions
|
||||
{t('apiKeysPermissionsSave')}
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSectionBody>
|
||||
|
|
|
@ -59,15 +59,18 @@ import CopyToClipboard from "@app/components/CopyToClipboard";
|
|||
import moment from "moment";
|
||||
import CopyTextBox from "@app/components/CopyTextBox";
|
||||
import PermissionsSelectBox from "@app/components/PermissionsSelectBox";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const createFormSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(2, {
|
||||
message: "Name must be at least 2 characters."
|
||||
message: t('apiKeysNameMin')
|
||||
})
|
||||
.max(255, {
|
||||
message: "Name must not be longer than 255 characters."
|
||||
message: t('apiKeysNameMax')
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -82,7 +85,7 @@ const copiedFormSchema = z
|
|||
return data.copied;
|
||||
},
|
||||
{
|
||||
message: "You must confirm that you have copied the API key.",
|
||||
message: t('apiKeysConfirmCopy2'),
|
||||
path: ["copied"]
|
||||
}
|
||||
);
|
||||
|
@ -127,7 +130,7 @@ export default function Page() {
|
|||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error creating API key",
|
||||
title: t('apiKeysErrorCreate'),
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
});
|
||||
|
@ -148,10 +151,10 @@ export default function Page() {
|
|||
)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error("Error setting permissions", e);
|
||||
console.error(t('apiKeysErrorSetPermission'), e);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error setting permissions",
|
||||
title: t('apiKeysErrorSetPermission'),
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
});
|
||||
|
@ -184,8 +187,8 @@ export default function Page() {
|
|||
<>
|
||||
<div className="flex justify-between">
|
||||
<HeaderTitle
|
||||
title="Generate API Key"
|
||||
description="Generate a new root access API key"
|
||||
title={t('apiKeysCreate')}
|
||||
description={t('apiKeysCreateDescription')}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
|
@ -205,7 +208,7 @@ export default function Page() {
|
|||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
API Key Information
|
||||
{t('apiKeysTitle')}
|
||||
</SettingsSectionTitle>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
|
@ -221,7 +224,7 @@ export default function Page() {
|
|||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Name
|
||||
{t('name')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
|
@ -242,10 +245,10 @@ export default function Page() {
|
|||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Permissions
|
||||
{t('apiKeysGeneralSettings')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Determine what this API key can do
|
||||
{t('apiKeysGeneralSettingsDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
|
@ -265,14 +268,14 @@ export default function Page() {
|
|||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Your API Key
|
||||
{t('apiKeysList')}
|
||||
</SettingsSectionTitle>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<InfoSections cols={2}>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
Name
|
||||
{t('name')}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<CopyToClipboard
|
||||
|
@ -282,7 +285,7 @@ export default function Page() {
|
|||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
Created
|
||||
{t('created')}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{moment(
|
||||
|
@ -295,17 +298,15 @@ export default function Page() {
|
|||
<Alert variant="neutral">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
Save Your API Key
|
||||
{t('apiKeysSave')}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
You will only be able to see this
|
||||
once. Make sure to copy it to a
|
||||
secure place.
|
||||
{t('apiKeysSaveDescription')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<h4 className="font-semibold">
|
||||
Your API key is:
|
||||
{t('apiKeysInfo')}
|
||||
</h4>
|
||||
|
||||
<CopyTextBox
|
||||
|
@ -343,8 +344,7 @@ export default function Page() {
|
|||
htmlFor="terms"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
I have copied
|
||||
the API key
|
||||
{t('apiKeysConfirmCopy')}
|
||||
</label>
|
||||
</div>
|
||||
<FormMessage />
|
||||
|
@ -368,7 +368,7 @@ export default function Page() {
|
|||
router.push(`/admin/api-keys`);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
)}
|
||||
{!apiKey && (
|
||||
|
@ -380,7 +380,7 @@ export default function Page() {
|
|||
form.handleSubmit(onSubmit)();
|
||||
}}
|
||||
>
|
||||
Generate
|
||||
{t('generate')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
|
@ -391,7 +391,7 @@ export default function Page() {
|
|||
copiedForm.handleSubmit(onCopiedSubmit)();
|
||||
}}
|
||||
>
|
||||
Done
|
||||
{t('done')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,7 @@ import { AxiosResponse } from "axios";
|
|||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import { ListRootApiKeysResponse } from "@server/routers/apiKeys";
|
||||
import ApiKeysTable, { ApiKeyRow } from "./ApiKeysTable";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type ApiKeyPageProps = {};
|
||||
|
||||
|
@ -16,6 +17,7 @@ export const dynamic = "force-dynamic";
|
|||
|
||||
export default async function ApiKeysPage(props: ApiKeyPageProps) {
|
||||
let apiKeys: ListRootApiKeysResponse["apiKeys"] = [];
|
||||
const t = useTranslations();
|
||||
try {
|
||||
const res = await internal.get<AxiosResponse<ListRootApiKeysResponse>>(
|
||||
`/api-keys`,
|
||||
|
@ -36,8 +38,8 @@ export default async function ApiKeysPage(props: ApiKeyPageProps) {
|
|||
return (
|
||||
<>
|
||||
<SettingsSectionTitle
|
||||
title="Manage API Keys"
|
||||
description="API keys are used to authenticate with the integration API"
|
||||
title={t('apiKeysManage')}
|
||||
description={t('apiKeysDescription')}
|
||||
/>
|
||||
|
||||
<ApiKeysTable apiKeys={rows} />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue