diff --git a/web/app/components/header/account-setting/access-rules-page/access-rule-row-menu.tsx b/web/app/components/header/account-setting/access-rules-page/access-rule-row-menu.tsx new file mode 100644 index 0000000000..ace93a9d92 --- /dev/null +++ b/web/app/components/header/account-setting/access-rules-page/access-rule-row-menu.tsx @@ -0,0 +1,69 @@ +'use client' + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@langgenius/dify-ui/dropdown-menu' +import { useState } from 'react' +import ActionButton from '@/app/components/base/action-button' + +export type AccessRuleRowMenuProps = { + onEdit?: () => void + onCopy?: () => void + onDelete?: () => void +} + +const AccessRuleRowMenu = ({ + onEdit, + onCopy, + onDelete, +}: AccessRuleRowMenuProps) => { + const [open, setOpen] = useState(false) + + return ( + + + )} + > + + + + + Edit + + + Copy + + + + Delete + + + + ) +} + +export default AccessRuleRowMenu diff --git a/web/app/components/header/account-setting/access-rules-page/access-rule-row.tsx b/web/app/components/header/account-setting/access-rules-page/access-rule-row.tsx new file mode 100644 index 0000000000..e16704e0e6 --- /dev/null +++ b/web/app/components/header/account-setting/access-rules-page/access-rule-row.tsx @@ -0,0 +1,81 @@ +'use client' + +import { cn } from '@langgenius/dify-ui/cn' +import { memo, useCallback } from 'react' +import AccessRuleRowMenu from './access-rule-row-menu' +import RoleTag from './role-tag' + +export type AssignedRole = { + id: string + name: string +} + +export type AccessRule = { + id: string + name: string + description: string + assignedRoles: AssignedRole[] +} + +export type AccessRuleRowProps = { + rule: AccessRule + className?: string + onEdit?: (rule: AccessRule) => void + onCopy?: (rule: AccessRule) => void + onDelete?: (rule: AccessRule) => void + onAddRole?: (rule: AccessRule) => void + onRemoveRole?: (rule: AccessRule, role: AssignedRole) => void +} + +const AccessRuleRow = ({ + rule, + className, + onEdit, + onCopy, + onDelete, + onAddRole, + onRemoveRole, +}: AccessRuleRowProps) => { + const handleEdit = useCallback(() => onEdit?.(rule), [onEdit, rule]) + const handleCopy = useCallback(() => onCopy?.(rule), [onCopy, rule]) + const handleDelete = useCallback(() => onDelete?.(rule), [onDelete, rule]) + const handleAddRole = useCallback(() => onAddRole?.(rule), [onAddRole, rule]) + + return ( +
+
+
+ {rule.name} +
+

+ {rule.description} +

