chore: Tidy API key settings page

This commit is contained in:
Tom Moor
2024-06-05 07:13:35 -04:00
parent 593f7a79b8
commit cf16d25a67
7 changed files with 65 additions and 34 deletions

View File

@@ -0,0 +1,25 @@
import { PlusIcon } from "outline-icons";
import * as React from "react";
import stores from "~/stores";
import APIKeyNew from "~/scenes/APIKeyNew";
import { createAction } from "..";
import { SettingsSection } from "../sections";
export const createApiKey = createAction({
name: ({ t }) => t("New API key"),
analyticsName: "New API key",
section: SettingsSection,
icon: <PlusIcon />,
keywords: "create",
visible: () =>
stores.policies.abilities(stores.auth.team?.id || "").createApiKey,
perform: ({ t, event }) => {
event?.preventDefault();
event?.stopPropagation();
stores.dialogs.openModal({
title: t("New API key"),
content: <APIKeyNew onSubmit={stores.dialogs.closeAllModals} />,
});
},
});

View File

@@ -88,7 +88,7 @@ const useSettingsConfig = () => {
icon: EmailIcon,
},
{
name: t("API Tokens"),
name: t("API"),
path: settingsPath("tokens"),
component: ApiKeys,
enabled: can.createApiKey,

View File

@@ -1,6 +1,7 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { ApiKeyValidation } from "@shared/validations";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Input from "~/components/Input";
@@ -11,7 +12,7 @@ type Props = {
onSubmit: () => void;
};
function APITokenNew({ onSubmit }: Props) {
function APIKeyNew({ onSubmit }: Props) {
const [name, setName] = React.useState("");
const [isSaving, setIsSaving] = React.useState(false);
const { apiKeys } = useStores();
@@ -26,7 +27,7 @@ function APITokenNew({ onSubmit }: Props) {
await apiKeys.create({
name,
});
toast.success(t("API token created"));
toast.success(t("API Key created"));
onSubmit();
} catch (err) {
toast.error(err.message);
@@ -45,7 +46,7 @@ function APITokenNew({ onSubmit }: Props) {
<form onSubmit={handleSubmit}>
<Text as="p" type="secondary">
{t(
`Name your token something that will help you to remember it's use in the future, for example "local development", "production", or "continuous integration".`
`Name your key something that will help you to remember it's use in the future, for example "local development" or "continuous integration".`
)}
</Text>
<Flex>
@@ -54,16 +55,20 @@ function APITokenNew({ onSubmit }: Props) {
label={t("Name")}
onChange={handleNameChange}
value={name}
minLength={ApiKeyValidation.minNameLength}
maxLength={ApiKeyValidation.maxNameLength}
required
autoFocus
flex
/>
</Flex>
<Button type="submit" disabled={isSaving || !name}>
{isSaving ? `${t("Creating")}` : t("Create")}
</Button>
<Flex justify="flex-end">
<Button type="submit" disabled={isSaving || !name}>
{isSaving ? `${t("Creating")}` : t("Create")}
</Button>
</Flex>
</form>
);
}
export default APITokenNew;
export default APIKeyNew;

View File

@@ -3,15 +3,14 @@ import { CodeIcon } from "outline-icons";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import ApiKey from "~/models/ApiKey";
import APITokenNew from "~/scenes/APITokenNew";
import { Action } from "~/components/Actions";
import Button from "~/components/Button";
import Heading from "~/components/Heading";
import Modal from "~/components/Modal";
import PaginatedList from "~/components/PaginatedList";
import Scene from "~/components/Scene";
import Text from "~/components/Text";
import useBoolean from "~/hooks/useBoolean";
import { createApiKey } from "~/actions/definitions/apiKeys";
import useActionContext from "~/hooks/useActionContext";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
@@ -21,12 +20,12 @@ function ApiKeys() {
const team = useCurrentTeam();
const { t } = useTranslation();
const { apiKeys } = useStores();
const [newModalOpen, handleNewModalOpen, handleNewModalClose] = useBoolean();
const can = usePolicy(team);
const context = useActionContext();
return (
<Scene
title={t("API Tokens")}
title={t("API Keys")}
icon={<CodeIcon />}
actions={
<>
@@ -34,19 +33,20 @@ function ApiKeys() {
<Action>
<Button
type="submit"
value={`${t("New token")}`}
onClick={handleNewModalOpen}
value={`${t("New API key")}`}
action={createApiKey}
context={context}
/>
</Action>
)}
</>
}
>
<Heading>{t("API Tokens")}</Heading>
<Heading>{t("API Keys")}</Heading>
<Text as="p" type="secondary">
<Trans
defaults="You can create an unlimited amount of personal tokens to authenticate
with the API. Tokens have the same permissions as your user account.
defaults="Create personal API keys to authenticate with the API and programatically control
your workspace's data. API keys have the same permissions as your user account.
For more details see the <em>developer documentation</em>."
components={{
em: (
@@ -67,13 +67,6 @@ function ApiKeys() {
<ApiKeyListItem key={apiKey.id} apiKey={apiKey} />
)}
/>
<Modal
title={t("Create a token")}
onRequestClose={handleNewModalClose}
isOpen={newModalOpen}
>
<APITokenNew onSubmit={handleNewModalClose} />
</Modal>
</Scene>
);
}

View File

@@ -8,6 +8,7 @@ import {
BelongsTo,
ForeignKey,
} from "sequelize-typescript";
import { ApiKeyValidation } from "@shared/validations";
import User from "./User";
import ParanoidModel from "./base/ParanoidModel";
import Fix from "./decorators/Fix";
@@ -22,9 +23,9 @@ class ApiKey extends ParanoidModel<
static prefix = "ol_api_";
@Length({
min: 3,
max: 255,
msg: "Name must be between 3 and 255 characters",
min: ApiKeyValidation.minNameLength,
max: ApiKeyValidation.maxNameLength,
msg: `Name must be between ${ApiKeyValidation.minNameLength} and ${ApiKeyValidation.maxNameLength} characters`,
})
@Column
name: string;

View File

@@ -1,4 +1,5 @@
{
"New API key": "New API key",
"Open collection": "Open collection",
"New collection": "New collection",
"Create a collection": "Create a collection",
@@ -431,7 +432,7 @@
"Could not import file": "Could not import file",
"Unsubscribed from document": "Unsubscribed from document",
"Account": "Account",
"API Tokens": "API Tokens",
"API": "API",
"Details": "Details",
"Security": "Security",
"Features": "Features",
@@ -495,8 +496,8 @@
"left a comment on": "left a comment on",
"shared": "shared",
"invited you to": "invited you to",
"API token created": "API token created",
"Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".": "Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".",
"API Key created": "API Key created",
"Name your key something that will help you to remember it's use in the future, for example \"local development\" or \"continuous integration\".": "Name your key something that will help you to remember it's use in the future, for example \"local development\" or \"continuous integration\".",
"The document archive is empty at the moment.": "The document archive is empty at the moment.",
"Collection menu": "Collection menu",
"Drop documents to import": "Drop documents to import",
@@ -755,10 +756,9 @@
"We were unable to find the page youre looking for.": "We were unable to find the page youre looking for.",
"Search titles only": "Search titles only",
"No documents found for your search filters.": "No documents found for your search filters.",
"New token": "New token",
"You can create an unlimited amount of personal tokens to authenticate\n with the API. Tokens have the same permissions as your user account.\n For more details see the <em>developer documentation</em>.": "You can create an unlimited amount of personal tokens to authenticate\n with the API. Tokens have the same permissions as your user account.\n For more details see the <em>developer documentation</em>.",
"API Keys": "API Keys",
"Create personal API keys to authenticate with the API and programatically control\n your workspace's data. API keys have the same permissions as your user account.\n For more details see the <em>developer documentation</em>.": "Create personal API keys to authenticate with the API and programatically control\n your workspace's data. API keys have the same permissions as your user account.\n For more details see the <em>developer documentation</em>.",
"Active": "Active",
"Create a token": "Create a token",
"API token copied to clipboard": "API token copied to clipboard",
"Copied": "Copied",
"Revoking": "Revoking",

View File

@@ -19,6 +19,13 @@ export const AttachmentValidation = {
],
};
export const ApiKeyValidation = {
/** The minimum length of the API key name */
minNameLength: 3,
/** The maximum length of the API key name */
maxNameLength: 255,
};
export const CollectionValidation = {
/** The maximum length of the collection description */
maxDescriptionLength: 10 * 1000,