feat: enhance access rule management with view functionality and permission modal updates

This commit is contained in:
twwu
2026-05-21 17:21:04 +08:00
parent 3e59757b6c
commit 65ad573ed6
10 changed files with 111 additions and 28 deletions

View File

@ -12,11 +12,13 @@ import { useUpdateDatasetAccessRuleBindings } from '@/service/access-control/use
export type AccessRulesEditorProps = {
rules: AccessPolicyWithBindings[]
canManage: boolean
className?: string
}
const AccessRulesEditor = ({
rules,
canManage,
className,
}: AccessRulesEditorProps) => {
const { appId } = useParams() as { appId: string }
@ -102,6 +104,7 @@ const AccessRulesEditor = ({
<AccessRuleRow
key={rule.policy.id}
rule={rule}
canManage={canManage}
showMenu={false}
onAddRole={handleAddRole}
onRemove={handleRemoveRole}

View File

@ -1,8 +1,11 @@
'use client'
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
import { useMemo } from 'react'
import AccessRulesEditor from '@/app/components/access-rules-editor'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useAppAccessRules } from '@/service/access-control/use-app-access-config'
import { getAppACLCapabilities } from '@/utils/permission'
type AppAccessConfigPageProps = {
appId: string
@ -10,6 +13,11 @@ type AppAccessConfigPageProps = {
const AppAccessConfigPage = ({ appId }: AppAccessConfigPageProps) => {
const { data: appAccessRulesResponse } = useAppAccessRules(appId)
const appPermissionKeys = useAppStore(state => state.appDetail?.permission_keys)
const appACLCapabilities = useMemo(
() => getAppACLCapabilities(appPermissionKeys),
[appPermissionKeys],
)
const appAccessRules = appAccessRulesResponse?.items || []
@ -21,7 +29,7 @@ const AppAccessConfigPage = ({ appId }: AppAccessConfigPageProps) => {
<div className="w-full px-16 py-8">
<h1 className="title-2xl-semi-bold text-text-primary">Access Config</h1>
<div className="mt-6">
<AccessRulesEditor rules={appAccessRules} />
<AccessRulesEditor rules={appAccessRules} canManage={appACLCapabilities.canAccessConfig} />
</div>
</div>
</ScrollArea>

View File

@ -1,8 +1,11 @@
'use client'
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
import { useMemo } from 'react'
import AccessRulesEditor from '@/app/components/access-rules-editor'
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
import { useDatasetAccessRules } from '@/service/access-control/use-dataset-access-config'
import { getDatasetACLCapabilities } from '@/utils/permission'
type DatasetAccessConfigPageProps = {
datasetId: string
@ -10,6 +13,11 @@ type DatasetAccessConfigPageProps = {
const DatasetAccessConfigPage = ({ datasetId }: DatasetAccessConfigPageProps) => {
const { data: datasetAccessRulesResponse } = useDatasetAccessRules(datasetId)
const datasetPermissionKeys = useDatasetDetailContextWithSelector(state => state.dataset?.permission_keys)
const datasetACLCapabilities = useMemo(
() => getDatasetACLCapabilities(datasetPermissionKeys),
[datasetPermissionKeys],
)
const datasetAccessRules = datasetAccessRulesResponse?.items || []
@ -21,7 +29,7 @@ const DatasetAccessConfigPage = ({ datasetId }: DatasetAccessConfigPageProps) =>
<div className="px-12 py-8">
<h1 className="title-2xl-semi-bold text-text-primary">Access Config</h1>
<div className="mt-6">
<AccessRulesEditor rules={datasetAccessRules} />
<AccessRulesEditor rules={datasetAccessRules} canManage={datasetACLCapabilities.canAccessConfig} />
</div>
</div>
</ScrollArea>

View File

@ -50,7 +50,7 @@ const DocumentList = ({
}: DocumentListProps) => {
const { t } = useTranslation()
const datasetConfig = useDatasetDetailContext(s => s.dataset)
const datasetACLCapabilities = React.useMemo(() => getDatasetACLCapabilities(datasetConfig?.permission_keys), [datasetConfig?.permission_keys])
const datasetACLCapabilities = useMemo(() => getDatasetACLCapabilities(datasetConfig?.permission_keys), [datasetConfig?.permission_keys])
const chunkingMode = datasetConfig?.doc_form
const isGeneralMode = chunkingMode !== ChunkingMode.parentChild
const isQAMode = chunkingMode === ChunkingMode.qa

View File

@ -24,11 +24,13 @@ import { useCopyAccessRule, useDeleteAccessRule } from '@/service/access-control
export type AccessRuleRowMenuProps = {
rule: AccessPolicy
onView?: () => void
onEdit?: () => void
}
const AccessRuleRowMenu = ({
rule,
onView,
onEdit,
}: AccessRuleRowMenuProps) => {
const [open, setOpen] = useState(false)
@ -37,6 +39,10 @@ const AccessRuleRowMenu = ({
const { mutateAsync: copyAccessRule } = useCopyAccessRule(rule.resource_type)
const { mutateAsync: deleteAccessRule, isPending: isDeletingAccessRule } = useDeleteAccessRule(rule.resource_type)
const handleView = useCallback(() => {
onView?.()
}, [onView])
const handleCopyRules = useCallback(() => {
copyAccessRule(rule.id, {
onSuccess: () => {
@ -60,6 +66,8 @@ const AccessRuleRowMenu = ({
})
}, [deleteAccessRule, rule.id])
const isBuiltIn = rule.is_builtin
return (
<>
<DropdownMenu open={open} onOpenChange={setOpen}>
@ -79,26 +87,41 @@ const AccessRuleRowMenu = ({
sideOffset={4}
popupClassName="min-w-[140px]"
>
<DropdownMenuItem
className="system-sm-semibold text-text-secondary"
onClick={onEdit}
>
Edit
</DropdownMenuItem>
{isBuiltIn
? (
<DropdownMenuItem
className="system-sm-semibold text-text-secondary"
onClick={handleView}
>
View
</DropdownMenuItem>
)
: (
<DropdownMenuItem
className="system-sm-semibold text-text-secondary"
onClick={onEdit}
>
Edit
</DropdownMenuItem>
)}
<DropdownMenuItem
className="system-sm-semibold text-text-secondary"
onClick={handleCopyRules}
>
Copy
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
variant="destructive"
className="system-sm-semibold"
onClick={openDeleteConfirm}
>
Delete
</DropdownMenuItem>
{!isBuiltIn && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
variant="destructive"
className="system-sm-semibold"
onClick={openDeleteConfirm}
>
Delete
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
<AlertDialog open={showDeleteConfirm} onOpenChange={open => !open && setShowDeleteConfirm(false)}>

View File

@ -11,6 +11,7 @@ export type AccessRuleRowProps = {
canManage: boolean
className?: string
showMenu?: boolean
onView?: (rule: AccessPolicyWithBindings) => void
onEdit?: (rule: AccessPolicyWithBindings) => void
onAddRole?: (rule: AccessPolicyWithBindings) => void
onRemove?: (payload: RemoveBindingPayload) => void
@ -21,6 +22,7 @@ const AccessRuleRow = ({
canManage,
className,
showMenu = true,
onView,
onEdit,
onAddRole,
onRemove,
@ -28,6 +30,7 @@ const AccessRuleRow = ({
const { policy, roles, accounts } = rule
const { id: policyId, resource_type } = policy
const handleView = useCallback(() => onView?.(rule), [onView, rule])
const handleEdit = useCallback(() => onEdit?.(rule), [onEdit, rule])
const handleAddRole = useCallback(() => onAddRole?.(rule), [onAddRole, rule])
@ -95,6 +98,7 @@ const AccessRuleRow = ({
</div>
{showMenu && canManage && (
<AccessRuleRowMenu
onView={handleView}
onEdit={handleEdit}
rule={policy}
/>

View File

@ -13,6 +13,7 @@ type AccessRuleSectionProps = {
rules: AccessPolicyWithBindings[]
isLoadingRules: boolean
onCreate?: () => void
onViewRule?: (rule: AccessPolicyWithBindings) => void
onEditRule?: (rule: AccessPolicyWithBindings) => void
onAddRole?: (rule: AccessPolicyWithBindings) => void
onRemoveBinding?: (payload: RemoveBindingPayload) => void
@ -24,6 +25,7 @@ const AccessRuleSection = ({
rules,
isLoadingRules,
onCreate,
onViewRule,
onEditRule,
onAddRole,
onRemoveBinding,
@ -57,6 +59,7 @@ const AccessRuleSection = ({
rule={rule}
canManage={canManage}
className={cn(index > 0 && 'border-t border-divider-subtle')}
onView={onViewRule}
onEdit={onEditRule}
onAddRole={onAddRole}
onRemove={onRemoveBinding}

View File

@ -50,6 +50,18 @@ const AppAccessRuleSection = ({
setPermissionSetModalState({ mode: 'create' })
}, [])
const handleView = useCallback((rule: AccessPolicyWithBindings) => {
const { policy } = rule
setPermissionSetModalState({
mode: 'view',
initialValues: {
name: policy.name,
description: policy.description,
permissionKeys: policy.permission_keys,
},
})
}, [])
const handleEdit = useCallback((rule: AccessPolicyWithBindings) => {
const { policy } = rule
setPermissionSetModalState({
@ -136,6 +148,7 @@ const AppAccessRuleSection = ({
rules={appAccessRules}
isLoadingRules={isLoading}
onCreate={handleCreate}
onViewRule={handleView}
onEditRule={handleEdit}
onAddRole={handleAddRole}
onRemoveBinding={handleRemoveBinding}

View File

@ -50,6 +50,18 @@ const DatasetAccessRuleSection = ({
setPermissionSetModalState({ mode: 'create' })
}, [])
const handleView = useCallback((rule: AccessPolicyWithBindings) => {
const { policy } = rule
setPermissionSetModalState({
mode: 'view',
initialValues: {
name: policy.name,
description: policy.description,
permissionKeys: policy.permission_keys,
},
})
}, [])
const handleEdit = useCallback((rule: AccessPolicyWithBindings) => {
const { policy } = rule
setPermissionSetModalState({
@ -136,6 +148,7 @@ const DatasetAccessRuleSection = ({
rules={datasetAccessRules}
isLoadingRules={isLoading}
onCreate={handleCreate}
onViewRule={handleView}
onEditRule={handleEdit}
onAddRole={handleAddRole}
onRemoveBinding={handleRemoveBinding}

View File

@ -14,7 +14,7 @@ import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import PermissionPicker from './permission-picker'
export type PermissionSetModalMode = 'create' | 'edit'
export type PermissionSetModalMode = 'create' | 'edit' | 'view'
export type PermissionSetFormValues = {
name: string
@ -37,11 +37,13 @@ const RESOURCE_LABEL: Record<AccessPolicyResourceType, string> = {
}
const buildTitle = (mode: PermissionSetModalMode, resource: AccessPolicyResourceType): string => {
const verb = mode === 'create' ? 'Create' : 'Edit'
const verb = mode === 'create' ? 'Create' : mode === 'edit' ? 'Edit' : 'View'
return `${verb} ${RESOURCE_LABEL[resource]} permission set`
}
const buildDescription = (mode: PermissionSetModalMode, resource: AccessPolicyResourceType): string => {
if (mode === 'view')
return 'View the name, description, and permissions granted for this permission set.'
if (mode === 'edit')
return 'Modify the name, description, and permissions granted for this permission set.'
if (resource === 'app')
@ -63,10 +65,11 @@ const PermissionSetModalBody = ({
const [permissionKeys, setPermissionKeys] = useState<string[]>(initialValues?.permissionKeys ?? [])
const trimmedName = name.trim()
const readonly = mode === 'view'
const canSubmit = trimmedName.length > 0
const handleConfirm = () => {
if (!canSubmit)
if (readonly || !canSubmit)
return
onSubmit({
name: trimmedName,
@ -106,6 +109,7 @@ const PermissionSetModalBody = ({
value={name}
onChange={e => setName(e.target.value)}
placeholder="e.g. Can export DSL"
disabled={readonly}
/>
</div>
@ -119,6 +123,7 @@ const PermissionSetModalBody = ({
onChange={e => setDescription(e.target.value)}
placeholder="Describe what this permission set grants"
className="min-h-20 resize-none"
disabled={readonly}
/>
</div>
@ -128,6 +133,7 @@ const PermissionSetModalBody = ({
resourceType={resourceType}
value={permissionKeys}
onChange={setPermissionKeys}
readonly={readonly}
/>
</div>
</div>
@ -144,15 +150,17 @@ const PermissionSetModalBody = ({
</a>
<div className="flex items-center gap-2">
<Button variant="secondary" onClick={onClose}>
Cancel
</Button>
<Button
variant="primary"
disabled={!canSubmit}
onClick={handleConfirm}
>
Confirm
{readonly ? 'Close' : 'Cancel'}
</Button>
{!readonly && (
<Button
variant="primary"
disabled={!canSubmit}
onClick={handleConfirm}
>
Confirm
</Button>
)}
</div>
</div>
</DialogContent>