mirror of
https://github.com/langgenius/dify.git
synced 2026-05-28 12:53:23 +08:00
feat: implement RBAC support in permission handling and update related tests
This commit is contained in:
@ -2,23 +2,8 @@ import type { Member } from '@/models/common'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
|
||||
import { DatasetPermission } from '@/models/datasets'
|
||||
import { LicenseStatus } from '@/types/feature'
|
||||
import PermissionSelector from '../index'
|
||||
|
||||
const mockConfig = vi.hoisted(() => ({
|
||||
IS_CLOUD_EDITION: false,
|
||||
}))
|
||||
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
return {
|
||||
...actual,
|
||||
get IS_CLOUD_EDITION() {
|
||||
return mockConfig.IS_CLOUD_EDITION
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// Mock app-context
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: () => ({
|
||||
@ -48,7 +33,6 @@ describe('PermissionSelector', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockConfig.IS_CLOUD_EDITION = false
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@ -424,23 +408,10 @@ describe('PermissionSelector', () => {
|
||||
expect(triggerElement)!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show access config hint and remain closed in SaaS', () => {
|
||||
mockConfig.IS_CLOUD_EDITION = true
|
||||
renderWithSystemFeatures(<PermissionSelector {...defaultProps} />)
|
||||
|
||||
const trigger = screen.getByText(/form\.permissionsAccessConfig/)
|
||||
fireEvent.click(trigger)
|
||||
|
||||
expect(screen.getByText(/form\.permissionsAccessConfig/))!.toBeInTheDocument()
|
||||
expect(screen.queryByText(/form\.permissionsOnlyMe/))!.not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show access config hint and remain closed in enterprise edition', () => {
|
||||
it('should show access config hint and remain closed when RBAC is enabled', () => {
|
||||
renderWithSystemFeatures(<PermissionSelector {...defaultProps} />, {
|
||||
systemFeatures: {
|
||||
license: {
|
||||
status: LicenseStatus.ACTIVE,
|
||||
},
|
||||
rbac_enabled: true,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -11,11 +11,9 @@ import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { DatasetPermission } from '@/models/datasets'
|
||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||
import { LicenseStatus } from '@/types/feature'
|
||||
import MemberItem from './member-item'
|
||||
import Item from './permission-item'
|
||||
|
||||
@ -91,9 +89,8 @@ const PermissionSelector = ({
|
||||
const isAllTeamMembers = permission === DatasetPermission.allTeamMembers
|
||||
const isPartialMembers = permission === DatasetPermission.partialMembers
|
||||
const selectedMemberNames = selectedMembers.map(member => member.name).join(', ')
|
||||
const isEnterpriseEdition = systemFeatures.license.status !== LicenseStatus.NONE
|
||||
const isEditionDisabled = IS_CLOUD_EDITION || isEnterpriseEdition
|
||||
const isDisabled = disabled || isEditionDisabled
|
||||
const isDisabledByRBAC = systemFeatures.rbac_enabled
|
||||
const isDisabled = disabled || isDisabledByRBAC
|
||||
|
||||
return (
|
||||
<Popover
|
||||
@ -108,7 +105,7 @@ const PermissionSelector = ({
|
||||
<PopoverTrigger
|
||||
render={(
|
||||
<div className={cn('group flex cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal px-2 py-1 hover:bg-state-base-hover-alt data-popup-open:bg-state-base-hover-alt', isDisabled && 'cursor-not-allowed! bg-components-input-bg-disabled! hover:bg-components-input-bg-disabled!')}>
|
||||
{isEditionDisabled && (
|
||||
{isDisabledByRBAC && (
|
||||
<>
|
||||
<div className="flex size-6 shrink-0 items-center justify-center">
|
||||
<span className="i-ri-lock-2-line size-4 text-text-tertiary" />
|
||||
@ -119,7 +116,7 @@ const PermissionSelector = ({
|
||||
</>
|
||||
)}
|
||||
{
|
||||
!isEditionDisabled && isOnlyMe && (
|
||||
!isDisabledByRBAC && isOnlyMe && (
|
||||
<>
|
||||
<div className="flex size-6 shrink-0 items-center justify-center">
|
||||
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size="xs" />
|
||||
@ -131,7 +128,7 @@ const PermissionSelector = ({
|
||||
)
|
||||
}
|
||||
{
|
||||
!isEditionDisabled && isAllTeamMembers && (
|
||||
!isDisabledByRBAC && isAllTeamMembers && (
|
||||
<>
|
||||
<div className="flex size-6 shrink-0 items-center justify-center">
|
||||
<span className="i-ri-group-2-line size-4 text-text-secondary" />
|
||||
@ -143,7 +140,7 @@ const PermissionSelector = ({
|
||||
)
|
||||
}
|
||||
{
|
||||
!isEditionDisabled && isPartialMembers && (
|
||||
!isDisabledByRBAC && isPartialMembers && (
|
||||
<>
|
||||
<div className="relative flex size-6 shrink-0 items-center justify-center">
|
||||
{
|
||||
|
||||
@ -42,7 +42,7 @@ vi.mock('@/context/app-context', () => ({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0' },
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.manage', 'plugin.preference.manage'],
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.manage', 'plugin.debug'],
|
||||
}),
|
||||
}))
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ const createAppContext = (overrides: Partial<ReturnType<typeof useAppContext>> =
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
langGeniusVersionInfo: { current_version: '1.0.0' },
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.manage'],
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.manage', 'plugin.debug'],
|
||||
...overrides,
|
||||
}) as ReturnType<typeof useAppContext>
|
||||
|
||||
@ -54,115 +54,49 @@ describe('useReferenceSetting Hook', () => {
|
||||
vi.mocked(useInvalidateReferenceSettings).mockReturnValue(vi.fn())
|
||||
})
|
||||
|
||||
describe('hasPermission logic', () => {
|
||||
it('should return false when permission is undefined', () => {
|
||||
vi.mocked(useReferenceSettings).mockReturnValue({
|
||||
data: {
|
||||
permission: {
|
||||
install_permission: undefined,
|
||||
debug_permission: undefined,
|
||||
},
|
||||
},
|
||||
} as unknown as ReturnType<typeof useReferenceSettings>)
|
||||
describe('workspace permission key logic', () => {
|
||||
it('should return false when workspace permission keys are empty', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue(createAppContext({
|
||||
workspacePermissionKeys: [],
|
||||
}))
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting())
|
||||
|
||||
expect(result.current.canInstall).toBe(false)
|
||||
expect(result.current.canUpdate).toBe(false)
|
||||
expect(result.current.canViewInstalledPlugins).toBe(false)
|
||||
expect(result.current.canManagePlugin).toBe(false)
|
||||
expect(result.current.canUninstall).toBe(false)
|
||||
expect(result.current.canDebugger).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false when permission is noOne', () => {
|
||||
vi.mocked(useReferenceSettings).mockReturnValue({
|
||||
data: {
|
||||
permission: {
|
||||
install_permission: PermissionType.noOne,
|
||||
debug_permission: PermissionType.noOne,
|
||||
},
|
||||
},
|
||||
} as ReturnType<typeof useReferenceSettings>)
|
||||
it('should only allow debug with plugin.debug only', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue(createAppContext({
|
||||
workspacePermissionKeys: ['plugin.debug'],
|
||||
}))
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting())
|
||||
|
||||
expect(result.current.canInstall).toBe(false)
|
||||
expect(result.current.canDebugger).toBe(false)
|
||||
expect(result.current.canUpdate).toBe(false)
|
||||
expect(result.current.canViewInstalledPlugins).toBe(false)
|
||||
expect(result.current.canManagePlugin).toBe(false)
|
||||
expect(result.current.canUninstall).toBe(false)
|
||||
expect(result.current.canDebugger).toBe(true)
|
||||
})
|
||||
|
||||
it('should return true when permission is everyone', () => {
|
||||
vi.mocked(useReferenceSettings).mockReturnValue({
|
||||
data: {
|
||||
permission: {
|
||||
install_permission: PermissionType.everyone,
|
||||
debug_permission: PermissionType.everyone,
|
||||
},
|
||||
},
|
||||
} as ReturnType<typeof useReferenceSettings>)
|
||||
it('should allow install and update but not manage or debug with plugin.install only', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue(createAppContext({
|
||||
workspacePermissionKeys: ['plugin.install'],
|
||||
}))
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting())
|
||||
|
||||
expect(result.current.canInstall).toBe(true)
|
||||
expect(result.current.canDebugger).toBe(true)
|
||||
})
|
||||
|
||||
it('should return isAdmin when permission is admin and user is manager', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue(createAppContext({
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
}))
|
||||
|
||||
vi.mocked(useReferenceSettings).mockReturnValue({
|
||||
data: {
|
||||
permission: {
|
||||
install_permission: PermissionType.admin,
|
||||
debug_permission: PermissionType.admin,
|
||||
},
|
||||
},
|
||||
} as ReturnType<typeof useReferenceSettings>)
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting())
|
||||
|
||||
expect(result.current.canInstall).toBe(true)
|
||||
expect(result.current.canDebugger).toBe(true)
|
||||
})
|
||||
|
||||
it('should return isAdmin when permission is admin and user is owner', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue(createAppContext({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
}))
|
||||
|
||||
vi.mocked(useReferenceSettings).mockReturnValue({
|
||||
data: {
|
||||
permission: {
|
||||
install_permission: PermissionType.admin,
|
||||
debug_permission: PermissionType.admin,
|
||||
},
|
||||
},
|
||||
} as ReturnType<typeof useReferenceSettings>)
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting())
|
||||
|
||||
expect(result.current.canInstall).toBe(true)
|
||||
expect(result.current.canDebugger).toBe(true)
|
||||
})
|
||||
|
||||
it('should return false when permission is admin and user is not admin', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue(createAppContext({
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
}))
|
||||
|
||||
vi.mocked(useReferenceSettings).mockReturnValue({
|
||||
data: {
|
||||
permission: {
|
||||
install_permission: PermissionType.admin,
|
||||
debug_permission: PermissionType.admin,
|
||||
},
|
||||
},
|
||||
} as ReturnType<typeof useReferenceSettings>)
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting())
|
||||
|
||||
expect(result.current.canInstall).toBe(false)
|
||||
expect(result.current.canUpdate).toBe(true)
|
||||
expect(result.current.canViewInstalledPlugins).toBe(true)
|
||||
expect(result.current.canManagePlugin).toBe(false)
|
||||
expect(result.current.canUninstall).toBe(false)
|
||||
expect(result.current.canDebugger).toBe(false)
|
||||
})
|
||||
|
||||
@ -178,11 +112,12 @@ describe('useReferenceSetting Hook', () => {
|
||||
expect(result.current.canViewInstalledPlugins).toBe(true)
|
||||
expect(result.current.canManagePlugin).toBe(true)
|
||||
expect(result.current.canUninstall).toBe(true)
|
||||
expect(result.current.canDebugger).toBe(false)
|
||||
})
|
||||
|
||||
it('should allow install and update but not manage with plugin.install only', () => {
|
||||
it('should allow install and debug when both plugin.install and plugin.debug are present', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue(createAppContext({
|
||||
workspacePermissionKeys: ['plugin.install'],
|
||||
workspacePermissionKeys: ['plugin.install', 'plugin.debug'],
|
||||
}))
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting())
|
||||
@ -191,6 +126,7 @@ describe('useReferenceSetting Hook', () => {
|
||||
expect(result.current.canUpdate).toBe(true)
|
||||
expect(result.current.canViewInstalledPlugins).toBe(true)
|
||||
expect(result.current.canManagePlugin).toBe(false)
|
||||
expect(result.current.canDebugger).toBe(true)
|
||||
})
|
||||
|
||||
it('should not allow uninstall with legacy plugin.uninstall only', () => {
|
||||
@ -316,8 +252,9 @@ describe('useReferenceSetting Hook', () => {
|
||||
|
||||
const { result } = renderHook(() => useReferenceSetting())
|
||||
|
||||
expect(result.current.canInstall).toBe(false)
|
||||
expect(result.current.canDebugger).toBe(false)
|
||||
expect(result.current.referenceSetting).toBeNull()
|
||||
expect(result.current.canInstall).toBe(true)
|
||||
expect(result.current.canDebugger).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -365,14 +302,9 @@ describe('useCanInstallPluginFromMarketplace Hook', () => {
|
||||
})
|
||||
|
||||
it('should return false when canInstall is false', () => {
|
||||
vi.mocked(useReferenceSettings).mockReturnValue({
|
||||
data: {
|
||||
permission: {
|
||||
install_permission: PermissionType.noOne,
|
||||
debug_permission: PermissionType.noOne,
|
||||
},
|
||||
},
|
||||
} as ReturnType<typeof useReferenceSettings>)
|
||||
vi.mocked(useAppContext).mockReturnValue(createAppContext({
|
||||
workspacePermissionKeys: [],
|
||||
}))
|
||||
|
||||
const { result } = renderHook(() => useCanInstallPluginFromMarketplace(), {
|
||||
systemFeatures: { enable_marketplace: true },
|
||||
|
||||
@ -2,10 +2,6 @@
|
||||
import type { FC } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import {
|
||||
RiArrowRightUpLine,
|
||||
RiBugLine,
|
||||
} from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
@ -28,7 +24,7 @@ const DebugInfo: FC = () => {
|
||||
if (!info) {
|
||||
return (
|
||||
<Button className="size-full p-2 text-components-button-secondary-text" disabled>
|
||||
<RiBugLine className="size-4" />
|
||||
<span className="i-ri-bug-line size-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@ -38,7 +34,7 @@ const DebugInfo: FC = () => {
|
||||
<PopoverTrigger
|
||||
render={(
|
||||
<Button className="size-full p-2 text-components-button-secondary-text">
|
||||
<RiBugLine className="size-4" />
|
||||
<span className="i-ri-bug-line size-4" />
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
@ -57,7 +53,7 @@ const DebugInfo: FC = () => {
|
||||
className="flex cursor-pointer items-center gap-0.5 text-text-accent-light-mode-only"
|
||||
>
|
||||
<span className="system-xs-medium">{t(`${i18nPrefix}.viewDocs`, { ns: 'plugin' })}</span>
|
||||
<RiArrowRightUpLine className="size-3" />
|
||||
<span className="i-ri-arrow-right-up-line size-3" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
|
||||
@ -6,27 +6,12 @@ import { useAppContext } from '@/context/app-context'
|
||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||
import { useInvalidateReferenceSettings, useMutationReferenceSettings, useReferenceSettings } from '@/service/use-plugins'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { PermissionType } from '../types'
|
||||
|
||||
const hasPluginPermission = (permission: PermissionType | undefined, isAdmin: boolean) => {
|
||||
if (!permission)
|
||||
return false
|
||||
|
||||
if (permission === PermissionType.noOne)
|
||||
return false
|
||||
|
||||
if (permission === PermissionType.everyone)
|
||||
return true
|
||||
|
||||
return isAdmin
|
||||
}
|
||||
|
||||
const useReferenceSetting = () => {
|
||||
const { t } = useTranslation()
|
||||
const { isCurrentWorkspaceManager, isCurrentWorkspaceOwner, langGeniusVersionInfo, workspacePermissionKeys } = useAppContext()
|
||||
const { langGeniusVersionInfo, workspacePermissionKeys } = useAppContext()
|
||||
const { data } = useReferenceSettings()
|
||||
|
||||
const { permission: permissions } = data || {}
|
||||
const invalidateReferenceSettings = useInvalidateReferenceSettings()
|
||||
const { mutate: updateReferenceSetting, isPending: isUpdatePending } = useMutationReferenceSettings({
|
||||
onSuccess: () => {
|
||||
@ -34,13 +19,13 @@ const useReferenceSetting = () => {
|
||||
toast.success(t('api.actionSuccess', { ns: 'common' }))
|
||||
},
|
||||
})
|
||||
const isAdmin = isCurrentWorkspaceManager || isCurrentWorkspaceOwner
|
||||
|
||||
const canInstallPluginByPermissionKey = hasPermission(workspacePermissionKeys, 'plugin.install')
|
||||
const canUpdatePlugin = hasPermission(workspacePermissionKeys, ['plugin.install', 'plugin.manage'])
|
||||
const canViewInstalledPlugins = canUpdatePlugin
|
||||
const canManagePlugin = hasPermission(workspacePermissionKeys, 'plugin.manage')
|
||||
const canUninstall = canManagePlugin
|
||||
const canDebugger = hasPermission(workspacePermissionKeys, 'plugin.debug')
|
||||
const canSetPermissions = hasPermission(workspacePermissionKeys, 'plugin.install')
|
||||
const canSetAutoUpdate = hasPermission(workspacePermissionKeys, 'plugin.install')
|
||||
const canSetPreferences = canSetPermissions || canSetAutoUpdate
|
||||
@ -49,11 +34,11 @@ const useReferenceSetting = () => {
|
||||
referenceSetting: data,
|
||||
setReferenceSettings: updateReferenceSetting,
|
||||
canViewInstalledPlugins,
|
||||
canInstall: canInstallPluginByPermissionKey && hasPluginPermission(permissions?.install_permission, isAdmin),
|
||||
canInstall: canInstallPluginByPermissionKey,
|
||||
canUpdate: canUpdatePlugin,
|
||||
canManagePlugin,
|
||||
canUninstall,
|
||||
canDebugger: hasPluginPermission(permissions?.debug_permission, isAdmin),
|
||||
canDebugger,
|
||||
canSetPermissions,
|
||||
canSetAutoUpdate,
|
||||
canSetPreferences,
|
||||
|
||||
@ -9,7 +9,10 @@ import { PermissionType } from '@/app/components/plugins/types'
|
||||
import { AUTO_UPDATE_MODE, AUTO_UPDATE_STRATEGY } from '../auto-update-setting/types'
|
||||
import ReferenceSettingModal from '../index'
|
||||
|
||||
const mockSystemFeatures = { enable_marketplace: true }
|
||||
const mockSystemFeatures = {
|
||||
enable_marketplace: true,
|
||||
rbac_enabled: false,
|
||||
}
|
||||
|
||||
const render = (ui: ReactElement) =>
|
||||
renderWithSystemFeatures(ui, { systemFeatures: mockSystemFeatures })
|
||||
@ -37,19 +40,26 @@ vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
|
||||
// Mock OptionCard component
|
||||
vi.mock('@/app/components/workflow/nodes/_base/components/option-card', () => ({
|
||||
default: ({ title, onSelect, selected, className }: {
|
||||
default: ({ title, onSelect, selected, className, disabled, tooltip }: {
|
||||
title: string
|
||||
onSelect: () => void
|
||||
selected: boolean
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
tooltip?: string
|
||||
}) => (
|
||||
<button
|
||||
data-testid={`option-card-${title.toLowerCase().replace(/\s+/g, '-')}`}
|
||||
onClick={onSelect}
|
||||
onClick={() => {
|
||||
if (!disabled)
|
||||
onSelect()
|
||||
}}
|
||||
aria-pressed={selected}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
>
|
||||
{title}
|
||||
{tooltip && <span>{tooltip}</span>}
|
||||
</button>
|
||||
),
|
||||
}))
|
||||
@ -124,6 +134,7 @@ describe('reference-setting-modal', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockSystemFeatures.enable_marketplace = true
|
||||
mockSystemFeatures.rbac_enabled = false
|
||||
})
|
||||
|
||||
// Label component tests moved to label.spec.tsx
|
||||
@ -259,6 +270,16 @@ describe('reference-setting-modal', () => {
|
||||
// Assert
|
||||
expect(screen.getByTestId('modal-close'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should disable permission controls with settings permissions tooltip beside titles when RBAC is enabled', () => {
|
||||
mockSystemFeatures.rbac_enabled = true
|
||||
|
||||
render(<ReferenceSettingModal {...defaultProps} />)
|
||||
|
||||
expect(screen.getAllByLabelText('plugin.privilege.configurePermissionsInSettings')).toHaveLength(2)
|
||||
expect(screen.queryByText('plugin.privilege.configurePermissionsInSettings')).not.toBeInTheDocument()
|
||||
expect(screen.getAllByTestId(/option-card/).every(option => option.hasAttribute('disabled'))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('State Management', () => {
|
||||
@ -296,6 +317,16 @@ describe('reference-setting-modal', () => {
|
||||
expect(noOneOptions[0])!.toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
|
||||
it('should not update permission when RBAC disables permission controls', () => {
|
||||
mockSystemFeatures.rbac_enabled = true
|
||||
render(<ReferenceSettingModal {...defaultProps} />)
|
||||
|
||||
const noOneOptions = screen.getAllByTestId('option-card-plugin.privilege.noone')
|
||||
fireEvent.click(noOneOptions[0]!)
|
||||
|
||||
expect(noOneOptions[0])!.toHaveAttribute('aria-pressed', 'false')
|
||||
})
|
||||
|
||||
it('should initialize with payload auto_upgrade values', () => {
|
||||
// Arrange
|
||||
const payload = createMockReferenceSetting({
|
||||
|
||||
@ -35,18 +35,19 @@ const PluginSettingModal: FC<Props> = ({
|
||||
const { auto_upgrade: autoUpdateConfig, permission: privilege } = payload || {}
|
||||
const [tempPrivilege, setTempPrivilege] = useState<Permissions>(privilege)
|
||||
const [tempAutoUpdateConfig, setTempAutoUpdateConfig] = useState<AutoUpdateConfig>(autoUpdateConfig || autoUpdateDefaultValue)
|
||||
const { data: enable_marketplace } = useSuspenseQuery({
|
||||
...systemFeaturesQueryOptions(),
|
||||
select: s => s.enable_marketplace,
|
||||
})
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const isPermissionDisabledByRBAC = systemFeatures.rbac_enabled
|
||||
const permissionDisabledTip = t(`${i18nPrefix}.configurePermissionsInSettings`, { ns: 'plugin' })
|
||||
const handlePrivilegeChange = useCallback((key: string) => {
|
||||
return (value: PermissionType) => {
|
||||
if (isPermissionDisabledByRBAC)
|
||||
return
|
||||
setTempPrivilege({
|
||||
...tempPrivilege,
|
||||
[key]: value,
|
||||
})
|
||||
}
|
||||
}, [tempPrivilege])
|
||||
}, [isPermissionDisabledByRBAC, tempPrivilege])
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
await onSave({
|
||||
@ -78,7 +79,10 @@ const PluginSettingModal: FC<Props> = ({
|
||||
{ title: t(`${i18nPrefix}.whoCanDebug`, { ns: 'plugin' }), key: 'debug_permission', value: tempPrivilege?.debug_permission || PermissionType.noOne },
|
||||
].map(({ title, key, value }) => (
|
||||
<div key={key} className="flex flex-col items-start gap-1 self-stretch">
|
||||
<Label label={title} />
|
||||
<Label
|
||||
label={title}
|
||||
tooltip={isPermissionDisabledByRBAC ? permissionDisabledTip : undefined}
|
||||
/>
|
||||
<div className="flex w-full items-start justify-between gap-2">
|
||||
{[PermissionType.everyone, PermissionType.admin, PermissionType.noOne].map(option => (
|
||||
<OptionCard
|
||||
@ -86,6 +90,7 @@ const PluginSettingModal: FC<Props> = ({
|
||||
title={t(`${i18nPrefix}.${option}`, { ns: 'plugin' })}
|
||||
onSelect={() => handlePrivilegeChange(key)(option)}
|
||||
selected={value === option}
|
||||
disabled={isPermissionDisabledByRBAC}
|
||||
className="flex-1"
|
||||
/>
|
||||
))}
|
||||
@ -95,7 +100,7 @@ const PluginSettingModal: FC<Props> = ({
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
enable_marketplace && canSetAutoUpdate && (
|
||||
systemFeatures.enable_marketplace && canSetAutoUpdate && (
|
||||
<AutoUpdateSetting payload={tempAutoUpdateConfig} onChange={setTempAutoUpdateConfig} />
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,21 +1,41 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import * as React from 'react'
|
||||
|
||||
type Props = {
|
||||
label: string
|
||||
description?: string
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
const Label: FC<Props> = ({
|
||||
label,
|
||||
description,
|
||||
tooltip,
|
||||
}) => {
|
||||
const tooltipIcon = (
|
||||
<span
|
||||
aria-label={tooltip}
|
||||
className="ml-1 flex size-4 shrink-0 cursor-pointer items-center justify-center"
|
||||
>
|
||||
<span aria-hidden className="i-ri-question-line size-3.5 text-text-quaternary" />
|
||||
</span>
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={cn('flex h-6 items-center', description && 'h-4')}>
|
||||
<span className="system-sm-semibold text-text-secondary">{label}</span>
|
||||
{tooltip && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={tooltipIcon} />
|
||||
<TooltipContent>
|
||||
{tooltip}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
{description && (
|
||||
<div className="mt-1 body-xs-regular text-text-tertiary">
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
"page.datasets.access": "Access Datasets page",
|
||||
"page.explore.access": "Access Explore page",
|
||||
"page.tool.access": "Access Tool page",
|
||||
"plugin.debug": "Debug plugins",
|
||||
"plugin.install": "Install and update plugins",
|
||||
"plugin.manage": "Manage plugins",
|
||||
"tool.manage": "Manage tools",
|
||||
|
||||
@ -213,6 +213,7 @@
|
||||
"pluginInfoModal.repository": "Repository",
|
||||
"pluginInfoModal.title": "Plugin info",
|
||||
"privilege.admins": "Admins",
|
||||
"privilege.configurePermissionsInSettings": "Go to Settings > Permissions to configure plugin permissions.",
|
||||
"privilege.everyone": "Everyone",
|
||||
"privilege.noone": "No one",
|
||||
"privilege.title": "Plugin Preferences",
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
"page.datasets.access": "Datasets ページにアクセス",
|
||||
"page.explore.access": "Explore ページにアクセス",
|
||||
"page.tool.access": "Tool ページにアクセス",
|
||||
"plugin.debug": "プラグインをデバッグ",
|
||||
"plugin.install": "プラグインのインストールと更新",
|
||||
"plugin.manage": "プラグインを管理",
|
||||
"tool.manage": "ツールを管理",
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
"page.datasets.access": "访问 Datasets 页面",
|
||||
"page.explore.access": "访问 Explore 页面",
|
||||
"page.tool.access": "访问 Tool 页面",
|
||||
"plugin.debug": "调试插件",
|
||||
"plugin.install": "安装与更新插件",
|
||||
"plugin.manage": "管理插件",
|
||||
"tool.manage": "管理工具",
|
||||
|
||||
@ -213,6 +213,7 @@
|
||||
"pluginInfoModal.repository": "仓库",
|
||||
"pluginInfoModal.title": "插件信息",
|
||||
"privilege.admins": "管理员",
|
||||
"privilege.configurePermissionsInSettings": "前往设置 > 权限中配置插件权限。",
|
||||
"privilege.everyone": "所有人",
|
||||
"privilege.noone": "无人",
|
||||
"privilege.title": "插件偏好",
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
"page.datasets.access": "訪問 Datasets 頁面",
|
||||
"page.explore.access": "訪問 Explore 頁面",
|
||||
"page.tool.access": "訪問 Tool 頁面",
|
||||
"plugin.debug": "調試插件",
|
||||
"plugin.install": "安裝與更新插件",
|
||||
"plugin.manage": "管理插件",
|
||||
"tool.manage": "管理工具",
|
||||
|
||||
Reference in New Issue
Block a user