mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
cleanup
This commit is contained in:
@@ -621,7 +621,8 @@
|
|||||||
"Revoked successfully": "Revoked successfully",
|
"Revoked successfully": "Revoked successfully",
|
||||||
"Select expiration date": "Select expiration date",
|
"Select expiration date": "Select expiration date",
|
||||||
"This action cannot be undone. Any applications using this API key will stop working.": "This action cannot be undone. Any applications using this API key will stop working.",
|
"This action cannot be undone. Any applications using this API key will stop working.": "This action cannot be undone. Any applications using this API key will stop working.",
|
||||||
"Update API key": "Update API key",
|
"Update": "Update",
|
||||||
|
"Update {{credential}}": "Update {{credential}}",
|
||||||
"Manage API keys for all users in the workspace": "Manage API keys for all users in the workspace",
|
"Manage API keys for all users in the workspace": "Manage API keys for all users in the workspace",
|
||||||
"Restrict API key creation to admins": "Restrict API key creation to admins",
|
"Restrict API key creation to admins": "Restrict API key creation to admins",
|
||||||
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Only admins and owners can create new API keys. Existing member keys will continue to work.",
|
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Only admins and owners can create new API keys. Existing member keys will continue to work.",
|
||||||
@@ -894,6 +895,7 @@
|
|||||||
"SCIM endpoint URL": "SCIM endpoint URL",
|
"SCIM endpoint URL": "SCIM endpoint URL",
|
||||||
"SCIM provisioning": "SCIM provisioning",
|
"SCIM provisioning": "SCIM provisioning",
|
||||||
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
|
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
|
||||||
|
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
|
||||||
"SCIM token": "SCIM token",
|
"SCIM token": "SCIM token",
|
||||||
"SCIM tokens": "SCIM tokens",
|
"SCIM tokens": "SCIM tokens",
|
||||||
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
|
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function UpdateApiKeyModal({
|
|||||||
<Modal
|
<Modal
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={t("Update API key")}
|
title={t("Update {{credential}}", { credential: t("API key") })}
|
||||||
size="md"
|
size="md"
|
||||||
>
|
>
|
||||||
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
|
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const auditEventLabels: Record<string, string> = {
|
|||||||
"api_key.deleted": "Deleted API key",
|
"api_key.deleted": "Deleted API key",
|
||||||
|
|
||||||
"scim_token.created": "Created SCIM token",
|
"scim_token.created": "Created SCIM token",
|
||||||
|
"scim_token.updated": "Updated SCIM token",
|
||||||
"scim_token.deleted": "Deleted SCIM token",
|
"scim_token.deleted": "Deleted SCIM token",
|
||||||
|
|
||||||
"space.created": "Created space",
|
"space.created": "Created space",
|
||||||
@@ -181,6 +182,7 @@ export const eventFilterOptions: EventGroup[] = [
|
|||||||
group: "SCIM token",
|
group: "SCIM token",
|
||||||
items: [
|
items: [
|
||||||
{ value: "scim_token.created", label: "Created SCIM token" },
|
{ value: "scim_token.created", label: "Created SCIM token" },
|
||||||
|
{ value: "scim_token.updated", label: "Updated SCIM token" },
|
||||||
{ value: "scim_token.deleted", label: "Deleted SCIM token" },
|
{ value: "scim_token.deleted", label: "Deleted SCIM token" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ActionIcon, Group, Menu, Table, Text } from "@mantine/core";
|
import { ActionIcon, Group, Menu, Table, Text } from "@mantine/core";
|
||||||
import { IconDots, IconTrash } from "@tabler/icons-react";
|
import { IconDots, IconEdit, IconTrash } from "@tabler/icons-react";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
@@ -10,12 +10,14 @@ import { IScimToken } from "@/ee/scim/types/scim-token.types";
|
|||||||
interface ScimTokenTableProps {
|
interface ScimTokenTableProps {
|
||||||
tokens: IScimToken[];
|
tokens: IScimToken[];
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
onUpdate?: (token: IScimToken) => void;
|
||||||
onRevoke?: (token: IScimToken) => void;
|
onRevoke?: (token: IScimToken) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ScimTokenTable({
|
export function ScimTokenTable({
|
||||||
tokens,
|
tokens,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
onUpdate,
|
||||||
onRevoke,
|
onRevoke,
|
||||||
}: ScimTokenTableProps) {
|
}: ScimTokenTableProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -96,6 +98,14 @@ export function ScimTokenTable({
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
|
{onUpdate && (
|
||||||
|
<Menu.Item
|
||||||
|
leftSection={<IconEdit size={16} />}
|
||||||
|
onClick={() => onUpdate(token)}
|
||||||
|
>
|
||||||
|
{t("Rename")}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
{onRevoke && (
|
{onRevoke && (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<IconTrash size={16} />}
|
leftSection={<IconTrash size={16} />}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { Modal, TextInput, Button, Group, Stack } from "@mantine/core";
|
||||||
|
import { useForm } from "@mantine/form";
|
||||||
|
import { zod4Resolver } from "mantine-form-zod-resolver";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useUpdateScimTokenMutation } from "@/ee/scim/queries/scim-token-query";
|
||||||
|
import { IScimToken } from "@/ee/scim/types/scim-token.types";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
name: z.string().min(1, "Name is required"),
|
||||||
|
});
|
||||||
|
type FormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
interface UpdateScimTokenModalProps {
|
||||||
|
opened: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
scimToken: IScimToken | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UpdateScimTokenModal({
|
||||||
|
opened,
|
||||||
|
onClose,
|
||||||
|
scimToken,
|
||||||
|
}: UpdateScimTokenModalProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const updateMutation = useUpdateScimTokenMutation();
|
||||||
|
|
||||||
|
const form = useForm<FormValues>({
|
||||||
|
validate: zod4Resolver(formSchema),
|
||||||
|
initialValues: { name: "" },
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (opened && scimToken) {
|
||||||
|
form.setValues({ name: scimToken.name });
|
||||||
|
}
|
||||||
|
}, [opened, scimToken]);
|
||||||
|
|
||||||
|
const handleSubmit = async (data: FormValues) => {
|
||||||
|
if (!scimToken) return;
|
||||||
|
await updateMutation.mutateAsync({
|
||||||
|
tokenId: scimToken.id,
|
||||||
|
name: data.name,
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title={t("Update {{credential}}", { credential: t("SCIM token") })}
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label={t("Name")}
|
||||||
|
placeholder={t("Enter a descriptive name")}
|
||||||
|
required
|
||||||
|
{...form.getInputProps("name")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="flex-end" mt="md">
|
||||||
|
<Button variant="default" onClick={onClose}>
|
||||||
|
{t("Cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" loading={updateMutation.isPending}>
|
||||||
|
{t("Update")}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,11 +10,13 @@ import {
|
|||||||
createScimToken,
|
createScimToken,
|
||||||
getScimTokens,
|
getScimTokens,
|
||||||
revokeScimToken,
|
revokeScimToken,
|
||||||
|
updateScimToken,
|
||||||
} from "@/ee/scim/services/scim-token-service";
|
} from "@/ee/scim/services/scim-token-service";
|
||||||
import {
|
import {
|
||||||
IScimToken,
|
IScimToken,
|
||||||
ICreateScimTokenRequest,
|
ICreateScimTokenRequest,
|
||||||
IRevokeScimTokenRequest,
|
IRevokeScimTokenRequest,
|
||||||
|
IUpdateScimTokenRequest,
|
||||||
} from "@/ee/scim/types/scim-token.types";
|
} from "@/ee/scim/types/scim-token.types";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -25,8 +27,6 @@ export function useGetScimTokensQuery(
|
|||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["scim-token-list", params],
|
queryKey: ["scim-token-list", params],
|
||||||
queryFn: () => getScimTokens(params),
|
queryFn: () => getScimTokens(params),
|
||||||
staleTime: 0,
|
|
||||||
gcTime: 0,
|
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -55,6 +55,26 @@ export function useCreateScimTokenMutation() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useUpdateScimTokenMutation() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return useMutation<void, Error, IUpdateScimTokenRequest>({
|
||||||
|
mutationFn: (data) => updateScimToken(data),
|
||||||
|
onSuccess: () => {
|
||||||
|
notifications.show({ message: t("Updated successfully") });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
predicate: (item) =>
|
||||||
|
["scim-token-list"].includes(item.queryKey[0] as string),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
const errorMessage = error["response"]?.data?.message;
|
||||||
|
notifications.show({ message: errorMessage, color: "red" });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function useRevokeScimTokenMutation() {
|
export function useRevokeScimTokenMutation() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
IScimToken,
|
IScimToken,
|
||||||
ICreateScimTokenRequest,
|
ICreateScimTokenRequest,
|
||||||
IRevokeScimTokenRequest,
|
IRevokeScimTokenRequest,
|
||||||
|
IUpdateScimTokenRequest,
|
||||||
} from "@/ee/scim/types/scim-token.types";
|
} from "@/ee/scim/types/scim-token.types";
|
||||||
import { IPagination, QueryParams } from "@/lib/types.ts";
|
import { IPagination, QueryParams } from "@/lib/types.ts";
|
||||||
|
|
||||||
@@ -20,6 +21,12 @@ export async function createScimToken(
|
|||||||
return req.data;
|
return req.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateScimToken(
|
||||||
|
data: IUpdateScimTokenRequest,
|
||||||
|
): Promise<void> {
|
||||||
|
await api.post("/scim-tokens/update", data);
|
||||||
|
}
|
||||||
|
|
||||||
export async function revokeScimToken(
|
export async function revokeScimToken(
|
||||||
data: IRevokeScimTokenRequest,
|
data: IRevokeScimTokenRequest,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ export interface ICreateScimTokenRequest {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IUpdateScimTokenRequest {
|
||||||
|
tokenId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IRevokeScimTokenRequest {
|
export interface IRevokeScimTokenRequest {
|
||||||
tokenId: string;
|
tokenId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { getAppName, isCloud } from "@/lib/config.ts";
|
import { getAppName, isCloud } from "@/lib/config.ts";
|
||||||
import SettingsTitle from "@/components/settings/settings-title.tsx";
|
import SettingsTitle from "@/components/settings/settings-title.tsx";
|
||||||
import { Alert, Button, Card, Divider, Group, Space, Title } from "@mantine/core";
|
import {
|
||||||
|
Alert,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Divider,
|
||||||
|
Group,
|
||||||
|
Space,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from "@mantine/core";
|
||||||
import { IconInfoCircle } from "@tabler/icons-react";
|
import { IconInfoCircle } from "@tabler/icons-react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import useUserRole from "@/hooks/use-user-role.tsx";
|
import useUserRole from "@/hooks/use-user-role.tsx";
|
||||||
@@ -23,11 +32,14 @@ import { ScimTokenTable } from "@/ee/scim/components/scim-token-table";
|
|||||||
import { CreateScimTokenModal } from "@/ee/scim/components/create-scim-token-modal";
|
import { CreateScimTokenModal } from "@/ee/scim/components/create-scim-token-modal";
|
||||||
import { ScimTokenCreatedModal } from "@/ee/scim/components/scim-token-created-modal";
|
import { ScimTokenCreatedModal } from "@/ee/scim/components/scim-token-created-modal";
|
||||||
import { RevokeScimTokenModal } from "@/ee/scim/components/revoke-scim-token-modal";
|
import { RevokeScimTokenModal } from "@/ee/scim/components/revoke-scim-token-modal";
|
||||||
|
import { UpdateScimTokenModal } from "@/ee/scim/components/update-scim-token-modal";
|
||||||
import EnableScim from "@/ee/scim/components/enable-scim";
|
import EnableScim from "@/ee/scim/components/enable-scim";
|
||||||
import { useCursorPaginate } from "@/hooks/use-cursor-paginate";
|
import { useCursorPaginate } from "@/hooks/use-cursor-paginate";
|
||||||
import Paginate from "@/components/common/paginate";
|
import Paginate from "@/components/common/paginate";
|
||||||
import { IScimToken } from "@/ee/scim/types/scim-token.types";
|
import { IScimToken } from "@/ee/scim/types/scim-token.types";
|
||||||
|
|
||||||
|
const SCIM_TOKEN_LIMIT = 5;
|
||||||
|
|
||||||
export default function Security() {
|
export default function Security() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isAdmin } = useUserRole();
|
const { isAdmin } = useUserRole();
|
||||||
@@ -43,6 +55,7 @@ export default function Security() {
|
|||||||
|
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [createdToken, setCreatedToken] = useState<IScimToken | null>(null);
|
const [createdToken, setCreatedToken] = useState<IScimToken | null>(null);
|
||||||
|
const [updateTarget, setUpdateTarget] = useState<IScimToken | null>(null);
|
||||||
const [revokeTarget, setRevokeTarget] = useState<IScimToken | null>(null);
|
const [revokeTarget, setRevokeTarget] = useState<IScimToken | null>(null);
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
@@ -118,17 +131,30 @@ export default function Security() {
|
|||||||
|
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={5}>{t("SCIM tokens")}</Title>
|
<Title order={5}>{t("SCIM tokens")}</Title>
|
||||||
<Button onClick={() => setCreateOpen(true)}>
|
<Tooltip
|
||||||
|
label={t(
|
||||||
|
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
|
||||||
|
{ max: SCIM_TOKEN_LIMIT },
|
||||||
|
)}
|
||||||
|
disabled={(scimData?.items.length ?? 0) < SCIM_TOKEN_LIMIT}
|
||||||
|
refProp="rootRef"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => setCreateOpen(true)}
|
||||||
|
disabled={(scimData?.items.length ?? 0) >= SCIM_TOKEN_LIMIT}
|
||||||
|
>
|
||||||
{t("Create {{credential}}", {
|
{t("Create {{credential}}", {
|
||||||
credential: t("SCIM token"),
|
credential: t("SCIM token"),
|
||||||
})}
|
})}
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Card shadow="sm" radius="sm">
|
<Card shadow="sm" radius="sm">
|
||||||
<ScimTokenTable
|
<ScimTokenTable
|
||||||
tokens={scimData?.items}
|
tokens={scimData?.items}
|
||||||
isLoading={scimLoading}
|
isLoading={scimLoading}
|
||||||
|
onUpdate={setUpdateTarget}
|
||||||
onRevoke={setRevokeTarget}
|
onRevoke={setRevokeTarget}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -156,6 +182,12 @@ export default function Security() {
|
|||||||
scimToken={createdToken}
|
scimToken={createdToken}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<UpdateScimTokenModal
|
||||||
|
opened={!!updateTarget}
|
||||||
|
onClose={() => setUpdateTarget(null)}
|
||||||
|
scimToken={updateTarget}
|
||||||
|
/>
|
||||||
|
|
||||||
<RevokeScimTokenModal
|
<RevokeScimTokenModal
|
||||||
opened={!!revokeTarget}
|
opened={!!revokeTarget}
|
||||||
onClose={() => setRevokeTarget(null)}
|
onClose={() => setRevokeTarget(null)}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const AuditEvent = {
|
|||||||
|
|
||||||
// SCIM Tokens
|
// SCIM Tokens
|
||||||
SCIM_TOKEN_CREATED: 'scim_token.created',
|
SCIM_TOKEN_CREATED: 'scim_token.created',
|
||||||
|
SCIM_TOKEN_UPDATED: 'scim_token.updated',
|
||||||
SCIM_TOKEN_DELETED: 'scim_token.deleted',
|
SCIM_TOKEN_DELETED: 'scim_token.deleted',
|
||||||
|
|
||||||
// Space
|
// Space
|
||||||
|
|||||||
+1
-1
Submodule apps/server/src/ee updated: c10c645927...109829076c
Reference in New Issue
Block a user