+
+ {rule.assignedRoles.map(role => ( + onRemoveRole(rule, role) : undefined} + /> + ))} + +
+
+ +
+ ) +} + +export default memo(AccessRuleRow) diff --git a/web/app/components/header/account-setting/access-rules-page/access-rule-section.tsx b/web/app/components/header/account-setting/access-rules-page/access-rule-section.tsx new file mode 100644 index 0000000000..68664bff77 --- /dev/null +++ b/web/app/components/header/account-setting/access-rules-page/access-rule-section.tsx @@ -0,0 +1,62 @@ +'use client' + +import type { AccessRule, AssignedRole } from './access-rule-row' +import { Button } from '@langgenius/dify-ui/button' +import { cn } from '@langgenius/dify-ui/cn' +import { memo } from 'react' +import AccessRuleRow from './access-rule-row' + +export type AccessRuleSectionProps = { + title: string + rules: AccessRule[] + createButtonLabel: string + onCreate?: () => void + onEditRule?: (rule: AccessRule) => void + onCopyRule?: (rule: AccessRule) => void + onDeleteRule?: (rule: AccessRule) => void + onAddRole?: (rule: AccessRule) => void + onRemoveRole?: (rule: AccessRule, role: AssignedRole) => void + className?: string +} + +const AccessRuleSection = ({ + title, + rules, + createButtonLabel, + onCreate, + onEditRule, + onCopyRule, + onDeleteRule, + onAddRole, + onRemoveRole, + className, +}: AccessRuleSectionProps) => { + return ( +
+
+

+ {title} +

+ +
+
+ {rules.map((rule, index) => ( + 0 && 'border-t border-divider-subtle')} + onEdit={onEditRule} + onCopy={onCopyRule} + onDelete={onDeleteRule} + onAddRole={onAddRole} + onRemoveRole={onRemoveRole} + /> + ))} +
+
+ ) +} + +export default memo(AccessRuleSection) diff --git a/web/app/components/header/account-setting/access-rules-page/index.tsx b/web/app/components/header/account-setting/access-rules-page/index.tsx index 7e229e7e3d..786f9f2a70 100644 --- a/web/app/components/header/account-setting/access-rules-page/index.tsx +++ b/web/app/components/header/account-setting/access-rules-page/index.tsx @@ -1,22 +1,130 @@ -import { Button } from '@langgenius/dify-ui/button' +'use client' + +import type { AccessRule } from './access-rule-row' +import { useCallback } from 'react' +import AccessRuleSection from './access-rule-section' + +// todo: replace with API data when backend is ready +const APP_ACCESS_RULES: AccessRule[] = [ + { + id: 'app-full-access', + name: 'Full access', + description: 'Highest level. Can edit, publish, delete apps, and manage access for this app.', + assignedRoles: [ + { id: 'owner', name: 'Owner' }, + { id: 'admin', name: 'Admin' }, + { id: 'app-admin', name: 'App Admin' }, + { id: 'executive', name: 'Executive' }, + ], + }, + { + id: 'app-can-edit', + name: 'Can edit', + description: 'Modify Prompts, adjust workflows, change variables. Test and publish updates.', + assignedRoles: [ + { id: 'app-editor', name: 'App Editor' }, + { id: 'it-staff', name: 'IT Staff' }, + ], + }, + { + id: 'app-can-view-and-use', + name: 'Can view & use', + description: 'View and use the app. Access Prompt and workflow logs. Cannot modify.', + assignedRoles: [ + { id: 'tester', name: 'Tester' }, + { id: 'ops-staff', name: 'Ops Staff' }, + { id: 'member', name: 'Member' }, + ], + }, + { + id: 'app-can-preview', + name: 'Can preview', + description: 'View the app in the list only. Cannot open the editor or use the app.', + assignedRoles: [ + { id: 'partner', name: 'Partner' }, + ], + }, +] + +// todo: replace with API data when backend is ready +const KNOWLEDGE_BASE_ACCESS_RULES: AccessRule[] = [ + { + id: 'kb-full-access', + name: 'Full access', + description: 'Highest level. Can edit, publish, delete apps, and manage access for this knowledge base.', + assignedRoles: [ + { id: 'owner', name: 'Owner' }, + { id: 'admin', name: 'Admin' }, + { id: 'kb-admin', name: 'KB Admin' }, + { id: 'executive', name: 'Executive' }, + ], + }, + { + id: 'kb-can-edit', + name: 'Can edit', + description: 'Edit knowledge base content, modify settings, and run tests.', + assignedRoles: [ + { id: 'kb-editor', name: 'KB Editor' }, + { id: 'ops-staff', name: 'Ops Staff' }, + { id: 'it-staff', name: 'IT Staff' }, + ], + }, + { + id: 'kb-can-view', + name: 'Can view', + description: 'View knowledge base sources and logs. Cannot modify content.', + assignedRoles: [ + { id: 'member', name: 'Member' }, + ], + }, + { + id: 'kb-can-preview', + name: 'Can preview', + description: 'View in the list only. Cannot access the detail page.', + assignedRoles: [ + { id: 'partner', name: 'Partner' }, + ], + }, + { + id: 'kb-can-test', + name: 'Can test', + description: 'Test knowledge base retrieval efficiency in sandbox.', + assignedRoles: [ + { id: 'tester', name: 'Tester' }, + ], + }, +] const AccessRulesPage = () => { + const noop = useCallback(() => { + // TODO: wire up to API when backend is ready + }, []) + return ( - <> -
-
-
- App Access Rules -
- -
-
- +
+ + +
) } diff --git a/web/app/components/header/account-setting/access-rules-page/role-tag.tsx b/web/app/components/header/account-setting/access-rules-page/role-tag.tsx new file mode 100644 index 0000000000..c5a9dd6871 --- /dev/null +++ b/web/app/components/header/account-setting/access-rules-page/role-tag.tsx @@ -0,0 +1,39 @@ +'use client' + +import { cn } from '@langgenius/dify-ui/cn' +import { memo } from 'react' + +export type RoleTagProps = { + label: string + onRemove?: () => void + className?: string +} + +const RoleTag = ({ label, onRemove, className }: RoleTagProps) => { + return ( + + {label} + {onRemove && ( + + )} + + ) +} + +export default memo(RoleTag) diff --git a/web/app/components/header/account-setting/index.tsx b/web/app/components/header/account-setting/index.tsx index 3d552c84de..0aff426fbf 100644 --- a/web/app/components/header/account-setting/index.tsx +++ b/web/app/components/header/account-setting/index.tsx @@ -16,6 +16,7 @@ import MenuDialog from '@/app/components/header/account-setting/menu-dialog' import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' +import AccessRulesPage from './access-rules-page' import ApiBasedExtensionPage from './api-based-extension-page' import DataSourcePage from './data-source-page-new' import LanguagePage from './language-page' @@ -247,6 +248,7 @@ export default function AccountSetting({ {activeMenu === ACCOUNT_SETTING_TAB.PROVIDER && } {activeMenu === ACCOUNT_SETTING_TAB.MEMBERS && } {activeMenu === ACCOUNT_SETTING_TAB.PERMISSIONS && } + {activeMenu === ACCOUNT_SETTING_TAB.ACCESS_RULES && } {activeMenu === ACCOUNT_SETTING_TAB.BILLING && } {activeMenu === ACCOUNT_SETTING_TAB.DATA_SOURCE && } {activeMenu === ACCOUNT_SETTING_TAB.API_BASED_EXTENSION && } diff --git a/web/app/components/header/account-setting/permissions-page/role-list/index.tsx b/web/app/components/header/account-setting/permissions-page/role-list/index.tsx index 48fccb64b9..0b5c521eb8 100644 --- a/web/app/components/header/account-setting/permissions-page/role-list/index.tsx +++ b/web/app/components/header/account-setting/permissions-page/role-list/index.tsx @@ -41,10 +41,10 @@ const RoleList = ({ key={group.id} className={cn(groupIndex > 0 && 'mt-6')} > -

+

{group.title}

-
+
{group.items.map((row, rowIndex) => (