From 9566233c9bc8c89aea33f8d8b3752601731755ac Mon Sep 17 00:00:00 2001 From: cocowh Date: Wed, 29 Apr 2026 00:24:11 +0800 Subject: [PATCH] feat(frontend): add KB permission extension point for external permission systems Add ExtensionTabConfig interface and KbPermissionsPanel component to support custom permission tabs (e.g., ERP department permissions) in the knowledge base permission management UI. Changes: - New KbPermissionsPanel component with extensionTabs prop - Updated KnowledgeDetailPanel to accept permissionExtensionTabs - Exported ExtensionTabConfig type from knowledge module - Added English and Chinese documentation Internal projects can now inject custom permission tabs without modifying core components, following the same pattern as backend IKbPermissionResolver. Refs: PR #1025 backend extension point --- .../kb-permission-extension.md | 302 ++++++++++++++++++ .../kb-permission-extension.md | 302 ++++++++++++++++++ .../components/KnowledgeDetailPanel.tsx | 11 +- frontend/src/features/knowledge/index.ts | 4 + .../components/KbPermissionsPanel.tsx | 136 ++++++++ .../knowledge/permission/components/index.ts | 2 + 6 files changed, 755 insertions(+), 2 deletions(-) create mode 100644 docs/en/developer-guide/kb-permission-extension.md create mode 100644 docs/zh/developer-guide/kb-permission-extension.md create mode 100644 frontend/src/features/knowledge/permission/components/KbPermissionsPanel.tsx diff --git a/docs/en/developer-guide/kb-permission-extension.md b/docs/en/developer-guide/kb-permission-extension.md new file mode 100644 index 000000000..74579412a --- /dev/null +++ b/docs/en/developer-guide/kb-permission-extension.md @@ -0,0 +1,302 @@ +--- +sidebar_position: 3 +--- + +# Knowledge Base Permission Extension + +This document describes how to extend the knowledge base permission system with custom permission tabs, such as department-level permissions via external ERP systems. + +## Overview + +The knowledge base permission system provides an extension point that allows you to add additional permission management tabs beyond the default personal permissions tab. This is useful for integrating with external permission systems like ERP (Enterprise Resource Planning) or other enterprise identity management systems. + +## Extension Point + +The extension is implemented through the `ExtensionTabConfig` interface and the `KbPermissionsPanel` component. + +### ExtensionTabConfig Interface + +```typescript +interface ExtensionTabConfig { + /** Unique identifier for this tab */ + id: string + /** Display label for the tab */ + label: string + /** Lucide icon component */ + icon: React.ComponentType<{ className?: string }> + /** Component to render as tab content, receives kbId prop */ + component: React.ComponentType<{ kbId: number }> + /** Whether this tab requires manage permission to be visible */ + requiresManagePermission?: boolean +} +``` + +### Basic Usage + +To add a custom permission tab, wrap the `KnowledgeDetailPanel` component and inject your extension tabs: + +```typescript +'use client' + +import { Building2 } from 'lucide-react' +import { KnowledgeDetailPanel, type ExtensionTabConfig } from '@/features/knowledge' + +// Your custom permission management component +function DepartmentPermissionTab({ kbId }: { kbId: number }) { + // Implement your department permission UI here + return
Department permissions for KB {kbId}
+} + +// Define the extension tab +const departmentExtensionTab: ExtensionTabConfig = { + id: 'department', + label: 'Department', + icon: Building2, + component: DepartmentPermissionTab, + requiresManagePermission: true, +} + +// Extended knowledge detail panel +interface ExtendedKnowledgeDetailPanelProps { + selectedKb: KnowledgeBase | null + // ... other props +} + +export function ExtendedKnowledgeDetailPanel(props: ExtendedKnowledgeDetailPanelProps) { + return ( + + ) +} +``` + +## Implementation Guide + +### Step 1: Create Your Permission Component + +Create a component that implements your custom permission logic: + +```typescript +// features/knowledge/extensions/DepartmentPermissionTab.tsx +'use client' + +import { useState, useEffect } from 'react' +import { Card } from '@/components/ui/card' +import { Button } from '@/components/ui/button' + +interface DepartmentPermissionTabProps { + kbId: number +} + +export function DepartmentPermissionTab({ kbId }: DepartmentPermissionTabProps) { + const [permissions, setPermissions] = useState([]) + const [loading, setLoading] = useState(false) + + useEffect(() => { + // Fetch department permissions for this knowledge base + fetchDepartmentPermissions(kbId).then(setPermissions) + }, [kbId]) + + return ( +
+
+

