mirror of
https://github.com/docmost/docmost.git
synced 2026-08-28 01:07:08 +08:00
fix: small refactor
This commit is contained in:
@@ -707,7 +707,10 @@
|
||||
"Enable the MCP server to allow AI assistants and tools to interact with your workspace content.": "Enable the MCP server to allow AI assistants and tools to interact with your workspace content.",
|
||||
"MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.",
|
||||
"MCP Server URL": "MCP Server URL",
|
||||
"Connect with your Docmost account via OAuth when your client supports it, or use an API key from your account settings.": "Connect with your Docmost account via OAuth when your client supports it, or use an API key from your account settings.",
|
||||
"Connect AI assistants with your Docmost account via OAuth.": "Connect AI assistants with your Docmost account via OAuth.",
|
||||
"Require OAuth": "Require OAuth",
|
||||
"AI assistants must connect with a Docmost account via OAuth. API keys cannot be used with the MCP server.": "AI assistants must connect with a Docmost account via OAuth. API keys cannot be used with the MCP server.",
|
||||
"Toggle require OAuth for MCP": "Toggle require OAuth for MCP",
|
||||
"Supported tools": "Supported tools",
|
||||
"MCP server URL:": "MCP server URL:",
|
||||
"Learn more": "Learn more",
|
||||
@@ -1318,20 +1321,14 @@
|
||||
"Revoke access for {{name}}": "Revoke access for {{name}}",
|
||||
"Are you sure you want to revoke access for {{name}}? The application will no longer be able to access your account.": "Are you sure you want to revoke access for {{name}}? The application will no longer be able to access your account.",
|
||||
"Something went wrong. Please try again.": "Something went wrong. Please try again.",
|
||||
"Trusted applications": "Trusted applications",
|
||||
"Remove {{name}}": "Remove {{name}}",
|
||||
"This origin is already trusted.": "This origin is already trusted.",
|
||||
"Trusted application name": "Trusted application name",
|
||||
"Trusted application origin": "Trusted application origin",
|
||||
"I recognize this application and want to continue": "I recognize this application and want to continue",
|
||||
"I trust this application and want to continue": "I trust this application and want to continue",
|
||||
"You will be redirected to": "You will be redirected to",
|
||||
"View content without making changes.": "View content without making changes.",
|
||||
"Create and modify content.": "Create and modify content.",
|
||||
"This application is not on your workspace's trusted list. Authorize only if you recognize it.": "This application is not on your workspace's trusted list. Authorize only if you recognize it.",
|
||||
"Applications with these callback origins are trusted. Members will not see a warning when authorizing them.": "Applications with these callback origins are trusted. Members will not see a warning when authorizing them.",
|
||||
"Enter the app's callback origin, e.g. https://app.yourcompany.com": "Enter the app's callback origin, e.g. https://app.yourcompany.com",
|
||||
"Make sure you trust this application before authorizing it.": "Make sure you trust this application before authorizing it.",
|
||||
"Applications and AI assistants you have authorized to access your account.": "Applications and AI assistants you have authorized to access your account.",
|
||||
"Your workspace has MCP enabled. Connect AI assistants with your Docmost account, or with an API key.": "Your workspace has MCP enabled. Connect AI assistants with your Docmost account, or with an API key.",
|
||||
"Your workspace has MCP enabled. Connect AI assistants with your Docmost account via OAuth.": "Your workspace has MCP enabled. Connect AI assistants with your Docmost account via OAuth.",
|
||||
"Authorized apps": "Authorized apps",
|
||||
"No authorized apps yet.": "No authorized apps yet.",
|
||||
"Workspace knowledge only": "Workspace knowledge only",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {
|
||||
Anchor,
|
||||
Button,
|
||||
Badge,
|
||||
Group,
|
||||
List,
|
||||
Table,
|
||||
Text,
|
||||
Switch,
|
||||
TextInput,
|
||||
@@ -17,33 +16,14 @@ import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||
import React, { useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts";
|
||||
import { ITrustedOAuthClient } from "@/features/workspace/types/workspace.types.ts";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useHasFeature } from "@/ee/hooks/use-feature";
|
||||
import { Feature } from "@/ee/features";
|
||||
import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label";
|
||||
import { getAppUrl } from "@/lib/config.ts";
|
||||
import {
|
||||
IconCheck,
|
||||
IconCopy,
|
||||
IconInfoCircle,
|
||||
IconTrash,
|
||||
} from "@tabler/icons-react";
|
||||
import { IconCheck, IconCopy, IconInfoCircle } from "@tabler/icons-react";
|
||||
import { CopyButton } from "@/components/common/copy-button.tsx";
|
||||
|
||||
// Mirrors the server rule: an exact https origin, tolerating only a trailing slash.
|
||||
function parseTrustedOrigin(value: string): string | null {
|
||||
const input = value.trim().toLowerCase();
|
||||
try {
|
||||
const url = new URL(input);
|
||||
if (url.protocol !== "https:") return null;
|
||||
if (input !== url.origin && input !== `${url.origin}/`) return null;
|
||||
return url.origin;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default function McpSettings() {
|
||||
const { t } = useTranslation();
|
||||
const [workspace, setWorkspace] = useAtom(workspaceAtom);
|
||||
@@ -51,14 +31,7 @@ export default function McpSettings() {
|
||||
const hasAccess = useHasFeature(Feature.MCP);
|
||||
const upgradeLabel = useUpgradeLabel();
|
||||
|
||||
const [newClientName, setNewClientName] = useState("");
|
||||
const [newClientOrigin, setNewClientOrigin] = useState("");
|
||||
|
||||
const mcpUrl = `${getAppUrl()}/mcp`;
|
||||
const storedTrustedClients = workspace?.trustedOauthClients;
|
||||
const trustedClients = Array.isArray(storedTrustedClients)
|
||||
? storedTrustedClients
|
||||
: [];
|
||||
|
||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.currentTarget.checked;
|
||||
@@ -74,51 +47,6 @@ export default function McpSettings() {
|
||||
}
|
||||
};
|
||||
|
||||
const saveTrustedClients = async (next: ITrustedOAuthClient[]) => {
|
||||
try {
|
||||
const updatedWorkspace = await updateWorkspace({
|
||||
trustedOauthClients: next,
|
||||
});
|
||||
setWorkspace(updatedWorkspace);
|
||||
return true;
|
||||
} catch (err) {
|
||||
notifications.show({
|
||||
message: err?.response?.data?.message,
|
||||
color: "red",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddTrustedClient = async () => {
|
||||
const name = newClientName.trim();
|
||||
const origin = parseTrustedOrigin(newClientOrigin);
|
||||
if (!origin) {
|
||||
notifications.show({
|
||||
message: t("Enter the app's callback origin, e.g. https://app.yourcompany.com"),
|
||||
color: "red",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (trustedClients.some((client) => client.origin.toLowerCase() === origin)) {
|
||||
notifications.show({
|
||||
message: t("This origin is already trusted."),
|
||||
color: "red",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (await saveTrustedClients([...trustedClients, { origin, name }])) {
|
||||
setNewClientName("");
|
||||
setNewClientOrigin("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTrustedClient = (origin: string) => {
|
||||
void saveTrustedClients(
|
||||
trustedClients.filter((client) => client.origin !== origin),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
{!hasAccess && (
|
||||
@@ -180,11 +108,11 @@ export default function McpSettings() {
|
||||
</CopyButton>
|
||||
</Group>
|
||||
<Text size="sm" c="dimmed" mt="xs">
|
||||
{t(
|
||||
"Connect with your Docmost account via OAuth when your client supports it, or use an API key from your account settings.",
|
||||
)}
|
||||
{t("Connect AI assistants with your Docmost account via OAuth.")}
|
||||
</Text>
|
||||
|
||||
<McpOauthOnlySetting />
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={500} mt="md" mb={4}>
|
||||
{t("Supported tools")}
|
||||
@@ -222,83 +150,61 @@ export default function McpSettings() {
|
||||
</List.Item>
|
||||
</List>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={500} mt="md" mb={4}>
|
||||
{t("Trusted applications")}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" mb="xs">
|
||||
{t(
|
||||
"Applications with these callback origins are trusted. Members will not see a warning when authorizing them.",
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{trustedClients.length > 0 && (
|
||||
<Table verticalSpacing="xs" mb="xs">
|
||||
<Table.Tbody>
|
||||
{trustedClients.map((client) => (
|
||||
<Table.Tr key={client.origin}>
|
||||
<Table.Td>
|
||||
<Text size="sm" fw={500}>
|
||||
{client.name}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm" c="dimmed">
|
||||
{client.origin}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td w={40}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="red"
|
||||
aria-label={t("Remove {{name}}", {
|
||||
name: client.name,
|
||||
})}
|
||||
onClick={() =>
|
||||
handleRemoveTrustedClient(client.origin)
|
||||
}
|
||||
>
|
||||
<IconTrash size={16} />
|
||||
</ActionIcon>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
)}
|
||||
|
||||
<Group gap="xs">
|
||||
<TextInput
|
||||
value={newClientName}
|
||||
onChange={(event) =>
|
||||
setNewClientName(event.currentTarget.value)
|
||||
}
|
||||
placeholder={t("Name")}
|
||||
aria-label={t("Trusted application name")}
|
||||
maxLength={64}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<TextInput
|
||||
value={newClientOrigin}
|
||||
onChange={(event) =>
|
||||
setNewClientOrigin(event.currentTarget.value)
|
||||
}
|
||||
placeholder="https://app.yourcompany.com"
|
||||
aria-label={t("Trusted application origin")}
|
||||
style={{ flex: 2 }}
|
||||
/>
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={handleAddTrustedClient}
|
||||
disabled={!newClientName.trim() || !newClientOrigin.trim()}
|
||||
>
|
||||
{t("Add")}
|
||||
</Button>
|
||||
</Group>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function McpOauthOnlySetting() {
|
||||
const { t } = useTranslation();
|
||||
const [workspace, setWorkspace] = useAtom(workspaceAtom);
|
||||
const [checked, setChecked] = useState(workspace?.settings?.ai?.mcpOauthOnly);
|
||||
const hasAccess = useHasFeature(Feature.MCP_CONTROLS);
|
||||
const upgradeLabel = useUpgradeLabel();
|
||||
|
||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.currentTarget.checked;
|
||||
try {
|
||||
const updatedWorkspace = await updateWorkspace({ mcpOauthOnly: value });
|
||||
setChecked(value);
|
||||
setWorkspace(updatedWorkspace);
|
||||
} catch (err) {
|
||||
notifications.show({
|
||||
message: err?.response?.data?.message,
|
||||
color: "red",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Group justify="space-between" wrap="nowrap" gap="xl" mt="md">
|
||||
<div>
|
||||
<Group gap="xs" align="center">
|
||||
<Text size="sm" fw={500}>
|
||||
{t("Require OAuth")}
|
||||
</Text>
|
||||
{!hasAccess && (
|
||||
<Badge variant="light" size="sm" radius="sm">
|
||||
{t("Enterprise")}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t(
|
||||
"AI assistants must connect with a Docmost account via OAuth. API keys cannot be used with the MCP server.",
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Tooltip label={upgradeLabel} disabled={hasAccess} refProp="rootRef">
|
||||
<Switch
|
||||
defaultChecked={checked}
|
||||
onChange={handleChange}
|
||||
disabled={!hasAccess}
|
||||
aria-label={t("Toggle require OAuth for MCP")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function UserApiKeys() {
|
||||
<Alert variant="light" color="blue" mb="md" p="sm" icon={<IconInfoCircle />}>
|
||||
<Text size="sm">
|
||||
{t(
|
||||
"Your workspace has MCP enabled. Connect AI assistants with your Docmost account, or with an API key.",
|
||||
"Your workspace has MCP enabled. Connect AI assistants with your Docmost account via OAuth.",
|
||||
)}{" "}
|
||||
<Anchor
|
||||
href="https://docmost.com/docs/user-guide/mcp"
|
||||
|
||||
@@ -24,4 +24,5 @@ export const Feature = {
|
||||
BASES: 'bases',
|
||||
OAUTH: 'oauth',
|
||||
AI_CONTROLS: 'ai:controls',
|
||||
MCP_CONTROLS: 'mcp:controls',
|
||||
} as const;
|
||||
|
||||
@@ -311,7 +311,7 @@ function ConsentCard({ info, currentUser, params }: ConsentCardProps) {
|
||||
<Stack gap="xs">
|
||||
<Text size="sm">
|
||||
{t(
|
||||
"This application is not on your workspace's trusted list. Authorize only if you recognize it.",
|
||||
"Make sure you trust this application before authorizing it.",
|
||||
)}
|
||||
</Text>
|
||||
<Checkbox
|
||||
@@ -321,7 +321,7 @@ function ConsentCard({ info, currentUser, params }: ConsentCardProps) {
|
||||
onChange={(event) =>
|
||||
setAcknowledged(event.currentTarget.checked)
|
||||
}
|
||||
label={t("I recognize this application and want to continue")}
|
||||
label={t("I trust this application and want to continue")}
|
||||
/>
|
||||
</Stack>
|
||||
</Alert>
|
||||
|
||||
@@ -27,13 +27,13 @@ export interface IWorkspace {
|
||||
mcpEnabled?: boolean;
|
||||
aiChatReadOnly?: boolean;
|
||||
aiChatWorkspaceKnowledgeOnly?: boolean;
|
||||
mcpOauthOnly?: boolean;
|
||||
trashRetentionDays?: number;
|
||||
restrictApiToAdmins?: boolean;
|
||||
allowMemberTemplates?: boolean;
|
||||
allowPersonalSpaces?: boolean;
|
||||
defaultPageEditMode?: string;
|
||||
isScimEnabled?: boolean;
|
||||
trustedOauthClients?: ITrustedOAuthClient[];
|
||||
}
|
||||
|
||||
export interface IWorkspaceSettings {
|
||||
@@ -45,11 +45,6 @@ export interface IWorkspaceSettings {
|
||||
defaultPageEditMode?: string;
|
||||
}
|
||||
|
||||
export interface ITrustedOAuthClient {
|
||||
origin: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface IWorkspaceApiSettings {
|
||||
restrictToAdmins?: boolean;
|
||||
}
|
||||
@@ -58,6 +53,7 @@ export interface IWorkspaceAiSettings {
|
||||
search?: boolean;
|
||||
generative?: boolean;
|
||||
mcp?: boolean;
|
||||
mcpOauthOnly?: boolean;
|
||||
chat?: boolean;
|
||||
chatReadOnly?: boolean;
|
||||
chatWorkspaceKnowledgeOnly?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user