feat: enhance app permissions and access controls (#37933)

This commit is contained in:
Wu Tianwei 2026-06-25 17:28:58 +08:00 committed by GitHub
parent b33e8f0ddb
commit affdc89f84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 395 additions and 265 deletions

View File

@ -39,7 +39,7 @@ vi.mock('@/context/app-context', () => ({
},
workspacePermissionKeys: [
'plugin.install',
'plugin.manage',
'plugin.delete',
'plugin.plugin_preferences',
],
}),

View File

@ -23,12 +23,12 @@ vi.mock('@/app/components/plugins/hooks', () => ({
vi.mock('@/context/app-context', () => ({
useAppContext: () => ({
userProfile: { id: 'user-1', timezone: 'UTC' },
workspacePermissionKeys: ['tool.manage', 'mcp.manage', 'plugin.install', 'plugin.manage', 'plugin.plugin_preferences'],
workspacePermissionKeys: ['tool.manage', 'mcp.manage', 'plugin.install', 'plugin.delete', 'plugin.plugin_preferences'],
langGeniusVersionInfo: { current_version: '1.0.0' },
}),
useSelector: (selector: (state: { workspacePermissionKeys: string[] }) => unknown) =>
selector({
workspacePermissionKeys: ['tool.manage', 'mcp.manage', 'plugin.install', 'plugin.manage', 'plugin.plugin_preferences'],
workspacePermissionKeys: ['tool.manage', 'mcp.manage', 'plugin.install', 'plugin.delete', 'plugin.plugin_preferences'],
}),
}))

View File

@ -46,12 +46,12 @@ vi.mock('@/app/components/plugins/hooks', () => ({
vi.mock('@/context/app-context', () => ({
useAppContext: () => ({
userProfile: { id: 'user-1', timezone: 'UTC' },
workspacePermissionKeys: ['tool.manage', 'mcp.manage', 'plugin.install', 'plugin.manage', 'plugin.plugin_preferences'],
workspacePermissionKeys: ['tool.manage', 'mcp.manage', 'plugin.install', 'plugin.delete', 'plugin.plugin_preferences'],
langGeniusVersionInfo: { current_version: '1.0.0' },
}),
useSelector: (selector: (state: { workspacePermissionKeys: string[] }) => unknown) =>
selector({
workspacePermissionKeys: ['tool.manage', 'mcp.manage', 'plugin.install', 'plugin.manage', 'plugin.plugin_preferences'],
workspacePermissionKeys: ['tool.manage', 'mcp.manage', 'plugin.install', 'plugin.delete', 'plugin.plugin_preferences'],
}),
}))

View File

@ -128,7 +128,7 @@ describe('AppDetailLayout', () => {
expect(useStore.getState().appDetail).toBeUndefined()
})
it('should allow users with monitor access to open logs directly', async () => {
it('should redirect logs pages when log and annotation access is missing', async () => {
mockPathname = '/app/app-1/logs'
mockFetchAppDetailDirect.mockResolvedValue(createAppDetail({ permission_keys: [AppACLPermission.Monitor] }))
@ -138,9 +138,26 @@ describe('AppDetailLayout', () => {
</AppDetailLayout>,
)
await waitFor(() => {
expect(mockReplace).toHaveBeenCalledWith('/app/app-1/overview')
})
expect(screen.queryByText('App page content')).not.toBeInTheDocument()
expect(useStore.getState().appDetail).toBeUndefined()
})
it('should allow users with log and annotation access to open logs directly', async () => {
mockPathname = '/app/app-1/logs'
mockFetchAppDetailDirect.mockResolvedValue(createAppDetail({ permission_keys: [AppACLPermission.LogAndAnnotation] }))
render(
<AppDetailLayout appId="app-1">
<div>App page content</div>
</AppDetailLayout>,
)
await waitForAppContent()
expect(mockReplace).not.toHaveBeenCalledWith('/app/app-1/overview')
expect(mockReplace).not.toHaveBeenCalled()
expect(useStore.getState().appDetail?.id).toBe('app-1')
})
@ -289,7 +306,7 @@ describe('AppDetailLayout', () => {
expect(useStore.getState().appDetail).toBeUndefined()
})
it('should redirect annotation pages when edit access is missing', async () => {
it('should redirect annotation pages when log and annotation access is missing', async () => {
mockPathname = '/app/app-1/annotations'
mockFetchAppDetailDirect.mockResolvedValue(createAppDetail({
mode: AppModeEnum.CHAT,
@ -309,11 +326,11 @@ describe('AppDetailLayout', () => {
expect(useStore.getState().appDetail).toBeUndefined()
})
it('should allow users with edit access to open annotations directly', async () => {
it('should allow users with log and annotation access to open annotations directly', async () => {
mockPathname = '/app/app-1/annotations'
mockFetchAppDetailDirect.mockResolvedValue(createAppDetail({
mode: AppModeEnum.CHAT,
permission_keys: [AppACLPermission.Edit],
permission_keys: [AppACLPermission.LogAndAnnotation],
}))
render(

View File

@ -108,8 +108,8 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
const isAccessConfigPath = pathname.endsWith('access-config')
if (
(isLayoutPath && !appACLCapabilities.canAccessLayout)
|| (isLogsPath && !appACLCapabilities.canMonitor)
|| (isAnnotationsPath && !appACLCapabilities.canEdit)
|| (isLogsPath && !appACLCapabilities.canAccessLogAndAnnotation)
|| (isAnnotationsPath && !appACLCapabilities.canAccessLogAndAnnotation)
|| (isOverviewPath && !appACLCapabilities.canMonitor)
|| (isAccessConfigPath && !appACLCapabilities.canAccessConfig)
) {

View File

@ -69,14 +69,34 @@ describe('OverviewView monitor permission', () => {
expect(screen.queryByRole('button', { name: 'tracing' })).not.toBeInTheDocument()
})
it('should render overview page content when app monitor permission is granted', () => {
it('should render overview page content without tracing entry when only app monitor permission is granted', () => {
testState.appDetail.permission_keys = [AppACLPermission.Monitor]
render(<OverviewView appId="app-1" />)
expect(screen.getByText('api key info panel')).toBeInTheDocument()
expect(screen.getByText(/chart view app-1/)).toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'tracing' })).not.toBeInTheDocument()
})
it('should render tracing entry when app tracing config permission is granted with monitor access', () => {
testState.appDetail.permission_keys = [AppACLPermission.Monitor, AppACLPermission.TracingConfig]
render(<OverviewView appId="app-1" />)
expect(screen.getByText('api key info panel')).toBeInTheDocument()
expect(screen.getByText(/chart view app-1/)).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'tracing' })).toBeInTheDocument()
})
it('should not render overview page content when only app tracing config permission is granted', () => {
testState.appDetail.permission_keys = [AppACLPermission.TracingConfig]
render(<OverviewView appId="app-1" />)
expect(screen.queryByText('api key info panel')).not.toBeInTheDocument()
expect(screen.queryByText(/chart view app-1/)).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'tracing' })).not.toBeInTheDocument()
})
})
})

View File

@ -122,11 +122,24 @@ describe('Tracing overview panel permissions', () => {
})
})
it('allows tracing config when app ACL includes monitor permission', async () => {
it('marks tracing config as read-only with app monitor permission only', async () => {
testState.appPermissionKeys = [AppACLPermission.Monitor]
await renderPanel()
await waitFor(() => {
expect(testState.configButtonProps[0]).toMatchObject({
readOnly: true,
hasConfigured: false,
})
})
})
it('allows tracing config when app ACL includes tracing config permission', async () => {
testState.appPermissionKeys = [AppACLPermission.TracingConfig]
await renderPanel()
await waitFor(() => {
expect(testState.configButtonProps[0]).toMatchObject({
readOnly: false,

View File

@ -47,7 +47,7 @@ const Panel: FC = () => {
resourceMaintainer: appDetail?.maintainer,
workspacePermissionKeys,
}), [appDetail?.maintainer, appDetail?.permission_keys, currentUserId, workspacePermissionKeys])
const canConfigTracing = appACLCapabilities.canMonitor
const canConfigTracing = appACLCapabilities.canConfigureTracing
const readOnly = !canConfigTracing
const [isLoaded, {

View File

@ -16,13 +16,13 @@ const OverviewView = ({ appId }: OverviewViewProps) => {
const appDetail = useAppStore(state => state.appDetail)
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
const canMonitor = React.useMemo(() => getAppACLCapabilities(appDetail?.permission_keys, {
const appACLCapabilities = React.useMemo(() => getAppACLCapabilities(appDetail?.permission_keys, {
currentUserId,
resourceMaintainer: appDetail?.maintainer,
workspacePermissionKeys,
}).canMonitor, [appDetail?.maintainer, appDetail?.permission_keys, currentUserId, workspacePermissionKeys])
}), [appDetail?.maintainer, appDetail?.permission_keys, currentUserId, workspacePermissionKeys])
if (!appDetail || !canMonitor)
if (!appDetail || !appACLCapabilities.canMonitor)
return null
return (
@ -31,7 +31,7 @@ const OverviewView = ({ appId }: OverviewViewProps) => {
<div className="min-h-0 flex-1">
<ChartView
appId={appId}
headerRight={<TracingPanel />}
headerRight={appACLCapabilities.canConfigureTracing ? <TracingPanel /> : null}
/>
</div>
</div>

View File

@ -69,28 +69,30 @@ describe('AppDetailSection', () => {
// Rendering behavior for app detail navigation entries.
describe('Rendering', () => {
it('should render logs and overview for chat apps with app monitor permission', () => {
it('should render only overview for chat apps with app monitor permission', () => {
// Arrange
mockAppMode = 'chat'
// Act
render(<AppDetailSection />)
// Assert
expect(screen.getByRole('link', { name: 'common.appMenus.overview' })).toHaveAttribute('href', '/app/app-1/overview')
expect(screen.queryByRole('link', { name: 'common.appMenus.logs' })).not.toBeInTheDocument()
expect(screen.queryByRole('link', { name: 'common.appMenus.annotations' })).not.toBeInTheDocument()
expect(screen.queryAllByRole('separator')).toHaveLength(0)
})
it('should render logs and annotations for chat apps with app log and annotation permission', () => {
// Arrange
mockAppMode = 'chat'
mockAppPermissionKeys = [AppACLPermission.LogAndAnnotation]
// Act
render(<AppDetailSection />)
// Assert
expect(screen.getByRole('link', { name: 'common.appMenus.logs' })).toHaveAttribute('href', '/app/app-1/logs')
expect(screen.getByRole('link', { name: 'common.appMenus.overview' })).toHaveAttribute('href', '/app/app-1/overview')
expect(screen.queryByRole('link', { name: 'common.appMenus.annotations' })).not.toBeInTheDocument()
})
it('should render annotations for chat apps with app edit permission', () => {
// Arrange
mockAppMode = 'chat'
mockAppPermissionKeys = [AppACLPermission.Edit]
// Act
render(<AppDetailSection />)
// Assert
expect(screen.getByRole('link', { name: 'common.appMenus.annotations' })).toHaveAttribute('href', '/app/app-1/annotations')
expect(screen.getByRole('link', { name: 'common.appMenus.annotations' })).toHaveAttribute('data-icon', 'Annotations')
expect(screen.queryByRole('link', { name: 'common.appMenus.overview' })).not.toBeInTheDocument()
@ -99,7 +101,7 @@ describe('AppDetailSection', () => {
it('should render dividers before logs and after annotations for chat apps', () => {
// Arrange
mockAppMode = 'chat'
mockAppPermissionKeys = [AppACLPermission.Monitor, AppACLPermission.Edit]
mockAppPermissionKeys = [AppACLPermission.LogAndAnnotation]
// Act
render(<AppDetailSection />)
@ -111,6 +113,7 @@ describe('AppDetailSection', () => {
it('should only render logs navigation for workflow apps', () => {
// Arrange
mockAppMode = 'workflow'
mockAppPermissionKeys = [AppACLPermission.LogAndAnnotation]
// Act
render(<AppDetailSection />)
@ -123,6 +126,7 @@ describe('AppDetailSection', () => {
it('should render dividers before and after logs for workflow apps', () => {
// Arrange
mockAppMode = 'workflow'
mockAppPermissionKeys = [AppACLPermission.LogAndAnnotation]
// Act
render(<AppDetailSection />)
@ -134,6 +138,7 @@ describe('AppDetailSection', () => {
it('should only render logs navigation for completion apps', () => {
// Arrange
mockAppMode = 'completion'
mockAppPermissionKeys = [AppACLPermission.LogAndAnnotation]
// Act
render(<AppDetailSection />)
@ -143,9 +148,9 @@ describe('AppDetailSection', () => {
expect(screen.queryByRole('link', { name: 'common.appMenus.annotations' })).not.toBeInTheDocument()
})
it('should not render monitor group dividers without monitor or edit permission', () => {
it('should not render log and annotation group dividers without log and annotation permission', () => {
// Arrange
mockAppPermissionKeys = []
mockAppPermissionKeys = [AppACLPermission.Monitor]
// Act
render(<AppDetailSection />)
@ -154,19 +159,20 @@ describe('AppDetailSection', () => {
expect(screen.queryAllByRole('separator')).toHaveLength(0)
expect(screen.queryByRole('link', { name: 'common.appMenus.logs' })).not.toBeInTheDocument()
expect(screen.queryByRole('link', { name: 'common.appMenus.annotations' })).not.toBeInTheDocument()
expect(screen.queryByRole('link', { name: 'common.appMenus.overview' })).not.toBeInTheDocument()
expect(screen.getByRole('link', { name: 'common.appMenus.overview' })).toBeInTheDocument()
})
it('should render logs for users with app monitor permission', () => {
it('should render logs for users with app log and annotation permission', () => {
// Arrange
mockAppPermissionKeys = [AppACLPermission.Monitor]
mockAppPermissionKeys = [AppACLPermission.LogAndAnnotation]
// Act
render(<AppDetailSection />)
// Assert
expect(screen.getByRole('link', { name: 'common.appMenus.logs' })).toHaveAttribute('href', '/app/app-1/logs')
expect(screen.queryByRole('link', { name: 'common.appMenus.annotations' })).not.toBeInTheDocument()
expect(screen.getByRole('link', { name: 'common.appMenus.annotations' })).toHaveAttribute('href', '/app/app-1/annotations')
expect(screen.queryByRole('link', { name: 'common.appMenus.overview' })).not.toBeInTheDocument()
expect(screen.getAllByRole('separator')).toHaveLength(2)
})
@ -225,6 +231,9 @@ describe('AppDetailSection', () => {
})
it('should pass collapsed mode to app info and navigation links when collapsed', () => {
// Arrange
mockAppPermissionKeys = [AppACLPermission.LogAndAnnotation]
// Act
render(<AppDetailSection expand={false} />)

View File

@ -111,7 +111,7 @@ const AppDetailSection = ({
icon: RiTerminalBoxLine,
selectedIcon: RiTerminalBoxFill,
},
...(appACLCapabilities.canMonitor
...(appACLCapabilities.canAccessLogAndAnnotation
? [{
name: t('appMenus.logs', { ns: 'common' }),
href: `/app/${appId}/logs`,
@ -120,7 +120,7 @@ const AppDetailSection = ({
}]
: []
),
...(appACLCapabilities.canEdit && supportsAnnotations
...(appACLCapabilities.canAccessLogAndAnnotation && supportsAnnotations
? [{
name: t('appMenus.annotations', { ns: 'common' }),
href: `/app/${appId}/annotations`,

View File

@ -11,6 +11,8 @@ const expectedAppACLPermissionKeys = [
'app.acl.delete',
'app.acl.release_and_version',
'app.acl.monitor',
'app.acl.tracing_config',
'app.acl.log_and_annotation',
'app.acl.access_config',
]

View File

@ -69,7 +69,6 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
usePluginSettingsAccess: () => ({
canSetPermissions: true,
canSetPluginPreferences: true,
canViewInstalledPlugins: true,
}),
default: () => ({
canSetPermissions: true,

View File

@ -96,7 +96,7 @@ vi.mock('@/utils/var', () => ({
vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
usePluginSettingsAccess: () => ({
canManagePlugin: true,
canDeletePlugin: true,
canUpdatePlugin: true,
}),
}))

View File

@ -60,14 +60,13 @@ const DataSourcePage = ({
const [searchText, setSearchText] = useState('')
const {
canSetPluginPreferences,
canViewInstalledPlugins,
} = usePluginSettingsAccess()
const { data: enable_marketplace } = useSuspenseQuery({
...systemFeaturesQueryOptions(),
select: s => s.enable_marketplace,
})
const { data, isLoading: isDataSourceListLoading } = useGetDataSourceListAuth()
const { data: installedPluginList } = useInstalledPluginList(!canViewInstalledPlugins)
const { data: installedPluginList } = useInstalledPluginList()
const pluginListWithLatestVersion = usePluginsWithLatestVersion(installedPluginList?.plugins)
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
const invalidateDataSourceListAuth = useInvalidDataSourceListAuth()

View File

@ -50,7 +50,7 @@ const DataSourcePluginActions = ({
const locale = useLocale()
const readmeTriggerId = useId()
const openReadmePanel = useReadmePanelStore(s => s.openReadmePanel)
const { canManagePlugin, canUpdatePlugin } = usePluginSettingsAccess()
const { canDeletePlugin, canUpdatePlugin } = usePluginSettingsAccess()
const detailHeaderState = usePluginDetailHeader(detail)
const {
modalStates,
@ -69,7 +69,7 @@ const DataSourcePluginActions = ({
modalStates,
versionPicker,
isFromMarketplace,
canManagePlugin,
canDeletePlugin,
canUpdatePlugin,
onUpdate,
})
@ -150,7 +150,7 @@ const DataSourcePluginActions = ({
detailUrl={getDetailUrl(detail, locale, theme || 'light')}
triggerSize="xs"
showCheckVersion={canUpdatePlugin}
showRemove={canManagePlugin}
showRemove={canDeletePlugin}
/>
<HeaderModals
detail={detail}

View File

@ -67,7 +67,6 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
usePluginSettingsAccess: () => ({
canSetPermissions: true,
canSetPluginPreferences: true,
canViewInstalledPlugins: true,
}),
default: () => ({
referenceSetting: {

View File

@ -151,7 +151,6 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
usePluginSettingsAccess: () => ({
canSetPermissions: true,
canSetPluginPreferences: true,
canViewInstalledPlugins: true,
}),
default: () => ({
referenceSetting: mockReferenceSetting,

View File

@ -50,7 +50,6 @@ const ModelProviderPage = ({
const { t } = useTranslation()
const {
canSetPluginPreferences,
canViewInstalledPlugins,
} = usePluginSettingsAccess()
const { data: textGenerationDefaultModel, isLoading: isTextGenerationDefaultModelLoading } = useDefaultModel(ModelTypeEnum.textGeneration)
const { data: embeddingsDefaultModel, isLoading: isEmbeddingsDefaultModelLoading } = useDefaultModel(ModelTypeEnum.textEmbedding)
@ -65,7 +64,7 @@ const ModelProviderPage = ({
}, [providers])
const { data: installedPlugins } = useQuery(consoleQuery.plugins.checkInstalled.queryOptions({
input: { body: { plugin_ids: allPluginIds } },
enabled: canViewInstalledPlugins && allPluginIds.length > 0,
enabled: allPluginIds.length > 0,
staleTime: 0,
}))
const enrichedPlugins = usePluginsWithLatestVersion(installedPlugins?.plugins)

View File

@ -8,7 +8,7 @@ import { ConfigurationMethodEnum } from '../../declarations'
import ProviderAddedCard from '../index'
let mockIsCurrentWorkspaceManager = true
let mockWorkspacePermissionKeys: string[] = ['plugin.manage', 'credential.use', 'credential.create', 'credential.manage']
let mockWorkspacePermissionKeys: string[] = ['plugin.model_config', 'credential.use', 'credential.create', 'credential.manage']
const mockFetchModelProviderModels = vi.fn()
const mockQueryOptions = vi.fn(({ input, ...options }: { input: { params: { provider: string } }, enabled?: boolean }) => ({
queryKey: ['console', 'modelProviders', 'models', input.params.provider],
@ -105,7 +105,7 @@ describe('ProviderAddedCard', () => {
beforeEach(() => {
vi.clearAllMocks()
mockIsCurrentWorkspaceManager = true
mockWorkspacePermissionKeys = ['plugin.manage', 'credential.use', 'credential.create', 'credential.manage']
mockWorkspacePermissionKeys = ['plugin.model_config', 'credential.use', 'credential.create', 'credential.manage']
})
it('should render provider added card component', () => {
@ -201,7 +201,7 @@ describe('ProviderAddedCard', () => {
expect(screen.getByText('common.modelProvider.configureTip')).toBeInTheDocument()
})
it('should render custom model actions when user can manage plugins', () => {
it('should render custom model actions when user can configure models', () => {
const customConfigProvider = {
...mockProvider,
configurate_methods: [ConfigurationMethodEnum.customizableModel],
@ -218,12 +218,12 @@ describe('ProviderAddedCard', () => {
expect(screen.queryByTestId('manage-custom-model')).not.toBeInTheDocument()
})
it('should render custom model actions when user can manage plugins without credential permissions', () => {
it('should render custom model actions when user can configure models without credential permissions', () => {
const customConfigProvider = {
...mockProvider,
configurate_methods: [ConfigurationMethodEnum.customizableModel],
} as unknown as ModelProvider
mockWorkspacePermissionKeys = ['plugin.manage']
mockWorkspacePermissionKeys = ['plugin.model_config']
renderWithQueryClient(<ProviderAddedCard provider={customConfigProvider} />)

View File

@ -14,7 +14,7 @@ function createWrapper() {
let mockModelLoadBalancingEnabled = false
let mockPlanType: string = 'pro'
let mockWorkspacePermissionKeys: string[] = ['plugin.manage']
let mockWorkspacePermissionKeys: string[] = ['plugin.model_config']
vi.mock('@/context/app-context', () => ({
useAppContext: () => ({
@ -71,7 +71,7 @@ describe('ModelListItem', () => {
vi.clearAllMocks()
mockModelLoadBalancingEnabled = false
mockPlanType = 'pro'
mockWorkspacePermissionKeys = ['plugin.manage']
mockWorkspacePermissionKeys = ['plugin.model_config']
})
it('should render model item with icon and name', () => {
@ -144,8 +144,8 @@ describe('ModelListItem', () => {
expect(onModifyLoadBalancing).toHaveBeenCalledWith(mockModel)
})
it('should allow model status and load balancing controls with plugin.manage', () => {
mockWorkspacePermissionKeys = ['plugin.manage']
it('should allow model status and load balancing controls with plugin.model_config', () => {
mockWorkspacePermissionKeys = ['plugin.model_config']
mockModelLoadBalancingEnabled = true
render(
@ -162,7 +162,7 @@ describe('ModelListItem', () => {
expect(screen.getByRole('button', { name: 'modify load balancing' })).toBeInTheDocument()
})
it('should hide model status and load balancing controls without plugin.manage', () => {
it('should hide model status and load balancing controls without plugin.model_config', () => {
mockWorkspacePermissionKeys = []
mockModelLoadBalancingEnabled = true

View File

@ -4,7 +4,7 @@ import { ConfigurationMethodEnum } from '../../declarations'
import ModelList from '../model-list'
const mockSetShowModelLoadBalancingModal = vi.fn()
let mockWorkspacePermissionKeys: string[] = ['plugin.manage', 'credential.manage', 'credential.use']
let mockWorkspacePermissionKeys: string[] = ['plugin.model_config', 'credential.manage', 'credential.use']
vi.mock('@/context/app-context', () => ({
useSelector: (selector: (state: { workspacePermissionKeys: string[] }) => unknown) =>
@ -45,7 +45,7 @@ describe('ModelList', () => {
beforeEach(() => {
vi.clearAllMocks()
mockWorkspacePermissionKeys = ['plugin.manage', 'credential.manage', 'credential.use']
mockWorkspacePermissionKeys = ['plugin.model_config', 'credential.manage', 'credential.use']
})
it('should render model count and model items', () => {
@ -91,7 +91,7 @@ describe('ModelList', () => {
expect(mockSetShowModelLoadBalancingModal).toHaveBeenCalled()
})
it('should hide custom model actions without plugin.manage', () => {
it('should hide custom model actions without plugin.model_config', () => {
mockWorkspacePermissionKeys = []
render(
<ModelList
@ -230,13 +230,13 @@ describe('ModelList', () => {
expect(screen.queryByTestId('add-custom-model')).not.toBeInTheDocument()
})
it('should show custom model actions when provider is configurable and user can manage plugins', () => {
it('should show custom model actions when provider is configurable and user can configure models', () => {
const configurableProvider = {
provider: 'test-provider',
configurate_methods: [ConfigurationMethodEnum.customizableModel],
} as unknown as ModelProvider
mockWorkspacePermissionKeys = ['plugin.manage']
mockWorkspacePermissionKeys = ['plugin.model_config']
render(
<ModelList
@ -251,7 +251,7 @@ describe('ModelList', () => {
expect(screen.getByTestId('add-custom-model'))!.toBeInTheDocument()
})
it('should hide custom model actions when provider is configurable but user cannot manage plugins', () => {
it('should hide custom model actions when provider is configurable but user cannot configure models', () => {
const configurableProvider = {
provider: 'test-provider',
configurate_methods: [ConfigurationMethodEnum.customizableModel],

View File

@ -73,7 +73,7 @@ vi.mock('@/app/components/plugins/plugin-detail-panel/operation-dropdown', () =>
vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
usePluginSettingsAccess: () => ({
canManagePlugin: true,
canDeletePlugin: true,
canUpdatePlugin: true,
}),
default: () => ({

View File

@ -69,11 +69,11 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
const showCollapsedSection = !expanded || !hasFetchedModelList
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
const showModelProvider = systemConfig.enabled && MODEL_PROVIDER_QUOTA_GET_PAID.includes(currentProviderName as ModelProviderQuotaGetPaid) && !IS_CE_EDITION
const canManagePlugins = hasPermission(workspacePermissionKeys, 'plugin.manage')
const canConfigureModels = hasPermission(workspacePermissionKeys, 'plugin.model_config')
const { canUseCredential, canCreateCredential, canManageCredential } = useCredentialPermissions()
const canAccessCredentials = canUseCredential || canCreateCredential || canManageCredential
const showCredential = supportsPredefinedModel && canAccessCredentials
const showCustomModelActions = supportsCustomizableModel && canManagePlugins
const showCustomModelActions = supportsCustomizableModel && canConfigureModels
const refreshModelList = useCallback((targetProviderName: string) => {
if (targetProviderName !== currentProviderName)

View File

@ -33,7 +33,7 @@ const ModelListItem = ({ model, provider, isConfigurable, onChange, onModifyLoad
const { plan } = useProviderContext()
const modelLoadBalancingEnabled = useProviderContextSelector(state => state.modelLoadBalancingEnabled)
const { workspacePermissionKeys } = useAppContext()
const canManagePlugin = hasPermission(workspacePermissionKeys, 'plugin.manage')
const canConfigureModels = hasPermission(workspacePermissionKeys, 'plugin.model_config')
const queryClient = useQueryClient()
const updateModelList = useUpdateModelList()
const modelProviderModelListQueryKey = consoleQuery.modelProviders.models.queryKey({
@ -93,7 +93,7 @@ const ModelListItem = ({ model, provider, isConfigurable, onChange, onModifyLoad
</Badge>
)}
{
(canManagePlugin && (modelLoadBalancingEnabled || plan.type === Plan.sandbox) && !model.deprecated && [ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status)) && (
(canConfigureModels && (modelLoadBalancingEnabled || plan.type === Plan.sandbox) && !model.deprecated && [ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status)) && (
<ConfigModel
onClick={() => onModifyLoadBalancing?.(model)}
loadBalancingEnabled={model.load_balancing_enabled}
@ -112,7 +112,7 @@ const ModelListItem = ({ model, provider, isConfigurable, onChange, onModifyLoad
</PopoverContent>
</Popover>
)
: (canManagePlugin && (
: (canConfigureModels && (
<Switch
className="ml-2"
checked={model?.status === ModelStatusEnum.active}

View File

@ -34,7 +34,7 @@ const ModelList: FC<ModelListProps> = ({
const { t } = useTranslation()
const configurativeMethods = provider.configurate_methods.filter(method => method !== ConfigurationMethodEnum.fetchFromRemote)
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
const canManagePlugins = hasPermission(workspacePermissionKeys, 'plugin.manage')
const canConfigureModels = hasPermission(workspacePermissionKeys, 'plugin.model_config')
const isConfigurable = configurativeMethods.includes(ConfigurationMethodEnum.customizableModel)
const setShowModelLoadBalancingModal = useModalContextSelector(state => state.setShowModelLoadBalancingModal)
const onModifyLoadBalancing = useCallback((model: ModelItem, credential?: Credential) => {
@ -67,7 +67,7 @@ const ModelList: FC<ModelListProps> = ({
</span>
</span>
{
isConfigurable && canManagePlugins && (
isConfigurable && canConfigureModels && (
<div className="flex grow justify-end">
<ManageCustomModelCredentials
provider={provider}

View File

@ -25,7 +25,7 @@ const ProviderCardActions: FC<Props> = ({ detail, onUpdate }) => {
const { t } = useTranslation()
const { theme } = useTheme()
const locale = useLocale()
const { canManagePlugin, canUpdatePlugin } = usePluginSettingsAccess()
const { canDeletePlugin, canUpdatePlugin } = usePluginSettingsAccess()
const { source, version, latest_version, latest_unique_identifier, meta } = detail
const author = detail.declaration?.author ?? ''
@ -49,7 +49,7 @@ const ProviderCardActions: FC<Props> = ({ detail, onUpdate }) => {
modalStates,
versionPicker,
isFromMarketplace,
canManagePlugin,
canDeletePlugin,
canUpdatePlugin,
onUpdate,
})
@ -137,7 +137,7 @@ const ProviderCardActions: FC<Props> = ({ detail, onUpdate }) => {
placement="bottom-start"
destructiveRemove
showCheckVersion={canUpdatePlugin}
showRemove={canManagePlugin}
showRemove={canDeletePlugin}
/>
<HeaderModals

View File

@ -32,7 +32,7 @@ const mockInvalidateDefaultModel = vi.hoisted(() => vi.fn())
const mockUpdateDefaultModel = vi.hoisted(() => vi.fn(() => Promise.resolve({ result: 'success' })))
const mockModelSelectorProps = vi.hoisted(() => [] as Array<{ hideProviderSettingsFooter?: boolean, onConfigureEmptyState?: () => void, showModelMeta?: boolean }>)
let mockWorkspacePermissionKeys = ['plugin.manage']
let mockWorkspacePermissionKeys = ['plugin.model_config']
vi.mock('@/context/app-context', () => ({
useAppContext: () => ({
@ -108,7 +108,7 @@ describe('SystemModel', () => {
beforeEach(() => {
vi.clearAllMocks()
mockModelSelectorProps.length = 0
mockWorkspacePermissionKeys = ['plugin.manage']
mockWorkspacePermissionKeys = ['plugin.model_config']
})
it('should render settings button', () => {
@ -184,7 +184,7 @@ describe('SystemModel', () => {
expect(mockUpdateModelList).not.toHaveBeenCalled()
})
it('should disable save without plugin manage permission', async () => {
it('should disable save without model config permission', async () => {
mockWorkspacePermissionKeys = []
render(<SystemModel {...defaultProps} />)

View File

@ -68,7 +68,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
const { t } = useTranslation()
const { workspacePermissionKeys } = useAppContext()
const { textGenerationModelList } = useProviderContext()
const canManageSystemDefaultModel = hasPermission(workspacePermissionKeys, 'plugin.manage')
const canManageSystemDefaultModel = hasPermission(workspacePermissionKeys, 'plugin.model_config')
const updateModelList = useUpdateModelList()
const invalidateDefaultModel = useInvalidateDefaultModel()
const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding)

View File

@ -53,25 +53,23 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
usePluginSettingsAccess: () => ({
permission: mockReferenceSetting().permission,
canInstallPlugin: mockCanManagement(),
canManagePlugin: true,
canDeletePlugin: true,
canManagement: mockCanManagement(),
canDebugger: mockCanDebugger(),
canSetPermissions: mockCanSetPermissions(),
canSetPluginPreferences: mockCanSetPermissions(),
canUpdatePlugin: true,
canViewInstalledPlugins: true,
setPluginPermissionSettings: mockSetReferenceSettings,
}),
default: () => ({
referenceSetting: mockReferenceSetting(),
canInstallPlugin: mockCanManagement(),
canManagePlugin: true,
canDeletePlugin: true,
canManagement: mockCanManagement(),
canDebugger: mockCanDebugger(),
canSetPermissions: mockCanSetPermissions(),
canSetPluginPreferences: mockCanSetPermissions(),
canUpdatePlugin: true,
canViewInstalledPlugins: true,
setReferenceSettings: mockSetReferenceSettings,
}),
}))

View File

@ -142,9 +142,8 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
usePluginSettingsAccess: () => ({
canSetPermissions: mockCanSetPermissions(),
canSetPluginPreferences: mockCanSetPermissions(),
canManagePlugin: true,
canDeletePlugin: true,
canUpdatePlugin: true,
canViewInstalledPlugins: true,
}),
default: () => ({
referenceSetting: mockReferenceSetting(),

View File

@ -12,11 +12,10 @@ export function useIntegrationPermissions(section: IntegrationSection) {
permission,
canDebugger,
canInstallPlugin,
canManagePlugin,
canDeletePlugin,
canSetPluginPreferences,
canSetPermissions,
canUpdatePlugin,
canViewInstalledPlugins,
isPermissionLoading,
permissionError,
setPluginPermissionSettings,
@ -38,10 +37,9 @@ export function useIntegrationPermissions(section: IntegrationSection) {
return {
canDebugger,
canInstallPlugin,
canManagePlugin,
canDeletePlugin,
canSetPluginPreferences,
canUpdatePlugin,
canViewInstalledPlugins,
canManagement: canInstallPlugin,
handlePermissionChange,
isPluginCategory,

View File

@ -107,9 +107,8 @@ export default function IntegrationsPage({
const {
canDebugger,
canInstallPlugin,
canManagePlugin,
canDeletePlugin,
canUpdatePlugin,
canViewInstalledPlugins,
handlePermissionChange,
isPluginCategory,
permission,
@ -285,9 +284,8 @@ export default function IntegrationsPage({
onProviderSearchTextChange={setProviderSearchText}
onSwitchToMarketplace={handleSwitchToMarketplace}
canInstallPlugin={canInstallPlugin}
canManagePlugin={canManagePlugin}
canDeletePlugin={canDeletePlugin}
canUpdatePlugin={canUpdatePlugin}
canViewInstalledPlugins={canViewInstalledPlugins}
pluginCategoryToolbarAction={pluginSettingAction}
/>
</div>
@ -311,9 +309,8 @@ export default function IntegrationsPage({
onProviderSearchTextChange={setProviderSearchText}
onSwitchToMarketplace={handleSwitchToMarketplace}
canInstallPlugin={canInstallPlugin}
canManagePlugin={canManagePlugin}
canDeletePlugin={canDeletePlugin}
canUpdatePlugin={canUpdatePlugin}
canViewInstalledPlugins={canViewInstalledPlugins}
pluginCategoryToolbarAction={pluginSettingAction}
/>
</ScrollArea>

View File

@ -15,9 +15,8 @@ import { systemFeaturesQueryOptions } from '@/features/system-features/client'
type PluginCategoryPageProps = {
canInstall?: boolean
canManagePlugin?: boolean
canDeletePlugin?: boolean
canUpdatePlugin?: boolean
canViewInstalledPlugins?: boolean
category: PluginCategoryEnum
layout?: (parts: { body: ReactNode, toolbar: ReactNode }) => ReactNode
onSwitchToMarketplace?: () => void
@ -28,9 +27,8 @@ const supportedLocalPackageExtensions = SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS.sp
const PluginCategoryPageContent = ({
canInstall = true,
canManagePlugin = true,
canDeletePlugin = true,
canUpdatePlugin = true,
canViewInstalledPlugins = true,
category,
layout,
onSwitchToMarketplace,
@ -73,9 +71,8 @@ const PluginCategoryPageContent = ({
<div ref={containerRef} className="relative flex h-0 grow flex-col overflow-hidden bg-components-panel-bg">
<PluginsPanel
canInstall={canInstall}
canManagePlugin={canManagePlugin}
canDeletePlugin={canDeletePlugin}
canUpdatePlugin={canUpdatePlugin}
canViewInstalledPlugins={canViewInstalledPlugins}
contentInset="compact"
fixedCategory={category}
layout={layout}
@ -110,9 +107,8 @@ const PluginCategoryPageContent = ({
const PluginCategoryPage = ({
canInstall = true,
canManagePlugin = true,
canDeletePlugin = true,
canUpdatePlugin = true,
canViewInstalledPlugins = true,
category,
layout,
onSwitchToMarketplace,
@ -128,9 +124,8 @@ const PluginCategoryPage = ({
<PluginPageContextProvider key={category} initialFilters={initialFilters}>
<PluginCategoryPageContent
canInstall={canInstall}
canManagePlugin={canManagePlugin}
canDeletePlugin={canDeletePlugin}
canUpdatePlugin={canUpdatePlugin}
canViewInstalledPlugins={canViewInstalledPlugins}
category={category}
layout={layout}
onSwitchToMarketplace={onSwitchToMarketplace}

View File

@ -14,9 +14,8 @@ import ToolProviderList from './tool-provider-list'
type IntegrationSectionRendererProps = {
canInstallPlugin?: boolean
canManagePlugin?: boolean
canDeletePlugin?: boolean
canUpdatePlugin?: boolean
canViewInstalledPlugins?: boolean
description?: ReactNode
onProviderSearchTextChange: (value: string) => void
onSwitchToMarketplace?: () => void
@ -29,9 +28,8 @@ type IntegrationSectionRendererProps = {
const IntegrationSectionRenderer = ({
canInstallPlugin = true,
canManagePlugin = true,
canDeletePlugin = true,
canUpdatePlugin = true,
canViewInstalledPlugins = true,
description,
onProviderSearchTextChange,
onSwitchToMarketplace,
@ -75,9 +73,8 @@ const IntegrationSectionRenderer = ({
const renderPluginCategoryPage = (category: PluginCategoryEnum) => (
<PluginCategoryPage
canInstall={canInstallPlugin}
canManagePlugin={canManagePlugin}
canDeletePlugin={canDeletePlugin}
canUpdatePlugin={canUpdatePlugin}
canViewInstalledPlugins={canViewInstalledPlugins}
category={category}
layout={renderDirectLayout}
onSwitchToMarketplace={onSwitchToMarketplace}

View File

@ -88,10 +88,9 @@ const ProviderList = ({
const { t } = useTranslation()
const { getTagLabel } = useTags()
const {
canManagePlugin,
canDeletePlugin,
canSetPluginPreferences,
canUpdatePlugin,
canViewInstalledPlugins,
} = usePluginSettingsAccess()
const canManageTools = useCanManageTools()
const canManageMCP = useCanManageMCP()
@ -158,7 +157,7 @@ const ProviderList = ({
}, [currentProviderId, filteredCollectionList])
const { data: checkedInstalledData } = useCheckInstalled({
pluginIds: currentProvider?.plugin_id ? [currentProvider.plugin_id] : [],
enabled: canViewInstalledPlugins && !!currentProvider?.plugin_id,
enabled: !!currentProvider?.plugin_id,
})
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
const currentPluginDetail = useMemo(() => {
@ -262,7 +261,7 @@ const ProviderList = ({
detail={currentPluginDetail}
onUpdate={() => invalidateInstalledPluginList()}
onHide={() => setCurrentProviderId(undefined)}
canManagePlugin={canManagePlugin}
canDeletePlugin={canDeletePlugin}
canUpdatePlugin={canUpdatePlugin}
/>
</>

View File

@ -23,21 +23,25 @@ describe('useWorkspacePluginInstallPermission', () => {
expect(result.current.canInstallPlugin).toBe(true)
expect(result.current.canUpdatePlugin).toBe(true)
expect(result.current.canViewInstalledPlugins).toBe(true)
expect(result.current.canManagePlugin).toBe(false)
expect(result.current.canDeletePlugin).toBe(false)
expect(result.current.canDebugPlugin).toBe(false)
expect(result.current.canSetPluginPreferences).toBe(false)
})
it('should grant update, view, and manage capabilities but not install with plugin.manage', () => {
mockWorkspacePermissionKeys = ['plugin.manage']
it('should not expose installed plugin list viewing as a permission capability', () => {
const { result } = renderHook(() => useWorkspacePluginInstallPermission())
expect('canViewInstalledPlugins' in result.current).toBe(false)
})
it('should grant delete capability but not install or update with plugin.delete', () => {
mockWorkspacePermissionKeys = ['plugin.delete']
const { result } = renderHook(() => useWorkspacePluginInstallPermission())
expect(result.current.canInstallPlugin).toBe(false)
expect(result.current.canUpdatePlugin).toBe(true)
expect(result.current.canViewInstalledPlugins).toBe(true)
expect(result.current.canManagePlugin).toBe(true)
expect(result.current.canUpdatePlugin).toBe(false)
expect(result.current.canDeletePlugin).toBe(true)
expect(result.current.canDebugPlugin).toBe(false)
expect(result.current.canSetPluginPreferences).toBe(false)
})
@ -49,8 +53,7 @@ describe('useWorkspacePluginInstallPermission', () => {
expect(result.current.canInstallPlugin).toBe(false)
expect(result.current.canUpdatePlugin).toBe(false)
expect(result.current.canViewInstalledPlugins).toBe(false)
expect(result.current.canManagePlugin).toBe(false)
expect(result.current.canDeletePlugin).toBe(false)
expect(result.current.canDebugPlugin).toBe(true)
expect(result.current.canSetPluginPreferences).toBe(false)
})
@ -62,8 +65,7 @@ describe('useWorkspacePluginInstallPermission', () => {
expect(result.current.canInstallPlugin).toBe(false)
expect(result.current.canUpdatePlugin).toBe(false)
expect(result.current.canViewInstalledPlugins).toBe(false)
expect(result.current.canManagePlugin).toBe(false)
expect(result.current.canDeletePlugin).toBe(false)
expect(result.current.canDebugPlugin).toBe(false)
expect(result.current.canSetPluginPreferences).toBe(true)
})
@ -73,8 +75,7 @@ describe('useWorkspacePluginInstallPermission', () => {
expect(result.current.canInstallPlugin).toBe(false)
expect(result.current.canUpdatePlugin).toBe(false)
expect(result.current.canViewInstalledPlugins).toBe(false)
expect(result.current.canManagePlugin).toBe(false)
expect(result.current.canDeletePlugin).toBe(false)
expect(result.current.canDebugPlugin).toBe(false)
expect(result.current.canSetPluginPreferences).toBe(false)
})

View File

@ -2,8 +2,6 @@ import { useMemo } from 'react'
import { useAppContext } from '@/context/app-context'
import { hasPermission } from '@/utils/permission'
const pluginReadAndUpdatePermissionKeys = ['plugin.install', 'plugin.manage']
const useWorkspacePluginInstallPermission = () => {
const {
langGeniusVersionInfo,
@ -15,15 +13,11 @@ const useWorkspacePluginInstallPermission = () => {
}, [workspacePermissionKeys])
const canUpdatePlugin = useMemo(() => {
return hasPermission(workspacePermissionKeys, pluginReadAndUpdatePermissionKeys)
return hasPermission(workspacePermissionKeys, 'plugin.install')
}, [workspacePermissionKeys])
const canViewInstalledPlugins = useMemo(() => {
return hasPermission(workspacePermissionKeys, pluginReadAndUpdatePermissionKeys)
}, [workspacePermissionKeys])
const canManagePlugin = useMemo(() => {
return hasPermission(workspacePermissionKeys, 'plugin.manage')
const canDeletePlugin = useMemo(() => {
return hasPermission(workspacePermissionKeys, 'plugin.delete')
}, [workspacePermissionKeys])
const canDebugPlugin = useMemo(() => {
@ -37,8 +31,7 @@ const useWorkspacePluginInstallPermission = () => {
return {
canInstallPlugin,
canUpdatePlugin,
canViewInstalledPlugins,
canManagePlugin,
canDeletePlugin,
canDebugPlugin,
canSetPluginPreferences,
currentDifyVersion: langGeniusVersionInfo?.current_version,

View File

@ -14,7 +14,7 @@ import { checkForUpdates, fetchReleases } from '../../../install-plugin/hooks'
import { PluginSource } from '../../../types'
type UsePluginOperationsParams = {
canManagePlugin?: boolean
canDeletePlugin?: boolean
canUpdatePlugin?: boolean
detail: PluginDetail
modalStates: ModalStates
@ -33,7 +33,7 @@ type UsePluginOperationsReturn = {
}
export const usePluginOperations = ({
canManagePlugin = true,
canDeletePlugin = true,
canUpdatePlugin = true,
detail,
modalStates,
@ -118,7 +118,7 @@ export const usePluginOperations = ({
}, [handlePluginUpdated, modalStates])
const handleDelete = useCallback(async () => {
if (!canManagePlugin)
if (!canDeletePlugin)
return
modalStates.showDeleting()
@ -134,7 +134,7 @@ export const usePluginOperations = ({
trackEvent('plugin_uninstalled', { plugin_id, plugin_name: name })
}
}, [
canManagePlugin,
canDeletePlugin,
id,
category,
plugin_id,

View File

@ -34,7 +34,7 @@ import { HeaderModals, PluginSourceBadge } from './components'
import { useDetailHeaderState, usePluginOperations } from './hooks'
type Props = Readonly<{
canManagePlugin?: boolean
canDeletePlugin?: boolean
canUpdatePlugin?: boolean
detail: PluginDetail
isReadmeView?: boolean
@ -71,7 +71,7 @@ const getDetailUrl = (
}
const DetailHeader = ({
canManagePlugin = true,
canDeletePlugin = true,
canUpdatePlugin = true,
detail,
isReadmeView = false,
@ -123,7 +123,7 @@ const DetailHeader = ({
modalStates,
versionPicker,
isFromMarketplace,
canManagePlugin,
canDeletePlugin,
canUpdatePlugin,
onUpdate,
})
@ -271,7 +271,7 @@ const DetailHeader = ({
onViewReadme={canViewReadme ? handleViewReadme : undefined}
detailUrl={detailUrl}
showCheckVersion={canUpdatePlugin}
showRemove={canManagePlugin}
showRemove={canDeletePlugin}
/>
<ActionButton onClick={onHide}>
<span aria-hidden className="i-ri-close-line size-4" />

View File

@ -24,7 +24,7 @@ import { SubscriptionList } from './subscription-list'
import { TriggerEventsList } from './trigger/event-list'
type Props = Readonly<{
canManagePlugin?: boolean
canDeletePlugin?: boolean
canUpdatePlugin?: boolean
detail?: PluginDetail
onUpdate: () => void
@ -32,7 +32,7 @@ type Props = Readonly<{
}>
const PluginDetailPanel: FC<Props> = ({
canManagePlugin = true,
canDeletePlugin = true,
canUpdatePlugin = true,
detail,
onUpdate,
@ -83,7 +83,7 @@ const PluginDetailPanel: FC<Props> = ({
detail={detail}
onUpdate={handleUpdate}
onHide={onHide}
canManagePlugin={canManagePlugin}
canDeletePlugin={canDeletePlugin}
canUpdatePlugin={canUpdatePlugin}
/>
<div className="grow overflow-y-auto">

View File

@ -33,14 +33,14 @@ import { PluginSource } from '../types'
import Action from './action'
type Props = Readonly<{
canManagePlugin?: boolean
canDeletePlugin?: boolean
canUpdatePlugin?: boolean
className?: string
plugin: PluginDetail
}>
const PluginItem: FC<Props> = ({
canManagePlugin = true,
canDeletePlugin = true,
canUpdatePlugin = true,
className,
plugin,
@ -158,7 +158,7 @@ const PluginItem: FC<Props> = ({
usedInApps={5}
isShowFetchNewVersion={canUpdatePlugin && source === PluginSource.github}
isShowInfo={source === PluginSource.github}
isShowDelete={canManagePlugin}
isShowDelete={canDeletePlugin}
meta={meta}
onDelete={handleDelete}
category={category}

View File

@ -43,7 +43,7 @@ vi.mock('@/context/app-context', () => ({
isCurrentWorkspaceManager: true,
isCurrentWorkspaceOwner: false,
langGeniusVersionInfo: { current_version: '1.0.0' },
workspacePermissionKeys: ['plugin.install', 'plugin.manage', 'plugin.debug', 'plugin.plugin_preferences'],
workspacePermissionKeys: ['plugin.install', 'plugin.delete', 'plugin.debug', 'plugin.plugin_preferences'],
}),
}))

View File

@ -74,6 +74,12 @@ describe('useReferenceSetting Hook', () => {
})
describe('permission key access', () => {
it('should not expose installed plugin list viewing as a permission capability', () => {
const { result } = renderHook(() => useReferenceSetting(PluginCategoryEnum.tool))
expect('canViewInstalledPlugins' in result.current).toBe(false)
})
it('should return false without plugin permission keys', () => {
vi.mocked(usePluginPermissionSettings).mockReturnValue({
data: {
@ -192,7 +198,7 @@ describe('useReferenceSetting Hook', () => {
isCurrentWorkspaceManager: true,
isCurrentWorkspaceOwner: false,
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
workspacePermissionKeys: ['plugin.install', 'plugin.manage', 'plugin.debug'],
workspacePermissionKeys: ['plugin.install', 'plugin.delete', 'plugin.debug'],
} as ReturnType<typeof useAppContext>)
vi.mocked(usePluginPermissionSettings).mockReturnValue({
data: {
@ -208,8 +214,7 @@ describe('useReferenceSetting Hook', () => {
expect(result.current.canInstallPlugin).toBe(false)
expect(result.current.canManagement).toBe(false)
expect(result.current.canUpdatePlugin).toBe(false)
expect(result.current.canViewInstalledPlugins).toBe(true)
expect(result.current.canManagePlugin).toBe(false)
expect(result.current.canDeletePlugin).toBe(false)
expect(result.current.canDebugPlugin).toBe(false)
expect(result.current.canDebugger).toBe(false)
})
@ -351,7 +356,7 @@ describe('useReferenceSetting Hook', () => {
langGeniusVersionInfo: { current_version: '1.0.0', latest_version: '', version: '' },
workspacePermissionKeys: [
'plugin.install',
'plugin.manage',
'plugin.delete',
'plugin.debug',
'plugin.plugin_preferences',
],
@ -370,8 +375,7 @@ describe('useReferenceSetting Hook', () => {
expect(result.current.canInstallPlugin).toBe(true)
expect(result.current.canManagement).toBe(true)
expect(result.current.canUpdatePlugin).toBe(true)
expect(result.current.canViewInstalledPlugins).toBe(true)
expect(result.current.canManagePlugin).toBe(true)
expect(result.current.canDeletePlugin).toBe(true)
expect(result.current.canDebugPlugin).toBe(true)
expect(result.current.canDebugger).toBe(true)
expect(result.current.canSetPermissions).toBe(false)
@ -393,8 +397,7 @@ describe('useReferenceSetting Hook', () => {
expect(result.current.canInstallPlugin).toBe(false)
expect(result.current.canManagement).toBe(false)
expect(result.current.canUpdatePlugin).toBe(false)
expect(result.current.canViewInstalledPlugins).toBe(false)
expect(result.current.canManagePlugin).toBe(false)
expect(result.current.canDeletePlugin).toBe(false)
expect(result.current.canDebugPlugin).toBe(false)
expect(result.current.canDebugger).toBe(false)
expect(result.current.canSetPermissions).toBe(false)

View File

@ -56,9 +56,8 @@ export type PluginPageProps = {
}
type PluginPanelPermissionProps = {
canInstall?: boolean
canManagePlugin?: boolean
canDeletePlugin?: boolean
canUpdatePlugin?: boolean
canViewInstalledPlugins?: boolean
}
const PluginPage = ({
plugins,
@ -89,8 +88,7 @@ const PluginPage = ({
referenceSetting,
canInstallPlugin,
canUpdatePlugin,
canViewInstalledPlugins,
canManagePlugin,
canDeletePlugin,
canDebugger,
canSetPermissions,
canSetPluginPreferences,
@ -184,11 +182,10 @@ const PluginPage = ({
return cloneElement(plugins as React.ReactElement<PluginPanelPermissionProps>, {
canInstall: canInstallPlugin,
canManagePlugin,
canDeletePlugin,
canUpdatePlugin,
canViewInstalledPlugins,
})
}, [canInstallPlugin, canManagePlugin, canUpdatePlugin, canViewInstalledPlugins, plugins])
}, [canInstallPlugin, canDeletePlugin, canUpdatePlugin, plugins])
return (
<div

View File

@ -3,14 +3,14 @@ import type { PluginDetail } from '../../types'
import PluginItem from '../../plugin-item'
type IPluginListProps = {
canManagePlugin?: boolean
canDeletePlugin?: boolean
canUpdatePlugin?: boolean
children?: ReactNode
pluginList: PluginDetail[]
}
const PluginList: FC<IPluginListProps> = ({
canManagePlugin = true,
canDeletePlugin = true,
canUpdatePlugin = true,
children,
pluginList,
@ -22,7 +22,7 @@ const PluginList: FC<IPluginListProps> = ({
<PluginItem
key={plugin.plugin_id}
plugin={plugin}
canManagePlugin={canManagePlugin}
canDeletePlugin={canDeletePlugin}
canUpdatePlugin={canUpdatePlugin}
/>
))}

View File

@ -58,7 +58,7 @@ const BuiltinMarketplacePanel = ({
}
type PluginsPanelResultsProps = {
canManagePlugin: boolean
canDeletePlugin: boolean
canUpdatePlugin: boolean
containerRef: RefObject<HTMLDivElement | null>
contentFrameClassName: string
@ -80,7 +80,7 @@ type PluginsPanelResultsProps = {
}
const PluginsPanelResults = ({
canManagePlugin,
canDeletePlugin,
canUpdatePlugin,
containerRef,
contentFrameClassName,
@ -123,7 +123,7 @@ const PluginsPanelResults = ({
{(hasVisiblePlugins || hasVisibleBuiltinTools) && (
<List
pluginList={filteredList}
canManagePlugin={canManagePlugin}
canDeletePlugin={canDeletePlugin}
canUpdatePlugin={canUpdatePlugin}
>
{filteredBuiltinTools.map(collection => (

View File

@ -52,9 +52,8 @@ const matchesSearchQuery = (plugin: PluginDetail & { latest_version: string }, q
type PluginsPanelProps = {
canInstall?: boolean
canManagePlugin?: boolean
canDeletePlugin?: boolean
canUpdatePlugin?: boolean
canViewInstalledPlugins?: boolean
contentInset?: PluginPageContentInset
fixedCategory?: PluginCategoryEnum
layout?: (parts: { body: ReactNode, toolbar: ReactNode }) => ReactNode
@ -64,9 +63,8 @@ type PluginsPanelProps = {
const PluginsPanel = ({
canInstall = true,
canManagePlugin = true,
canDeletePlugin = true,
canUpdatePlugin = true,
canViewInstalledPlugins = true,
contentInset = 'default',
fixedCategory,
layout,
@ -88,7 +86,7 @@ const PluginsPanel = ({
select: s => s.enable_marketplace,
})
const { data: pluginList, isLoading: isPluginListLoading, isFetching, isLastPage, loadNextPage } = useInstalledPluginList(
!canViewInstalledPlugins,
false,
100,
fixedCategory
? {
@ -223,7 +221,7 @@ const PluginsPanel = ({
scrollAreaLabel={scrollAreaLabel}
setCurrentBuiltinToolID={setCurrentBuiltinToolID}
tagFilterValue={filters.tags}
canManagePlugin={canManagePlugin}
canDeletePlugin={canDeletePlugin}
canUpdatePlugin={canUpdatePlugin}
/>
)
@ -259,7 +257,7 @@ const PluginsPanel = ({
detail={currentPluginDetail}
onUpdate={() => invalidateInstalledPluginList()}
onHide={handleHide}
canManagePlugin={canManagePlugin}
canDeletePlugin={canDeletePlugin}
canUpdatePlugin={canUpdatePlugin}
/>
{currentBuiltinTool && !currentBuiltinTool.plugin_id && (

View File

@ -9,8 +9,6 @@ import { useInvalidateReferenceSettings, useMutationPluginPermissionSettings, us
import { hasPermission } from '@/utils/permission'
import { hasLegacyPluginPermissionAccess } from '../plugin-permissions'
const pluginReadAndUpdatePermissionKeys = ['plugin.install', 'plugin.manage']
const useCanSetPluginSettings = () => {
const { workspacePermissionKeys } = useAppContext()
const { data: rbacEnabled } = useSuspenseQuery({
@ -52,9 +50,8 @@ export const usePluginSettingsAccess = () => {
rbacEnabled,
})
const canInstallPlugin = hasPermission(workspacePermissionKeys, 'plugin.install') && legacyCanInstallPlugin
const canUpdatePlugin = hasPermission(workspacePermissionKeys, pluginReadAndUpdatePermissionKeys) && legacyCanInstallPlugin
const canViewInstalledPlugins = hasPermission(workspacePermissionKeys, pluginReadAndUpdatePermissionKeys)
const canManagePlugin = hasPermission(workspacePermissionKeys, 'plugin.manage') && legacyCanInstallPlugin
const canUpdatePlugin = hasPermission(workspacePermissionKeys, 'plugin.install') && legacyCanInstallPlugin
const canDeletePlugin = hasPermission(workspacePermissionKeys, 'plugin.delete') && legacyCanInstallPlugin
const canDebugPlugin = hasPermission(workspacePermissionKeys, 'plugin.debug') && legacyCanDebugPlugin
return {
@ -62,8 +59,7 @@ export const usePluginSettingsAccess = () => {
setPluginPermissionSettings,
canInstallPlugin,
canUpdatePlugin,
canViewInstalledPlugins,
canManagePlugin,
canDeletePlugin,
canDebugPlugin,
canSetPluginPreferences,
canManagement: canInstallPlugin,
@ -103,8 +99,7 @@ const useReferenceSetting = (category: PluginCategoryEnum) => {
canDebugger: permissionAccess.canDebugger,
canInstallPlugin: permissionAccess.canInstallPlugin,
canUpdatePlugin: permissionAccess.canUpdatePlugin,
canViewInstalledPlugins: permissionAccess.canViewInstalledPlugins,
canManagePlugin: permissionAccess.canManagePlugin,
canDeletePlugin: permissionAccess.canDeletePlugin,
canDebugPlugin: permissionAccess.canDebugPlugin,
canSetPermissions: permissionAccess.canSetPermissions,
canSetPluginPreferences: permissionAccess.canSetPluginPreferences,

View File

@ -5,10 +5,12 @@
"app.acl.delete": "حذف التطبيق",
"app.acl.edit": "تعديل التطبيق وتنسيقه",
"app.acl.import_export_dsl": "استيراد / تصدير DSL",
"app.acl.monitor": "المراقبة والعمليات",
"app.acl.log_and_annotation": "الوصول إلى السجلات والتعليقات التوضيحية",
"app.acl.monitor": "الوصول إلى صفحة المراقبة",
"app.acl.preview": "معاينة التطبيق",
"app.acl.release_and_version": "نشر التطبيق وإدارة الإصدارات",
"app.acl.test_and_run": "اختبار التطبيق واستخدامه",
"app.acl.tracing_config": "تكوين التتبع الخارجي",
"app.acl.view_layout": "صفحة التنسيق للقراءة فقط",
"app.create_and_management": "إنشاء التطبيقات",
"app.tag.manage": "إدارة وسوم التطبيق",
@ -40,9 +42,10 @@
"dataset.tag.manage": "إدارة وسوم قاعدة المعرفة",
"mcp.manage": "إدارة MCP",
"plugin.debug": "تصحيح الإضافات",
"plugin.delete": "حذف الإضافات",
"plugin.install": "تثبيت الإضافات وتحديثها",
"plugin.manage": "إدارة الإضافات",
"plugin.plugin_preferences": "إدارة تفضيلات الإضافات",
"plugin.model_config": "تكوين النماذج",
"plugin.plugin_preferences": "تكوين التحديثات التلقائية",
"snippets.create_and_modify": "إنشاء المقتطفات وتعديلها",
"snippets.management": "إدارة المقتطفات",
"tool.manage": "إدارة الأدوات",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "App löschen",
"app.acl.edit": "App bearbeiten und orchestrieren",
"app.acl.import_export_dsl": "DSL importieren / exportieren",
"app.acl.monitor": "Überwachung und Betrieb",
"app.acl.log_and_annotation": "Auf Logs und Annotationen zugreifen",
"app.acl.monitor": "Auf Monitoring-Seite zugreifen",
"app.acl.preview": "App-Vorschau",
"app.acl.release_and_version": "App-Veröffentlichung und Versionsverwaltung",
"app.acl.test_and_run": "App testen und verwenden",
"app.acl.tracing_config": "Externes Tracing konfigurieren",
"app.acl.view_layout": "Schreibgeschützte Orchestrierungsseite",
"app.create_and_management": "Apps erstellen",
"app.tag.manage": "App-Tags verwalten",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Wissensdatenbank-Tags verwalten",
"mcp.manage": "MCP verwalten",
"plugin.debug": "Plugins debuggen",
"plugin.delete": "Plugins löschen",
"plugin.install": "Plugins installieren und aktualisieren",
"plugin.manage": "Plugins verwalten",
"plugin.plugin_preferences": "Plugin-Einstellungen verwalten",
"plugin.model_config": "Modelle konfigurieren",
"plugin.plugin_preferences": "Automatische Updates konfigurieren",
"snippets.create_and_modify": "Snippets erstellen und ändern",
"snippets.management": "Snippets verwalten",
"tool.manage": "Tools verwalten",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Delete app",
"app.acl.edit": "Edit and orchestrate app",
"app.acl.import_export_dsl": "Import / export DSL",
"app.acl.monitor": "Monitoring and operations",
"app.acl.log_and_annotation": "Access logs and annotations",
"app.acl.monitor": "Access monitoring page",
"app.acl.preview": "Preview app",
"app.acl.release_and_version": "App publishing and version management",
"app.acl.test_and_run": "Test and use app",
"app.acl.tracing_config": "Configure external tracing",
"app.acl.view_layout": "Read-only orchestration page",
"app.create_and_management": "Create apps",
"app.tag.manage": "Manage app tags",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Manage knowledge base tags",
"mcp.manage": "Manage MCP",
"plugin.debug": "Debug plugins",
"plugin.delete": "Delete plugins",
"plugin.install": "Install and update plugins",
"plugin.manage": "Manage plugins",
"plugin.plugin_preferences": "Manage plugin preferences",
"plugin.model_config": "Configure models",
"plugin.plugin_preferences": "Configure auto updates",
"snippets.create_and_modify": "Create and modify snippets",
"snippets.management": "Manage snippets",
"tool.manage": "Manage tools",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Eliminar app",
"app.acl.edit": "Editar y orquestar la app",
"app.acl.import_export_dsl": "Importar / exportar DSL",
"app.acl.monitor": "Supervisión y operaciones",
"app.acl.log_and_annotation": "Acceder a logs y anotaciones",
"app.acl.monitor": "Acceder a la página de supervisión",
"app.acl.preview": "Previsualizar app",
"app.acl.release_and_version": "Publicación de la app y gestión de versiones",
"app.acl.test_and_run": "Probar y usar la app",
"app.acl.tracing_config": "Configurar tracing externo",
"app.acl.view_layout": "Página de orquestación de solo lectura",
"app.create_and_management": "Crear apps",
"app.tag.manage": "Gestionar etiquetas de apps",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Gestionar etiquetas de la base de conocimiento",
"mcp.manage": "Gestionar MCP",
"plugin.debug": "Depurar plugins",
"plugin.delete": "Eliminar plugins",
"plugin.install": "Instalar y actualizar plugins",
"plugin.manage": "Gestionar plugins",
"plugin.plugin_preferences": "Gestionar las preferencias de plugins",
"plugin.model_config": "Configurar modelos",
"plugin.plugin_preferences": "Configurar actualizaciones automáticas",
"snippets.create_and_modify": "Crear y modificar fragmentos",
"snippets.management": "Gestionar fragmentos",
"tool.manage": "Gestionar herramientas",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "حذف برنامه",
"app.acl.edit": "ویرایش و هماهنگ‌سازی برنامه",
"app.acl.import_export_dsl": "وارد کردن / صادر کردن DSL",
"app.acl.monitor": "نظارت و عملیات",
"app.acl.log_and_annotation": "دسترسی به گزارش‌ها و حاشیه‌نویسی‌ها",
"app.acl.monitor": "دسترسی به صفحه نظارت",
"app.acl.preview": "پیش‌نمایش برنامه",
"app.acl.release_and_version": "انتشار برنامه و مدیریت نسخه",
"app.acl.test_and_run": "آزمایش و استفاده از برنامه",
"app.acl.tracing_config": "پیکربندی رهگیری خارجی",
"app.acl.view_layout": "صفحه هماهنگ‌سازی فقط‌خواندنی",
"app.create_and_management": "ایجاد برنامه‌ها",
"app.tag.manage": "مدیریت برچسب‌های برنامه",
@ -40,9 +42,10 @@
"dataset.tag.manage": "مدیریت برچسب‌های پایگاه دانش",
"mcp.manage": "مدیریت MCP",
"plugin.debug": "اشکال‌زدایی افزونه‌ها",
"plugin.delete": "حذف افزونه‌ها",
"plugin.install": "نصب و به‌روزرسانی افزونه‌ها",
"plugin.manage": "مدیریت افزونهها",
"plugin.plugin_preferences": "مدیریت ترجیحات افزونه",
"plugin.model_config": "پیکربندی مدلها",
"plugin.plugin_preferences": "پیکربندی به‌روزرسانی‌های خودکار",
"snippets.create_and_modify": "ایجاد و اصلاح قطعه‌کدها",
"snippets.management": "مدیریت قطعه‌کدها",
"tool.manage": "مدیریت ابزارها",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Supprimer l'application",
"app.acl.edit": "Modifier et orchestrer l'application",
"app.acl.import_export_dsl": "Importer / exporter le DSL",
"app.acl.monitor": "Surveillance et exploitation",
"app.acl.log_and_annotation": "Accéder aux journaux et annotations",
"app.acl.monitor": "Accéder à la page de surveillance",
"app.acl.preview": "Prévisualiser l'application",
"app.acl.release_and_version": "Publication de l'application et gestion des versions",
"app.acl.test_and_run": "Tester et utiliser l'application",
"app.acl.tracing_config": "Configurer le traçage externe",
"app.acl.view_layout": "Page d'orchestration en lecture seule",
"app.create_and_management": "Créer des applications",
"app.tag.manage": "Gérer les étiquettes d'applications",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Gérer les étiquettes de bases de connaissances",
"mcp.manage": "Gérer MCP",
"plugin.debug": "Déboguer les plugins",
"plugin.delete": "Supprimer les plugins",
"plugin.install": "Installer et mettre à jour les plugins",
"plugin.manage": "Gérer les plugins",
"plugin.plugin_preferences": "Gérer les préférences des plugins",
"plugin.model_config": "Configurer les modèles",
"plugin.plugin_preferences": "Configurer les mises à jour automatiques",
"snippets.create_and_modify": "Créer et modifier des extraits",
"snippets.management": "Gérer les extraits",
"tool.manage": "Gérer les outils",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "ऐप हटाएं",
"app.acl.edit": "ऐप संपादित और ऑर्केस्ट्रेट करें",
"app.acl.import_export_dsl": "DSL आयात / निर्यात करें",
"app.acl.monitor": "निगरानी और संचालन",
"app.acl.log_and_annotation": "लॉग और एनोटेशन तक पहुंच",
"app.acl.monitor": "मॉनिटरिंग पेज तक पहुंच",
"app.acl.preview": "ऐप पूर्वावलोकन करें",
"app.acl.release_and_version": "ऐप प्रकाशन और संस्करण प्रबंधन",
"app.acl.test_and_run": "ऐप परीक्षण और उपयोग करें",
"app.acl.tracing_config": "बाहरी ट्रेसिंग कॉन्फ़िगर करें",
"app.acl.view_layout": "केवल-पढ़ने योग्य ऑर्केस्ट्रेशन पृष्ठ",
"app.create_and_management": "ऐप्स बनाएं",
"app.tag.manage": "ऐप टैग प्रबंधित करें",
@ -40,9 +42,10 @@
"dataset.tag.manage": "ज्ञान आधार टैग प्रबंधित करें",
"mcp.manage": "MCP प्रबंधित करें",
"plugin.debug": "प्लगइन्स डिबग करें",
"plugin.delete": "प्लगइन्स हटाएं",
"plugin.install": "प्लगइन्स इंस्टॉल और अपडेट करें",
"plugin.manage": "प्लगइन्स प्रबंधित करें",
"plugin.plugin_preferences": "प्लगइन प्राथमिकताएं प्रबंधित करें",
"plugin.model_config": "मॉडल कॉन्फ़िगर करें",
"plugin.plugin_preferences": "ऑटो अपडेट कॉन्फ़िगर करें",
"snippets.create_and_modify": "स्निपेट बनाएं और संशोधित करें",
"snippets.management": "स्निपेट प्रबंधित करें",
"tool.manage": "टूल प्रबंधित करें",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Hapus aplikasi",
"app.acl.edit": "Edit dan orkestrasikan aplikasi",
"app.acl.import_export_dsl": "Impor / ekspor DSL",
"app.acl.monitor": "Pemantauan dan operasi",
"app.acl.log_and_annotation": "Akses log dan anotasi",
"app.acl.monitor": "Akses halaman pemantauan",
"app.acl.preview": "Pratinjau aplikasi",
"app.acl.release_and_version": "Penerbitan aplikasi dan manajemen versi",
"app.acl.test_and_run": "Uji dan gunakan aplikasi",
"app.acl.tracing_config": "Konfigurasikan tracing eksternal",
"app.acl.view_layout": "Halaman orkestrasi hanya-baca",
"app.create_and_management": "Buat aplikasi",
"app.tag.manage": "Kelola tag aplikasi",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Kelola tag basis pengetahuan",
"mcp.manage": "Kelola MCP",
"plugin.debug": "Debug plugin",
"plugin.delete": "Hapus plugin",
"plugin.install": "Instal dan perbarui plugin",
"plugin.manage": "Kelola plugin",
"plugin.plugin_preferences": "Kelola preferensi plugin",
"plugin.model_config": "Konfigurasi model",
"plugin.plugin_preferences": "Konfigurasi pembaruan otomatis",
"snippets.create_and_modify": "Buat dan ubah snippet",
"snippets.management": "Kelola snippet",
"tool.manage": "Kelola alat",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Elimina app",
"app.acl.edit": "Modifica e orchestra l'app",
"app.acl.import_export_dsl": "Importa / esporta DSL",
"app.acl.monitor": "Monitoraggio e operazioni",
"app.acl.log_and_annotation": "Accedi a log e annotazioni",
"app.acl.monitor": "Accedi alla pagina di monitoraggio",
"app.acl.preview": "Anteprima app",
"app.acl.release_and_version": "Pubblicazione dell'app e gestione delle versioni",
"app.acl.test_and_run": "Testa e usa l'app",
"app.acl.tracing_config": "Configura il tracing esterno",
"app.acl.view_layout": "Pagina di orchestrazione in sola lettura",
"app.create_and_management": "Crea app",
"app.tag.manage": "Gestisci i tag delle app",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Gestisci i tag della knowledge base",
"mcp.manage": "Gestisci MCP",
"plugin.debug": "Esegui il debug dei plugin",
"plugin.delete": "Elimina plugin",
"plugin.install": "Installa e aggiorna plugin",
"plugin.manage": "Gestisci plugin",
"plugin.plugin_preferences": "Gestisci le preferenze dei plugin",
"plugin.model_config": "Configura modelli",
"plugin.plugin_preferences": "Configura aggiornamenti automatici",
"snippets.create_and_modify": "Crea e modifica snippet",
"snippets.management": "Gestisci snippet",
"tool.manage": "Gestisci strumenti",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "アプリを削除",
"app.acl.edit": "アプリの編集とオーケストレーション",
"app.acl.import_export_dsl": "DSLのインポート / エクスポート",
"app.acl.monitor": "監視と運用",
"app.acl.log_and_annotation": "ログと注釈へのアクセス",
"app.acl.monitor": "監視ページへのアクセス",
"app.acl.preview": "アプリをプレビュー",
"app.acl.release_and_version": "アプリ公開とバージョン管理",
"app.acl.test_and_run": "アプリのテストと使用",
"app.acl.tracing_config": "外部トレーシングの設定",
"app.acl.view_layout": "オーケストレーションページの読み取り専用",
"app.create_and_management": "アプリを作成",
"app.tag.manage": "アプリタグを管理",
@ -40,9 +42,10 @@
"dataset.tag.manage": "ナレッジベースタグを管理",
"mcp.manage": "MCPを管理",
"plugin.debug": "プラグインをデバッグ",
"plugin.delete": "プラグインを削除",
"plugin.install": "プラグインのインストールと更新",
"plugin.manage": "プラグインを管理",
"plugin.plugin_preferences": "プラグイン設定を管理",
"plugin.model_config": "モデルを設定",
"plugin.plugin_preferences": "自動更新を設定",
"snippets.create_and_modify": "スニペットの作成と変更",
"snippets.management": "スニペットを管理",
"tool.manage": "ツールを管理",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "앱 삭제",
"app.acl.edit": "앱 편집 및 오케스트레이션",
"app.acl.import_export_dsl": "DSL 가져오기 / 내보내기",
"app.acl.monitor": "모니터링 및 운영",
"app.acl.log_and_annotation": "로그 및 주석 접근",
"app.acl.monitor": "모니터링 페이지 접근",
"app.acl.preview": "앱 미리보기",
"app.acl.release_and_version": "앱 게시 및 버전 관리",
"app.acl.test_and_run": "앱 테스트 및 사용",
"app.acl.tracing_config": "외부 추적 구성",
"app.acl.view_layout": "읽기 전용 오케스트레이션 페이지",
"app.create_and_management": "앱 생성",
"app.tag.manage": "앱 태그 관리",
@ -40,9 +42,10 @@
"dataset.tag.manage": "지식 베이스 태그 관리",
"mcp.manage": "MCP 관리",
"plugin.debug": "플러그인 디버그",
"plugin.delete": "플러그인 삭제",
"plugin.install": "플러그인 설치 및 업데이트",
"plugin.manage": "플러그인 관리",
"plugin.plugin_preferences": "플러그인 환경설정 관리",
"plugin.model_config": "모델 구성",
"plugin.plugin_preferences": "자동 업데이트 구성",
"snippets.create_and_modify": "스니펫 생성 및 수정",
"snippets.management": "스니펫 관리",
"tool.manage": "도구 관리",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "App verwijderen",
"app.acl.edit": "App bewerken en orkestreren",
"app.acl.import_export_dsl": "DSL importeren / exporteren",
"app.acl.monitor": "Monitoring en bewerkingen",
"app.acl.log_and_annotation": "Logs en annotaties openen",
"app.acl.monitor": "Monitoringpagina openen",
"app.acl.preview": "App voorbeeld bekijken",
"app.acl.release_and_version": "App publiceren en versiebeheer",
"app.acl.test_and_run": "App testen en gebruiken",
"app.acl.tracing_config": "Externe tracing configureren",
"app.acl.view_layout": "Alleen-lezen orkestratiepagina",
"app.create_and_management": "Apps maken",
"app.tag.manage": "App-tags beheren",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Kennisbank-tags beheren",
"mcp.manage": "MCP beheren",
"plugin.debug": "Plug-ins debuggen",
"plugin.delete": "Plug-ins verwijderen",
"plugin.install": "Plug-ins installeren en bijwerken",
"plugin.manage": "Plug-ins beheren",
"plugin.plugin_preferences": "Plug-invoorkeuren beheren",
"plugin.model_config": "Modellen configureren",
"plugin.plugin_preferences": "Automatische updates configureren",
"snippets.create_and_modify": "Snippets maken en wijzigen",
"snippets.management": "Snippets beheren",
"tool.manage": "Tools beheren",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Usuń aplikację",
"app.acl.edit": "Edytuj i orkiestruj aplikację",
"app.acl.import_export_dsl": "Importuj / eksportuj DSL",
"app.acl.monitor": "Monitorowanie i operacje",
"app.acl.log_and_annotation": "Dostęp do logów i adnotacji",
"app.acl.monitor": "Dostęp do strony monitorowania",
"app.acl.preview": "Podgląd aplikacji",
"app.acl.release_and_version": "Publikowanie aplikacji i zarządzanie wersjami",
"app.acl.test_and_run": "Testuj i używaj aplikacji",
"app.acl.tracing_config": "Konfiguruj zewnętrzne śledzenie",
"app.acl.view_layout": "Strona orkiestracji tylko do odczytu",
"app.create_and_management": "Twórz aplikacje",
"app.tag.manage": "Zarządzaj tagami aplikacji",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Zarządzaj tagami bazy wiedzy",
"mcp.manage": "Zarządzaj MCP",
"plugin.debug": "Debuguj wtyczki",
"plugin.delete": "Usuń wtyczki",
"plugin.install": "Instaluj i aktualizuj wtyczki",
"plugin.manage": "Zarządzaj wtyczkami",
"plugin.plugin_preferences": "Zarządzaj preferencjami wtyczek",
"plugin.model_config": "Konfiguruj modele",
"plugin.plugin_preferences": "Konfiguruj automatyczne aktualizacje",
"snippets.create_and_modify": "Twórz i modyfikuj fragmenty kodu",
"snippets.management": "Zarządzaj fragmentami kodu",
"tool.manage": "Zarządzaj narzędziami",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Excluir aplicativo",
"app.acl.edit": "Editar e orquestrar aplicativo",
"app.acl.import_export_dsl": "Importar / exportar DSL",
"app.acl.monitor": "Monitoramento e operações",
"app.acl.log_and_annotation": "Acessar logs e anotações",
"app.acl.monitor": "Acessar página de monitoramento",
"app.acl.preview": "Visualizar aplicativo",
"app.acl.release_and_version": "Publicação de aplicativo e gerenciamento de versões",
"app.acl.test_and_run": "Testar e usar aplicativo",
"app.acl.tracing_config": "Configurar rastreamento externo",
"app.acl.view_layout": "Página de orquestração somente leitura",
"app.create_and_management": "Criar aplicativos",
"app.tag.manage": "Gerenciar tags de aplicativo",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Gerenciar tags do Conhecimento",
"mcp.manage": "Gerenciar MCP",
"plugin.debug": "Depurar plug-ins",
"plugin.delete": "Excluir plug-ins",
"plugin.install": "Instalar e atualizar plug-ins",
"plugin.manage": "Gerenciar plug-ins",
"plugin.plugin_preferences": "Gerenciar preferências de plug-ins",
"plugin.model_config": "Configurar modelos",
"plugin.plugin_preferences": "Configurar atualizações automáticas",
"snippets.create_and_modify": "Criar e modificar snippets",
"snippets.management": "Gerenciar snippets",
"tool.manage": "Gerenciar ferramentas",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Șterge aplicația",
"app.acl.edit": "Editează și orchestrează aplicația",
"app.acl.import_export_dsl": "Importă / exportă DSL",
"app.acl.monitor": "Monitorizare și operațiuni",
"app.acl.log_and_annotation": "Accesează jurnale și adnotări",
"app.acl.monitor": "Accesează pagina de monitorizare",
"app.acl.preview": "Previzualizează aplicația",
"app.acl.release_and_version": "Publicarea aplicației și gestionarea versiunilor",
"app.acl.test_and_run": "Testează și utilizează aplicația",
"app.acl.tracing_config": "Configurează tracing extern",
"app.acl.view_layout": "Pagină de orchestrare doar pentru citire",
"app.create_and_management": "Creează aplicații",
"app.tag.manage": "Gestionează etichetele aplicațiilor",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Gestionează etichetele bazei de cunoștințe",
"mcp.manage": "Gestionează MCP",
"plugin.debug": "Depanează plugin-uri",
"plugin.delete": "Șterge plugin-uri",
"plugin.install": "Instalează și actualizează plugin-uri",
"plugin.manage": "Gestionează plugin-uri",
"plugin.plugin_preferences": "Gestionează preferințele plugin-urilor",
"plugin.model_config": "Configurează modele",
"plugin.plugin_preferences": "Configurează actualizările automate",
"snippets.create_and_modify": "Creează și modifică fragmente",
"snippets.management": "Gestionează fragmente",
"tool.manage": "Gestionează instrumente",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Удаление приложения",
"app.acl.edit": "Редактирование и оркестрация приложения",
"app.acl.import_export_dsl": "Импорт / экспорт DSL",
"app.acl.monitor": "Мониторинг и эксплуатация",
"app.acl.log_and_annotation": "Доступ к журналам и аннотациям",
"app.acl.monitor": "Доступ к странице мониторинга",
"app.acl.preview": "Предпросмотр приложения",
"app.acl.release_and_version": "Публикация приложения и управление версиями",
"app.acl.test_and_run": "Тестирование и использование приложения",
"app.acl.tracing_config": "Настройка внешнего трассинга",
"app.acl.view_layout": "Страница оркестрации только для чтения",
"app.create_and_management": "Создание приложений",
"app.tag.manage": "Управление тегами приложений",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Управление тегами баз знаний",
"mcp.manage": "Управление MCP",
"plugin.debug": "Отладка плагинов",
"plugin.delete": "Удаление плагинов",
"plugin.install": "Установка и обновление плагинов",
"plugin.manage": "Управление плагинами",
"plugin.plugin_preferences": "Управление настройками плагинов",
"plugin.model_config": "Настройка моделей",
"plugin.plugin_preferences": "Настройка автообновлений",
"snippets.create_and_modify": "Создание и изменение сниппетов",
"snippets.management": "Управление сниппетами",
"tool.manage": "Управление инструментами",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Izbriši aplikacijo",
"app.acl.edit": "Uredi in orkestriraj aplikacijo",
"app.acl.import_export_dsl": "Uvozi / izvozi DSL",
"app.acl.monitor": "Spremljanje in operacije",
"app.acl.log_and_annotation": "Dostop do dnevnikov in opomb",
"app.acl.monitor": "Dostop do strani za spremljanje",
"app.acl.preview": "Predogled aplikacije",
"app.acl.release_and_version": "Objavljanje aplikacije in upravljanje različic",
"app.acl.test_and_run": "Preizkusi in uporabi aplikacijo",
"app.acl.tracing_config": "Konfiguracija zunanjega sledenja",
"app.acl.view_layout": "Stran za orkestracijo samo za branje",
"app.create_and_management": "Ustvarjanje aplikacij",
"app.tag.manage": "Upravljanje oznak aplikacij",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Upravljanje oznak baze znanja",
"mcp.manage": "Upravljanje MCP",
"plugin.debug": "Razhroščevanje vtičnikov",
"plugin.delete": "Brisanje vtičnikov",
"plugin.install": "Namesti in posodobi vtičnike",
"plugin.manage": "Upravljanje vtičnikov",
"plugin.plugin_preferences": "Upravljanje nastavitev vtičnikov",
"plugin.model_config": "Konfiguriranje modelov",
"plugin.plugin_preferences": "Konfiguriranje samodejnih posodobitev",
"snippets.create_and_modify": "Ustvarjanje in spreminjanje izsekov",
"snippets.management": "Upravljanje izsekov",
"tool.manage": "Upravljanje orodij",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "ลบแอป",
"app.acl.edit": "แก้ไขและจัดวางแอป",
"app.acl.import_export_dsl": "นําเข้า / ส่งออก DSL",
"app.acl.monitor": "การตรวจสอบและการดําเนินการ",
"app.acl.log_and_annotation": "เข้าถึงบันทึกและคำอธิบายประกอบ",
"app.acl.monitor": "เข้าถึงหน้าการตรวจสอบ",
"app.acl.preview": "ดูตัวอย่างแอป",
"app.acl.release_and_version": "การเผยแพร่แอปและการจัดการเวอร์ชัน",
"app.acl.test_and_run": "ทดสอบและใช้งานแอป",
"app.acl.tracing_config": "กำหนดค่าการติดตามภายนอก",
"app.acl.view_layout": "หน้าจัดวางแบบอ่านอย่างเดียว",
"app.create_and_management": "สร้างแอป",
"app.tag.manage": "จัดการแท็กแอป",
@ -40,9 +42,10 @@
"dataset.tag.manage": "จัดการแท็กฐานความรู้",
"mcp.manage": "จัดการ MCP",
"plugin.debug": "ดีบักปลั๊กอิน",
"plugin.delete": "ลบปลั๊กอิน",
"plugin.install": "ติดตั้งและอัปเดตปลั๊กอิน",
"plugin.manage": "จัดการปลั๊กอิน",
"plugin.plugin_preferences": "จัดการการตั้งค่าปลั๊กอิน",
"plugin.model_config": "กำหนดค่าโมเดล",
"plugin.plugin_preferences": "กำหนดค่าการอัปเดตอัตโนมัติ",
"snippets.create_and_modify": "สร้างและแก้ไขสนิปเปต",
"snippets.management": "จัดการสนิปเปต",
"tool.manage": "จัดการเครื่องมือ",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Uygulamayı sil",
"app.acl.edit": "Uygulamayı düzenle ve düzenle",
"app.acl.import_export_dsl": "DSL içe / dışa aktar",
"app.acl.monitor": "İzleme ve operasyonlar",
"app.acl.log_and_annotation": "Günlüklere ve açıklamalara eriş",
"app.acl.monitor": "İzleme sayfasına eriş",
"app.acl.preview": "Uygulamayı önizle",
"app.acl.release_and_version": "Uygulama yayınlama ve sürüm yönetimi",
"app.acl.test_and_run": "Uygulamayı test et ve kullan",
"app.acl.tracing_config": "Harici izlemeyi yapılandır",
"app.acl.view_layout": "Salt okunur düzenleme sayfası",
"app.create_and_management": "Uygulama oluştur",
"app.tag.manage": "Uygulama etiketlerini yönet",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Bilgi tabanı etiketlerini yönet",
"mcp.manage": "MCP yönet",
"plugin.debug": "Eklentileri hata ayıkla",
"plugin.delete": "Eklentileri sil",
"plugin.install": "Eklentileri yükle ve güncelle",
"plugin.manage": "Eklentileri yönet",
"plugin.plugin_preferences": "Eklenti tercihlerini yönet",
"plugin.model_config": "Modelleri yapılandır",
"plugin.plugin_preferences": "Otomatik güncellemeleri yapılandır",
"snippets.create_and_modify": "Snippet oluştur ve değiştir",
"snippets.management": "Snippet'leri yönet",
"tool.manage": "Araçları yönet",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Видалити застосунок",
"app.acl.edit": "Редагувати та оркеструвати застосунок",
"app.acl.import_export_dsl": "Імпорт / експорт DSL",
"app.acl.monitor": "Моніторинг та операції",
"app.acl.log_and_annotation": "Доступ до журналів і анотацій",
"app.acl.monitor": "Доступ до сторінки моніторингу",
"app.acl.preview": "Попередній перегляд застосунку",
"app.acl.release_and_version": "Публікація застосунку та керування версіями",
"app.acl.test_and_run": "Тестувати та використовувати застосунок",
"app.acl.tracing_config": "Налаштування зовнішнього трасування",
"app.acl.view_layout": "Сторінка оркестрації лише для читання",
"app.create_and_management": "Створювати застосунки",
"app.tag.manage": "Керування тегами застосунків",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Керування тегами бази знань",
"mcp.manage": "Керування MCP",
"plugin.debug": "Налагодження плагінів",
"plugin.delete": "Видалення плагінів",
"plugin.install": "Встановлення та оновлення плагінів",
"plugin.manage": "Керування плагінами",
"plugin.plugin_preferences": "Керування налаштуваннями плагінів",
"plugin.model_config": "Налаштування моделей",
"plugin.plugin_preferences": "Налаштування автоматичних оновлень",
"snippets.create_and_modify": "Створювати та змінювати фрагменти",
"snippets.management": "Керування фрагментами",
"tool.manage": "Керування інструментами",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "Xóa ứng dụng",
"app.acl.edit": "Chỉnh sửa và điều phối ứng dụng",
"app.acl.import_export_dsl": "Nhập / xuất DSL",
"app.acl.monitor": "Giám sát và vận hành",
"app.acl.log_and_annotation": "Truy cập nhật ký và chú thích",
"app.acl.monitor": "Truy cập trang giám sát",
"app.acl.preview": "Xem trước ứng dụng",
"app.acl.release_and_version": "Phát hành ứng dụng và quản lý phiên bản",
"app.acl.test_and_run": "Kiểm thử và sử dụng ứng dụng",
"app.acl.tracing_config": "Cấu hình tracing bên ngoài",
"app.acl.view_layout": "Trang điều phối chỉ đọc",
"app.create_and_management": "Tạo ứng dụng",
"app.tag.manage": "Quản lý thẻ ứng dụng",
@ -40,9 +42,10 @@
"dataset.tag.manage": "Quản lý thẻ cơ sở tri thức",
"mcp.manage": "Quản lý MCP",
"plugin.debug": "Gỡ lỗi plugin",
"plugin.delete": "Xóa plugin",
"plugin.install": "Cài đặt và cập nhật plugin",
"plugin.manage": "Quản lý plugin",
"plugin.plugin_preferences": "Quản lý tùy chọn plugin",
"plugin.model_config": "Cấu hình mô hình",
"plugin.plugin_preferences": "Cấu hình cập nhật tự động",
"snippets.create_and_modify": "Tạo và chỉnh sửa đoạn mã",
"snippets.management": "Quản lý đoạn mã",
"tool.manage": "Quản lý công cụ",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "删除应用",
"app.acl.edit": "编辑与编排应用",
"app.acl.import_export_dsl": "导入 / 导出 DSL",
"app.acl.monitor": "监测与运维",
"app.acl.log_and_annotation": "访问日志与标注",
"app.acl.monitor": "访问监测页面",
"app.acl.preview": "预览应用",
"app.acl.release_and_version": "应用发布与版本管理",
"app.acl.test_and_run": "测试与使用应用",
"app.acl.tracing_config": "配置外部追踪",
"app.acl.view_layout": "编排页只读",
"app.create_and_management": "创建应用",
"app.tag.manage": "管理应用标签",
@ -40,9 +42,10 @@
"dataset.tag.manage": "管理知识库标签",
"mcp.manage": "管理MCP",
"plugin.debug": "调试插件",
"plugin.delete": "删除插件",
"plugin.install": "安装与更新插件",
"plugin.manage": "管理插件",
"plugin.plugin_preferences": "管理插件偏好",
"plugin.model_config": "配置模型",
"plugin.plugin_preferences": "配置自动更新",
"snippets.create_and_modify": "创建和修改 Snippets",
"snippets.management": "管理 Snippets",
"tool.manage": "管理工具",

View File

@ -5,10 +5,12 @@
"app.acl.delete": "刪除應用",
"app.acl.edit": "編輯與編排應用",
"app.acl.import_export_dsl": "匯入 / 匯出 DSL",
"app.acl.monitor": "監測與維運",
"app.acl.log_and_annotation": "訪問日誌與標註",
"app.acl.monitor": "訪問監測頁面",
"app.acl.preview": "預覽應用",
"app.acl.release_and_version": "應用發布與版本管理",
"app.acl.test_and_run": "測試與使用應用",
"app.acl.tracing_config": "配置外部追蹤",
"app.acl.view_layout": "編排頁唯讀",
"app.create_and_management": "建立應用",
"app.tag.manage": "管理應用標籤",
@ -40,9 +42,10 @@
"dataset.tag.manage": "管理知識庫標籤",
"mcp.manage": "管理MCP",
"plugin.debug": "調試插件",
"plugin.delete": "刪除插件",
"plugin.install": "安裝與更新插件",
"plugin.manage": "管理插件",
"plugin.plugin_preferences": "管理插件偏好設定",
"plugin.model_config": "設定模型",
"plugin.plugin_preferences": "設定自動更新",
"snippets.create_and_modify": "建立和修改 Snippets",
"snippets.management": "管理 Snippets",
"tool.manage": "管理工具",

View File

@ -85,6 +85,12 @@ describe('app-redirection', () => {
expect(getRedirectionPath(app)).toBe('/app/app-123/overview')
})
it('returns logs path when app ACL can only access logs and annotations', () => {
const app = { id: 'app-123', mode: AppModeEnum.CHAT, permission_keys: [AppACLPermission.LogAndAnnotation] }
expect(getRedirectionPath(app)).toBe('/app/app-123/logs')
})
})
/**

View File

@ -24,6 +24,9 @@ export const getRedirectionPath = (
if (appACLCapabilities.canMonitor)
return `/app/${app.id}/overview`
if (appACLCapabilities.canAccessLogAndAnnotation)
return `/app/${app.id}/logs`
if (appACLCapabilities.canAccessConfig)
return `/app/${app.id}/access-config`

View File

@ -42,6 +42,24 @@ describe('permission', () => {
expect(capabilities.canComment).toBe(true)
expect(capabilities.canTestAndRun).toBe(false)
})
it('keeps monitor, tracing config, and log/annotation permissions independent', () => {
const monitorCapabilities = getAppACLCapabilities([AppACLPermission.Monitor])
const tracingCapabilities = getAppACLCapabilities([AppACLPermission.TracingConfig])
const logAndAnnotationCapabilities = getAppACLCapabilities([AppACLPermission.LogAndAnnotation])
expect(monitorCapabilities.canMonitor).toBe(true)
expect(monitorCapabilities.canConfigureTracing).toBe(false)
expect(monitorCapabilities.canAccessLogAndAnnotation).toBe(false)
expect(tracingCapabilities.canMonitor).toBe(false)
expect(tracingCapabilities.canConfigureTracing).toBe(true)
expect(tracingCapabilities.canAccessLogAndAnnotation).toBe(false)
expect(logAndAnnotationCapabilities.canMonitor).toBe(false)
expect(logAndAnnotationCapabilities.canConfigureTracing).toBe(false)
expect(logAndAnnotationCapabilities.canAccessLogAndAnnotation).toBe(true)
})
})
describe('hasOnlyAppPreviewPermission', () => {
@ -87,6 +105,8 @@ describe('permission', () => {
expect(capabilities.canDelete).toBe(true)
expect(capabilities.canReleaseAndVersion).toBe(true)
expect(capabilities.canMonitor).toBe(true)
expect(capabilities.canConfigureTracing).toBe(true)
expect(capabilities.canAccessLogAndAnnotation).toBe(true)
expect(capabilities.canAccessConfig).toBe(true)
expect(permissionKeys).toEqual([])
})

View File

@ -9,6 +9,8 @@ export const AppACLPermission = {
Delete: 'app.acl.delete',
ReleaseAndVersion: 'app.acl.release_and_version',
Monitor: 'app.acl.monitor',
TracingConfig: 'app.acl.tracing_config',
LogAndAnnotation: 'app.acl.log_and_annotation',
AccessConfig: 'app.acl.access_config',
} as const
@ -51,6 +53,8 @@ type AppACLCapabilities = {
canDelete: boolean
canReleaseAndVersion: boolean
canMonitor: boolean
canConfigureTracing: boolean
canAccessLogAndAnnotation: boolean
canAccessConfig: boolean
}
@ -124,6 +128,8 @@ export const getAppACLCapabilities = (
canDelete: hasResourcePermission(permissionKeys, AppACLPermission.Delete, hasMaintainerPermissions),
canReleaseAndVersion: hasResourcePermission(permissionKeys, AppACLPermission.ReleaseAndVersion, hasMaintainerPermissions),
canMonitor: hasResourcePermission(permissionKeys, AppACLPermission.Monitor, hasMaintainerPermissions),
canConfigureTracing: hasResourcePermission(permissionKeys, AppACLPermission.TracingConfig, hasMaintainerPermissions),
canAccessLogAndAnnotation: hasResourcePermission(permissionKeys, AppACLPermission.LogAndAnnotation, hasMaintainerPermissions),
canAccessConfig: Boolean(options?.isRbacEnabled) && hasResourcePermission(permissionKeys, AppACLPermission.AccessConfig, hasMaintainerPermissions),
}
}