Department Permissions

+ +
+ {/* Render your permission management UI */} + {permissions.map(dept => ( + {dept.name} + ))} +
+ ) +} +``` + +### Step 2: Configure the Extension Tab + +Define your extension tab configuration: + +```typescript +import { Building2 } from 'lucide-react' +import type { ExtensionTabConfig } from '@/features/knowledge' +import { DepartmentPermissionTab } from './DepartmentPermissionTab' + +export const departmentExtensionTab: ExtensionTabConfig = { + id: 'department', + label: 'Department', + icon: Building2, + component: DepartmentPermissionTab, + requiresManagePermission: true, // Only show for users with manage permission +} +``` + +### Step 3: Inject into KnowledgeDetailPanel + +Wrap the `KnowledgeDetailPanel` to inject your extension: + +```typescript +// features/knowledge/extensions/ExtendedKnowledgeDetailPanel.tsx +import { KnowledgeDetailPanel, type ExtensionTabConfig } from '@/features/knowledge' +import { departmentExtensionTab } from './departmentExtensionTab' + +interface ExtendedKnowledgeDetailPanelProps { + selectedKb: KnowledgeBase | null + // ... other props from KnowledgeDetailPanelProps +} + +export function ExtendedKnowledgeDetailPanel(props: ExtendedKnowledgeDetailPanelProps) { + return ( + + ) +} +``` + +## Backend Integration + +Your custom permission tab should integrate with the backend through the `IKbPermissionResolver` interface. See the [backend documentation](../../concepts/permission-system.md) for details. + +### API Endpoints + +You should create the following API endpoints for your custom permission system: + +- `GET /api/knowledge-bases/{kb_id}/department-permissions` - List department permissions +- `POST /api/knowledge-bases/{kb_id}/department-permissions` - Add department permission +- `DELETE /api/knowledge-bases/{kb_id}/department-permissions/{id}` - Remove department permission + +### Backend Extension + +Implement the `IKbPermissionResolver` interface in your backend: + +```python +# backend/app/extensions/myext/kb_permissions.py +from app.services.readers.kb_permissions import IKbPermissionResolver + +class DepartmentPermissionResolver(IKbPermissionResolver): + def resolve(self, db, kb_id, user_id, kb): + # Check if user has access via department + if self._has_department_access(db, kb_id, user_id): + return "Developer" + return None + + def get_accessible_kb_ids(self, db, user_id): + # Return KB IDs accessible via department + return self._get_dept_accessible_kbs(db, user_id) + +def wrap(base: IKbPermissionResolver) -> DepartmentPermissionResolver: + return DepartmentPermissionResolver() +``` + +Configure the extension via environment variable: + +```bash +SERVICE_EXTENSION=app.extensions.myext +``` + +## Examples + +### ERP Department Permissions + +```typescript +import { Building2 } from 'lucide-react' +import { useTranslation } from '@/hooks/useTranslation' +import { useErpDepartments } from './hooks/useErpDepartments' + +function ErpDeptTab({ kbId }: { kbId: number }) { + const { t } = useTranslation('knowledge') + const { departments, loading, addDepartment, removeDepartment } = useErpDepartments(kbId) + + return ( +
+
+

{t('document.permission.erpDepartments')}

+ +
+ {/* List of authorized departments */} +
+ ) +} + +export const erpExtensionTab: ExtensionTabConfig = { + id: 'department', + label: 'Department', + icon: Building2, + component: ErpDeptTab, + requiresManagePermission: true, +} +``` + +### Multiple Extension Tabs + +You can add multiple extension tabs: + +```typescript +const extensionTabs: ExtensionTabConfig[] = [ + { + id: 'department', + label: 'Department', + icon: Building2, + component: DepartmentTab, + requiresManagePermission: true, + }, + { + id: 'employee', + label: 'Employee', + icon: Users, + component: EmployeeTab, + requiresManagePermission: true, + }, +] + + +``` + +## Best Practices + +1. **Unique IDs**: Ensure your extension tab IDs are unique to avoid conflicts. + +2. **Permission Checks**: Use `requiresManagePermission` appropriately to hide tabs from users without management rights. + +3. **Lazy Loading**: For heavy components, use dynamic imports: + + ```typescript + const DepartmentTab = lazy(() => import('./DepartmentTab')) + ``` + +4. **Error Boundaries**: Wrap your extension components with error boundaries to prevent crashes from affecting the entire panel. + +5. **Consistent Styling**: Use the project's design system components (Card, Button, etc.) for consistent UI. + +6. **i18n Support**: Use the `useTranslation` hook for all user-facing text. + +## Troubleshooting + +### Extension Tab Not Showing + +- Check that the tab ID is unique +- Verify `requiresManagePermission` logic if applicable +- Ensure the component is properly exported and imported + +### Permission Not Applied + +- Verify the backend `IKbPermissionResolver` implementation +- Check that `SERVICE_EXTENSION` environment variable is set +- Review backend logs for extension loading errors + +### Type Errors + +- Ensure `ExtensionTabConfig` is imported from the correct location +- Verify the component prop types match `{ kbId: number }` + +## See Also + +- [Permission System Concepts](../../concepts/permission-system.md) +- [Backend Extension Guide](../backend-extensions.md) +- [Component Design Guidelines](../component-design.md) diff --git a/docs/zh/developer-guide/kb-permission-extension.md b/docs/zh/developer-guide/kb-permission-extension.md new file mode 100644 index 000000000..ded901eea --- /dev/null +++ b/docs/zh/developer-guide/kb-permission-extension.md @@ -0,0 +1,302 @@ +--- +sidebar_position: 3 +--- + +# 知识库权限扩展 + +本文档介绍如何通过自定义权限标签页扩展知识库权限系统,例如通过外部 ERP 系统实现部门级权限管理。 + +## 概述 + +知识库权限系统提供了一个扩展点,允许您在默认的个人权限标签页之外添加额外的权限管理标签页。这对于集成外部权限系统(如 ERP 或企业身份管理系统)非常有用。 + +## 扩展点 + +扩展通过 `ExtensionTabConfig` 接口和 `KbPermissionsPanel` 组件实现。 + +### ExtensionTabConfig 接口 + +```typescript +interface ExtensionTabConfig { + /** 标签页唯一标识符 */ + id: string + /** 标签页显示标签 */ + label: string + /** Lucide 图标组件 */ + icon: React.ComponentType<{ className?: string }> + /** 标签页内容组件,接收 kbId 属性 */ + component: React.ComponentType<{ kbId: number }> + /** 此标签页是否需要管理权限才可见 */ + requiresManagePermission?: boolean +} +``` + +### 基本用法 + +要添加自定义权限标签页,请包装 `KnowledgeDetailPanel` 组件并注入您的扩展标签页: + +```typescript +'use client' + +import { Building2 } from 'lucide-react' +import { KnowledgeDetailPanel, type ExtensionTabConfig } from '@/features/knowledge' + +// 您的自定义权限管理组件 +function DepartmentPermissionTab({ kbId }: { kbId: number }) { + // 在此实现部门权限 UI + return
知识库 {kbId} 的部门权限
+} + +// 定义扩展标签页 +const departmentExtensionTab: ExtensionTabConfig = { + id: 'department', + label: '部门', + icon: Building2, + component: DepartmentPermissionTab, + requiresManagePermission: true, +} + +// 扩展的知识库详情面板 +interface ExtendedKnowledgeDetailPanelProps { + selectedKb: KnowledgeBase | null + // ... 其他属性 +} + +export function ExtendedKnowledgeDetailPanel(props: ExtendedKnowledgeDetailPanelProps) { + return ( + + ) +} +``` + +## 实现指南 + +### 第一步:创建权限组件 + +创建一个实现自定义权限逻辑的组件: + +```typescript +// features/knowledge/extensions/DepartmentPermissionTab.tsx +'use client' + +import { useState, useEffect } from 'react' +import { Card } from '@/components/ui/card' +import { Button } from '@/components/ui/button' + +interface DepartmentPermissionTabProps { + kbId: number +} + +export function DepartmentPermissionTab({ kbId }: DepartmentPermissionTabProps) { + const [permissions, setPermissions] = useState([]) + const [loading, setLoading] = useState(false) + + useEffect(() => { + // 获取此知识库的部门权限 + fetchDepartmentPermissions(kbId).then(setPermissions) + }, [kbId]) + + return ( +
+
+

