From affdc89f844a831f9190b3b743421b6c5dccd6b5 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Thu, 25 Jun 2026 17:28:58 +0800 Subject: [PATCH] feat: enhance app permissions and access controls (#37933) --- .../plugins/plugin-page-shell-flow.test.tsx | 2 +- .../tools/provider-list-shell-flow.test.tsx | 4 +- .../tool-browsing-and-filtering.test.tsx | 4 +- .../[appId]/__tests__/layout-main.spec.tsx | 27 ++++++++-- .../(appDetailLayout)/[appId]/layout-main.tsx | 4 +- .../[appId]/overview/__tests__/view.spec.tsx | 22 +++++++- .../overview/tracing/__tests__/panel.spec.tsx | 15 +++++- .../[appId]/overview/tracing/panel.tsx | 2 +- .../[appId]/overview/view.tsx | 8 +-- .../__tests__/app-detail-section.spec.tsx | 51 +++++++++++-------- .../app-sidebar/app-detail-section.tsx | 4 +- .../__tests__/index.spec.tsx | 2 + .../__tests__/index.spec.tsx | 1 - .../__tests__/plugin-actions.spec.tsx | 2 +- .../data-source-page-new/index.tsx | 3 +- .../data-source-page-new/plugin-actions.tsx | 6 +-- .../__tests__/index.non-cloud.spec.tsx | 1 - .../__tests__/index.spec.tsx | 1 - .../model-provider-page/index.tsx | 3 +- .../__tests__/index.spec.tsx | 10 ++-- .../__tests__/model-list-item.spec.tsx | 10 ++-- .../__tests__/model-list.spec.tsx | 12 ++--- .../__tests__/provider-card-actions.spec.tsx | 2 +- .../provider-added-card/index.tsx | 4 +- .../provider-added-card/model-list-item.tsx | 6 +-- .../provider-added-card/model-list.tsx | 4 +- .../provider-card-actions.tsx | 6 +-- .../__tests__/index.spec.tsx | 6 +-- .../system-model-selector/index.tsx | 2 +- .../integrations/__tests__/page.spec.tsx | 6 +-- .../__tests__/tool-provider-list.spec.tsx | 3 +- .../hooks/use-integration-permissions.ts | 6 +-- web/app/components/integrations/page.tsx | 9 ++-- .../integrations/plugin-category-page.tsx | 15 ++---- .../integrations/section-renderer.tsx | 9 ++-- .../integrations/tool-provider-list.tsx | 7 ++- ...orkspace-plugin-install-permission.spec.ts | 27 +++++----- ...use-workspace-plugin-install-permission.ts | 15 ++---- .../hooks/use-plugin-operations.ts | 8 +-- .../detail-header/index.tsx | 8 +-- .../plugins/plugin-detail-panel/index.tsx | 6 +-- .../components/plugins/plugin-item/index.tsx | 6 +-- .../plugin-page/__tests__/index.spec.tsx | 2 +- .../__tests__/use-reference-setting.spec.ts | 19 ++++--- .../components/plugins/plugin-page/index.tsx | 11 ++-- .../plugins/plugin-page/list/index.tsx | 6 +-- .../plugin-page/plugins-panel-results.tsx | 6 +-- .../plugins/plugin-page/plugins-panel.tsx | 12 ++--- .../plugin-page/use-reference-setting.ts | 13 ++--- web/i18n/ar-TN/permission-keys.json | 9 ++-- web/i18n/de-DE/permission-keys.json | 9 ++-- web/i18n/en-US/permission-keys.json | 9 ++-- web/i18n/es-ES/permission-keys.json | 9 ++-- web/i18n/fa-IR/permission-keys.json | 9 ++-- web/i18n/fr-FR/permission-keys.json | 9 ++-- web/i18n/hi-IN/permission-keys.json | 9 ++-- web/i18n/id-ID/permission-keys.json | 9 ++-- web/i18n/it-IT/permission-keys.json | 9 ++-- web/i18n/ja-JP/permission-keys.json | 9 ++-- web/i18n/ko-KR/permission-keys.json | 9 ++-- web/i18n/nl-NL/permission-keys.json | 9 ++-- web/i18n/pl-PL/permission-keys.json | 9 ++-- web/i18n/pt-BR/permission-keys.json | 9 ++-- web/i18n/ro-RO/permission-keys.json | 9 ++-- web/i18n/ru-RU/permission-keys.json | 9 ++-- web/i18n/sl-SI/permission-keys.json | 9 ++-- web/i18n/th-TH/permission-keys.json | 9 ++-- web/i18n/tr-TR/permission-keys.json | 9 ++-- web/i18n/uk-UA/permission-keys.json | 9 ++-- web/i18n/vi-VN/permission-keys.json | 9 ++-- web/i18n/zh-Hans/permission-keys.json | 9 ++-- web/i18n/zh-Hant/permission-keys.json | 9 ++-- web/utils/app-redirection.spec.ts | 6 +++ web/utils/app-redirection.ts | 3 ++ web/utils/permission.spec.ts | 20 ++++++++ web/utils/permission.ts | 6 +++ 76 files changed, 395 insertions(+), 265 deletions(-) diff --git a/web/__tests__/plugins/plugin-page-shell-flow.test.tsx b/web/__tests__/plugins/plugin-page-shell-flow.test.tsx index ac090ec577b..fde85d7abcd 100644 --- a/web/__tests__/plugins/plugin-page-shell-flow.test.tsx +++ b/web/__tests__/plugins/plugin-page-shell-flow.test.tsx @@ -39,7 +39,7 @@ vi.mock('@/context/app-context', () => ({ }, workspacePermissionKeys: [ 'plugin.install', - 'plugin.manage', + 'plugin.delete', 'plugin.plugin_preferences', ], }), diff --git a/web/__tests__/tools/provider-list-shell-flow.test.tsx b/web/__tests__/tools/provider-list-shell-flow.test.tsx index dcf88dda46e..39677decc59 100644 --- a/web/__tests__/tools/provider-list-shell-flow.test.tsx +++ b/web/__tests__/tools/provider-list-shell-flow.test.tsx @@ -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'], }), })) diff --git a/web/__tests__/tools/tool-browsing-and-filtering.test.tsx b/web/__tests__/tools/tool-browsing-and-filtering.test.tsx index be00661f710..fa1706b047b 100644 --- a/web/__tests__/tools/tool-browsing-and-filtering.test.tsx +++ b/web/__tests__/tools/tool-browsing-and-filtering.test.tsx @@ -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'], }), })) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/__tests__/layout-main.spec.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/__tests__/layout-main.spec.tsx index ec7aad05b74..34fb3859958 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/__tests__/layout-main.spec.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/__tests__/layout-main.spec.tsx @@ -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', () => { , ) + 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( + +
App page content
+
, + ) + 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( diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx index 517c8819e91..fd1e605d935 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx @@ -108,8 +108,8 @@ const AppDetailLayout: FC = (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) ) { diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/view.spec.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/view.spec.tsx index 5ac607fc301..f95ff2b17e0 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/view.spec.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/__tests__/view.spec.tsx @@ -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() + 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() + 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() + + 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() + }) }) }) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/panel.spec.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/panel.spec.tsx index dc2970513a0..13d770439cf 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/panel.spec.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/panel.spec.tsx @@ -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, diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index 7c0de99e0f5..6183110742b 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -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, { diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/view.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/view.tsx index 7f27286a657..9929dab3713 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/view.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/view.tsx @@ -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) => {
} + headerRight={appACLCapabilities.canConfigureTracing ? : null} />
diff --git a/web/app/components/app-sidebar/__tests__/app-detail-section.spec.tsx b/web/app/components/app-sidebar/__tests__/app-detail-section.spec.tsx index 8d83f6c7711..55f92717768 100644 --- a/web/app/components/app-sidebar/__tests__/app-detail-section.spec.tsx +++ b/web/app/components/app-sidebar/__tests__/app-detail-section.spec.tsx @@ -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() + // 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() + // 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() - - // 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() @@ -111,6 +113,7 @@ describe('AppDetailSection', () => { it('should only render logs navigation for workflow apps', () => { // Arrange mockAppMode = 'workflow' + mockAppPermissionKeys = [AppACLPermission.LogAndAnnotation] // Act render() @@ -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() @@ -134,6 +138,7 @@ describe('AppDetailSection', () => { it('should only render logs navigation for completion apps', () => { // Arrange mockAppMode = 'completion' + mockAppPermissionKeys = [AppACLPermission.LogAndAnnotation] // Act render() @@ -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() @@ -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() // 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() diff --git a/web/app/components/app-sidebar/app-detail-section.tsx b/web/app/components/app-sidebar/app-detail-section.tsx index 15d963244cb..c85866e33e6 100644 --- a/web/app/components/app-sidebar/app-detail-section.tsx +++ b/web/app/components/app-sidebar/app-detail-section.tsx @@ -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`, diff --git a/web/app/components/header/account-setting/access-rules-page/permission-set-modal/__tests__/index.spec.tsx b/web/app/components/header/account-setting/access-rules-page/permission-set-modal/__tests__/index.spec.tsx index b28a95a78ee..bb893525126 100644 --- a/web/app/components/header/account-setting/access-rules-page/permission-set-modal/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/access-rules-page/permission-set-modal/__tests__/index.spec.tsx @@ -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', ] diff --git a/web/app/components/header/account-setting/data-source-page-new/__tests__/index.spec.tsx b/web/app/components/header/account-setting/data-source-page-new/__tests__/index.spec.tsx index 871ab0b4321..fb94b6b0776 100644 --- a/web/app/components/header/account-setting/data-source-page-new/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/__tests__/index.spec.tsx @@ -69,7 +69,6 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({ usePluginSettingsAccess: () => ({ canSetPermissions: true, canSetPluginPreferences: true, - canViewInstalledPlugins: true, }), default: () => ({ canSetPermissions: true, diff --git a/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx b/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx index 792dc0b3f75..f7bece10a3d 100644 --- a/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx @@ -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, }), })) diff --git a/web/app/components/header/account-setting/data-source-page-new/index.tsx b/web/app/components/header/account-setting/data-source-page-new/index.tsx index 5200a4df097..a76f33892c8 100644 --- a/web/app/components/header/account-setting/data-source-page-new/index.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/index.tsx @@ -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() diff --git a/web/app/components/header/account-setting/data-source-page-new/plugin-actions.tsx b/web/app/components/header/account-setting/data-source-page-new/plugin-actions.tsx index 5e9c1db9d89..9cfd6bfaf1f 100644 --- a/web/app/components/header/account-setting/data-source-page-new/plugin-actions.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/plugin-actions.tsx @@ -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} /> ({ usePluginSettingsAccess: () => ({ canSetPermissions: true, canSetPluginPreferences: true, - canViewInstalledPlugins: true, }), default: () => ({ referenceSetting: { diff --git a/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx index b82929ca04b..a6500b5e064 100644 --- a/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx @@ -151,7 +151,6 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({ usePluginSettingsAccess: () => ({ canSetPermissions: true, canSetPluginPreferences: true, - canViewInstalledPlugins: true, }), default: () => ({ referenceSetting: mockReferenceSetting, diff --git a/web/app/components/header/account-setting/model-provider-page/index.tsx b/web/app/components/header/account-setting/model-provider-page/index.tsx index 5d51f78c1e9..7ac05246b07 100644 --- a/web/app/components/header/account-setting/model-provider-page/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/index.tsx @@ -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) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/index.spec.tsx index 66f60949904..52e8c21f755 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/index.spec.tsx @@ -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() diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list-item.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list-item.spec.tsx index d21df4c5a89..6b6540ec261 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list-item.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list-item.spec.tsx @@ -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 diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx index 215b349eb0d..c307407c0fd 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx @@ -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( { 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( { 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], diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx index 55a67db72f9..2b47e77d72f 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx @@ -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: () => ({ diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx index c91db81e6b7..a5dd63eeb1e 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx @@ -69,11 +69,11 @@ const ProviderAddedCard: FC = ({ 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) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx index 7fda66c8e4f..ff7e9f5b0af 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx @@ -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 )} { - (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)) && ( onModifyLoadBalancing?.(model)} loadBalancingEnabled={model.load_balancing_enabled} @@ -112,7 +112,7 @@ const ModelListItem = ({ model, provider, isConfigurable, onChange, onModifyLoad ) - : (canManagePlugin && ( + : (canConfigureModels && ( = ({ 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 = ({ { - isConfigurable && canManagePlugins && ( + isConfigurable && canConfigureModels && (
= ({ 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 = ({ detail, onUpdate }) => { modalStates, versionPicker, isFromMarketplace, - canManagePlugin, + canDeletePlugin, canUpdatePlugin, onUpdate, }) @@ -137,7 +137,7 @@ const ProviderCardActions: FC = ({ detail, onUpdate }) => { placement="bottom-start" destructiveRemove showCheckVersion={canUpdatePlugin} - showRemove={canManagePlugin} + showRemove={canDeletePlugin} /> 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() diff --git a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx index 6ed2482b1fb..b98124e6b2d 100644 --- a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx @@ -68,7 +68,7 @@ const SystemModel: FC = ({ 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) diff --git a/web/app/components/integrations/__tests__/page.spec.tsx b/web/app/components/integrations/__tests__/page.spec.tsx index 73a8929b2f0..0badbe00cec 100644 --- a/web/app/components/integrations/__tests__/page.spec.tsx +++ b/web/app/components/integrations/__tests__/page.spec.tsx @@ -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, }), })) diff --git a/web/app/components/integrations/__tests__/tool-provider-list.spec.tsx b/web/app/components/integrations/__tests__/tool-provider-list.spec.tsx index 36dcf43e80b..853cf525625 100644 --- a/web/app/components/integrations/__tests__/tool-provider-list.spec.tsx +++ b/web/app/components/integrations/__tests__/tool-provider-list.spec.tsx @@ -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(), diff --git a/web/app/components/integrations/hooks/use-integration-permissions.ts b/web/app/components/integrations/hooks/use-integration-permissions.ts index 0b694b555e1..869a5065aa1 100644 --- a/web/app/components/integrations/hooks/use-integration-permissions.ts +++ b/web/app/components/integrations/hooks/use-integration-permissions.ts @@ -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, diff --git a/web/app/components/integrations/page.tsx b/web/app/components/integrations/page.tsx index a9ca7cb24a1..ce07fb0586e 100644 --- a/web/app/components/integrations/page.tsx +++ b/web/app/components/integrations/page.tsx @@ -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} />
@@ -311,9 +309,8 @@ export default function IntegrationsPage({ onProviderSearchTextChange={setProviderSearchText} onSwitchToMarketplace={handleSwitchToMarketplace} canInstallPlugin={canInstallPlugin} - canManagePlugin={canManagePlugin} + canDeletePlugin={canDeletePlugin} canUpdatePlugin={canUpdatePlugin} - canViewInstalledPlugins={canViewInstalledPlugins} pluginCategoryToolbarAction={pluginSettingAction} /> diff --git a/web/app/components/integrations/plugin-category-page.tsx b/web/app/components/integrations/plugin-category-page.tsx index 783266c8dd9..c6cc7b2abe7 100644 --- a/web/app/components/integrations/plugin-category-page.tsx +++ b/web/app/components/integrations/plugin-category-page.tsx @@ -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 = ({
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) => ( { @@ -262,7 +261,7 @@ const ProviderList = ({ detail={currentPluginDetail} onUpdate={() => invalidateInstalledPluginList()} onHide={() => setCurrentProviderId(undefined)} - canManagePlugin={canManagePlugin} + canDeletePlugin={canDeletePlugin} canUpdatePlugin={canUpdatePlugin} /> diff --git a/web/app/components/plugins/install-plugin/hooks/__tests__/use-workspace-plugin-install-permission.spec.ts b/web/app/components/plugins/install-plugin/hooks/__tests__/use-workspace-plugin-install-permission.spec.ts index 8fdf23de333..f3df9915058 100644 --- a/web/app/components/plugins/install-plugin/hooks/__tests__/use-workspace-plugin-install-permission.spec.ts +++ b/web/app/components/plugins/install-plugin/hooks/__tests__/use-workspace-plugin-install-permission.spec.ts @@ -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) }) diff --git a/web/app/components/plugins/install-plugin/hooks/use-workspace-plugin-install-permission.ts b/web/app/components/plugins/install-plugin/hooks/use-workspace-plugin-install-permission.ts index de1a2d09870..efaba878813 100644 --- a/web/app/components/plugins/install-plugin/hooks/use-workspace-plugin-install-permission.ts +++ b/web/app/components/plugins/install-plugin/hooks/use-workspace-plugin-install-permission.ts @@ -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, diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts index 57ed30fe15d..1bacf1d2a91 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts +++ b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts @@ -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, diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header/index.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header/index.tsx index 64f8ee94038..a69444ee6a0 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header/index.tsx @@ -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} /> diff --git a/web/app/components/plugins/plugin-detail-panel/index.tsx b/web/app/components/plugins/plugin-detail-panel/index.tsx index f72e251a224..a64da14d5ba 100644 --- a/web/app/components/plugins/plugin-detail-panel/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/index.tsx @@ -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 = ({ - canManagePlugin = true, + canDeletePlugin = true, canUpdatePlugin = true, detail, onUpdate, @@ -83,7 +83,7 @@ const PluginDetailPanel: FC = ({ detail={detail} onUpdate={handleUpdate} onHide={onHide} - canManagePlugin={canManagePlugin} + canDeletePlugin={canDeletePlugin} canUpdatePlugin={canUpdatePlugin} />
diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx index 8db022b5d30..d64afaccdf0 100644 --- a/web/app/components/plugins/plugin-item/index.tsx +++ b/web/app/components/plugins/plugin-item/index.tsx @@ -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 = ({ - canManagePlugin = true, + canDeletePlugin = true, canUpdatePlugin = true, className, plugin, @@ -158,7 +158,7 @@ const PluginItem: FC = ({ usedInApps={5} isShowFetchNewVersion={canUpdatePlugin && source === PluginSource.github} isShowInfo={source === PluginSource.github} - isShowDelete={canManagePlugin} + isShowDelete={canDeletePlugin} meta={meta} onDelete={handleDelete} category={category} diff --git a/web/app/components/plugins/plugin-page/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-page/__tests__/index.spec.tsx index 3a74f455cb2..751f1ce21c8 100644 --- a/web/app/components/plugins/plugin-page/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-page/__tests__/index.spec.tsx @@ -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'], }), })) diff --git a/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts b/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts index dc81db854a7..2ab99dfe878 100644 --- a/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts +++ b/web/app/components/plugins/plugin-page/__tests__/use-reference-setting.spec.ts @@ -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) 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) diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index 914ed72efb0..478e307cb50 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -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, { canInstall: canInstallPlugin, - canManagePlugin, + canDeletePlugin, canUpdatePlugin, - canViewInstalledPlugins, }) - }, [canInstallPlugin, canManagePlugin, canUpdatePlugin, canViewInstalledPlugins, plugins]) + }, [canInstallPlugin, canDeletePlugin, canUpdatePlugin, plugins]) return (
= ({ - canManagePlugin = true, + canDeletePlugin = true, canUpdatePlugin = true, children, pluginList, @@ -22,7 +22,7 @@ const PluginList: FC = ({ ))} diff --git a/web/app/components/plugins/plugin-page/plugins-panel-results.tsx b/web/app/components/plugins/plugin-page/plugins-panel-results.tsx index ce84b37f933..b8a80ee2377 100644 --- a/web/app/components/plugins/plugin-page/plugins-panel-results.tsx +++ b/web/app/components/plugins/plugin-page/plugins-panel-results.tsx @@ -58,7 +58,7 @@ const BuiltinMarketplacePanel = ({ } type PluginsPanelResultsProps = { - canManagePlugin: boolean + canDeletePlugin: boolean canUpdatePlugin: boolean containerRef: RefObject contentFrameClassName: string @@ -80,7 +80,7 @@ type PluginsPanelResultsProps = { } const PluginsPanelResults = ({ - canManagePlugin, + canDeletePlugin, canUpdatePlugin, containerRef, contentFrameClassName, @@ -123,7 +123,7 @@ const PluginsPanelResults = ({ {(hasVisiblePlugins || hasVisibleBuiltinTools) && ( {filteredBuiltinTools.map(collection => ( diff --git a/web/app/components/plugins/plugin-page/plugins-panel.tsx b/web/app/components/plugins/plugin-page/plugins-panel.tsx index d1ccc4e36f0..e22fdf361f5 100644 --- a/web/app/components/plugins/plugin-page/plugins-panel.tsx +++ b/web/app/components/plugins/plugin-page/plugins-panel.tsx @@ -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 && ( diff --git a/web/app/components/plugins/plugin-page/use-reference-setting.ts b/web/app/components/plugins/plugin-page/use-reference-setting.ts index 0d9d6900b58..20c306658fc 100644 --- a/web/app/components/plugins/plugin-page/use-reference-setting.ts +++ b/web/app/components/plugins/plugin-page/use-reference-setting.ts @@ -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, diff --git a/web/i18n/ar-TN/permission-keys.json b/web/i18n/ar-TN/permission-keys.json index 082b345ba66..9f959e4f43e 100644 --- a/web/i18n/ar-TN/permission-keys.json +++ b/web/i18n/ar-TN/permission-keys.json @@ -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": "إدارة الأدوات", diff --git a/web/i18n/de-DE/permission-keys.json b/web/i18n/de-DE/permission-keys.json index 09da9ee9462..c44fd6e940b 100644 --- a/web/i18n/de-DE/permission-keys.json +++ b/web/i18n/de-DE/permission-keys.json @@ -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", diff --git a/web/i18n/en-US/permission-keys.json b/web/i18n/en-US/permission-keys.json index aa2aa3aa26e..c7671008f9a 100644 --- a/web/i18n/en-US/permission-keys.json +++ b/web/i18n/en-US/permission-keys.json @@ -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", diff --git a/web/i18n/es-ES/permission-keys.json b/web/i18n/es-ES/permission-keys.json index b1b551d3caf..1b7c6ddf684 100644 --- a/web/i18n/es-ES/permission-keys.json +++ b/web/i18n/es-ES/permission-keys.json @@ -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", diff --git a/web/i18n/fa-IR/permission-keys.json b/web/i18n/fa-IR/permission-keys.json index a3a2f81c66e..06433b45983 100644 --- a/web/i18n/fa-IR/permission-keys.json +++ b/web/i18n/fa-IR/permission-keys.json @@ -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": "مدیریت ابزارها", diff --git a/web/i18n/fr-FR/permission-keys.json b/web/i18n/fr-FR/permission-keys.json index 931088820dd..d3f7ece339f 100644 --- a/web/i18n/fr-FR/permission-keys.json +++ b/web/i18n/fr-FR/permission-keys.json @@ -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", diff --git a/web/i18n/hi-IN/permission-keys.json b/web/i18n/hi-IN/permission-keys.json index b91e15050ca..b94323257d2 100644 --- a/web/i18n/hi-IN/permission-keys.json +++ b/web/i18n/hi-IN/permission-keys.json @@ -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": "टूल प्रबंधित करें", diff --git a/web/i18n/id-ID/permission-keys.json b/web/i18n/id-ID/permission-keys.json index 3904eb23819..9c1902c20d2 100644 --- a/web/i18n/id-ID/permission-keys.json +++ b/web/i18n/id-ID/permission-keys.json @@ -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", diff --git a/web/i18n/it-IT/permission-keys.json b/web/i18n/it-IT/permission-keys.json index 970a2a58f71..683fa54bf5b 100644 --- a/web/i18n/it-IT/permission-keys.json +++ b/web/i18n/it-IT/permission-keys.json @@ -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", diff --git a/web/i18n/ja-JP/permission-keys.json b/web/i18n/ja-JP/permission-keys.json index 7c6c0c5e12a..5338e8323fe 100644 --- a/web/i18n/ja-JP/permission-keys.json +++ b/web/i18n/ja-JP/permission-keys.json @@ -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": "ツールを管理", diff --git a/web/i18n/ko-KR/permission-keys.json b/web/i18n/ko-KR/permission-keys.json index 363dee55a35..09bb846b200 100644 --- a/web/i18n/ko-KR/permission-keys.json +++ b/web/i18n/ko-KR/permission-keys.json @@ -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": "도구 관리", diff --git a/web/i18n/nl-NL/permission-keys.json b/web/i18n/nl-NL/permission-keys.json index b9a21ae620f..1c0e4bb342e 100644 --- a/web/i18n/nl-NL/permission-keys.json +++ b/web/i18n/nl-NL/permission-keys.json @@ -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", diff --git a/web/i18n/pl-PL/permission-keys.json b/web/i18n/pl-PL/permission-keys.json index 8854ba92135..becbbe93cf8 100644 --- a/web/i18n/pl-PL/permission-keys.json +++ b/web/i18n/pl-PL/permission-keys.json @@ -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", diff --git a/web/i18n/pt-BR/permission-keys.json b/web/i18n/pt-BR/permission-keys.json index 09d623193f6..074c94a2ab6 100644 --- a/web/i18n/pt-BR/permission-keys.json +++ b/web/i18n/pt-BR/permission-keys.json @@ -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", diff --git a/web/i18n/ro-RO/permission-keys.json b/web/i18n/ro-RO/permission-keys.json index cdb49270e3b..8788174f88c 100644 --- a/web/i18n/ro-RO/permission-keys.json +++ b/web/i18n/ro-RO/permission-keys.json @@ -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", diff --git a/web/i18n/ru-RU/permission-keys.json b/web/i18n/ru-RU/permission-keys.json index 059c11b5b68..11a3d40948e 100644 --- a/web/i18n/ru-RU/permission-keys.json +++ b/web/i18n/ru-RU/permission-keys.json @@ -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": "Управление инструментами", diff --git a/web/i18n/sl-SI/permission-keys.json b/web/i18n/sl-SI/permission-keys.json index 0734599f931..c66d297ecb3 100644 --- a/web/i18n/sl-SI/permission-keys.json +++ b/web/i18n/sl-SI/permission-keys.json @@ -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", diff --git a/web/i18n/th-TH/permission-keys.json b/web/i18n/th-TH/permission-keys.json index 5cb9d6ef8a5..bbb7569e45c 100644 --- a/web/i18n/th-TH/permission-keys.json +++ b/web/i18n/th-TH/permission-keys.json @@ -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": "จัดการเครื่องมือ", diff --git a/web/i18n/tr-TR/permission-keys.json b/web/i18n/tr-TR/permission-keys.json index 0932407ea8f..e36366db431 100644 --- a/web/i18n/tr-TR/permission-keys.json +++ b/web/i18n/tr-TR/permission-keys.json @@ -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", diff --git a/web/i18n/uk-UA/permission-keys.json b/web/i18n/uk-UA/permission-keys.json index d977a2b0e61..5112bc98222 100644 --- a/web/i18n/uk-UA/permission-keys.json +++ b/web/i18n/uk-UA/permission-keys.json @@ -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": "Керування інструментами", diff --git a/web/i18n/vi-VN/permission-keys.json b/web/i18n/vi-VN/permission-keys.json index 7cb6c5d09b4..0c599629e66 100644 --- a/web/i18n/vi-VN/permission-keys.json +++ b/web/i18n/vi-VN/permission-keys.json @@ -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ụ", diff --git a/web/i18n/zh-Hans/permission-keys.json b/web/i18n/zh-Hans/permission-keys.json index c6a790af1a3..4e76b85d9f3 100644 --- a/web/i18n/zh-Hans/permission-keys.json +++ b/web/i18n/zh-Hans/permission-keys.json @@ -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": "管理工具", diff --git a/web/i18n/zh-Hant/permission-keys.json b/web/i18n/zh-Hant/permission-keys.json index 39c2607b5d9..57b4974a8b8 100644 --- a/web/i18n/zh-Hant/permission-keys.json +++ b/web/i18n/zh-Hant/permission-keys.json @@ -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": "管理工具", diff --git a/web/utils/app-redirection.spec.ts b/web/utils/app-redirection.spec.ts index 944cc893e3a..d134e94f0ab 100644 --- a/web/utils/app-redirection.spec.ts +++ b/web/utils/app-redirection.spec.ts @@ -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') + }) }) /** diff --git a/web/utils/app-redirection.ts b/web/utils/app-redirection.ts index b7e02f936e3..88427d89432 100644 --- a/web/utils/app-redirection.ts +++ b/web/utils/app-redirection.ts @@ -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` diff --git a/web/utils/permission.spec.ts b/web/utils/permission.spec.ts index 5376c5cfa65..735c3336489 100644 --- a/web/utils/permission.spec.ts +++ b/web/utils/permission.spec.ts @@ -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([]) }) diff --git a/web/utils/permission.ts b/web/utils/permission.ts index ee399a9e143..90017c7ff8f 100644 --- a/web/utils/permission.ts +++ b/web/utils/permission.ts @@ -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), } }