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) => (