部门权限

+ +
+ {/* 渲染权限管理 UI */} + {permissions.map(dept => ( + {dept.name} + ))} +
+ ) +} +``` + +### 第二步:配置扩展标签页 + +定义您的扩展标签页配置: + +```typescript +import { Building2 } from 'lucide-react' +import type { ExtensionTabConfig } from '@/features/knowledge' +import { DepartmentPermissionTab } from './DepartmentPermissionTab' + +export const departmentExtensionTab: ExtensionTabConfig = { + id: 'department', + label: '部门', + icon: Building2, + component: DepartmentPermissionTab, + requiresManagePermission: true, // 仅对具有管理权限的用户显示 +} +``` + +### 第三步:注入 KnowledgeDetailPanel + +包装 `KnowledgeDetailPanel` 以注入您的扩展: + +```typescript +// features/knowledge/extensions/ExtendedKnowledgeDetailPanel.tsx +import { KnowledgeDetailPanel, type ExtensionTabConfig } from '@/features/knowledge' +import { departmentExtensionTab } from './departmentExtensionTab' + +interface ExtendedKnowledgeDetailPanelProps { + selectedKb: KnowledgeBase | null + // ... KnowledgeDetailPanelProps 的其他属性 +} + +export function ExtendedKnowledgeDetailPanel(props: ExtendedKnowledgeDetailPanelProps) { + return ( + + ) +} +``` + +## 后端集成 + +您的自定义权限标签页应通过 `IKbPermissionResolver` 接口与后端集成。详情请参阅[后端文档](../../concepts/permission-system.md)。 + +### API 端点 + +您应该为自定义权限系统创建以下 API 端点: + +- `GET /api/knowledge-bases/{kb_id}/department-permissions` - 列出部门权限 +- `POST /api/knowledge-bases/{kb_id}/department-permissions` - 添加部门权限 +- `DELETE /api/knowledge-bases/{kb_id}/department-permissions/{id}` - 移除部门权限 + +### 后端扩展 + +在后端实现 `IKbPermissionResolver` 接口: + +```python +# backend/app/extensions/myext/kb_permissions.py +from app.services.readers.kb_permissions import IKbPermissionResolver + +class DepartmentPermissionResolver(IKbPermissionResolver): + def resolve(self, db, kb_id, user_id, kb): + # 检查用户是否通过部门拥有访问权限 + if self._has_department_access(db, kb_id, user_id): + return "Developer" + return None + + def get_accessible_kb_ids(self, db, user_id): + # 返回通过部门可访问的 KB ID + return self._get_dept_accessible_kbs(db, user_id) + +def wrap(base: IKbPermissionResolver) -> DepartmentPermissionResolver: + return DepartmentPermissionResolver() +``` + +通过环境变量配置扩展: + +```bash +SERVICE_EXTENSION=app.extensions.myext +``` + +## 示例 + +### ERP 部门权限 + +```typescript +import { Building2 } from 'lucide-react' +import { useTranslation } from '@/hooks/useTranslation' +import { useErpDepartments } from './hooks/useErpDepartments' + +function ErpDeptTab({ kbId }: { kbId: number }) { + const { t } = useTranslation('knowledge') + const { departments, loading, addDepartment, removeDepartment } = useErpDepartments(kbId) + + return ( +
+
+

{t('document.permission.erpDepartments')}

+ +
+ {/* 授权部门列表 */} +
+ ) +} + +export const erpExtensionTab: ExtensionTabConfig = { + id: 'department', + label: '部门', + icon: Building2, + component: ErpDeptTab, + requiresManagePermission: true, +} +``` + +### 多个扩展标签页 + +您可以添加多个扩展标签页: + +```typescript +const extensionTabs: ExtensionTabConfig[] = [ + { + id: 'department', + label: '部门', + icon: Building2, + component: DepartmentTab, + requiresManagePermission: true, + }, + { + id: 'employee', + label: '员工', + icon: Users, + component: EmployeeTab, + requiresManagePermission: true, + }, +] + + +``` + +## 最佳实践 + +1. **唯一 ID**:确保您的扩展标签页 ID 唯一以避免冲突。 + +2. **权限检查**:适当使用 `requiresManagePermission` 向没有管理权限的用户隐藏标签页。 + +3. **懒加载**:对于较重的组件,使用动态导入: + + ```typescript + const DepartmentTab = lazy(() => import('./DepartmentTab')) + ``` + +4. **错误边界**:使用错误边界包装您的扩展组件,以防止崩溃影响整个面板。 + +5. **一致的样式**:使用项目的设计系统组件(Card、Button 等)以保持 UI 一致。 + +6. **i18n 支持**:使用 `useTranslation` 钩子处理所有面向用户的文本。 + +## 故障排除 + +### 扩展标签页不显示 + +- 检查标签页 ID 是否唯一 +- 验证 `requiresManagePermission` 逻辑(如适用) +- 确保组件正确导出和导入 + +### 权限未应用 + +- 验证后端 `IKbPermissionResolver` 实现 +- 检查 `SERVICE_EXTENSION` 环境变量是否已设置 +- 查看后端日志中的扩展加载错误 + +### 类型错误 + +- 确保 `ExtensionTabConfig` 从正确的位置导入 +- 验证组件属性类型匹配 `{ kbId: number }` + +## 另请参阅 + +- [权限系统概念](../../concepts/permission-system.md) +- [后端扩展指南](../backend-extensions.md) +- [组件设计指南](../component-design.md) diff --git a/frontend/src/features/knowledge/document/components/KnowledgeDetailPanel.tsx b/frontend/src/features/knowledge/document/components/KnowledgeDetailPanel.tsx index dc2264da8..0e04e16e5 100644 --- a/frontend/src/features/knowledge/document/components/KnowledgeDetailPanel.tsx +++ b/frontend/src/features/knowledge/document/components/KnowledgeDetailPanel.tsx @@ -24,7 +24,7 @@ import { ChatArea } from '@/features/tasks/components/chat' import { DocumentList, type KbGroupInfo } from './DocumentList' import { DocumentPanel } from './DocumentPanel' import { KnowledgeBaseSummaryCard } from './KnowledgeBaseSummaryCard' -import { PermissionManagementTab } from '../../permission/components/PermissionManagementTab' +import { KbPermissionsPanel, type ExtensionTabConfig } from '../../permission/components/KbPermissionsPanel' import { useKnowledgePermissions } from '../../permission/hooks/useKnowledgePermissions' import { useNamespaceRoleMap } from '../hooks/useNamespaceRoleMap' import { @@ -50,6 +50,8 @@ interface KnowledgeDetailPanelProps { onGroupClick?: (groupId: string, groupType?: string) => void /** Initial document path to auto-open (from virtual URL path segments) */ initialDocPath?: string + /** Extension tabs for permission management (e.g., ERP department permissions) */ + permissionExtensionTabs?: ExtensionTabConfig[] } export function KnowledgeDetailPanel({ @@ -60,6 +62,7 @@ export function KnowledgeDetailPanel({ groupInfo, onGroupClick, initialDocPath, + permissionExtensionTabs = [], }: KnowledgeDetailPanelProps) { const { t } = useTranslation('knowledge') const { user } = useUser() @@ -261,7 +264,11 @@ export function KnowledgeDetailPanel({ {headerActions} - + )} diff --git a/frontend/src/features/knowledge/index.ts b/frontend/src/features/knowledge/index.ts index a41e3a242..e73fa2b30 100644 --- a/frontend/src/features/knowledge/index.ts +++ b/frontend/src/features/knowledge/index.ts @@ -32,3 +32,7 @@ export type { ContentWriteSummary, ContentWrite } from './wikiUtils' // Document Knowledge exports export { KnowledgeDocumentPage } from './document/components' export { useKnowledgeBases, useDocuments } from './document/hooks' + +// Permission extension exports +export { KbPermissionsPanel } from './permission/components' +export type { ExtensionTabConfig } from './permission/components' diff --git a/frontend/src/features/knowledge/permission/components/KbPermissionsPanel.tsx b/frontend/src/features/knowledge/permission/components/KbPermissionsPanel.tsx new file mode 100644 index 000000000..13fc20792 --- /dev/null +++ b/frontend/src/features/knowledge/permission/components/KbPermissionsPanel.tsx @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2025 Wegent, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * KbPermissionsPanel renders the permission management panel for a knowledge base. + * + * This component provides an extension point for adding additional permission tabs + * (e.g., department-level permissions via external ERP systems). By default, it + * only shows the personal permission management tab. + * + * Extension Example: + * ```typescript + * import { Building2 } from 'lucide-react' + * import { ErpDeptTab } from '@wecode/features/knowledge/components' + * + * const erpExtensionTab: ExtensionTabConfig = { + * id: 'department', + * label: 'Department', + * icon: Building2, + * component: ErpDeptTab, + * requiresManagePermission: true, + * } + * + * + * ``` + */ + +'use client' + +import { useState, useMemo } from 'react' +import { Users } from 'lucide-react' +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' +import { useTranslation } from '@/hooks/useTranslation' +import { PermissionManagementTab } from './PermissionManagementTab' + +/** + * Configuration for an extension permission tab. + */ +export interface ExtensionTabConfig { + /** Unique identifier for this tab */ + id: string + /** Display label for the tab */ + label: string + /** Lucide icon component */ + icon: React.ComponentType<{ className?: string }> + /** Component to render as tab content, receives kbId prop */ + component: React.ComponentType<{ kbId: number }> + /** Whether this tab requires manage permission to be visible */ + requiresManagePermission?: boolean +} + +interface KbPermissionsPanelProps { + /** Knowledge base ID */ + kbId: number + /** Whether the current user can manage permissions */ + canManagePermissions: boolean + /** Additional permission tabs injected by extensions */ + extensionTabs?: ExtensionTabConfig[] +} + +/** + * Knowledge base permissions panel with support for extension tabs. + * + * By default, only shows the personal permission management tab. Extensions can + * provide additional tabs (e.g., department permissions) via the extensionTabs prop. + */ +export function KbPermissionsPanel({ + kbId, + canManagePermissions, + extensionTabs = [], +}: KbPermissionsPanelProps) { + const { t } = useTranslation('knowledge') + + // Build list of all visible tabs + const visibleTabs = useMemo(() => { + const tabs = [ + { + id: 'personal', + label: t('document.permission.personal') || 'Personal', + icon: Users, + component: PermissionManagementTab, + requiresManagePermission: false, + }, + ...extensionTabs.filter( + tab => !tab.requiresManagePermission || canManagePermissions + ), + ] + return tabs + }, [extensionTabs, canManagePermissions, t]) + + const [activeTab, setActiveTab] = useState(visibleTabs[0]?.id || 'personal') + + // Single tab mode: no tabs UI, just render the content + if (visibleTabs.length === 1) { + const TabComponent = visibleTabs[0].component + return + } + + // Multi-tab mode: render tabs + return ( +
+ + + {visibleTabs.map(tab => { + const Icon = tab.icon + return ( + + + {tab.label} + + ) + })} + + + {visibleTabs.map(tab => { + const TabComponent = tab.component + return ( + + + + ) + })} + +
+ ) +} diff --git a/frontend/src/features/knowledge/permission/components/index.ts b/frontend/src/features/knowledge/permission/components/index.ts index d98b2a4f3..b33f5df88 100644 --- a/frontend/src/features/knowledge/permission/components/index.ts +++ b/frontend/src/features/knowledge/permission/components/index.ts @@ -5,3 +5,5 @@ export { ShareLinkDialog } from './ShareLinkDialog' export { PermissionManagementTab } from './PermissionManagementTab' export { AddUserDialog } from './add-user-dialog' +export { KbPermissionsPanel } from './KbPermissionsPanel' +export type { ExtensionTabConfig } from './KbPermissionsPanel'