mirror of
https://github.com/langgenius/dify.git
synced 2026-06-25 22:31:10 +08:00
fix: Fix frontend rbac issues (#37872)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
93dd955deb
commit
599d92ef6b
@ -4473,11 +4473,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/provider/detail.tsx": {
|
||||
"jsx-a11y/anchor-has-content": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/provider/tool-item.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
|
||||
@ -5,7 +5,7 @@ import { AppPublisher } from '@/app/components/app/app-publisher'
|
||||
import { AccessMode } from '@/models/access-control'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
const mockFetchAppDetailDirect = vi.fn()
|
||||
const mockFetchAppDetail = vi.fn()
|
||||
const mockSetAppDetail = vi.fn()
|
||||
const mockRefetch = vi.fn()
|
||||
|
||||
@ -69,7 +69,7 @@ vi.mock('@/service/access-control/use-app-access-control', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/service/apps', () => ({
|
||||
fetchAppDetailDirect: (...args: unknown[]) => mockFetchAppDetailDirect(...args),
|
||||
fetchAppDetail: (...args: unknown[]) => mockFetchAppDetail(...args),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/app/overview/embedded', () => ({
|
||||
@ -120,7 +120,7 @@ describe('App Access Control Flow', () => {
|
||||
access_token: 'token-1',
|
||||
},
|
||||
}
|
||||
mockFetchAppDetailDirect.mockResolvedValue({
|
||||
mockFetchAppDetail.mockResolvedValue({
|
||||
...mockAppDetail,
|
||||
access_mode: AccessMode.PUBLIC,
|
||||
})
|
||||
@ -128,7 +128,7 @@ describe('App Access Control Flow', () => {
|
||||
|
||||
it('refreshes app detail after confirming access control updates', async () => {
|
||||
const { queryClient } = renderWithQueryClient(<AppPublisher publishedAt={1700000000} />)
|
||||
const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries').mockResolvedValue()
|
||||
const setQueryDataSpy = vi.spyOn(queryClient, 'setQueryData')
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'workflow.common.publish' }))
|
||||
fireEvent.click(screen.getByText('app.accessControlDialog.accessItems.specific'))
|
||||
@ -138,8 +138,14 @@ describe('App Access Control Flow', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'confirm-access-control' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['apps', 'detail', 'app-1'] })
|
||||
expect(mockFetchAppDetail).toHaveBeenCalledWith({ url: '/apps', id: 'app-1' })
|
||||
})
|
||||
expect(setQueryDataSpy).toHaveBeenCalledWith(['apps', 'detail', 'app-1'], expect.objectContaining({
|
||||
access_mode: AccessMode.PUBLIC,
|
||||
}))
|
||||
expect(mockSetAppDetail).toHaveBeenCalledWith(expect.objectContaining({
|
||||
access_mode: AccessMode.PUBLIC,
|
||||
}))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('access-control-modal')).not.toBeInTheDocument()
|
||||
|
||||
@ -15,7 +15,8 @@ const mockAppState = vi.hoisted(() => ({
|
||||
const mockUpdateAppSiteStatus = vi.hoisted(() => vi.fn())
|
||||
const mockUpdateAppSiteConfig = vi.hoisted(() => vi.fn())
|
||||
const mockUpdateAppSiteAccessToken = vi.hoisted(() => vi.fn())
|
||||
const mockInvalidateQueries = vi.hoisted(() => vi.fn())
|
||||
const mockFetchAppDetail = vi.hoisted(() => vi.fn())
|
||||
const mockSetQueryData = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/app/components/app/store', () => ({
|
||||
useStore: <T,>(selector: (state: typeof mockAppState) => T): T => selector(mockAppState),
|
||||
@ -26,6 +27,7 @@ vi.mock('@/service/use-workflow', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/service/apps', () => ({
|
||||
fetchAppDetail: (...args: unknown[]) => mockFetchAppDetail(...args),
|
||||
updateAppSiteStatus: (...args: unknown[]) => mockUpdateAppSiteStatus(...args),
|
||||
updateAppSiteConfig: (...args: unknown[]) => mockUpdateAppSiteConfig(...args),
|
||||
updateAppSiteAccessToken: (...args: unknown[]) => mockUpdateAppSiteAccessToken(...args),
|
||||
@ -33,7 +35,7 @@ vi.mock('@/service/apps', () => ({
|
||||
|
||||
vi.mock('@tanstack/react-query', () => ({
|
||||
useQueryClient: () => ({
|
||||
invalidateQueries: mockInvalidateQueries,
|
||||
setQueryData: mockSetQueryData,
|
||||
}),
|
||||
}))
|
||||
|
||||
@ -104,7 +106,14 @@ describe('CardView ACL edit guards', () => {
|
||||
mockUpdateAppSiteStatus.mockResolvedValue(mockAppState.appDetail as App)
|
||||
mockUpdateAppSiteConfig.mockResolvedValue(mockAppState.appDetail as App)
|
||||
mockUpdateAppSiteAccessToken.mockResolvedValue({ code: 'token' })
|
||||
mockInvalidateQueries.mockResolvedValue(undefined)
|
||||
mockFetchAppDetail.mockResolvedValue({
|
||||
id: 'app-1',
|
||||
mode: 'chat',
|
||||
permission_keys: ['app.acl.edit'],
|
||||
site: {
|
||||
title: 'Saved site title',
|
||||
},
|
||||
} as unknown as App)
|
||||
})
|
||||
|
||||
// User-facing card actions should not mutate app settings without app ACL edit permission.
|
||||
@ -122,6 +131,7 @@ describe('CardView ACL edit guards', () => {
|
||||
expect(mockUpdateAppSiteStatus).not.toHaveBeenCalled()
|
||||
expect(mockUpdateAppSiteConfig).not.toHaveBeenCalled()
|
||||
expect(mockUpdateAppSiteAccessToken).not.toHaveBeenCalled()
|
||||
expect(mockFetchAppDetail).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call write APIs when app ACL edit permission is present', async () => {
|
||||
@ -153,7 +163,35 @@ describe('CardView ACL edit guards', () => {
|
||||
expect(mockUpdateAppSiteAccessToken).toHaveBeenCalledWith({
|
||||
url: '/apps/app-1/site/access-token-reset',
|
||||
})
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['apps', 'detail', 'app-1'] })
|
||||
await waitFor(() => {
|
||||
expect(mockFetchAppDetail).toHaveBeenCalled()
|
||||
})
|
||||
expect(mockFetchAppDetail).toHaveBeenCalledWith({ url: '/apps', id: 'app-1' })
|
||||
expect(mockSetQueryData).toHaveBeenCalledWith(['apps', 'detail', 'app-1'], expect.objectContaining({
|
||||
site: expect.objectContaining({ title: 'Saved site title' }),
|
||||
}))
|
||||
expect(mockAppState.setAppDetail).toHaveBeenCalledWith(expect.objectContaining({
|
||||
site: expect.objectContaining({ title: 'Saved site title' }),
|
||||
}))
|
||||
})
|
||||
|
||||
it('should refresh the Zustand app detail after saving webapp settings', async () => {
|
||||
const user = userEvent.setup()
|
||||
mockAppState.appDetail.permission_keys = ['app.acl.edit']
|
||||
|
||||
render(<CardView appId="app-1" />)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /save webapp/ }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetchAppDetail).toHaveBeenCalledWith({ url: '/apps', id: 'app-1' })
|
||||
})
|
||||
expect(mockSetQueryData).toHaveBeenCalledWith(['apps', 'detail', 'app-1'], expect.objectContaining({
|
||||
site: expect.objectContaining({ title: 'Saved site title' }),
|
||||
}))
|
||||
expect(mockAppState.setAppDetail).toHaveBeenCalledWith(expect.objectContaining({
|
||||
site: expect.objectContaining({ title: 'Saved site title' }),
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -20,6 +20,7 @@ import { webSocketClient } from '@/app/components/workflow/collaboration/core/we
|
||||
import { isTriggerNode } from '@/app/components/workflow/types'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import {
|
||||
fetchAppDetail,
|
||||
updateAppSiteAccessToken,
|
||||
updateAppSiteConfig,
|
||||
updateAppSiteStatus,
|
||||
@ -40,6 +41,7 @@ const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const setAppDetail = useAppStore(state => state.setAppDetail)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const canEditApp = useMemo(() => getAppACLCapabilities(appDetail?.permission_keys, {
|
||||
@ -88,12 +90,14 @@ const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
|
||||
|
||||
const updateAppDetail = useCallback(async () => {
|
||||
try {
|
||||
await queryClient.invalidateQueries({ queryKey: [...appDetailQueryKeyPrefix, appId] })
|
||||
const res = await fetchAppDetail({ url: '/apps', id: appId })
|
||||
queryClient.setQueryData([...appDetailQueryKeyPrefix, appId], res)
|
||||
setAppDetail({ ...res })
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}, [appId, queryClient])
|
||||
}, [appId, queryClient, setAppDetail])
|
||||
|
||||
const handleCallbackResult = (err: Error | null, message?: I18nKeysByPrefix<'common', 'actionMsg.'>) => {
|
||||
const type = err ? 'error' : 'success'
|
||||
|
||||
@ -19,7 +19,7 @@ const mockRefetch = vi.fn()
|
||||
const mockUseGetUserCanAccessApp = vi.fn()
|
||||
const mockOpenAsyncWindow = vi.fn()
|
||||
const mockFetchInstalledAppList = vi.fn()
|
||||
const mockFetchAppDetailDirect = vi.fn()
|
||||
const mockFetchAppDetail = vi.fn()
|
||||
const mockToastError = vi.fn()
|
||||
const mockWindowOpen = vi.fn()
|
||||
const mockInvalidateAppWorkflow = vi.fn()
|
||||
@ -90,7 +90,7 @@ vi.mock('@/service/explore', () => ({
|
||||
const mockPublishToCreatorsPlatform = vi.fn()
|
||||
|
||||
vi.mock('@/service/apps', () => ({
|
||||
fetchAppDetailDirect: (...args: unknown[]) => mockFetchAppDetailDirect(...args),
|
||||
fetchAppDetail: (...args: unknown[]) => mockFetchAppDetail(...args),
|
||||
publishToCreatorsPlatform: (...args: unknown[]) => mockPublishToCreatorsPlatform(...args),
|
||||
}))
|
||||
|
||||
@ -211,7 +211,7 @@ describe('AppPublisher', () => {
|
||||
mockFetchInstalledAppList.mockResolvedValue({
|
||||
installed_apps: [{ id: 'installed-1' }],
|
||||
})
|
||||
mockFetchAppDetailDirect.mockResolvedValue({
|
||||
mockFetchAppDetail.mockResolvedValue({
|
||||
id: 'app-1',
|
||||
access_mode: AccessMode.PUBLIC,
|
||||
})
|
||||
@ -416,7 +416,7 @@ describe('AppPublisher', () => {
|
||||
publishedAt={Date.now()}
|
||||
/>,
|
||||
)
|
||||
const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries').mockResolvedValue()
|
||||
const setQueryDataSpy = vi.spyOn(queryClient, 'setQueryData')
|
||||
|
||||
fireEvent.click(screen.getByText('common.publish'))
|
||||
fireEvent.click(screen.getByText('publisher-access-control'))
|
||||
@ -426,8 +426,14 @@ describe('AppPublisher', () => {
|
||||
fireEvent.click(screen.getByText('confirm-access-control'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['apps', 'detail', 'app-1'] })
|
||||
expect(mockFetchAppDetail).toHaveBeenCalledWith({ url: '/apps', id: 'app-1' })
|
||||
})
|
||||
expect(setQueryDataSpy).toHaveBeenCalledWith(['apps', 'detail', 'app-1'], expect.objectContaining({
|
||||
access_mode: AccessMode.PUBLIC,
|
||||
}))
|
||||
expect(mockSetAppDetail).toHaveBeenCalledWith(expect.objectContaining({
|
||||
access_mode: AccessMode.PUBLIC,
|
||||
}))
|
||||
})
|
||||
|
||||
it('should open the installed explore page through the async window helper', async () => {
|
||||
@ -667,7 +673,7 @@ describe('AppPublisher', () => {
|
||||
fireEvent.click(screen.getByText('confirm-access-control'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetchAppDetailDirect).not.toHaveBeenCalled()
|
||||
expect(mockFetchAppDetail).not.toHaveBeenCalled()
|
||||
})
|
||||
expect(screen.getByTestId('access-control'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -39,7 +39,7 @@ import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import { AccessMode } from '@/models/access-control'
|
||||
import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/access-control/use-app-access-control'
|
||||
import { publishToCreatorsPlatform } from '@/service/apps'
|
||||
import { fetchAppDetail, publishToCreatorsPlatform } from '@/service/apps'
|
||||
import { fetchInstalledAppList } from '@/service/explore'
|
||||
import { appDetailQueryKeyPrefix } from '@/service/use-apps'
|
||||
import { useInvalidateAppWorkflow } from '@/service/use-workflow'
|
||||
@ -130,6 +130,7 @@ export function AppPublisher({
|
||||
|
||||
const workflowStore = use(WorkflowContext)
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const setAppDetail = useAppStore(state => state.setAppDetail)
|
||||
const canManageTools = useCanManageTools()
|
||||
const queryClient = useQueryClient()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
@ -242,7 +243,9 @@ export function AppPublisher({
|
||||
if (!appDetail)
|
||||
return
|
||||
try {
|
||||
await queryClient.invalidateQueries({ queryKey: [...appDetailQueryKeyPrefix, appDetail.id] })
|
||||
const res = await fetchAppDetail({ url: '/apps', id: appDetail.id })
|
||||
queryClient.setQueryData([...appDetailQueryKeyPrefix, appDetail.id], res)
|
||||
setAppDetail({ ...res })
|
||||
}
|
||||
finally {
|
||||
setShowAppAccessControl(false)
|
||||
|
||||
@ -17,6 +17,7 @@ const mockPush = vi.fn()
|
||||
const mockSetAppDetail = vi.fn()
|
||||
const mockOnChangeStatus = vi.fn()
|
||||
const mockOnGenerateCode = vi.fn()
|
||||
const mockFetchAppDetail = vi.fn()
|
||||
|
||||
let mockWorkflow: { graph?: { nodes?: Array<{ data?: { type?: string, variables?: Array<Record<string, unknown>> } }> } } | null = null
|
||||
let mockAccessSubjects: { groups?: unknown[], members?: unknown[] } = { groups: [], members: [] }
|
||||
@ -59,6 +60,10 @@ vi.mock('@/service/access-control/use-app-access-control', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/apps', () => ({
|
||||
fetchAppDetail: (...args: unknown[]) => mockFetchAppDetail(...args),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/develop/secret-key/secret-key-button', () => ({
|
||||
default: ({ appId }: { appId: string }) => <div data-testid="secret-key-button">{appId}</div>,
|
||||
}))
|
||||
@ -125,6 +130,14 @@ describe('AppCard', () => {
|
||||
groups: [],
|
||||
members: [],
|
||||
}
|
||||
mockFetchAppDetail.mockResolvedValue({
|
||||
id: 'app-1',
|
||||
access_mode: AccessMode.PUBLIC,
|
||||
site: {
|
||||
app_base_url: 'https://example.com',
|
||||
access_token: 'access-token',
|
||||
},
|
||||
} as AppDetailResponse)
|
||||
})
|
||||
|
||||
it('should open the published webapp when launch is clicked', () => {
|
||||
@ -405,7 +418,7 @@ describe('AppCard', () => {
|
||||
onGenerateCode={mockOnGenerateCode}
|
||||
/>,
|
||||
)
|
||||
const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries').mockResolvedValue()
|
||||
const setQueryDataSpy = vi.spyOn(queryClient, 'setQueryData')
|
||||
|
||||
fireEvent.click(screen.getByText('publishApp.notSet'))
|
||||
expect(screen.getByTestId('access-control-modal')).toBeInTheDocument()
|
||||
@ -413,8 +426,14 @@ describe('AppCard', () => {
|
||||
fireEvent.click(screen.getByText('confirm-access-control'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['apps', 'detail', 'app-1'] })
|
||||
expect(mockFetchAppDetail).toHaveBeenCalledWith({ url: '/apps', id: 'app-1' })
|
||||
})
|
||||
expect(setQueryDataSpy).toHaveBeenCalledWith(['apps', 'detail', 'app-1'], expect.objectContaining({
|
||||
access_mode: AccessMode.PUBLIC,
|
||||
}))
|
||||
expect(mockSetAppDetail).toHaveBeenCalledWith(expect.objectContaining({
|
||||
access_mode: AccessMode.PUBLIC,
|
||||
}))
|
||||
})
|
||||
|
||||
it('should surface the learn-more tooltip action for workflows without a start node', () => {
|
||||
@ -467,14 +486,14 @@ describe('AppCard', () => {
|
||||
|
||||
it('should report refresh failures from access control updates', async () => {
|
||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { })
|
||||
mockFetchAppDetail.mockRejectedValueOnce(new Error('refresh failed'))
|
||||
|
||||
const { queryClient } = render(
|
||||
render(
|
||||
<AppCard
|
||||
appInfo={appInfo}
|
||||
onChangeStatus={mockOnChangeStatus}
|
||||
/>,
|
||||
)
|
||||
vi.spyOn(queryClient, 'invalidateQueries').mockRejectedValueOnce(new Error('refresh failed'))
|
||||
|
||||
fireEvent.click(screen.getByText('publishApp.notSet'))
|
||||
fireEvent.click(screen.getByText('confirm-access-control'))
|
||||
|
||||
@ -19,6 +19,7 @@ import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { AccessMode } from '@/models/access-control'
|
||||
import { usePathname, useRouter } from '@/next/navigation'
|
||||
import { useAppWhiteListSubjects } from '@/service/access-control/use-app-access-control'
|
||||
import { fetchAppDetail } from '@/service/apps'
|
||||
import { appDetailQueryKeyPrefix } from '@/service/use-apps'
|
||||
import { useAppWorkflow } from '@/service/use-workflow'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
@ -83,6 +84,7 @@ function AppCard({
|
||||
const { data: currentWorkflow } = useAppWorkflow(shouldFetchWorkflow ? appInfo.id : '')
|
||||
const docLink = useDocLink()
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const setAppDetail = useAppStore(state => state.setAppDetail)
|
||||
const [showSettingsModal, setShowSettingsModal] = useState(false)
|
||||
const [showEmbedded, setShowEmbedded] = useState(false)
|
||||
const [showCustomizeModal, setShowCustomizeModal] = useState(false)
|
||||
@ -157,13 +159,15 @@ function AppCard({
|
||||
return
|
||||
|
||||
try {
|
||||
await queryClient.invalidateQueries({ queryKey: [...appDetailQueryKeyPrefix, appDetail.id] })
|
||||
const res = await fetchAppDetail({ url: '/apps', id: appDetail.id })
|
||||
queryClient.setQueryData([...appDetailQueryKeyPrefix, appDetail.id], res)
|
||||
setAppDetail({ ...res })
|
||||
setShowAccessControl(false)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to fetch app detail:', error)
|
||||
}
|
||||
}, [appDetail, queryClient])
|
||||
}, [appDetail, queryClient, setAppDetail])
|
||||
|
||||
const operationKeys = useMemo(() => getAppCardOperationKeys({
|
||||
cardType,
|
||||
|
||||
@ -13,8 +13,8 @@ describe('AccountSetting Constants', () => {
|
||||
it('should have correct ACCOUNT_SETTING_TAB values', () => {
|
||||
expect(ACCOUNT_SETTING_TAB.PROVIDER).toBe('provider')
|
||||
expect(ACCOUNT_SETTING_TAB.MEMBERS).toBe('members')
|
||||
expect(ACCOUNT_SETTING_TAB.PERMISSIONS).toBe('permissions')
|
||||
expect(ACCOUNT_SETTING_TAB.ACCESS_RULES).toBe('access-rules')
|
||||
expect(ACCOUNT_SETTING_TAB.ROLES_AND_PERMISSIONS).toBe('roles-and-permissions')
|
||||
expect(ACCOUNT_SETTING_TAB.PERMISSION_SET).toBe('permission-set')
|
||||
expect(ACCOUNT_SETTING_TAB.BILLING).toBe('billing')
|
||||
expect(ACCOUNT_SETTING_TAB.DATA_SOURCE).toBe('data-source')
|
||||
expect(ACCOUNT_SETTING_TAB.API_BASED_EXTENSION).toBe('custom-endpoint')
|
||||
@ -28,8 +28,8 @@ describe('AccountSetting Constants', () => {
|
||||
})
|
||||
|
||||
it('isValidSettingsTab should include integrations tabs', () => {
|
||||
expect(isValidSettingsTab('permissions')).toBe(true)
|
||||
expect(isValidSettingsTab('access-rules')).toBe(true)
|
||||
expect(isValidSettingsTab('roles-and-permissions')).toBe(true)
|
||||
expect(isValidSettingsTab('permission-set')).toBe(true)
|
||||
expect(isValidSettingsTab('billing')).toBe(true)
|
||||
expect(isValidSettingsTab('preferences')).toBe(true)
|
||||
expect(isValidSettingsTab('language')).toBe(true)
|
||||
|
||||
@ -241,7 +241,7 @@ describe('AccountSetting', () => {
|
||||
expect(screen.queryByText('common.settings.provider'))!.not.toBeInTheDocument()
|
||||
expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(0)
|
||||
expect(screen.getByRole('button', { name: 'common.settings.rolesAndPermissions' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'common.settings.resourceAccess' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'common.settings.permissionSet' })).toBeInTheDocument()
|
||||
expect(screen.getByText('common.settings.billing'))!.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.dataSource'))!.not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.customEndpoint'))!.not.toBeInTheDocument()
|
||||
@ -357,7 +357,7 @@ describe('AccountSetting', () => {
|
||||
// Assert
|
||||
expect(screen.getByRole('button', { name: 'common.settings.members' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'common.settings.rolesAndPermissions' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'common.settings.resourceAccess' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'common.settings.permissionSet' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'common.settings.billing' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'custom.custom' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'common.settings.preferences' })).toBeInTheDocument()
|
||||
@ -400,7 +400,7 @@ describe('AccountSetting', () => {
|
||||
expect(screen.getByText('common.settings.preferences'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide role and resource access entries when role management permission is missing', () => {
|
||||
it('should hide role and permission set entries when role management permission is missing', () => {
|
||||
// Arrange
|
||||
const contextWithoutRoleManagePermission = {
|
||||
...baseAppContextValue,
|
||||
@ -415,22 +415,22 @@ describe('AccountSetting', () => {
|
||||
// Assert
|
||||
expect(screen.getByRole('button', { name: 'common.settings.members' })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'common.settings.rolesAndPermissions' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'common.settings.resourceAccess' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'common.settings.permissionSet' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide role and resource access entries when RBAC is disabled', () => {
|
||||
it('should hide role and permission set entries when RBAC is disabled', () => {
|
||||
// Act
|
||||
renderAccountSetting({ rbacEnabled: false })
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('button', { name: 'common.settings.members' })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'common.settings.rolesAndPermissions' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'common.settings.resourceAccess' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'common.settings.permissionSet' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render direct role pages when RBAC is disabled', () => {
|
||||
// Act
|
||||
renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.ACCESS_RULES, rbacEnabled: false })
|
||||
renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.PERMISSION_SET, rbacEnabled: false })
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByTestId('access-rules-page')).not.toBeInTheDocument()
|
||||
@ -554,9 +554,9 @@ describe('AccountSetting', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.settings.rolesAndPermissions' }))
|
||||
expect(screen.getByTestId('permissions-page')).toBeInTheDocument()
|
||||
|
||||
// Resource Access
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.settings.resourceAccess' }))
|
||||
expect(screen.getByText('common.settings.resourceAccessDescription')).toBeInTheDocument()
|
||||
// Permission Set
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.settings.permissionSet' }))
|
||||
expect(screen.getByText('common.settings.permissionSetDescription')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('access-rules-page')).toBeInTheDocument()
|
||||
|
||||
// Language
|
||||
|
||||
@ -73,4 +73,46 @@ describe('WorkspaceRoleCheckboxList', () => {
|
||||
expect(screen.getByRole('radio', { name: /First role/i })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('checkbox', { name: /First role/i })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show legacy role descriptions when only one role is allowed', () => {
|
||||
vi.mocked(useWorkspaceRoleList).mockReturnValue({
|
||||
data: {
|
||||
pages: [{
|
||||
data: [
|
||||
createRole({ id: 'admin', name: 'admin' }),
|
||||
createRole({ id: 'editor', name: 'editor' }),
|
||||
createRole({ id: 'normal', name: 'normal' }),
|
||||
createRole({ id: 'dataset_operator', name: 'dataset_operator' }),
|
||||
],
|
||||
pagination: {
|
||||
total_count: 4,
|
||||
per_page: 20,
|
||||
current_page: 1,
|
||||
total_pages: 1,
|
||||
},
|
||||
}],
|
||||
pageParams: [1],
|
||||
},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
hasNextPage: false,
|
||||
isFetchingNextPage: false,
|
||||
fetchNextPage: vi.fn(),
|
||||
} as unknown as ReturnType<typeof useWorkspaceRoleList>)
|
||||
|
||||
render(
|
||||
<WorkspaceRoleCheckboxList
|
||||
selectedRoleIds={['editor']}
|
||||
selectedRoles={[createRole({ id: 'editor', name: 'editor' })]}
|
||||
allowMultipleRoles={false}
|
||||
onSelectedRolesChange={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('common.members.adminTip')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.members.editorTip')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.members.normalTip')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.members.datasetOperatorTip')).toBeInTheDocument()
|
||||
expect(screen.queryByText('permission.role.noDescription')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -6,8 +6,8 @@ export const ACCOUNT_SETTING_MODAL_ACTION = 'showSettings'
|
||||
export const ACCOUNT_SETTING_TAB = {
|
||||
PROVIDER: 'provider',
|
||||
MEMBERS: 'members',
|
||||
PERMISSIONS: 'permissions',
|
||||
ACCESS_RULES: 'access-rules',
|
||||
ROLES_AND_PERMISSIONS: 'roles-and-permissions',
|
||||
PERMISSION_SET: 'permission-set',
|
||||
BILLING: 'billing',
|
||||
DATA_SOURCE: 'data-source',
|
||||
API_BASED_EXTENSION: 'custom-endpoint',
|
||||
@ -22,8 +22,8 @@ export const DEFAULT_ACCOUNT_SETTING_TAB = ACCOUNT_SETTING_TAB.MEMBERS
|
||||
|
||||
const WORKSPACE_SETTING_TAB_VALUES = [
|
||||
ACCOUNT_SETTING_TAB.MEMBERS,
|
||||
ACCOUNT_SETTING_TAB.PERMISSIONS,
|
||||
ACCOUNT_SETTING_TAB.ACCESS_RULES,
|
||||
ACCOUNT_SETTING_TAB.ROLES_AND_PERMISSIONS,
|
||||
ACCOUNT_SETTING_TAB.PERMISSION_SET,
|
||||
ACCOUNT_SETTING_TAB.BILLING,
|
||||
ACCOUNT_SETTING_TAB.CUSTOM,
|
||||
] as const
|
||||
|
||||
@ -63,7 +63,7 @@ export default function AccountSetting({
|
||||
const activeMenu = (() => {
|
||||
if (normalizedActiveTab === ACCOUNT_SETTING_TAB.BILLING && !canViewBilling)
|
||||
return ACCOUNT_SETTING_TAB.PREFERENCES
|
||||
if ((normalizedActiveTab === ACCOUNT_SETTING_TAB.PERMISSIONS || normalizedActiveTab === ACCOUNT_SETTING_TAB.ACCESS_RULES) && !canManageWorkspaceRoles)
|
||||
if ((normalizedActiveTab === ACCOUNT_SETTING_TAB.ROLES_AND_PERMISSIONS || normalizedActiveTab === ACCOUNT_SETTING_TAB.PERMISSION_SET) && !canManageWorkspaceRoles)
|
||||
return ACCOUNT_SETTING_TAB.MEMBERS
|
||||
return normalizedActiveTab
|
||||
})()
|
||||
@ -83,15 +83,15 @@ export default function AccountSetting({
|
||||
activeIcon: <span className={cn('i-ri-group-2-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.PERMISSIONS,
|
||||
key: ACCOUNT_SETTING_TAB.ROLES_AND_PERMISSIONS,
|
||||
name: t('settings.rolesAndPermissions', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-shield-user-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-shield-user-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.ACCESS_RULES,
|
||||
name: t('settings.resourceAccess', { ns: 'common' }),
|
||||
description: t('settings.resourceAccessDescription', { ns: 'common' }),
|
||||
key: ACCOUNT_SETTING_TAB.PERMISSION_SET,
|
||||
name: t('settings.permissionSet', { ns: 'common' }),
|
||||
description: t('settings.permissionSetDescription', { ns: 'common' }),
|
||||
icon: <span className={cn('i-ri-lock-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-lock-2-fill', iconClassName)} />,
|
||||
},
|
||||
@ -136,8 +136,8 @@ export default function AccountSetting({
|
||||
visibleTabs.push(ACCOUNT_SETTING_TAB.MEMBERS)
|
||||
|
||||
if (canManageWorkspaceRoles) {
|
||||
visibleTabs.push(ACCOUNT_SETTING_TAB.PERMISSIONS)
|
||||
visibleTabs.push(ACCOUNT_SETTING_TAB.ACCESS_RULES)
|
||||
visibleTabs.push(ACCOUNT_SETTING_TAB.ROLES_AND_PERMISSIONS)
|
||||
visibleTabs.push(ACCOUNT_SETTING_TAB.PERMISSION_SET)
|
||||
}
|
||||
|
||||
if (canViewBilling)
|
||||
@ -262,8 +262,8 @@ export default function AccountSetting({
|
||||
/>
|
||||
)}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.MEMBERS && <MembersPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.PERMISSIONS && <PermissionsPage containerRef={scrollContainerRef} />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.ACCESS_RULES && <AccessRulesPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.ROLES_AND_PERMISSIONS && <PermissionsPage containerRef={scrollContainerRef} />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.PERMISSION_SET && <AccessRulesPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.BILLING && <BillingPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.DATA_SOURCE && <DataSourcePage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.API_BASED_EXTENSION && <ApiBasedExtensionPage />}
|
||||
|
||||
@ -224,6 +224,18 @@ describe('MembersPage', () => {
|
||||
expect(screen.getByTestId('member-row-1').children[2])!.toHaveClass('min-w-0', 'grow')
|
||||
})
|
||||
|
||||
it('should render plural roles column header when RBAC is enabled', () => {
|
||||
renderWithSystemFeatures(<MembersPage />, {
|
||||
systemFeatures: {
|
||||
is_email_setup: true,
|
||||
rbac_enabled: true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(screen.getByText('common.members.roles', { selector: '.system-xs-medium-uppercase' }))!.toHaveClass('min-w-0', 'grow')
|
||||
expect(screen.queryByText('common.members.role', { selector: '.system-xs-medium-uppercase' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should open and close invite modal', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import type { Role } from '@/models/access-control'
|
||||
import type { Member } from '@/models/common'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { screen } from '@testing-library/react'
|
||||
import { screen, waitFor, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
|
||||
import { useUpdateRolesOfMember } from '@/service/access-control/use-member-roles'
|
||||
@ -97,6 +97,32 @@ describe('MemberMenu', () => {
|
||||
} as unknown as ReturnType<typeof useWorkspaceRoleList>)
|
||||
})
|
||||
|
||||
it('should show edit role copy when multiple roles are disabled', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
renderWithSystemFeatures(
|
||||
<MemberMenu
|
||||
member={member}
|
||||
isCurrentUser={false}
|
||||
allowMultipleRoles={false}
|
||||
/>,
|
||||
{
|
||||
systemFeatures: {
|
||||
rbac_enabled: false,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /members\.memberActions/i }))
|
||||
|
||||
expect(screen.getByRole('menuitem', { name: /common\.members\.editRole/i })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('menuitem', { name: /common\.members\.assignRoles/i })).not.toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('menuitem', { name: /common\.members\.editRole/i }))
|
||||
|
||||
expect(screen.getByRole('dialog', { name: /common\.members\.editRole/i })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should submit only one selected role from the assign modal when RBAC is disabled', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
@ -114,7 +140,7 @@ describe('MemberMenu', () => {
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /members\.memberActions/i }))
|
||||
await user.click(screen.getByRole('menuitem', { name: /members\.assignRoles/i }))
|
||||
await user.click(screen.getByRole('menuitem', { name: /members\.editRole/i }))
|
||||
await user.click(screen.getByRole('radio', { name: /Second role/i }))
|
||||
await user.click(screen.getByRole('button', { name: /common\.operation\.confirm/i }))
|
||||
|
||||
@ -124,7 +150,7 @@ describe('MemberMenu', () => {
|
||||
}, expect.any(Object))
|
||||
})
|
||||
|
||||
it('should refresh and invalidate members after removing a member', async () => {
|
||||
it('should require confirmation before removing a member', async () => {
|
||||
const user = userEvent.setup()
|
||||
const queryClient = createQueryClient()
|
||||
const membersQueryKey = [...commonQueryKeys.members, 'en-US']
|
||||
@ -143,8 +169,18 @@ describe('MemberMenu', () => {
|
||||
await user.click(screen.getByRole('button', { name: /members\.memberActions/i }))
|
||||
await user.click(screen.getByRole('menuitem', { name: /members\.removeFromTeam/i }))
|
||||
|
||||
expect(deleteMemberOrCancelInvitation).toHaveBeenCalledWith({
|
||||
url: '/workspaces/current/members/member-1',
|
||||
const dialog = screen.getByRole('alertdialog', {
|
||||
name: /common\.members\.removeFromTeamConfirmTitle:\{"memberName":"Member User"\}/i,
|
||||
})
|
||||
expect(dialog).toHaveTextContent('common.members.removeFromTeamConfirmDescription')
|
||||
expect(deleteMemberOrCancelInvitation).not.toHaveBeenCalled()
|
||||
|
||||
await user.click(within(dialog).getByRole('button', { name: /common\.operation\.confirm/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(deleteMemberOrCancelInvitation).toHaveBeenCalledWith({
|
||||
url: '/workspaces/current/members/member-1',
|
||||
})
|
||||
})
|
||||
expect(queryClient.getQueryState(membersQueryKey)?.isInvalidated).toBe(true)
|
||||
expect(toast.success).toHaveBeenCalledWith('common.actionMsg.modifiedSuccessfully')
|
||||
|
||||
@ -19,4 +19,14 @@ describe('RoleBadges', () => {
|
||||
expect(screen.queryByTitle('Editor')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should keep the wrapper rendered when role names are empty', () => {
|
||||
const { container } = render(<RoleBadges roleNames={[]} className="role-badges-empty" />)
|
||||
|
||||
const wrapper = container.querySelector('.role-badges-empty')
|
||||
expect(wrapper).toBeInTheDocument()
|
||||
expect(wrapper).toBeEmptyDOMElement()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -49,6 +49,33 @@ describe('AssignRolesModal', () => {
|
||||
})
|
||||
|
||||
describe('Role selection', () => {
|
||||
it('should hide selected count when multiple roles are disabled', () => {
|
||||
render(
|
||||
<AssignRolesModal
|
||||
selectedRoles={[roles[0]!]}
|
||||
allowMultipleRoles={false}
|
||||
onClose={vi.fn()}
|
||||
onSubmit={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText(/common\.members\.assignRolesModal\.selectedCount/i)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show single-role description when multiple roles are disabled', () => {
|
||||
render(
|
||||
<AssignRolesModal
|
||||
selectedRoles={[roles[0]!]}
|
||||
allowMultipleRoles={false}
|
||||
onClose={vi.fn()}
|
||||
onSubmit={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText(/common\.members\.assignRolesModal\.singleDescription/i)).toBeInTheDocument()
|
||||
expect(screen.queryByText(/common\.members\.assignRolesModal\.description/i)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should disable confirm when the last selected role is unchecked', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
|
||||
@ -32,6 +32,19 @@ const AssignRolesModalBody = ({
|
||||
const [selected, setSelected] = useState(selectedRoles)
|
||||
const selectedRoleIds = selected.map(role => role.id)
|
||||
const isConfirmDisabled = selected.length === 0
|
||||
const title = allowMultipleRoles
|
||||
? t('members.assignRolesModal.title', { ns: 'common', defaultValue: 'Assign Roles' })
|
||||
: t('members.editRole', { ns: 'common', defaultValue: 'Edit Role' })
|
||||
const description = allowMultipleRoles
|
||||
? t('members.assignRolesModal.description', {
|
||||
ns: 'common',
|
||||
defaultValue:
|
||||
'Select roles to assign to this member. All permissions from selected roles will be combined.',
|
||||
})
|
||||
: t('members.assignRolesModal.singleDescription', {
|
||||
ns: 'common',
|
||||
defaultValue: 'Select one role to assign to this member.',
|
||||
})
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (isConfirmDisabled)
|
||||
@ -50,14 +63,10 @@ const AssignRolesModalBody = ({
|
||||
<DialogCloseButton />
|
||||
<div className="pr-8">
|
||||
<DialogTitle className="system-xl-semibold text-text-primary">
|
||||
{t('members.assignRolesModal.title', { ns: 'common', defaultValue: 'Assign Roles' })}
|
||||
{title}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="mt-1 system-sm-regular text-text-tertiary">
|
||||
{t('members.assignRolesModal.description', {
|
||||
ns: 'common',
|
||||
defaultValue:
|
||||
'Select roles to assign to this member. All permissions from selected roles will be combined.',
|
||||
})}
|
||||
{description}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,14 +78,16 @@ const AssignRolesModalBody = ({
|
||||
onSelectedRolesChange={setSelected}
|
||||
/>
|
||||
|
||||
<div className="flex shrink-0 items-center justify-between gap-3 border-t border-divider-subtle px-6 py-4">
|
||||
<div className="system-xs-regular text-text-tertiary">
|
||||
{t('members.assignRolesModal.selectedCount', {
|
||||
ns: 'common',
|
||||
count: selected.length,
|
||||
})}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex shrink-0 items-center gap-3 border-t border-divider-subtle px-6 py-4">
|
||||
{allowMultipleRoles && (
|
||||
<div className="system-xs-regular text-text-tertiary">
|
||||
{t('members.assignRolesModal.selectedCount', {
|
||||
ns: 'common',
|
||||
count: selected.length,
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button variant="secondary" onClick={onClose}>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
|
||||
@ -45,6 +45,9 @@ const MembersPage = () => {
|
||||
const [detailsMember, setDetailsMember] = useState<Member | null>(null)
|
||||
|
||||
const canManageMembers = hasPermission(workspacePermissionKeys, 'workspace.member.manage')
|
||||
const roleColumnLabel = systemFeatures.rbac_enabled
|
||||
? t('members.roles', { ns: 'common' })
|
||||
: t('members.role', { ns: 'common' })
|
||||
|
||||
const handleOpenDetails = useCallback((member: Member) => {
|
||||
setDetailsMember(member)
|
||||
@ -151,7 +154,7 @@ const MembersPage = () => {
|
||||
<div className="flex min-w-120 items-center border-b border-divider-regular py-1.75">
|
||||
<div className="w-65 shrink-0 px-3 system-xs-medium-uppercase text-text-tertiary">{t('members.name', { ns: 'common' })}</div>
|
||||
<div className="w-30 shrink-0 system-xs-medium-uppercase text-text-tertiary">{t('members.lastActive', { ns: 'common' })}</div>
|
||||
<div className="min-w-0 grow px-3 system-xs-medium-uppercase text-text-tertiary">{t('members.role', { ns: 'common' })}</div>
|
||||
<div className="min-w-0 grow px-3 system-xs-medium-uppercase text-text-tertiary">{roleColumnLabel}</div>
|
||||
</div>
|
||||
<div className="relative min-w-120">
|
||||
{accounts.map(account => (
|
||||
|
||||
@ -114,6 +114,39 @@ describe('RoleSelector', () => {
|
||||
expect(getRoleOption('Editor')).toHaveAttribute('aria-checked', 'true')
|
||||
})
|
||||
|
||||
it('should show legacy descriptions for built-in roles without descriptions', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
mockUseWorkspaceRoleList({
|
||||
pages: [{
|
||||
data: [
|
||||
createRole({ id: 'admin', name: 'admin', description: '' }),
|
||||
createRole({ id: 'editor', name: 'editor', description: '' }),
|
||||
createRole({ id: 'normal', name: 'normal', description: '' }),
|
||||
createRole({ id: 'dataset_operator', name: 'dataset_operator', description: '' }),
|
||||
],
|
||||
pagination: {
|
||||
total_count: 4,
|
||||
per_page: 20,
|
||||
current_page: 1,
|
||||
total_pages: 1,
|
||||
},
|
||||
}],
|
||||
})
|
||||
|
||||
render(<RoleSelectorWrapper initialRole="" />)
|
||||
|
||||
await user.click(getTrigger())
|
||||
|
||||
const roleMenu = getRoleMenu()
|
||||
|
||||
expect(within(roleMenu).getByText(/common\.members\.adminTip/i)).toBeInTheDocument()
|
||||
expect(within(roleMenu).getByText(/common\.members\.editorTip/i)).toBeInTheDocument()
|
||||
expect(within(roleMenu).getByText(/common\.members\.normalTip/i)).toBeInTheDocument()
|
||||
expect(within(roleMenu).getByText(/common\.members\.datasetOperatorTip/i)).toBeInTheDocument()
|
||||
expect(within(roleMenu).queryByText(/permission\.role\.noDescription/i)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should update selected role name after user chooses a role', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { Role } from '@/models/access-control'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
DropdownMenu,
|
||||
@ -19,6 +20,28 @@ type RoleSelectorProps = {
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
const LEGACY_ROLE_DESCRIPTION_KEY_MAP = {
|
||||
admin: 'members.adminTip',
|
||||
editor: 'members.editorTip',
|
||||
normal: 'members.normalTip',
|
||||
dataset_operator: 'members.datasetOperatorTip',
|
||||
} as const
|
||||
|
||||
type LegacyRoleKey = keyof typeof LEGACY_ROLE_DESCRIPTION_KEY_MAP
|
||||
|
||||
const normalizeLegacyRoleKey = (value: string) => value.trim().toLowerCase()
|
||||
|
||||
const isLegacyRoleKey = (value: string): value is LegacyRoleKey =>
|
||||
Object.prototype.hasOwnProperty.call(LEGACY_ROLE_DESCRIPTION_KEY_MAP, value)
|
||||
|
||||
const getLegacyRoleDescriptionKey = (role: Role) => {
|
||||
const candidateKeys = [
|
||||
normalizeLegacyRoleKey(role.name),
|
||||
normalizeLegacyRoleKey(role.id),
|
||||
]
|
||||
|
||||
return candidateKeys.find(isLegacyRoleKey)
|
||||
}
|
||||
|
||||
const RoleSelector = ({ value, onChange }: RoleSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
@ -88,6 +111,26 @@ const RoleSelector = ({ value, onChange }: RoleSelectorProps) => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const getRoleDescription = (role: Role) => {
|
||||
if (role.description)
|
||||
return role.description
|
||||
|
||||
const legacyRoleDescriptionKey = getLegacyRoleDescriptionKey(role)
|
||||
|
||||
switch (legacyRoleDescriptionKey) {
|
||||
case 'admin':
|
||||
return t('members.adminTip', { ns: 'common' })
|
||||
case 'editor':
|
||||
return t('members.editorTip', { ns: 'common' })
|
||||
case 'normal':
|
||||
return t('members.normalTip', { ns: 'common' })
|
||||
case 'dataset_operator':
|
||||
return t('members.datasetOperatorTip', { ns: 'common' })
|
||||
}
|
||||
|
||||
return t('role.noDescription', { ns: 'permission' })
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
open={open}
|
||||
@ -141,7 +184,7 @@ const RoleSelector = ({ value, onChange }: RoleSelectorProps) => {
|
||||
>
|
||||
<div className="relative min-w-0 pl-5">
|
||||
<div className="truncate text-sm leading-5 text-text-secondary">{role.name}</div>
|
||||
<div className="line-clamp-2 text-xs leading-4.5 text-text-tertiary">{role.description || t('role.noDescription', { ns: 'permission' })}</div>
|
||||
<div className="line-clamp-2 text-xs leading-4.5 text-text-tertiary">{getRoleDescription(role)}</div>
|
||||
{value === role.id && (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
|
||||
@ -76,6 +76,39 @@ describe('MemberDetailsModal', () => {
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render edit role action when multiple roles are disabled', () => {
|
||||
render(
|
||||
<MemberDetailsModal
|
||||
member={member}
|
||||
canAssignRoles
|
||||
allowMultipleRoles={false}
|
||||
onClose={vi.fn()}
|
||||
onAssignSubmit={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
const editButton = screen.getByRole('button', { name: /common\.operation\.edit/i })
|
||||
|
||||
expect(editButton).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: /members\.memberDetails\.assign/i })).not.toBeInTheDocument()
|
||||
expect(editButton.querySelector('.i-ri-edit-line')).toBeInTheDocument()
|
||||
expect(editButton.querySelector('.i-ri-add-line')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render singular assigned role label when there is one role', () => {
|
||||
render(
|
||||
<MemberDetailsModal
|
||||
member={member}
|
||||
canAssignRoles
|
||||
onClose={vi.fn()}
|
||||
onAssignSubmit={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText(/common\.members\.memberDetails\.assignedRole:/i)).toBeInTheDocument()
|
||||
expect(screen.queryByText(/common\.members\.memberDetails\.assignedRoles/i)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render role loading state without assigned role chips or count', () => {
|
||||
vi.mocked(useRolesOfMember).mockReturnValue({
|
||||
data: undefined,
|
||||
@ -99,6 +132,26 @@ describe('MemberDetailsModal', () => {
|
||||
})
|
||||
|
||||
describe('Role actions', () => {
|
||||
it('should keep role chips readonly when multiple roles are disabled', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
render(
|
||||
<MemberDetailsModal
|
||||
member={member}
|
||||
canAssignRoles
|
||||
allowMultipleRoles={false}
|
||||
onClose={vi.fn()}
|
||||
onAssignSubmit={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByRole('button', { name: /Custom role/i })).not.toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByText('Custom role'))
|
||||
|
||||
expect(screen.queryByRole('menuitem', { name: /common\.operation\.remove/i })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not show role removal controls when role assignment is not allowed', () => {
|
||||
render(
|
||||
<MemberDetailsModal
|
||||
@ -190,7 +243,7 @@ describe('MemberDetailsModal', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /members\.memberDetails\.assign/i }))
|
||||
await user.click(screen.getByRole('button', { name: /common\.operation\.edit/i }))
|
||||
await user.click(screen.getByRole('radio', { name: /Second role/i }))
|
||||
await user.click(screen.getByRole('button', { name: /common\.operation\.confirm/i }))
|
||||
await user.click(screen.getByRole('button', { name: /common\.operation\.save/i }))
|
||||
|
||||
@ -45,6 +45,25 @@ const MemberDetailsModal = ({
|
||||
const roles = useMemo(() => rolesOfMember?.roles ?? [], [rolesOfMember?.roles])
|
||||
const selectedRoles = pendingRoles ?? roles
|
||||
const selectedRoleIds = useMemo(() => selectedRoles.map(role => role.id), [selectedRoles])
|
||||
const canRemoveRoles = canAssignRoles && allowMultipleRoles
|
||||
const assignedRolesLabel = selectedRoleIds.length === 1
|
||||
? t('members.memberDetails.assignedRole', {
|
||||
ns: 'common',
|
||||
defaultValue: 'Assigned Role',
|
||||
})
|
||||
: t('members.memberDetails.assignedRoles', {
|
||||
ns: 'common',
|
||||
defaultValue: 'Assigned Roles',
|
||||
})
|
||||
const assignActionIconClassName = allowMultipleRoles
|
||||
? 'mr-0.5 i-ri-add-line h-3.5 w-3.5'
|
||||
: 'mr-0.5 i-ri-edit-line h-3.5 w-3.5'
|
||||
const assignActionLabel = allowMultipleRoles
|
||||
? t('members.memberDetails.assign', {
|
||||
ns: 'common',
|
||||
defaultValue: 'Assign',
|
||||
})
|
||||
: t('operation.edit', { ns: 'common' })
|
||||
|
||||
const builtinRoles = useMemo(() => selectedRoles.filter(role => role.is_builtin), [selectedRoles])
|
||||
const customRoles = useMemo(() => selectedRoles.filter(role => !role.is_builtin), [selectedRoles])
|
||||
@ -110,10 +129,7 @@ const MemberDetailsModal = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1.5 system-sm-semibold text-text-secondary">
|
||||
<span>
|
||||
{t('members.memberDetails.assignedRoles', {
|
||||
ns: 'common',
|
||||
defaultValue: 'Assigned Roles',
|
||||
})}
|
||||
{assignedRolesLabel}
|
||||
</span>
|
||||
{!isLoadingRolesOfMember && (
|
||||
<span className="system-xs-medium text-text-tertiary">
|
||||
@ -129,12 +145,9 @@ const MemberDetailsModal = ({
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="mr-0.5 i-ri-add-line h-3.5 w-3.5"
|
||||
className={assignActionIconClassName}
|
||||
/>
|
||||
{t('members.memberDetails.assign', {
|
||||
ns: 'common',
|
||||
defaultValue: 'Assign',
|
||||
})}
|
||||
{assignActionLabel}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -162,7 +175,7 @@ const MemberDetailsModal = ({
|
||||
label={role.name}
|
||||
isOwner={role.role_tag === 'owner'}
|
||||
permissionKeys={role.permission_keys}
|
||||
onRemove={canAssignRoles ? handleRemove : undefined}
|
||||
onRemove={canRemoveRoles ? handleRemove : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@ -183,7 +196,7 @@ const MemberDetailsModal = ({
|
||||
label={role.name}
|
||||
isOwner={role.role_tag === 'owner'}
|
||||
permissionKeys={role.permission_keys}
|
||||
onRemove={canAssignRoles ? handleRemove : undefined}
|
||||
onRemove={canRemoveRoles ? handleRemove : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,15 @@
|
||||
'use client'
|
||||
import type { Role } from '@/models/access-control'
|
||||
import type { Member } from '@/models/common'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
DropdownMenu,
|
||||
@ -38,6 +47,8 @@ const MemberMenu = ({
|
||||
const queryClient = useQueryClient()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [assignModalOpen, setAssignModalOpen] = useState(false)
|
||||
const [removeConfirmOpen, setRemoveConfirmOpen] = useState(false)
|
||||
const [removing, setRemoving] = useState(false)
|
||||
|
||||
const isOwner = member.role === 'owner'
|
||||
const canAssignRoles = !isOwner && !isCurrentUser
|
||||
@ -45,6 +56,10 @@ const MemberMenu = ({
|
||||
const showTransferOwnership = isOwner && canTransferOwnership
|
||||
|
||||
const selectedRoles = member.roles || []
|
||||
const memberName = member.name || member.email
|
||||
const assignRolesLabel = allowMultipleRoles
|
||||
? t('members.assignRoles', { ns: 'common', defaultValue: 'Assign Roles' })
|
||||
: t('members.editRole', { ns: 'common', defaultValue: 'Edit Role' })
|
||||
|
||||
const handleOpenAssignRoles = useCallback(() => {
|
||||
setOpen(false)
|
||||
@ -68,15 +83,24 @@ const MemberMenu = ({
|
||||
})
|
||||
}, [allowMultipleRoles, member.id, t, updateRolesOfMember])
|
||||
|
||||
const handleRemove = useCallback(async () => {
|
||||
const handleOpenRemoveConfirm = useCallback(() => {
|
||||
setOpen(false)
|
||||
setRemoveConfirmOpen(true)
|
||||
}, [])
|
||||
|
||||
const handleRemove = useCallback(async () => {
|
||||
setRemoving(true)
|
||||
try {
|
||||
await deleteMemberOrCancelInvitation({ url: `/workspaces/current/members/${member.id}` })
|
||||
void queryClient.invalidateQueries({ queryKey: commonQueryKeys.members })
|
||||
toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' }))
|
||||
setRemoveConfirmOpen(false)
|
||||
}
|
||||
catch {
|
||||
}
|
||||
finally {
|
||||
setRemoving(false)
|
||||
}
|
||||
}, [member.id, queryClient, t])
|
||||
|
||||
const handleTransferOwnership = useCallback(() => {
|
||||
@ -120,7 +144,7 @@ const MemberMenu = ({
|
||||
className="system-sm-medium text-text-secondary"
|
||||
onClick={handleOpenAssignRoles}
|
||||
>
|
||||
{t('members.assignRoles', { ns: 'common', defaultValue: 'Assign Roles' })}
|
||||
{assignRolesLabel}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{showTransferOwnership && (
|
||||
@ -138,13 +162,34 @@ const MemberMenu = ({
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
className="system-sm-medium"
|
||||
onClick={handleRemove}
|
||||
onClick={handleOpenRemoveConfirm}
|
||||
>
|
||||
{t('members.removeFromTeam', { ns: 'common' })}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<AlertDialog open={removeConfirmOpen} onOpenChange={open => !open && setRemoveConfirmOpen(false)}>
|
||||
<AlertDialogContent backdropProps={{ forceRender: true }}>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('members.removeFromTeamConfirmTitle', { ns: 'common', memberName })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('members.removeFromTeamConfirmDescription', { ns: 'common' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton
|
||||
disabled={removing}
|
||||
onClick={handleRemove}
|
||||
>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{assignModalOpen && (
|
||||
<AssignRolesModal
|
||||
selectedRoles={selectedRoles}
|
||||
|
||||
@ -29,9 +29,6 @@ type RoleBadgesProps = {
|
||||
}
|
||||
|
||||
const RoleBadges = ({ roleNames, max = 2, className }: RoleBadgesProps) => {
|
||||
if (!roleNames.length)
|
||||
return null
|
||||
|
||||
const visible = roleNames.slice(0, max)
|
||||
const overflow = roleNames.slice(max)
|
||||
|
||||
|
||||
@ -24,6 +24,14 @@ type WorkspaceRoleCheckboxListProps = {
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
const EMPTY_DISABLED_ROLE_IDS: string[] = []
|
||||
const LEGACY_ROLE_DESCRIPTION_KEY_MAP = {
|
||||
admin: 'members.adminTip',
|
||||
editor: 'members.editorTip',
|
||||
normal: 'members.normalTip',
|
||||
dataset_operator: 'members.datasetOperatorTip',
|
||||
} as const
|
||||
|
||||
type LegacyRoleKey = keyof typeof LEGACY_ROLE_DESCRIPTION_KEY_MAP
|
||||
|
||||
const createSelectedRolePlaceholder = (id: string): Role => ({
|
||||
id,
|
||||
@ -37,6 +45,20 @@ const createSelectedRolePlaceholder = (id: string): Role => ({
|
||||
role_tag: '',
|
||||
})
|
||||
|
||||
const normalizeLegacyRoleKey = (value: string) => value.trim().toLowerCase()
|
||||
|
||||
const isLegacyRoleKey = (value: string): value is LegacyRoleKey =>
|
||||
Object.prototype.hasOwnProperty.call(LEGACY_ROLE_DESCRIPTION_KEY_MAP, value)
|
||||
|
||||
const getLegacyRoleDescriptionKey = (role: Role) => {
|
||||
const candidateKeys = [
|
||||
normalizeLegacyRoleKey(role.name),
|
||||
normalizeLegacyRoleKey(role.id),
|
||||
]
|
||||
|
||||
return candidateKeys.find(isLegacyRoleKey)
|
||||
}
|
||||
|
||||
const WorkspaceRoleCheckboxList = ({
|
||||
selectedRoleIds,
|
||||
selectedRoles,
|
||||
@ -138,16 +160,34 @@ const WorkspaceRoleCheckboxList = ({
|
||||
onSelectedRolesChange([role])
|
||||
}, [disabledRoleIdSet, onSelectedRolesChange, roleById])
|
||||
|
||||
const renderRoleText = (role: Role) => (
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="system-sm-semibold text-text-secondary">
|
||||
{role.name}
|
||||
const getRoleDescription = (role: Role) => {
|
||||
if (role.description)
|
||||
return role.description
|
||||
|
||||
const legacyRoleDescriptionKey = allowMultipleRoles
|
||||
? undefined
|
||||
: getLegacyRoleDescriptionKey(role)
|
||||
|
||||
if (legacyRoleDescriptionKey)
|
||||
return t(LEGACY_ROLE_DESCRIPTION_KEY_MAP[legacyRoleDescriptionKey], { ns: 'common' })
|
||||
|
||||
return t('role.noDescription', { ns: 'permission' })
|
||||
}
|
||||
|
||||
const renderRoleText = (role: Role) => {
|
||||
const description = getRoleDescription(role)
|
||||
|
||||
return (
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="system-sm-semibold text-text-secondary">
|
||||
{role.name}
|
||||
</div>
|
||||
<div className="mt-0.5 system-xs-regular text-text-tertiary">
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-0.5 system-xs-regular text-text-tertiary">
|
||||
{role.description || t('role.noDescription', { ns: 'permission' })}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -417,22 +417,20 @@ describe('IntegrationsPage', () => {
|
||||
expect(screen.getByTestId('data-source-page')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render the MCP management route without mcp.manage', () => {
|
||||
it('renders the MCP route as read-only without mcp.manage', () => {
|
||||
mockAppContextState.workspacePermissionKeys = ['tool.manage']
|
||||
|
||||
const { container } = renderIntegrationsPage(undefined, 'mcp')
|
||||
renderIntegrationsPage(undefined, 'mcp')
|
||||
|
||||
expect(screen.queryByTestId('tool-provider-list')).not.toBeInTheDocument()
|
||||
expect(container.firstElementChild).toBeNull()
|
||||
expect(screen.getByTestId('tool-provider-list')).toHaveTextContent('mcp')
|
||||
})
|
||||
|
||||
it.each(['custom-tool', 'workflow-tool'] as const)('does not render the %s management route without tool.manage', (section) => {
|
||||
it.each(['custom-tool', 'workflow-tool'] as const)('renders the %s route as read-only without tool.manage', (section) => {
|
||||
mockAppContextState.workspacePermissionKeys = ['mcp.manage']
|
||||
|
||||
const { container } = renderIntegrationsPage(undefined, section)
|
||||
renderIntegrationsPage(undefined, section)
|
||||
|
||||
expect(screen.queryByTestId('tool-provider-list')).not.toBeInTheDocument()
|
||||
expect(container.firstElementChild).toBeNull()
|
||||
expect(screen.getByTestId('tool-provider-list')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('remounts the tools section content when the route section changes', () => {
|
||||
@ -529,7 +527,7 @@ describe('IntegrationsPage', () => {
|
||||
expect(onSectionChange).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('hides custom and workflow tool entries without tool.manage while keeping MCP with mcp.manage', () => {
|
||||
it('keeps custom, workflow, and MCP tool entries visible without manage permissions', () => {
|
||||
mockAppContextState.workspacePermissionKeys = ['mcp.manage']
|
||||
renderIntegrationsPage(undefined, { section: 'provider', onSectionChange: vi.fn() })
|
||||
|
||||
@ -537,8 +535,8 @@ describe('IntegrationsPage', () => {
|
||||
|
||||
expect(screen.getByRole('button', { name: 'common.toolsPage.toolPlugin' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'MCP' })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'workflow.common.workflowAsTool' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'common.settings.swaggerAPIAsTool' })).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'workflow.common.workflowAsTool' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'common.settings.swaggerAPIAsTool' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('opens tools to the tools plugin page when the parent tools nav is clicked', () => {
|
||||
|
||||
@ -365,18 +365,18 @@ describe('ProviderList', () => {
|
||||
expect(screen.getByText('MCP')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('hides custom and workflow tabs without tool.manage while keeping MCP with mcp.manage', () => {
|
||||
it('keeps custom and workflow tabs visible without tool.manage', () => {
|
||||
mockAppContextState.workspacePermissionKeys = ['mcp.manage']
|
||||
|
||||
renderProviderList()
|
||||
|
||||
expect(screen.getByText('tools.type.builtIn')).toBeInTheDocument()
|
||||
expect(screen.queryByText('tools.type.custom')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('tools.type.workflow')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('tools.type.custom')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.type.workflow')).toBeInTheDocument()
|
||||
expect(screen.getByText('MCP')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('hides MCP tab without mcp.manage', () => {
|
||||
it('keeps MCP tab visible without mcp.manage', () => {
|
||||
mockAppContextState.workspacePermissionKeys = ['tool.manage']
|
||||
|
||||
renderProviderList()
|
||||
@ -384,20 +384,19 @@ describe('ProviderList', () => {
|
||||
expect(screen.getByText('tools.type.builtIn')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.type.custom')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.type.workflow')).toBeInTheDocument()
|
||||
expect(screen.queryByText('MCP')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('MCP')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it.each([
|
||||
['api'],
|
||||
['workflow'],
|
||||
] as const)('does not query providers for %s category without tool.manage', (category) => {
|
||||
['api', 'card-my-api'],
|
||||
['workflow', 'card-wf-tool'],
|
||||
] as const)('renders %s category read-only without tool.manage', (category, cardTestId) => {
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
|
||||
renderProviderList({ category })
|
||||
|
||||
expect(mockUseAllToolProviders).toHaveBeenCalledWith(false)
|
||||
expect(screen.queryByTestId('card-my-api')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('card-wf-tool')).not.toBeInTheDocument()
|
||||
expect(mockUseAllToolProviders).toHaveBeenCalledWith(undefined)
|
||||
expect(screen.getByTestId(cardTestId)).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('custom-create-card')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('toolbar-add-custom-tool')).not.toBeInTheDocument()
|
||||
})
|
||||
@ -800,13 +799,14 @@ describe('ProviderList', () => {
|
||||
expect(screen.getByTestId('mcp-list')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render or query MCP management list without mcp.manage', () => {
|
||||
it('renders MCP list read-only without mcp.manage', () => {
|
||||
mockAppContextState.workspacePermissionKeys = ['tool.manage']
|
||||
|
||||
renderProviderList({ category: 'mcp' })
|
||||
|
||||
expect(mockUseAllToolProviders).toHaveBeenCalledWith(false)
|
||||
expect(screen.queryByTestId('mcp-list')).not.toBeInTheDocument()
|
||||
expect(mockUseAllToolProviders).toHaveBeenCalledWith(undefined)
|
||||
expect(screen.getByTestId('mcp-list')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('mcp-list')).toHaveAttribute('data-show-create-card', 'false')
|
||||
expect(screen.queryByTestId('toolbar-add-mcp')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
@ -9,11 +9,6 @@ export type IntegrationHeader = {
|
||||
title: string
|
||||
}
|
||||
|
||||
type IntegrationNavOptions = {
|
||||
canManageTools?: boolean
|
||||
canManageMCP?: boolean
|
||||
}
|
||||
|
||||
export const getPluginCategoryBySection = (section: IntegrationSection) => {
|
||||
if (section === 'builtin')
|
||||
return PluginCategoryEnum.tool
|
||||
@ -25,12 +20,8 @@ export const getPluginCategoryBySection = (section: IntegrationSection) => {
|
||||
return PluginCategoryEnum.extension
|
||||
}
|
||||
|
||||
export function useIntegrationNav(section: IntegrationSection, options: IntegrationNavOptions = {}) {
|
||||
export function useIntegrationNav(section: IntegrationSection) {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
canManageMCP = true,
|
||||
canManageTools = true,
|
||||
} = options
|
||||
const providerItem = useMemo<IntegrationSidebarNavItemData>(() => ({
|
||||
section: 'provider',
|
||||
label: t('settings.provider', { ns: 'common' }),
|
||||
@ -59,37 +50,32 @@ export function useIntegrationNav(section: IntegrationSection, options: Integrat
|
||||
},
|
||||
]
|
||||
|
||||
if (canManageMCP) {
|
||||
items.push({
|
||||
items.push(
|
||||
{
|
||||
section: 'mcp',
|
||||
label: 'MCP',
|
||||
icon: 'i-custom-vender-integrations-mcp',
|
||||
iconClassName: 'h-[14.5px] w-[13.5px]',
|
||||
className: 'pl-8',
|
||||
})
|
||||
}
|
||||
|
||||
if (canManageTools) {
|
||||
items.push(
|
||||
{
|
||||
section: 'workflow-tool',
|
||||
label: t('common.workflowAsTool', { ns: 'workflow' }),
|
||||
icon: 'i-custom-vender-integrations-workflow-as-tool',
|
||||
iconClassName: 'size-4',
|
||||
className: 'pl-8',
|
||||
},
|
||||
{
|
||||
section: 'custom-tool',
|
||||
label: t('settings.swaggerAPIAsTool', { ns: 'common' }),
|
||||
icon: 'i-custom-vender-integrations-custom-tool',
|
||||
iconClassName: 'h-[14.5px] w-[12.5px]',
|
||||
className: 'pl-8',
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
section: 'workflow-tool',
|
||||
label: t('common.workflowAsTool', { ns: 'workflow' }),
|
||||
icon: 'i-custom-vender-integrations-workflow-as-tool',
|
||||
iconClassName: 'size-4',
|
||||
className: 'pl-8',
|
||||
},
|
||||
{
|
||||
section: 'custom-tool',
|
||||
label: t('settings.swaggerAPIAsTool', { ns: 'common' }),
|
||||
icon: 'i-custom-vender-integrations-custom-tool',
|
||||
iconClassName: 'h-[14.5px] w-[12.5px]',
|
||||
className: 'pl-8',
|
||||
},
|
||||
)
|
||||
|
||||
return items
|
||||
}, [canManageMCP, canManageTools, t])
|
||||
}, [t])
|
||||
const secondaryItems = useMemo<IntegrationSidebarNavItemData[]>(() => [
|
||||
{
|
||||
section: 'trigger',
|
||||
|
||||
@ -13,7 +13,6 @@ import {
|
||||
buildMarketplaceUrlPathByIntegrationSection,
|
||||
toolCategoryBySection,
|
||||
} from '@/app/components/integrations/routes'
|
||||
import { useCanManageMCP, useCanManageTools } from '@/app/components/tools/hooks/use-tool-permissions'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import Link from '@/next/link'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
@ -105,8 +104,6 @@ export default function IntegrationsPage({
|
||||
const docLink = useDocLink()
|
||||
const router = useRouter()
|
||||
const section = useIntegrationSection(routeSection)
|
||||
const canManageMCP = useCanManageMCP()
|
||||
const canManageTools = useCanManageTools()
|
||||
const {
|
||||
canDebugger,
|
||||
canInstallPlugin,
|
||||
@ -130,7 +127,7 @@ export default function IntegrationsPage({
|
||||
providerItem,
|
||||
secondaryItems,
|
||||
toolItems,
|
||||
} = useIntegrationNav(section, { canManageMCP, canManageTools })
|
||||
} = useIntegrationNav(section)
|
||||
const isToolSection = Boolean(toolCategoryBySection[section])
|
||||
const [isToolsExpanded, setIsToolsExpanded] = useState(isToolSection)
|
||||
const useFillLayout = section === 'provider' || section === 'data-source' || section === 'custom-endpoint' || isToolSection || isPluginCategory
|
||||
@ -199,11 +196,6 @@ export default function IntegrationsPage({
|
||||
</>
|
||||
)
|
||||
|
||||
if (section === 'mcp' && !canManageMCP)
|
||||
return null
|
||||
if ((section === 'custom-tool' || section === 'workflow-tool') && !canManageTools)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 w-full flex-1 bg-components-panel-bg" style={sidebarWidthStyle}>
|
||||
<aside className={cn(
|
||||
|
||||
@ -104,22 +104,11 @@ const ProviderList = ({
|
||||
const toolListFrameClassName = cn(contentPaddingClassName, toolsUnifiedContentFrameClassName)
|
||||
const showToolsUpdateSetting = activeTab === 'builtin' && canSetPluginPreferences
|
||||
const showLabelFilter = activeTab === 'builtin'
|
||||
const isToolManageTab = activeTab === 'api' || activeTab === 'workflow'
|
||||
const isMCPManageTab = activeTab === 'mcp'
|
||||
const canReadActiveTab = isMCPManageTab ? canManageMCP : !isToolManageTab || canManageTools
|
||||
const options = [
|
||||
{ value: 'builtin', text: t('type.builtIn', { ns: 'tools' }) },
|
||||
...(canManageTools
|
||||
? [
|
||||
{ value: 'api', text: t('type.custom', { ns: 'tools' }) },
|
||||
{ value: 'workflow', text: t('type.workflow', { ns: 'tools' }) },
|
||||
]
|
||||
: []),
|
||||
...(canManageMCP
|
||||
? [
|
||||
{ value: 'mcp', text: 'MCP' },
|
||||
]
|
||||
: []),
|
||||
{ value: 'api', text: t('type.custom', { ns: 'tools' }) },
|
||||
{ value: 'workflow', text: t('type.workflow', { ns: 'tools' }) },
|
||||
{ value: 'mcp', text: 'MCP' },
|
||||
]
|
||||
const [tagFilterValue, setTagFilterValue] = useState<string[]>([])
|
||||
const handleTagsChange = (value: string[]) => {
|
||||
@ -136,13 +125,10 @@ const ProviderList = ({
|
||||
const handleCreatedMCPProviderHandled = useCallback(() => {
|
||||
setCreatedMCPProviderId(undefined)
|
||||
}, [])
|
||||
const { data: collectionList = [], isLoading: isCollectionListLoading, refetch } = useAllToolProviders(canReadActiveTab)
|
||||
const { data: collectionList = [], isLoading: isCollectionListLoading, refetch } = useAllToolProviders()
|
||||
const activeTabCollectionList = useMemo(() => {
|
||||
if (!canReadActiveTab)
|
||||
return []
|
||||
|
||||
return collectionList.filter(collection => collection.type === activeTab)
|
||||
}, [activeTab, canReadActiveTab, collectionList])
|
||||
}, [activeTab, collectionList])
|
||||
const hasCategoryCollections = activeTabCollectionList.length > 0
|
||||
const shouldShowCustomToolCreateCard = canManageTools && !(activeTab === 'api' && !isCollectionListLoading && hasCategoryCollections)
|
||||
const shouldShowMCPCreateCard = canManageMCP && !(activeTab === 'mcp' && hasCategoryCollections)
|
||||
@ -249,7 +235,7 @@ const ProviderList = ({
|
||||
tagFilterValue={tagFilterValue}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'mcp' && canManageMCP && (
|
||||
{activeTab === 'mcp' && (
|
||||
<MCPList
|
||||
searchText={keywords}
|
||||
contentInset={contentInset}
|
||||
|
||||
@ -7,6 +7,7 @@ const {
|
||||
mockDownloadBlob,
|
||||
mockExportMutateAsync,
|
||||
mockOnRefresh,
|
||||
mockRenderTagSelector,
|
||||
mockIsCurrentWorkspaceEditor,
|
||||
mockWorkspacePermissionKeys,
|
||||
mockToastError,
|
||||
@ -19,6 +20,7 @@ const {
|
||||
mockIsCurrentWorkspaceEditor: vi.fn(() => true),
|
||||
mockWorkspacePermissionKeys: vi.fn(() => ['snippets.create_and_modify', 'snippets.management']),
|
||||
mockOnRefresh: vi.fn(),
|
||||
mockRenderTagSelector: vi.fn(),
|
||||
mockToastError: vi.fn(),
|
||||
mockToastSuccess: vi.fn(),
|
||||
mockUpdateMutate: vi.fn(),
|
||||
@ -79,21 +81,23 @@ vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/features/tag-management/components/tag-selector', () => ({
|
||||
TagSelector: ({
|
||||
onOpenTagManagement,
|
||||
onTagsChange,
|
||||
value,
|
||||
}: {
|
||||
TagSelector: (props: {
|
||||
onOpenTagManagement: () => void
|
||||
onTagsChange: () => void
|
||||
value: Array<{ name: string }>
|
||||
}) => (
|
||||
<div data-testid="snippet-tags">
|
||||
<span>{value.map(tag => tag.name).join(', ')}</span>
|
||||
<button type="button" onClick={onOpenTagManagement}>manage tags</button>
|
||||
<button type="button" onClick={onTagsChange}>sync tags</button>
|
||||
</div>
|
||||
),
|
||||
canBindOrUnbindTags?: boolean
|
||||
}) => {
|
||||
mockRenderTagSelector(props)
|
||||
const { onOpenTagManagement, onTagsChange, value } = props
|
||||
|
||||
return (
|
||||
<div data-testid="snippet-tags">
|
||||
<span>{value.map(tag => tag.name).join(', ')}</span>
|
||||
<button type="button" onClick={onOpenTagManagement}>manage tags</button>
|
||||
<button type="button" onClick={onTagsChange}>sync tags</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
const createSnippet = (overrides: Partial<SnippetListItem> = {}): SnippetListItem => ({
|
||||
@ -194,6 +198,30 @@ describe('SnippetCard', () => {
|
||||
expect(await screen.findByRole('menuitem', { name: 'snippet.menu.deleteSnippet' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should pass snippet management permission to tag binding capability', () => {
|
||||
mockWorkspacePermissionKeys.mockReturnValue(['snippets.management'])
|
||||
|
||||
render(<SnippetCard snippet={createSnippet()} />)
|
||||
|
||||
expect(mockRenderTagSelector).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'snippet',
|
||||
targetId: 'snippet-1',
|
||||
canBindOrUnbindTags: true,
|
||||
}))
|
||||
})
|
||||
|
||||
it('should disable tag binding capability without snippet management permission', () => {
|
||||
mockWorkspacePermissionKeys.mockReturnValue(['snippets.create_and_modify'])
|
||||
|
||||
render(<SnippetCard snippet={createSnippet()} />)
|
||||
|
||||
expect(mockRenderTagSelector).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'snippet',
|
||||
targetId: 'snippet-1',
|
||||
canBindOrUnbindTags: false,
|
||||
}))
|
||||
})
|
||||
|
||||
it('should forward tag selector actions without navigating the card link', () => {
|
||||
const onOpenTagManagement = vi.fn()
|
||||
const onTagsChange = vi.fn()
|
||||
|
||||
@ -170,6 +170,7 @@ const SnippetCard = ({
|
||||
value={snippet.tags}
|
||||
onOpenTagManagement={onOpenTagManagement}
|
||||
onTagsChange={onTagsChange}
|
||||
canBindOrUnbindTags={canManageSnippet}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -110,13 +110,16 @@ describe('MCPList', () => {
|
||||
expect(screen.getByTestId('create-card')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render or query providers when user lacks mcp.manage', () => {
|
||||
it('should render providers read-only when user lacks mcp.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockProviders = [
|
||||
{ id: '1', name: 'Provider 1', type: 'mcp' },
|
||||
]
|
||||
|
||||
const { container } = render(<MCPList searchText="" />)
|
||||
render(<MCPList searchText="" />)
|
||||
|
||||
expect(container.firstElementChild).toBeNull()
|
||||
expect(mockUseAllToolProviders).toHaveBeenCalledWith(false)
|
||||
expect(mockUseAllToolProviders).toHaveBeenCalledWith(undefined)
|
||||
expect(screen.getByTestId('provider-card-1')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('create-card')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
@ -231,13 +231,13 @@ describe('MCPDetailContent', () => {
|
||||
expect(screen.getByTestId('operation-dropdown')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render detail when user lacks mcp.manage', () => {
|
||||
it('should render read-only detail when user lacks mcp.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
|
||||
render(<MCPDetailContent {...defaultProps} />, { wrapper: createWrapper() })
|
||||
|
||||
expect(screen.queryByTestId('operation-dropdown')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('Test MCP Server')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('Test MCP Server')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -465,7 +465,7 @@ describe('MCPDetailContent', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should not render authorize action when user lacks mcp.manage', () => {
|
||||
it('should disable authorize action when user lacks mcp.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
const detail = createMockDetail({ is_team_authorization: false })
|
||||
render(
|
||||
@ -473,7 +473,7 @@ describe('MCPDetailContent', () => {
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
|
||||
expect(screen.queryByText('tools.mcp.authorize')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.authorize').closest('button')).toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -755,7 +755,7 @@ describe('MCPDetailContent', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should not render OAuth authorization action when user lacks mcp.manage', async () => {
|
||||
it('should not run OAuth authorization when user lacks mcp.manage', async () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
mockAuthorizeMcp.mockResolvedValue({ authorization_url: 'https://oauth.example.com' })
|
||||
const detail = createMockDetail({ is_team_authorization: false })
|
||||
@ -765,7 +765,9 @@ describe('MCPDetailContent', () => {
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
|
||||
expect(screen.queryByText('tools.mcp.authorize')).not.toBeInTheDocument()
|
||||
const authorizeButton = screen.getByText('tools.mcp.authorize').closest('button')!
|
||||
expect(authorizeButton).toBeDisabled()
|
||||
fireEvent.click(authorizeButton)
|
||||
expect(mockAuthorizeMcp).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@ -797,7 +799,7 @@ describe('MCPDetailContent', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should not render authorized button when user lacks mcp.manage', () => {
|
||||
it('should disable authorized button when user lacks mcp.manage', () => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
const detail = createMockDetail({ is_team_authorization: true })
|
||||
render(
|
||||
@ -805,7 +807,7 @@ describe('MCPDetailContent', () => {
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
|
||||
expect(screen.queryByText('tools.auth.authorized')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('tools.auth.authorized').closest('button')).toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ const MCPDetailContent: FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
const canManageMCP = useCanManageMCP()
|
||||
|
||||
const { data, isFetching: isGettingTools } = useMCPTools(canManageMCP && detail.is_team_authorization ? detail.id : '')
|
||||
const { data, isFetching: isGettingTools } = useMCPTools(detail.is_team_authorization ? detail.id : '')
|
||||
const invalidateMCPTools = useInvalidateMCPTools()
|
||||
const invalidateAllMCPTools = useInvalidateAllMCPTools()
|
||||
const { mutateAsync: updateTools, isPending: isUpdating } = useUpdateMCPTools()
|
||||
@ -162,7 +162,7 @@ const MCPDetailContent: FC<Props> = ({
|
||||
handleAuthorize()
|
||||
}, [])
|
||||
|
||||
if (!detail || !canManageMCP)
|
||||
if (!detail)
|
||||
return null
|
||||
const identifierLabel = t('mcp.identifier', { ns: 'tools' })
|
||||
const serverUrlLabel = t('mcp.modal.serverUrl', { ns: 'tools' })
|
||||
@ -280,6 +280,7 @@ const MCPDetailContent: FC<Props> = ({
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleUpdateTools}
|
||||
disabled={!canManageMCP}
|
||||
>
|
||||
{t('mcp.getTools', { ns: 'tools' })}
|
||||
</Button>
|
||||
@ -293,7 +294,7 @@ const MCPDetailContent: FC<Props> = ({
|
||||
{toolList.length === 1 && <div className="system-sm-semibold-uppercase text-text-secondary">{t('mcp.onlyTool', { ns: 'tools' })}</div>}
|
||||
</div>
|
||||
<div>
|
||||
<Button size="small" onClick={showUpdateConfirm}>
|
||||
<Button size="small" onClick={showUpdateConfirm} disabled={!canManageMCP}>
|
||||
<span aria-hidden className="mr-1 i-ri-loop-left-line size-3.5" />
|
||||
{t('mcp.update', { ns: 'tools' })}
|
||||
</Button>
|
||||
@ -344,7 +345,7 @@ const MCPDetailContent: FC<Props> = ({
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<AlertDialog open={isShowUpdateConfirm} onOpenChange={open => !open && hideUpdateConfirm()}>
|
||||
<AlertDialog open={canManageMCP && isShowUpdateConfirm} onOpenChange={open => !open && hideUpdateConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
|
||||
@ -29,7 +29,7 @@ const MCPList = ({
|
||||
showCreateCard = true,
|
||||
}: Props) => {
|
||||
const canManageMCP = useCanManageMCP()
|
||||
const { data: list = [] as ToolWithProvider[], isLoading, refetch } = useAllToolProviders(canManageMCP)
|
||||
const { data: list = [] as ToolWithProvider[], isLoading, refetch } = useAllToolProviders()
|
||||
const [isTriggerAuthorize, setIsTriggerAuthorize] = useState<boolean>(false)
|
||||
|
||||
const filteredList = useMemo(() => {
|
||||
@ -95,9 +95,6 @@ const MCPList = ({
|
||||
}
|
||||
const contentPaddingClassName = toolsContentInsetClassNames[contentInset]
|
||||
const contentFrameClassName = cn(contentPaddingClassName, toolsUnifiedContentFrameClassName)
|
||||
if (!canManageMCP)
|
||||
return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@ -107,7 +104,7 @@ const MCPList = ({
|
||||
isLoading && 'h-[calc(100vh-136px)] overflow-hidden',
|
||||
)}
|
||||
>
|
||||
{!isLoading && showCreateCard && <NewMCPCard handleCreate={handleCreate} />}
|
||||
{!isLoading && canManageMCP && showCreateCard && <NewMCPCard handleCreate={handleCreate} />}
|
||||
{isLoading
|
||||
? <ToolCardSkeletonGrid variant="mcp" />
|
||||
: filteredList.map(provider => (
|
||||
|
||||
@ -338,8 +338,11 @@ describe('ProviderDetail', () => {
|
||||
expect(configureButton.querySelector('.i-ri-equalizer-2-line')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not fetch or enable custom tool management without tool.manage', async () => {
|
||||
it('renders custom tool details read-only without tool.manage', async () => {
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
mockFetchCustomToolList.mockResolvedValue([
|
||||
{ name: 'custom-tool', label: { en_US: 'Custom Tool' }, description: { en_US: 'desc' }, parameters: [], labels: [], author: '', output_schema: {} },
|
||||
])
|
||||
|
||||
render(
|
||||
<ProviderDetail
|
||||
@ -352,7 +355,8 @@ describe('ProviderDetail', () => {
|
||||
const configureButton = (await screen.findByText('tools.createTool.editAction')).closest('button')!
|
||||
|
||||
expect(mockFetchCustomCollection).not.toHaveBeenCalled()
|
||||
expect(mockFetchCustomToolList).not.toHaveBeenCalled()
|
||||
expect(mockFetchCustomToolList).toHaveBeenCalledWith('test-collection')
|
||||
expect(screen.getByTestId('tool-custom-tool')).toBeInTheDocument()
|
||||
expect(configureButton).toBeDisabled()
|
||||
})
|
||||
})
|
||||
@ -421,7 +425,7 @@ describe('ProviderDetail', () => {
|
||||
expect(actions).toHaveClass('-mx-4', 'px-4', 'border-b-[0.5px]', 'border-divider-subtle')
|
||||
})
|
||||
|
||||
it('does not fetch or show workflow tool edit actions without tool.manage', () => {
|
||||
it('renders workflow tool details read-only without tool.manage', async () => {
|
||||
mockAppContextState.workspacePermissionKeys = []
|
||||
|
||||
render(
|
||||
@ -432,9 +436,16 @@ describe('ProviderDetail', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(mockFetchWorkflowToolDetail).not.toHaveBeenCalled()
|
||||
expect(screen.queryByText('tools.openInStudio')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('tools.createTool.editAction')).not.toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(mockFetchWorkflowToolDetail).toHaveBeenCalledWith('test-id')
|
||||
})
|
||||
|
||||
const configureButton = (await screen.findByText('tools.createTool.editAction')).closest('button')!
|
||||
expect(screen.getByText('tools.openInStudio')).toBeInTheDocument()
|
||||
expect(configureButton).toBeDisabled()
|
||||
|
||||
fireEvent.click(configureButton)
|
||||
expect(screen.queryByTestId('workflow-tool-drawer')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -164,9 +164,6 @@ const ProviderDetail = ({
|
||||
// workflow provider
|
||||
const [workflowToolDrawerOpen, setWorkflowToolDrawerOpen] = useState(false)
|
||||
const getWorkflowToolProvider = useCallback(async () => {
|
||||
if (!canManageTools)
|
||||
return
|
||||
|
||||
setIsDetailLoading(true)
|
||||
const res = await fetchWorkflowToolDetail(collection.id)
|
||||
const payload = {
|
||||
@ -184,7 +181,7 @@ const ProviderDetail = ({
|
||||
}
|
||||
setCustomCollection(payload)
|
||||
setIsDetailLoading(false)
|
||||
}, [canManageTools, collection.id])
|
||||
}, [collection.id])
|
||||
const removeWorkflowToolProvider = async () => {
|
||||
if (!canManageTools)
|
||||
return
|
||||
@ -243,24 +240,18 @@ const ProviderDetail = ({
|
||||
setToolList([])
|
||||
}
|
||||
else {
|
||||
if (!canManageTools) {
|
||||
setToolList([])
|
||||
setIsDetailLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
const list = await fetchCustomToolList(collection.name)
|
||||
setToolList(list)
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
setIsDetailLoading(false)
|
||||
}, [canManageTools, collection.name, collection.type])
|
||||
}, [collection.name, collection.type])
|
||||
|
||||
useEffect(() => {
|
||||
if (collection.type === CollectionType.custom && canManageTools)
|
||||
getCustomProvider()
|
||||
if (collection.type === CollectionType.workflow && canManageTools)
|
||||
if (collection.type === CollectionType.workflow)
|
||||
getWorkflowToolProvider()
|
||||
getProviderToolList()
|
||||
}, [canManageTools, collection.name, collection.type, getCustomProvider, getProviderToolList, getWorkflowToolProvider])
|
||||
@ -331,7 +322,7 @@ const ProviderDetail = ({
|
||||
nativeButton={false}
|
||||
variant="primary"
|
||||
className={cn('my-3 h-8 min-w-0 flex-1 rounded-lg px-3 py-2')}
|
||||
render={<a href={`${basePath}/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel="noreferrer" target="_blank" />}
|
||||
render={<a href={`${basePath}/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel="noreferrer" target="_blank" aria-label={t('openInStudio', { ns: 'tools' })} />}
|
||||
>
|
||||
<span className="min-w-0 truncate px-0.5 system-sm-medium">{t('openInStudio', { ns: 'tools' })}</span>
|
||||
<span aria-hidden className="i-ri-arrow-right-up-line size-4 shrink-0" />
|
||||
|
||||
@ -47,6 +47,7 @@ const mockResetWorkflowVersionHistory = vi.fn()
|
||||
const mockInvalidateAppTriggers = vi.fn()
|
||||
const mockFetchAppDetail = vi.fn()
|
||||
const mockInvalidateQueries = vi.fn()
|
||||
const mockSetQueryData = vi.fn()
|
||||
const mockSetPublishedAt = vi.fn()
|
||||
const mockSetLastPublishedHasUserInput = vi.fn()
|
||||
|
||||
@ -102,6 +103,7 @@ vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
...actual,
|
||||
useQueryClient: () => ({
|
||||
invalidateQueries: mockInvalidateQueries,
|
||||
setQueryData: mockSetQueryData,
|
||||
}),
|
||||
}
|
||||
})
|
||||
@ -223,7 +225,7 @@ describe('FeaturesTrigger', () => {
|
||||
mockUseEdges.mockReturnValue([])
|
||||
// Set up app store state
|
||||
useAppStore.setState({ appDetail: { id: 'app-id' } as unknown as App })
|
||||
mockFetchAppDetail.mockResolvedValue({ id: 'app-id' })
|
||||
mockFetchAppDetail.mockResolvedValue({ id: 'app-id', name: 'Updated App' })
|
||||
mockInvalidateQueries.mockResolvedValue(undefined)
|
||||
mockPublishWorkflow.mockResolvedValue({ created_at: '2024-01-01T00:00:00Z' })
|
||||
})
|
||||
@ -468,8 +470,13 @@ describe('FeaturesTrigger', () => {
|
||||
expect(mockSetLastPublishedHasUserInput).toHaveBeenCalledWith(true)
|
||||
expect(mockResetWorkflowVersionHistory).toHaveBeenCalled()
|
||||
expect(toastMocks.call).toHaveBeenCalledWith({ type: 'success', message: 'common.api.actionSuccess' })
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['apps', 'detail', 'app-id'] })
|
||||
expect(useAppStore.getState().appDetail).toBeDefined()
|
||||
expect(mockFetchAppDetail).toHaveBeenCalledWith({ url: '/apps', id: 'app-id' })
|
||||
expect(mockSetQueryData).toHaveBeenCalledWith(['apps', 'detail', 'app-id'], expect.objectContaining({
|
||||
name: 'Updated App',
|
||||
}))
|
||||
expect(useAppStore.getState().appDetail).toEqual(expect.objectContaining({
|
||||
name: 'Updated App',
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
@ -616,7 +623,7 @@ describe('FeaturesTrigger', () => {
|
||||
// Arrange
|
||||
const user = userEvent.setup()
|
||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined)
|
||||
mockInvalidateQueries.mockRejectedValue(new Error('fetch failed'))
|
||||
mockFetchAppDetail.mockRejectedValueOnce(new Error('fetch failed'))
|
||||
|
||||
renderWithToast(<FeaturesTrigger />)
|
||||
|
||||
|
||||
@ -42,6 +42,7 @@ import {
|
||||
} from '@/app/components/workflow/types'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import { fetchAppDetail } from '@/service/apps'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { appDetailQueryKeyPrefix } from '@/service/use-apps'
|
||||
import { useInvalidateAppTriggers } from '@/service/use-tools'
|
||||
@ -54,6 +55,7 @@ const FeaturesTrigger = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const queryClient = useQueryClient()
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const setAppDetail = useAppStore(s => s.setAppDetail)
|
||||
const appID = appDetail?.id
|
||||
const { nodesReadOnly, getNodesReadOnly } = useNodesReadOnly()
|
||||
const canReleaseAndVersion = useHooksStore(s => s.accessControl.canReleaseAndVersion)
|
||||
@ -148,12 +150,17 @@ const FeaturesTrigger = () => {
|
||||
|
||||
const updateAppDetail = useCallback(async () => {
|
||||
try {
|
||||
await queryClient.invalidateQueries({ queryKey: [...appDetailQueryKeyPrefix, appID!] })
|
||||
if (!appID)
|
||||
return
|
||||
|
||||
const res = await fetchAppDetail({ url: '/apps', id: appID })
|
||||
queryClient.setQueryData([...appDetailQueryKeyPrefix, appID], res)
|
||||
setAppDetail({ ...res })
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}, [appID, queryClient])
|
||||
}, [appID, queryClient, setAppDetail])
|
||||
|
||||
const { mutateAsync: publishWorkflow } = usePublishWorkflow()
|
||||
// const { validateBeforeRun } = useWorkflowRunValidation()
|
||||
|
||||
@ -41,6 +41,7 @@ type PanelHarnessProps = {
|
||||
type?: TagType
|
||||
value?: Tag[]
|
||||
tagList?: Tag[]
|
||||
canBindOrUnbindTags?: boolean
|
||||
onOpenTagManagement?: () => void
|
||||
}
|
||||
|
||||
@ -52,6 +53,7 @@ const PanelHarness = ({
|
||||
type = 'app',
|
||||
value = [appTags[0]!],
|
||||
tagList = [...appTags, knowledgeTag],
|
||||
canBindOrUnbindTags,
|
||||
onOpenTagManagement,
|
||||
}: PanelHarnessProps) => {
|
||||
const [selectedTags, setSelectedTags] = useState<Tag[]>(value)
|
||||
@ -92,6 +94,7 @@ const PanelHarness = ({
|
||||
type={type}
|
||||
inputValue={inputValue}
|
||||
onInputValueChange={setInputValue}
|
||||
canBindOrUnbindTags={canBindOrUnbindTags}
|
||||
onOpenTagManagement={onOpenTagManagement}
|
||||
/>
|
||||
</Combobox>
|
||||
@ -211,6 +214,31 @@ describe('TagSearchContent', () => {
|
||||
expect(screen.getByRole('option', { name: /Frontend/i })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not update selection when neither tag management nor binding permission is available', async () => {
|
||||
const user = userEvent.setup()
|
||||
mockWorkspacePermissionKeys.value = []
|
||||
|
||||
render(<PanelHarness />)
|
||||
|
||||
await user.click(screen.getByRole('option', { name: /Backend/i }))
|
||||
|
||||
expect(onValueChangeSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('updates selection with binding capability without tag management permission', async () => {
|
||||
const user = userEvent.setup()
|
||||
mockWorkspacePermissionKeys.value = []
|
||||
|
||||
render(<PanelHarness canBindOrUnbindTags />)
|
||||
|
||||
await user.click(screen.getByRole('option', { name: /Backend/i }))
|
||||
|
||||
expect(onValueChangeSpy).toHaveBeenLastCalledWith(expect.arrayContaining([
|
||||
expect.objectContaining({ id: 'tag-2' }),
|
||||
]))
|
||||
expect(screen.queryByRole('button', { name: i18n.manageTags })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders knowledge tags when the panel type is knowledge', () => {
|
||||
render(<PanelHarness type="knowledge" value={[]} />)
|
||||
expect(screen.getByRole('option', { name: /KnowledgeDB/i })).toBeInTheDocument()
|
||||
|
||||
@ -90,6 +90,7 @@ vi.mock('../hooks/use-tag-mutations', () => ({
|
||||
|
||||
const i18n = {
|
||||
addTag: 'common.tag.addTag',
|
||||
noTag: 'common.tag.noTag',
|
||||
selectorPlaceholder: 'common.tag.selectorPlaceholder',
|
||||
manageTags: 'common.tag.manageTags',
|
||||
modifiedSuccessfully: 'common.actionMsg.modifiedSuccessfully',
|
||||
@ -122,9 +123,16 @@ describe('TagSelector', () => {
|
||||
expect(screen.getByText('Frontend')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders the add tag trigger when no current tag is visible in the workspace tag list', () => {
|
||||
it('renders the no tag trigger when no current tag is visible and binding is unavailable', () => {
|
||||
render(<TagSelector {...defaultProps} value={[{ id: 'orphan', name: 'Orphan', type: 'app', binding_count: 0 }]} />)
|
||||
expect(screen.queryByText('Orphan')).not.toBeInTheDocument()
|
||||
expect(screen.getByText(i18n.noTag)).toBeInTheDocument()
|
||||
expect(screen.queryByText(i18n.addTag)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders the add tag trigger when no current tag is visible and binding is available', () => {
|
||||
render(<TagSelector {...defaultProps} value={[{ id: 'orphan', name: 'Orphan', type: 'app', binding_count: 0 }]} canBindOrUnbindTags />)
|
||||
expect(screen.queryByText('Orphan')).not.toBeInTheDocument()
|
||||
expect(screen.getByText(i18n.addTag)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -242,7 +250,7 @@ describe('TagSelector', () => {
|
||||
const user = userEvent.setup()
|
||||
render(<TagSelector {...defaultProps} type="knowledge" value={[]} />)
|
||||
|
||||
await user.click(screen.getByRole('combobox', { name: i18n.addTag }))
|
||||
await user.click(screen.getByRole('combobox', { name: i18n.noTag }))
|
||||
await user.type(await screen.findByRole('combobox', { name: i18n.selectorPlaceholder }), 'NewKnowledgeTag')
|
||||
await user.click(await screen.findByRole('option', { name: /NewKnowledgeTag/i }))
|
||||
|
||||
@ -275,6 +283,22 @@ describe('TagSelector', () => {
|
||||
expect(screen.queryByRole('button', { name: i18n.manageTags })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('applies added tags with binding capability even without workspace tag management permission', async () => {
|
||||
const user = userEvent.setup()
|
||||
mockWorkspacePermissionKeys.value = []
|
||||
|
||||
render(<TagSelector {...defaultProps} canBindOrUnbindTags />)
|
||||
|
||||
const trigger = screen.getByRole('combobox', { name: /Frontend/i })
|
||||
await user.click(trigger)
|
||||
await user.click(await screen.findByRole('option', { name: /Backend/i }))
|
||||
await user.click(trigger)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(bindTag).toHaveBeenCalledWith(['tag-2'], 'target-1', 'app')
|
||||
})
|
||||
})
|
||||
|
||||
it('does not create new tags when only binding capability is available', async () => {
|
||||
const user = userEvent.setup()
|
||||
mockWorkspacePermissionKeys.value = []
|
||||
|
||||
@ -8,9 +8,16 @@ describe('Trigger', () => {
|
||||
|
||||
// Rendering behavior for empty and populated states.
|
||||
describe('Rendering', () => {
|
||||
it('should render add-tag placeholder when tags are empty', () => {
|
||||
it('should render no-tag placeholder when tags are empty without binding permission', () => {
|
||||
render(<TagTrigger tags={[]} />)
|
||||
|
||||
expect(screen.getByText('common.tag.noTag')).toBeInTheDocument()
|
||||
expect(screen.queryByText('common.tag.addTag')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render add-tag placeholder when tags are empty with binding permission', () => {
|
||||
render(<TagTrigger tags={[]} canBindOrUnbindTags />)
|
||||
|
||||
expect(screen.getByText('common.tag.addTag')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -27,11 +34,12 @@ describe('Trigger', () => {
|
||||
describe('Props', () => {
|
||||
it('should update from placeholder to tag badges when tags prop changes', () => {
|
||||
const { rerender } = render(<TagTrigger tags={[]} />)
|
||||
expect(screen.getByText('common.tag.addTag')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.tag.noTag')).toBeInTheDocument()
|
||||
|
||||
rerender(<TagTrigger tags={['Database']} />)
|
||||
|
||||
expect(screen.getByText('Database')).toBeInTheDocument()
|
||||
expect(screen.queryByText('common.tag.noTag')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.tag.addTag')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -14,6 +14,7 @@ type TagSearchContentProps = {
|
||||
onInputValueChange: (value: string) => void
|
||||
onOpenTagManagement?: () => void
|
||||
onClose?: () => void
|
||||
canBindOrUnbindTags?: boolean
|
||||
}
|
||||
|
||||
export const TagSearchContent = ({
|
||||
@ -22,6 +23,7 @@ export const TagSearchContent = ({
|
||||
onInputValueChange,
|
||||
onOpenTagManagement,
|
||||
onClose,
|
||||
canBindOrUnbindTags = false,
|
||||
}: TagSearchContentProps) => {
|
||||
const { t } = useTranslation()
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
@ -56,7 +58,7 @@ export const TagSearchContent = ({
|
||||
</div>
|
||||
<ComboboxList className="max-h-58">
|
||||
{(tag: TagComboboxItem) => {
|
||||
if (isCreateTagOption(tag)) {
|
||||
if (isCreateTagOption(tag) && canManageTags) {
|
||||
return (
|
||||
<Fragment key={tag.id}>
|
||||
<ComboboxItem
|
||||
@ -76,7 +78,11 @@ export const TagSearchContent = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<ComboboxItem key={tag.id} value={tag}>
|
||||
<ComboboxItem
|
||||
key={tag.id}
|
||||
value={tag}
|
||||
disabled={!canBindOrUnbindTags && !canManageTags}
|
||||
>
|
||||
<ComboboxItemText title={tag.name}>{tag.name}</ComboboxItemText>
|
||||
<ComboboxItemIndicator />
|
||||
</ComboboxItem>
|
||||
|
||||
@ -98,7 +98,10 @@ export const TagSelector = ({
|
||||
return tagName ? [tagName] : []
|
||||
})
|
||||
}, [tagList, value])
|
||||
const triggerLabel = tagNames.length ? tagNames.join(', ') : t('tag.addTag', { ns: 'common' })
|
||||
const emptyTriggerLabel = canBindOrUnbindTags
|
||||
? t('tag.addTag', { ns: 'common' })
|
||||
: t('tag.noTag', { ns: 'common' })
|
||||
const triggerLabel = tagNames.length ? tagNames.join(', ') : emptyTriggerLabel
|
||||
|
||||
const items = useMemo<TagComboboxItem[]>(() => {
|
||||
const tagIds = new Set<string>()
|
||||
@ -227,7 +230,10 @@ export const TagSelector = ({
|
||||
)}
|
||||
icon={false}
|
||||
>
|
||||
<TagTrigger tags={tagNames} />
|
||||
<TagTrigger
|
||||
tags={tagNames}
|
||||
canBindOrUnbindTags={canBindOrUnbindTags}
|
||||
/>
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent
|
||||
placement={placement}
|
||||
@ -242,6 +248,7 @@ export const TagSelector = ({
|
||||
type={type}
|
||||
inputValue={inputValue}
|
||||
onInputValueChange={setInputValue}
|
||||
canBindOrUnbindTags={canBindOrUnbindTags}
|
||||
onOpenTagManagement={onOpenTagManagement}
|
||||
onClose={() => handleOpenChange(false)}
|
||||
/>
|
||||
|
||||
@ -1,17 +1,26 @@
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type TriggerProps = {
|
||||
tags: string[]
|
||||
canBindOrUnbindTags?: boolean
|
||||
}
|
||||
|
||||
export const TagTrigger = ({
|
||||
tags,
|
||||
canBindOrUnbindTags = false,
|
||||
}: TriggerProps) => {
|
||||
const { t } = useTranslation()
|
||||
const emptyTagLabel = canBindOrUnbindTags
|
||||
? t('tag.addTag', { ns: 'common' })
|
||||
: t('tag.noTag', { ns: 'common' })
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex w-full cursor-pointer items-center gap-1 overflow-hidden rounded-lg p-1 hover:bg-state-base-hover"
|
||||
className={cn(
|
||||
'flex w-full cursor-pointer items-center gap-1 overflow-hidden rounded-lg p-1 hover:bg-state-base-hover',
|
||||
!canBindOrUnbindTags && 'pointer-events-none opacity-50',
|
||||
)}
|
||||
role={tags.length ? 'list' : undefined}
|
||||
>
|
||||
{!tags.length
|
||||
@ -19,7 +28,7 @@ export const TagTrigger = ({
|
||||
<div className="flex max-w-full min-w-0 items-center gap-x-0.5 rounded-[5px] border border-dashed border-divider-deep bg-components-badge-bg-dimm px-1.25 py-0.75">
|
||||
<span aria-hidden="true" className="i-ri-price-tag-3-line size-3 shrink-0 text-text-quaternary" />
|
||||
<div className="truncate system-2xs-medium-uppercase text-text-tertiary">
|
||||
{t('tag.addTag', { ns: 'common' })}
|
||||
{emptyTagLabel}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "اتصل بخوادم MCP وأدرها لمنح تطبيقاتك إمكانية الوصول إلى الأدوات والخدمات الخارجية.",
|
||||
"members.adminTip": "يمكنه بناء التطبيقات وإدارة إعدادات الفريق",
|
||||
"members.alreadyInTeam": "موجود بالفعل في الفريق",
|
||||
"members.alreadyInTeamTip": "هؤلاء المستخدمون لديهم بالفعل إمكانية الوصول إلى مساحة العمل هذه.",
|
||||
"members.assignRoles": "تعيين الأدوار",
|
||||
"members.assignRolesModal.description": "اختر الأدوار لتعيينها لهذا العضو. سيتم دمج جميع الصلاحيات من الأدوار المختارة.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} مختار",
|
||||
"members.assignRolesModal.singleDescription": "حدد دورًا واحدًا لتعيينه لهذا العضو.",
|
||||
"members.assignRolesModal.title": "تعيين الأدوار",
|
||||
"members.datasetOperatorTip": "يمكنه إدارة قاعدة المعرفة فقط",
|
||||
"members.editRole": "Edit Role",
|
||||
"members.editorTip": "يمكنه بناء وتعديل التطبيقات",
|
||||
"members.email": "البريد الإلكتروني",
|
||||
"members.emailInvalid": "تنسيق البريد الإلكتروني غير صالح",
|
||||
"members.emailNotSetup": "لم يتم إعداد خادم البريد الإلكتروني، لذا لا يمكن إرسال رسائل بريد إلكتروني للدعوة. يرجى إخطار المستخدمين برابط الدعوة الذي سيتم إصداره بعد الدعوة بدلاً من ذلك.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "آخر نشاط",
|
||||
"members.memberActions": "إجراءات العضو",
|
||||
"members.memberDetails.assign": "تعيين",
|
||||
"members.memberDetails.assignedRole": "الدور المعيّن",
|
||||
"members.memberDetails.assignedRoles": "الأدوار المعينة",
|
||||
"members.memberDetails.customGroup": "مخصص",
|
||||
"members.memberDetails.generalGroup": "عام",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "تفاصيل العضو",
|
||||
"members.name": "الاسم",
|
||||
"members.noNewInvitationsSent": "لم يتم إرسال دعوات جديدة",
|
||||
"members.normalTip": "يمكنه استخدام التطبيقات فقط، ولا يمكنه بناء التطبيقات",
|
||||
"members.ok": "موافق",
|
||||
"members.pending": "قيد الانتظار...",
|
||||
"members.removeFromTeam": "إزالة من الفريق",
|
||||
"members.removeFromTeamConfirmDescription": "يرجى تأكيد إزالة هذا العضو. لا يمكن التراجع عن هذا الإجراء.",
|
||||
"members.removeFromTeamConfirmTitle": "إزالة {{memberName}} من الفريق",
|
||||
"members.role": "الأدوار",
|
||||
"members.roles": "الأدوار",
|
||||
"members.selectRole": "اختر دورًا",
|
||||
"members.sendInvite": "إرسال دعوة",
|
||||
"members.transferModal.codeLabel": "رمز التحقق",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "التكاملات",
|
||||
"settings.members": "الأعضاء",
|
||||
"settings.permissionSet": "مجموعة الأذونات",
|
||||
"settings.permissionSetDescription": "قم بتكوين مجموعات الأذونات لاستخدامها مع التطبيقات وقواعد المعرفة. مجموعة الأذونات هي مجموعة قابلة لإعادة الاستخدام من أذونات عمليات الموارد يمكن تعيينها للأعضاء لموارد محددة.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "مزود النموذج",
|
||||
"settings.resourceAccess": "الوصول إلى الموارد",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "جميع الأعضاء الذين لديهم أذونات الأدوار",
|
||||
"accessRule.allPermittedMembersDescription": "يمكن للأعضاء الذين لديهم أذونات أدوار مطابقة الوصول إلى هذا المورد.",
|
||||
"accessRule.appDescription": "تحكم في من يُفتح له هذا التطبيق. لا يزال الأعضاء بحاجة إلى أذونات الأدوار لعرضه أو تشغيله.",
|
||||
"accessRule.appTitle": "قواعد الوصول إلى التطبيق",
|
||||
"accessRule.appTitle": "مجموعة أذونات التطبيق",
|
||||
"accessRule.changeOpenScopeDescription": "سيؤدي تغيير نطاق الفتح إلى إعادة تعيين جميع إعدادات الأذونات الفردية لهذا المورد. ستحتاج إلى إضافة أذونات خاصة بالأعضاء مرة أخرى بعد التبديل.",
|
||||
"accessRule.changeOpenScopeTitle": "تغيير نطاق فتح المورد؟",
|
||||
"accessRule.collapseSection": "طي {{title}}",
|
||||
"accessRule.copied": "تم نسخ قاعدة الوصول بنجاح",
|
||||
"accessRule.created": "تم إنشاء قاعدة الوصول بنجاح",
|
||||
"accessRule.datasetDescription": "تحكم في من تُفتح له قاعدة المعرفة هذه. لا يزال الأعضاء بحاجة إلى أذونات الأدوار لعرضها أو تشغيلها.",
|
||||
"accessRule.datasetTitle": "قواعد الوصول إلى قاعدة المعرفة",
|
||||
"accessRule.datasetTitle": "مجموعة أذونات قاعدة المعرفة",
|
||||
"accessRule.defaultPermission": "حسب أذونات الأدوار",
|
||||
"accessRule.deleteDescription": "سيتم حذف قاعدة الوصول هذه نهائيًا وإزالتها من قائمة تفويض المورد.",
|
||||
"accessRule.deleteTitle": "حذف \"{{name}}\"؟",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Verbinde und verwalte MCP-Server, damit deine Apps auf externe Tools und Dienste zugreifen können.",
|
||||
"members.adminTip": "Kann Apps erstellen & Team-Einstellungen verwalten",
|
||||
"members.alreadyInTeam": "Bereits im Team",
|
||||
"members.alreadyInTeamTip": "Diese Benutzer haben bereits Zugriff auf diesen Arbeitsbereich.",
|
||||
"members.assignRoles": "Rollen zuweisen",
|
||||
"members.assignRolesModal.description": "Wählen Sie Rollen aus, die diesem Mitglied zugewiesen werden sollen. Alle Berechtigungen der ausgewählten Rollen werden kombiniert.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} ausgewählt",
|
||||
"members.assignRolesModal.singleDescription": "Wählen Sie eine Rolle aus, die diesem Mitglied zugewiesen werden soll.",
|
||||
"members.assignRolesModal.title": "Rollen zuweisen",
|
||||
"members.datasetOperatorTip": "Kann die Wissensdatenbank nur verwalten",
|
||||
"members.editRole": "Rolle bearbeiten",
|
||||
"members.editorTip": "Kann Apps erstellen und bearbeiten",
|
||||
"members.email": "E-Mail",
|
||||
"members.emailInvalid": "Ungültiges E-Mail-Format",
|
||||
"members.emailNotSetup": "E-Mail-Server ist nicht eingerichtet, daher können keine Einladungs-E-Mails versendet werden. Bitte informieren Sie die Benutzer über den Einladungslink, der nach der Einladung ausgestellt wird.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "ZULETZT AKTIV",
|
||||
"members.memberActions": "Mitgliederaktionen",
|
||||
"members.memberDetails.assign": "Zuweisen",
|
||||
"members.memberDetails.assignedRole": "Zugewiesene Rolle",
|
||||
"members.memberDetails.assignedRoles": "Zugewiesene Rollen",
|
||||
"members.memberDetails.customGroup": "BENUTZERDEFINIERT",
|
||||
"members.memberDetails.generalGroup": "ALLGEMEIN",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Mitgliederdetails",
|
||||
"members.name": "NAME",
|
||||
"members.noNewInvitationsSent": "Keine neuen Einladungen gesendet",
|
||||
"members.normalTip": "Kann nur Apps verwenden, kann keine Apps erstellen",
|
||||
"members.ok": "OK",
|
||||
"members.pending": "Ausstehend...",
|
||||
"members.removeFromTeam": "Vom Team entfernen",
|
||||
"members.removeFromTeamConfirmDescription": "Bestätige, dass dieses Mitglied entfernt werden soll. Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"members.removeFromTeamConfirmTitle": "{{memberName}} aus dem Team entfernen",
|
||||
"members.role": "ROLLEN",
|
||||
"members.roles": "ROLLEN",
|
||||
"members.selectRole": "Wählen Sie eine Rolle aus",
|
||||
"members.sendInvite": "Einladung senden",
|
||||
"members.transferModal.codeLabel": "Bestätigungscode",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Integrationen",
|
||||
"settings.members": "Mitglieder",
|
||||
"settings.permissionSet": "Berechtigungssatz",
|
||||
"settings.permissionSetDescription": "Konfigurieren Sie Berechtigungssätze zur Verwendung mit Apps und Wissensdatenbanken. Ein Berechtigungssatz ist eine wiederverwendbare Sammlung von Berechtigungen für Ressourcenoperationen, die Mitgliedern für bestimmte Ressourcen zugewiesen werden kann.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Modellanbieter",
|
||||
"settings.resourceAccess": "Ressourcenzugriff",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Alle Mitglieder mit Rollenberechtigungen",
|
||||
"accessRule.allPermittedMembersDescription": "Mitglieder mit passenden Rollenberechtigungen können auf diese Ressource zugreifen.",
|
||||
"accessRule.appDescription": "Steuern Sie, für wen diese App geöffnet ist. Mitglieder benötigen dennoch Rollenberechtigungen, um sie anzuzeigen oder zu bedienen.",
|
||||
"accessRule.appTitle": "App-Zugriffsregeln",
|
||||
"accessRule.appTitle": "App-Berechtigungssatz",
|
||||
"accessRule.changeOpenScopeDescription": "Das Ändern des Freigabebereichs setzt alle individuellen Berechtigungseinstellungen für diese Ressource zurück. Nach dem Wechsel müssen Sie mitgliederspezifische Berechtigungen erneut hinzufügen.",
|
||||
"accessRule.changeOpenScopeTitle": "Freigabebereich der Ressource ändern?",
|
||||
"accessRule.collapseSection": "{{title}} einklappen",
|
||||
"accessRule.copied": "Zugriffsregel erfolgreich kopiert",
|
||||
"accessRule.created": "Zugriffsregel erfolgreich erstellt",
|
||||
"accessRule.datasetDescription": "Steuern Sie, für wen diese Wissensdatenbank geöffnet ist. Mitglieder benötigen dennoch Rollenberechtigungen, um sie anzuzeigen oder zu bedienen.",
|
||||
"accessRule.datasetTitle": "Zugriffsregeln der Wissensdatenbank",
|
||||
"accessRule.datasetTitle": "Wissensdatenbank-Berechtigungssatz",
|
||||
"accessRule.defaultPermission": "Nach Rollenberechtigungen",
|
||||
"accessRule.deleteDescription": "Diese Zugriffsregel wird dauerhaft gelöscht und aus der Ressourcen-Autorisierungsliste entfernt.",
|
||||
"accessRule.deleteTitle": "\"{{name}}\" löschen?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Connect and manage MCP servers to give your apps access to external tools and services.",
|
||||
"members.adminTip": "Can build apps & manage team settings",
|
||||
"members.alreadyInTeam": "Already in team",
|
||||
"members.alreadyInTeamTip": "These users already have access to this workspace.",
|
||||
"members.assignRoles": "Assign Roles",
|
||||
"members.assignRolesModal.description": "Select roles to assign to this member. All permissions from selected roles will be combined.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} selected",
|
||||
"members.assignRolesModal.singleDescription": "Select one role to assign to this member.",
|
||||
"members.assignRolesModal.title": "Assign Roles",
|
||||
"members.datasetOperatorTip": "Only can manage the knowledge base",
|
||||
"members.editRole": "Edit Role",
|
||||
"members.editorTip": "Can build & edit apps",
|
||||
"members.email": "Email",
|
||||
"members.emailInvalid": "Invalid Email Format",
|
||||
"members.emailNotSetup": "Email server is not set up, so invitation emails cannot be sent. Please notify users of the invitation link that will be issued after invitation instead.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "LAST ACTIVE",
|
||||
"members.memberActions": "Member actions",
|
||||
"members.memberDetails.assign": "Assign",
|
||||
"members.memberDetails.assignedRole": "Assigned Role",
|
||||
"members.memberDetails.assignedRoles": "Assigned Roles",
|
||||
"members.memberDetails.customGroup": "CUSTOMIZED",
|
||||
"members.memberDetails.generalGroup": "GENERAL",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Member Details",
|
||||
"members.name": "NAME",
|
||||
"members.noNewInvitationsSent": "No new invitations sent",
|
||||
"members.normalTip": "Only can use apps, can not build apps",
|
||||
"members.ok": "OK",
|
||||
"members.pending": "Pending...",
|
||||
"members.removeFromTeam": "Remove from team",
|
||||
"members.role": "ROLES",
|
||||
"members.removeFromTeamConfirmDescription": "Confirm removing this member. This action cannot be undone.",
|
||||
"members.removeFromTeamConfirmTitle": "Remove {{memberName}} from team",
|
||||
"members.role": "ROLE",
|
||||
"members.roles": "ROLES",
|
||||
"members.selectRole": "Select a role",
|
||||
"members.sendInvite": "Send Invite",
|
||||
"members.transferModal.codeLabel": "Verification code",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Integrations",
|
||||
"settings.members": "Members",
|
||||
"settings.permissionSet": "Permission Set",
|
||||
"settings.permissionSetDescription": "Configure permission sets for use with applications and knowledge bases. A permission set is a reusable collection of resource operation permissions that can be assigned to members for specific resources.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Model Provider",
|
||||
"settings.resourceAccess": "Resource Access",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "All members with role permissions",
|
||||
"accessRule.allPermittedMembersDescription": "Members with matching role permissions can access this resource.",
|
||||
"accessRule.appDescription": "Control who this app is open to. Members still need role permissions to view or operate it.",
|
||||
"accessRule.appTitle": "App Access Rules",
|
||||
"accessRule.appTitle": "App Permission Set",
|
||||
"accessRule.changeOpenScopeDescription": "Changing the open scope will reset all individual permission settings for this resource. You'll need to add member-specific permissions again after switching.",
|
||||
"accessRule.changeOpenScopeTitle": "Change resource open scope?",
|
||||
"accessRule.collapseSection": "Collapse {{title}}",
|
||||
"accessRule.copied": "Access rule copied successfully",
|
||||
"accessRule.created": "Access rule created successfully",
|
||||
"accessRule.datasetDescription": "Control who this knowledge base is open to. Members still need role permissions to view or operate it.",
|
||||
"accessRule.datasetTitle": "Knowledge Base Access Rules",
|
||||
"accessRule.datasetTitle": "Knowledge Base Permission Set",
|
||||
"accessRule.defaultPermission": "By role permissions",
|
||||
"accessRule.deleteDescription": "This access rule will be permanently deleted and removed from the resource authorization list.",
|
||||
"accessRule.deleteTitle": "Delete \"{{name}}\"?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Conecta y gestiona servidores MCP para dar a tus apps acceso a herramientas y servicios externos.",
|
||||
"members.adminTip": "Puede crear aplicaciones y administrar configuraciones del equipo",
|
||||
"members.alreadyInTeam": "Ya está en el equipo",
|
||||
"members.alreadyInTeamTip": "Estos usuarios ya tienen acceso a este espacio de trabajo.",
|
||||
"members.assignRoles": "Asignar roles",
|
||||
"members.assignRolesModal.description": "Selecciona los roles que se asignarán a este miembro. Se combinarán todos los permisos de los roles seleccionados.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} seleccionados",
|
||||
"members.assignRolesModal.singleDescription": "Selecciona un rol para asignarlo a este miembro.",
|
||||
"members.assignRolesModal.title": "Asignar roles",
|
||||
"members.datasetOperatorTip": "Solo puede administrar la base de conocimiento",
|
||||
"members.editRole": "Editar rol",
|
||||
"members.editorTip": "Puede crear y editar apps",
|
||||
"members.email": "Correo electrónico",
|
||||
"members.emailInvalid": "Formato de correo electrónico inválido",
|
||||
"members.emailNotSetup": "El servidor de correo no está configurado, por lo que no se pueden enviar correos de invitación. En su lugar, notifique a los usuarios el enlace de invitación que se emitirá después de la invitación.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "ÚLTIMA ACTIVIDAD",
|
||||
"members.memberActions": "Acciones del miembro",
|
||||
"members.memberDetails.assign": "Asignar",
|
||||
"members.memberDetails.assignedRole": "Rol asignado",
|
||||
"members.memberDetails.assignedRoles": "Roles asignados",
|
||||
"members.memberDetails.customGroup": "PERSONALIZADO",
|
||||
"members.memberDetails.generalGroup": "GENERAL",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Detalles del miembro",
|
||||
"members.name": "NOMBRE",
|
||||
"members.noNewInvitationsSent": "No se han enviado nuevas invitaciones",
|
||||
"members.normalTip": "Solo puede usar aplicaciones, no puede crear aplicaciones",
|
||||
"members.ok": "OK",
|
||||
"members.pending": "Pendiente...",
|
||||
"members.removeFromTeam": "Eliminar del espacio de trabajo",
|
||||
"members.removeFromTeamConfirmDescription": "Confirma que quieres eliminar a este miembro. Esta acción no se puede deshacer.",
|
||||
"members.removeFromTeamConfirmTitle": "Eliminar a {{memberName}} del equipo",
|
||||
"members.role": "ROLES",
|
||||
"members.roles": "ROLES",
|
||||
"members.selectRole": "Selecciona un rol",
|
||||
"members.sendInvite": "Enviar invitación",
|
||||
"members.transferModal.codeLabel": "Código de verificación",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Integraciones",
|
||||
"settings.members": "Miembros",
|
||||
"settings.permissionSet": "Conjunto de permisos",
|
||||
"settings.permissionSetDescription": "Configura conjuntos de permisos para usarlos con aplicaciones y bases de conocimiento. Un conjunto de permisos es una colección reutilizable de permisos de operaciones sobre recursos que se puede asignar a miembros para recursos específicos.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Proveedor de Modelo",
|
||||
"settings.resourceAccess": "Acceso a recursos",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Todos los miembros con permisos de rol",
|
||||
"accessRule.allPermittedMembersDescription": "Los miembros con permisos de rol coincidentes pueden acceder a este recurso.",
|
||||
"accessRule.appDescription": "Controla a quién está abierta esta app. Los miembros todavía necesitan permisos de rol para verla u operarla.",
|
||||
"accessRule.appTitle": "Reglas de acceso de la app",
|
||||
"accessRule.appTitle": "Conjunto de permisos de app",
|
||||
"accessRule.changeOpenScopeDescription": "Cambiar el ámbito de apertura restablecerá todos los ajustes de permisos individuales para este recurso. Tendrás que volver a añadir los permisos específicos de cada miembro después de cambiarlo.",
|
||||
"accessRule.changeOpenScopeTitle": "¿Cambiar el ámbito de apertura del recurso?",
|
||||
"accessRule.collapseSection": "Contraer {{title}}",
|
||||
"accessRule.copied": "Regla de acceso copiada correctamente",
|
||||
"accessRule.created": "Regla de acceso creada correctamente",
|
||||
"accessRule.datasetDescription": "Controla a quién está abierta esta base de conocimiento. Los miembros todavía necesitan permisos de rol para verla u operarla.",
|
||||
"accessRule.datasetTitle": "Reglas de acceso de la base de conocimiento",
|
||||
"accessRule.datasetTitle": "Conjunto de permisos de base de conocimiento",
|
||||
"accessRule.defaultPermission": "Por permisos de rol",
|
||||
"accessRule.deleteDescription": "Esta regla de acceso se eliminará de forma permanente y se quitará de la lista de autorización del recurso.",
|
||||
"accessRule.deleteTitle": "¿Eliminar \"{{name}}\"?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "سرورهای MCP را وصل و مدیریت کنید تا برنامههای شما به ابزارها و سرویسهای خارجی دسترسی داشته باشند.",
|
||||
"members.adminTip": "میتواند برنامهها را بسازد و تنظیمات تیم را مدیریت کند",
|
||||
"members.alreadyInTeam": "در حال حاضر در تیم است",
|
||||
"members.alreadyInTeamTip": "این کاربران از قبل به این فضای کاری دسترسی دارند.",
|
||||
"members.assignRoles": "تخصیص نقشها",
|
||||
"members.assignRolesModal.description": "نقشهایی را برای تخصیص به این عضو انتخاب کنید. تمام مجوزهای نقشهای انتخابشده با هم ترکیب خواهند شد.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} انتخابشده",
|
||||
"members.assignRolesModal.singleDescription": "یک نقش را برای اختصاص به این عضو انتخاب کنید.",
|
||||
"members.assignRolesModal.title": "تخصیص نقشها",
|
||||
"members.datasetOperatorTip": "فقط میتواند پایگاه دانش را مدیریت کند",
|
||||
"members.editRole": "ویرایش نقش",
|
||||
"members.editorTip": "میتواند برنامهها را بسازد و ویرایش کند",
|
||||
"members.email": "ایمیل",
|
||||
"members.emailInvalid": "فرمت ایمیل نامعتبر است",
|
||||
"members.emailNotSetup": "سرور ایمیل راهاندازی نشده است، بنابراین ایمیلهای دعوت نمیتوانند ارسال شوند. لطفاً کاربران را از لینک دعوت که پس از دعوت صادر خواهد شد مطلع کنید。",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "آخرین فعالیت",
|
||||
"members.memberActions": "اقدامات عضو",
|
||||
"members.memberDetails.assign": "تخصیص",
|
||||
"members.memberDetails.assignedRole": "نقش اختصاصیافته",
|
||||
"members.memberDetails.assignedRoles": "نقشهای تخصیصیافته",
|
||||
"members.memberDetails.customGroup": "سفارشیشده",
|
||||
"members.memberDetails.generalGroup": "عمومی",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "جزئیات عضو",
|
||||
"members.name": "نام",
|
||||
"members.noNewInvitationsSent": "هیچ دعوتنامه جدیدی ارسال نشد",
|
||||
"members.normalTip": "فقط میتواند از برنامهها استفاده کند، نمیتواند برنامه بسازد",
|
||||
"members.ok": "تایید",
|
||||
"members.pending": "در انتظار...",
|
||||
"members.removeFromTeam": "حذف از تیم",
|
||||
"members.removeFromTeamConfirmDescription": "حذف این عضو را تأیید کنید. این عمل قابل بازگشت نیست.",
|
||||
"members.removeFromTeamConfirmTitle": "حذف {{memberName}} از تیم",
|
||||
"members.role": "نقشها",
|
||||
"members.roles": "نقشها",
|
||||
"members.selectRole": "یک نقش انتخاب کنید",
|
||||
"members.sendInvite": "ارسال دعوت",
|
||||
"members.transferModal.codeLabel": "کد تأیید",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "ادغامها",
|
||||
"settings.members": "اعضا",
|
||||
"settings.permissionSet": "مجموعه مجوزها",
|
||||
"settings.permissionSetDescription": "مجموعههای مجوز را برای استفاده با برنامهها و پایگاههای دانش پیکربندی کنید. مجموعه مجوز، مجموعهای قابل استفاده مجدد از مجوزهای عملیات منبع است که میتوان آن را برای منابع مشخص به اعضا اختصاص داد.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "ارائه دهنده مدل",
|
||||
"settings.resourceAccess": "دسترسی به منابع",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "همه اعضای دارای مجوزهای نقش",
|
||||
"accessRule.allPermittedMembersDescription": "اعضای دارای مجوزهای نقش منطبق میتوانند به این منبع دسترسی داشته باشند.",
|
||||
"accessRule.appDescription": "کنترل کنید این برنامه برای چه کسانی باز است. اعضا همچنان برای مشاهده یا کار با آن به مجوزهای نقش نیاز دارند.",
|
||||
"accessRule.appTitle": "قوانین دسترسی برنامه",
|
||||
"accessRule.appTitle": "مجموعه مجوز برنامه",
|
||||
"accessRule.changeOpenScopeDescription": "تغییر دامنه دسترسی، همه تنظیمات مجوز فردی این منبع را بازنشانی میکند. پس از تغییر، باید مجوزهای خاص اعضا را دوباره اضافه کنید.",
|
||||
"accessRule.changeOpenScopeTitle": "دامنه دسترسی منبع تغییر کند؟",
|
||||
"accessRule.collapseSection": "جمع کردن {{title}}",
|
||||
"accessRule.copied": "قانون دسترسی با موفقیت کپی شد",
|
||||
"accessRule.created": "قانون دسترسی با موفقیت ایجاد شد",
|
||||
"accessRule.datasetDescription": "کنترل کنید این پایگاه دانش برای چه کسانی باز است. اعضا همچنان برای مشاهده یا کار با آن به مجوزهای نقش نیاز دارند.",
|
||||
"accessRule.datasetTitle": "قوانین دسترسی پایگاه دانش",
|
||||
"accessRule.datasetTitle": "مجموعه مجوز پایگاه دانش",
|
||||
"accessRule.defaultPermission": "بر اساس مجوزهای نقش",
|
||||
"accessRule.deleteDescription": "این قانون دسترسی به طور دائمی حذف شده و از فهرست مجوزدهی منبع برداشته میشود.",
|
||||
"accessRule.deleteTitle": "حذف «{{name}}»؟",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Connectez et gérez des serveurs MCP pour donner à vos apps accès à des outils et services externes.",
|
||||
"members.adminTip": "Peut construire des applications & gérer les paramètres de l'équipe",
|
||||
"members.alreadyInTeam": "Déjà dans l’équipe",
|
||||
"members.alreadyInTeamTip": "Ces utilisateurs ont déjà accès à cet espace de travail.",
|
||||
"members.assignRoles": "Attribuer des rôles",
|
||||
"members.assignRolesModal.description": "Sélectionnez les rôles à attribuer à ce membre. Toutes les autorisations des rôles sélectionnés seront combinées.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} sélectionné(s)",
|
||||
"members.assignRolesModal.singleDescription": "Sélectionnez un rôle à attribuer à ce membre.",
|
||||
"members.assignRolesModal.title": "Attribuer des rôles",
|
||||
"members.datasetOperatorTip": "Seul peut gérer la base de connaissances",
|
||||
"members.editRole": "Modifier le rôle",
|
||||
"members.editorTip": "Peut créer et modifier des apps",
|
||||
"members.email": "Courrier électronique",
|
||||
"members.emailInvalid": "Format de courriel invalide",
|
||||
"members.emailNotSetup": "Le serveur de messagerie n'est pas configuré, les e-mails d'invitation ne peuvent donc pas être envoyés. Veuillez informer les utilisateurs du lien d'invitation qui sera émis après l'invitation.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "DERNIÈRE ACTIVITÉ",
|
||||
"members.memberActions": "Actions du membre",
|
||||
"members.memberDetails.assign": "Attribuer",
|
||||
"members.memberDetails.assignedRole": "Rôle attribué",
|
||||
"members.memberDetails.assignedRoles": "Rôles attribués",
|
||||
"members.memberDetails.customGroup": "PERSONNALISÉ",
|
||||
"members.memberDetails.generalGroup": "GÉNÉRAL",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Détails du membre",
|
||||
"members.name": "NOM",
|
||||
"members.noNewInvitationsSent": "Aucune nouvelle invitation envoyée",
|
||||
"members.normalTip": "Peut seulement utiliser des applications, ne peut pas construire des applications",
|
||||
"members.ok": "D'accord",
|
||||
"members.pending": "En attente...",
|
||||
"members.removeFromTeam": "Retirer de l'équipe",
|
||||
"members.removeFromTeamConfirmDescription": "Confirmez le retrait de ce membre. Cette action est irréversible.",
|
||||
"members.removeFromTeamConfirmTitle": "Retirer {{memberName}} de l'équipe",
|
||||
"members.role": "RÔLES",
|
||||
"members.roles": "RÔLES",
|
||||
"members.selectRole": "Sélectionner un rôle",
|
||||
"members.sendInvite": "Envoyer une invitation",
|
||||
"members.transferModal.codeLabel": "Code de vérification",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Intégrations",
|
||||
"settings.members": "Membres",
|
||||
"settings.permissionSet": "Ensemble d’autorisations",
|
||||
"settings.permissionSetDescription": "Configurez des ensembles d’autorisations à utiliser avec les apps et les bases de connaissances. Un ensemble d’autorisations est une collection réutilisable d’autorisations d’opérations sur les ressources pouvant être attribuée aux membres pour des ressources spécifiques.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Fournisseur de Modèle",
|
||||
"settings.resourceAccess": "Accès aux ressources",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Tous les membres ayant les autorisations de rôle",
|
||||
"accessRule.allPermittedMembersDescription": "Les membres disposant d'autorisations de rôle correspondantes peuvent accéder à cette ressource.",
|
||||
"accessRule.appDescription": "Contrôlez à qui cette application est ouverte. Les membres ont toujours besoin d'autorisations de rôle pour la consulter ou l'utiliser.",
|
||||
"accessRule.appTitle": "Règles d'accès à l'application",
|
||||
"accessRule.appTitle": "Ensemble d'autorisations d'application",
|
||||
"accessRule.changeOpenScopeDescription": "Modifier la portée d'ouverture réinitialisera tous les paramètres d'autorisation individuels de cette ressource. Vous devrez ajouter à nouveau les autorisations spécifiques aux membres après le changement.",
|
||||
"accessRule.changeOpenScopeTitle": "Modifier la portée d'ouverture de la ressource ?",
|
||||
"accessRule.collapseSection": "Réduire {{title}}",
|
||||
"accessRule.copied": "Règle d'accès copiée avec succès",
|
||||
"accessRule.created": "Règle d'accès créée avec succès",
|
||||
"accessRule.datasetDescription": "Contrôlez à qui cette base de connaissances est ouverte. Les membres ont toujours besoin d'autorisations de rôle pour la consulter ou l'utiliser.",
|
||||
"accessRule.datasetTitle": "Règles d'accès à la base de connaissances",
|
||||
"accessRule.datasetTitle": "Ensemble d'autorisations de base de connaissances",
|
||||
"accessRule.defaultPermission": "Selon les autorisations de rôle",
|
||||
"accessRule.deleteDescription": "Cette règle d'accès sera définitivement supprimée et retirée de la liste d'autorisation de la ressource.",
|
||||
"accessRule.deleteTitle": "Supprimer \"{{name}}\" ?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "MCP सर्वर कनेक्ट और प्रबंधित करें ताकि आपके ऐप्स बाहरी टूल और सेवाओं तक पहुँच सकें।",
|
||||
"members.adminTip": "ऐप्स बना सकते हैं और टीम सेटिंग्स का प्रबंधन कर सकते हैं",
|
||||
"members.alreadyInTeam": "पहले से टीम में हैं",
|
||||
"members.alreadyInTeamTip": "इन उपयोगकर्ताओं के पास पहले से इस वर्कस्पेस की पहुंच है।",
|
||||
"members.assignRoles": "भूमिकाएँ असाइन करें",
|
||||
"members.assignRolesModal.description": "इस सदस्य को असाइन करने के लिए भूमिकाएँ चुनें। चयनित भूमिकाओं की सभी अनुमतियाँ संयुक्त की जाएंगी।",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} चयनित",
|
||||
"members.assignRolesModal.singleDescription": "इस सदस्य को असाइन करने के लिए एक भूमिका चुनें।",
|
||||
"members.assignRolesModal.title": "भूमिकाएँ असाइन करें",
|
||||
"members.datasetOperatorTip": "केवल नॉलेज बेस प्रबंधित कर सकते हैं",
|
||||
"members.editRole": "भूमिका संपादित करें",
|
||||
"members.editorTip": "ऐप्स बना और संपादित कर सकता है",
|
||||
"members.email": "ईमेल",
|
||||
"members.emailInvalid": "अवैध ईमेल प्रारूप",
|
||||
"members.emailNotSetup": "ईमेल सर्वर सेट नहीं है, इसलिए आमंत्रण ईमेल नहीं भेजे जा सकते। कृपया उपयोगकर्ताओं को आमंत्रण के बाद जारी किए जाने वाले आमंत्रण लिंक के बारे में सूचित करें。",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "अंतिम सक्रियता",
|
||||
"members.memberActions": "सदस्य कार्रवाइयाँ",
|
||||
"members.memberDetails.assign": "असाइन करें",
|
||||
"members.memberDetails.assignedRole": "असाइन की गई भूमिका",
|
||||
"members.memberDetails.assignedRoles": "असाइन की गई भूमिकाएँ",
|
||||
"members.memberDetails.customGroup": "अनुकूलित",
|
||||
"members.memberDetails.generalGroup": "सामान्य",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "सदस्य विवरण",
|
||||
"members.name": "नाम",
|
||||
"members.noNewInvitationsSent": "कोई नया आमंत्रण नहीं भेजा गया",
|
||||
"members.normalTip": "केवल ऐप्स का उपयोग कर सकते हैं, ऐप्स नहीं बना सकते",
|
||||
"members.ok": "ठीक है",
|
||||
"members.pending": "लंबित...",
|
||||
"members.removeFromTeam": "टीम से हटाएं",
|
||||
"members.removeFromTeamConfirmDescription": "इस सदस्य को हटाने की पुष्टि करें। यह कार्रवाई वापस नहीं की जा सकती।",
|
||||
"members.removeFromTeamConfirmTitle": "{{memberName}} को टीम से हटाएं",
|
||||
"members.role": "भूमिकाएं",
|
||||
"members.roles": "भूमिकाएं",
|
||||
"members.selectRole": "एक भूमिका चुनें",
|
||||
"members.sendInvite": "आमंत्रण भेजें",
|
||||
"members.transferModal.codeLabel": "पुष्टिकरण कोड",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "एकीकरण",
|
||||
"settings.members": "सदस्य",
|
||||
"settings.permissionSet": "अनुमति सेट",
|
||||
"settings.permissionSetDescription": "ऐप्लिकेशन और नॉलेज बेस के साथ उपयोग के लिए अनुमति सेट कॉन्फ़िगर करें। अनुमति सेट संसाधन संचालन अनुमतियों का पुन: उपयोग योग्य संग्रह है, जिसे विशिष्ट संसाधनों के लिए सदस्यों को असाइन किया जा सकता है।",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "मॉडल प्रदाता",
|
||||
"settings.resourceAccess": "संसाधन पहुँच",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "भूमिका अनुमतियों वाले सभी सदस्य",
|
||||
"accessRule.allPermittedMembersDescription": "मिलती-जुलती भूमिका अनुमतियों वाले सदस्य इस संसाधन तक पहुंच सकते हैं।",
|
||||
"accessRule.appDescription": "नियंत्रित करें कि यह ऐप किसके लिए खुला है। सदस्यों को इसे देखने या संचालित करने के लिए अभी भी भूमिका अनुमतियों की आवश्यकता होती है।",
|
||||
"accessRule.appTitle": "ऐप एक्सेस नियम",
|
||||
"accessRule.appTitle": "ऐप अनुमति सेट",
|
||||
"accessRule.changeOpenScopeDescription": "खुले दायरे को बदलने से इस संसाधन के लिए सभी व्यक्तिगत अनुमति सेटिंग्स रीसेट हो जाएंगी। स्विच करने के बाद आपको सदस्य-विशिष्ट अनुमतियां फिर से जोड़नी होंगी।",
|
||||
"accessRule.changeOpenScopeTitle": "संसाधन का खुला दायरा बदलें?",
|
||||
"accessRule.collapseSection": "{{title}} संक्षिप्त करें",
|
||||
"accessRule.copied": "एक्सेस नियम सफलतापूर्वक कॉपी किया गया",
|
||||
"accessRule.created": "एक्सेस नियम सफलतापूर्वक बनाया गया",
|
||||
"accessRule.datasetDescription": "नियंत्रित करें कि यह ज्ञान आधार किसके लिए खुला है। सदस्यों को इसे देखने या संचालित करने के लिए अभी भी भूमिका अनुमतियों की आवश्यकता होती है।",
|
||||
"accessRule.datasetTitle": "ज्ञान आधार एक्सेस नियम",
|
||||
"accessRule.datasetTitle": "ज्ञान आधार अनुमति सेट",
|
||||
"accessRule.defaultPermission": "भूमिका अनुमतियों के अनुसार",
|
||||
"accessRule.deleteDescription": "यह एक्सेस नियम स्थायी रूप से हटा दिया जाएगा और संसाधन प्राधिकरण सूची से हटा दिया जाएगा।",
|
||||
"accessRule.deleteTitle": "\"{{name}}\" हटाएं?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Hubungkan dan kelola server MCP agar aplikasi Anda dapat mengakses alat dan layanan eksternal.",
|
||||
"members.adminTip": "Dapat membangun aplikasi & mengelola pengaturan tim",
|
||||
"members.alreadyInTeam": "Sudah ada di tim",
|
||||
"members.alreadyInTeamTip": "Pengguna ini sudah memiliki akses ke ruang kerja ini.",
|
||||
"members.assignRoles": "Tetapkan Peran",
|
||||
"members.assignRolesModal.description": "Pilih peran untuk ditetapkan ke anggota ini. Semua izin dari peran yang dipilih akan digabungkan.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} dipilih",
|
||||
"members.assignRolesModal.singleDescription": "Pilih satu peran untuk ditetapkan ke anggota ini.",
|
||||
"members.assignRolesModal.title": "Tetapkan Peran",
|
||||
"members.datasetOperatorTip": "Hanya dapat mengelola basis pengetahuan",
|
||||
"members.editRole": "Edit Peran",
|
||||
"members.editorTip": "Dapat membuat dan mengedit aplikasi",
|
||||
"members.email": "Email",
|
||||
"members.emailInvalid": "Format Email Tidak Valid",
|
||||
"members.emailNotSetup": "Server email tidak disiapkan, sehingga email undangan tidak dapat dikirim. Harap beri tahu pengguna tentang tautan undangan yang akan dikeluarkan setelah undangan.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "TERAKHIR AKTIF",
|
||||
"members.memberActions": "Tindakan anggota",
|
||||
"members.memberDetails.assign": "Tetapkan",
|
||||
"members.memberDetails.assignedRole": "Peran yang ditetapkan",
|
||||
"members.memberDetails.assignedRoles": "Peran yang Ditetapkan",
|
||||
"members.memberDetails.customGroup": "DISESUAIKAN",
|
||||
"members.memberDetails.generalGroup": "UMUM",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Detail Anggota",
|
||||
"members.name": "NAMA",
|
||||
"members.noNewInvitationsSent": "Tidak ada undangan baru yang dikirim",
|
||||
"members.normalTip": "Hanya dapat menggunakan aplikasi, tidak dapat membuat aplikasi",
|
||||
"members.ok": "OKE",
|
||||
"members.pending": "Tertunda...",
|
||||
"members.removeFromTeam": "Hapus dari tim",
|
||||
"members.removeFromTeamConfirmDescription": "Konfirmasi untuk menghapus anggota ini. Tindakan ini tidak dapat dibatalkan.",
|
||||
"members.removeFromTeamConfirmTitle": "Hapus {{memberName}} dari tim",
|
||||
"members.role": "PERAN",
|
||||
"members.roles": "PERAN",
|
||||
"members.selectRole": "Pilih peran",
|
||||
"members.sendInvite": "Kirim Undangan",
|
||||
"members.transferModal.codeLabel": "Kode verifikasi",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Integrasi",
|
||||
"settings.members": "Anggota",
|
||||
"settings.permissionSet": "Kumpulan Izin",
|
||||
"settings.permissionSetDescription": "Konfigurasikan kumpulan izin untuk digunakan dengan aplikasi dan basis pengetahuan. Kumpulan izin adalah koleksi izin operasi sumber daya yang dapat digunakan kembali dan dapat ditetapkan kepada anggota untuk sumber daya tertentu.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Penyedia Model",
|
||||
"settings.resourceAccess": "Akses Sumber Daya",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Semua anggota dengan izin peran",
|
||||
"accessRule.allPermittedMembersDescription": "Anggota dengan izin peran yang cocok dapat mengakses sumber daya ini.",
|
||||
"accessRule.appDescription": "Kontrol siapa yang dapat mengakses aplikasi ini. Anggota tetap memerlukan izin peran untuk melihat atau mengoperasikannya.",
|
||||
"accessRule.appTitle": "Aturan Akses Aplikasi",
|
||||
"accessRule.appTitle": "Set izin Aplikasi",
|
||||
"accessRule.changeOpenScopeDescription": "Mengubah cakupan akses akan mengatur ulang semua pengaturan izin individu untuk sumber daya ini. Anda perlu menambahkan kembali izin khusus anggota setelah beralih.",
|
||||
"accessRule.changeOpenScopeTitle": "Ubah cakupan akses sumber daya?",
|
||||
"accessRule.collapseSection": "Ciutkan {{title}}",
|
||||
"accessRule.copied": "Aturan akses berhasil disalin",
|
||||
"accessRule.created": "Aturan akses berhasil dibuat",
|
||||
"accessRule.datasetDescription": "Kontrol siapa yang dapat mengakses basis pengetahuan ini. Anggota tetap memerlukan izin peran untuk melihat atau mengoperasikannya.",
|
||||
"accessRule.datasetTitle": "Aturan Akses Basis Pengetahuan",
|
||||
"accessRule.datasetTitle": "Set izin Basis Pengetahuan",
|
||||
"accessRule.defaultPermission": "Berdasarkan izin peran",
|
||||
"accessRule.deleteDescription": "Aturan akses ini akan dihapus secara permanen dan dihapus dari daftar otorisasi sumber daya.",
|
||||
"accessRule.deleteTitle": "Hapus \"{{name}}\"?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Connetti e gestisci server MCP per dare alle tue app accesso a strumenti e servizi esterni.",
|
||||
"members.adminTip": "Può creare app e gestire le impostazioni del team",
|
||||
"members.alreadyInTeam": "Già nel team",
|
||||
"members.alreadyInTeamTip": "Questi utenti hanno già accesso a questo spazio di lavoro.",
|
||||
"members.assignRoles": "Assegna ruoli",
|
||||
"members.assignRolesModal.description": "Seleziona i ruoli da assegnare a questo membro. Tutte le autorizzazioni dei ruoli selezionati verranno combinate.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} selezionati",
|
||||
"members.assignRolesModal.singleDescription": "Seleziona un ruolo da assegnare a questo membro.",
|
||||
"members.assignRolesModal.title": "Assegna ruoli",
|
||||
"members.datasetOperatorTip": "Può solo gestire la base di conoscenza",
|
||||
"members.editRole": "Modifica ruolo",
|
||||
"members.editorTip": "Può creare e modificare app",
|
||||
"members.email": "Email",
|
||||
"members.emailInvalid": "Formato Email non valido",
|
||||
"members.emailNotSetup": "Il server email non è configurato, quindi non è possibile inviare email di invito. Si prega di notificare agli utenti il link di invito che verrà emesso dopo l'invito.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "ULTIMA ATTIVITÀ",
|
||||
"members.memberActions": "Azioni membro",
|
||||
"members.memberDetails.assign": "Assegna",
|
||||
"members.memberDetails.assignedRole": "Ruolo assegnato",
|
||||
"members.memberDetails.assignedRoles": "Ruoli assegnati",
|
||||
"members.memberDetails.customGroup": "PERSONALIZZATO",
|
||||
"members.memberDetails.generalGroup": "GENERALE",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Dettagli membro",
|
||||
"members.name": "NOME",
|
||||
"members.noNewInvitationsSent": "Nessun nuovo invito inviato",
|
||||
"members.normalTip": "Può solo usare le app, non può crearle",
|
||||
"members.ok": "OK",
|
||||
"members.pending": "In attesa...",
|
||||
"members.removeFromTeam": "Rimuovi dal team",
|
||||
"members.removeFromTeamConfirmDescription": "Conferma la rimozione di questo membro. Questa azione non può essere annullata.",
|
||||
"members.removeFromTeamConfirmTitle": "Rimuovi {{memberName}} dal team",
|
||||
"members.role": "RUOLI",
|
||||
"members.roles": "RUOLI",
|
||||
"members.selectRole": "Seleziona un ruolo",
|
||||
"members.sendInvite": "Invia Invito",
|
||||
"members.transferModal.codeLabel": "Codice di verifica",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Integrazioni",
|
||||
"settings.members": "Membri",
|
||||
"settings.permissionSet": "Set di autorizzazioni",
|
||||
"settings.permissionSetDescription": "Configura set di autorizzazioni da usare con app e basi di conoscenza. Un set di autorizzazioni è una raccolta riutilizzabile di autorizzazioni per operazioni sulle risorse che può essere assegnata ai membri per risorse specifiche.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Fornitore di Modelli",
|
||||
"settings.resourceAccess": "Accesso alle risorse",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Tutti i membri con permessi di ruolo",
|
||||
"accessRule.allPermittedMembersDescription": "I membri con permessi di ruolo corrispondenti possono accedere a questa risorsa.",
|
||||
"accessRule.appDescription": "Controlla a chi è aperta questa app. I membri necessitano comunque dei permessi di ruolo per visualizzarla o utilizzarla.",
|
||||
"accessRule.appTitle": "Regole di accesso all'app",
|
||||
"accessRule.appTitle": "Set di permessi per app",
|
||||
"accessRule.changeOpenScopeDescription": "La modifica dell'ambito di apertura reimposterà tutte le impostazioni dei permessi individuali per questa risorsa. Dovrai aggiungere nuovamente i permessi specifici dei membri dopo il cambio.",
|
||||
"accessRule.changeOpenScopeTitle": "Modificare l'ambito di apertura della risorsa?",
|
||||
"accessRule.collapseSection": "Comprimi {{title}}",
|
||||
"accessRule.copied": "Regola di accesso copiata con successo",
|
||||
"accessRule.created": "Regola di accesso creata con successo",
|
||||
"accessRule.datasetDescription": "Controlla a chi è aperta questa knowledge base. I membri necessitano comunque dei permessi di ruolo per visualizzarla o utilizzarla.",
|
||||
"accessRule.datasetTitle": "Regole di accesso alla knowledge base",
|
||||
"accessRule.datasetTitle": "Set di permessi per knowledge base",
|
||||
"accessRule.defaultPermission": "In base ai permessi di ruolo",
|
||||
"accessRule.deleteDescription": "Questa regola di accesso verrà eliminata definitivamente e rimossa dall'elenco delle autorizzazioni della risorsa.",
|
||||
"accessRule.deleteTitle": "Eliminare \"{{name}}\"?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "最終閲覧",
|
||||
"mainNav.workspace.sort.openMenu": "ワークスペースを並べ替え",
|
||||
"mcpPage.description": "MCP サーバーを接続・管理して、アプリから外部ツールやサービスにアクセスできるようにします。",
|
||||
"members.adminTip": "アプリの構築およびチーム設定の管理ができます",
|
||||
"members.alreadyInTeam": "すでにチームに参加済み",
|
||||
"members.alreadyInTeamTip": "これらのユーザーはすでにこのワークスペースにアクセスできます。",
|
||||
"members.assignRoles": "ロールを割り当て",
|
||||
"members.assignRolesModal.description": "このメンバーに割り当てるロールを選択します。選択したロールのすべての権限が結合されます。",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} 件選択済み",
|
||||
"members.assignRolesModal.singleDescription": "このメンバーに割り当てるロールを1つ選択します。",
|
||||
"members.assignRolesModal.title": "ロールを割り当て",
|
||||
"members.datasetOperatorTip": "ナレッジベースのみを管理できる",
|
||||
"members.editRole": "ロールを編集",
|
||||
"members.editorTip": "アプリを作成・編集できます",
|
||||
"members.email": "メール",
|
||||
"members.emailInvalid": "無効なメール形式",
|
||||
"members.emailNotSetup": "メールサーバーがセットアップされていないので、招待メールを送信することはできません。代わりに招待後に発行される招待リンクをユーザーに通知してください。",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "最終アクティブ",
|
||||
"members.memberActions": "メンバー操作",
|
||||
"members.memberDetails.assign": "割り当て",
|
||||
"members.memberDetails.assignedRole": "割り当て済みロール",
|
||||
"members.memberDetails.assignedRoles": "割り当て済みロール",
|
||||
"members.memberDetails.customGroup": "カスタム",
|
||||
"members.memberDetails.generalGroup": "一般",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "メンバー詳細",
|
||||
"members.name": "名前",
|
||||
"members.noNewInvitationsSent": "新しい招待は送信されませんでした",
|
||||
"members.normalTip": "アプリの使用のみが可能で、アプリの構築はできません",
|
||||
"members.ok": "OK",
|
||||
"members.pending": "保留中...",
|
||||
"members.removeFromTeam": "チームから削除",
|
||||
"members.removeFromTeamConfirmDescription": "このメンバーを削除することを確認してください。この操作は元に戻せません。",
|
||||
"members.removeFromTeamConfirmTitle": "{{memberName}}をチームから削除",
|
||||
"members.role": "ロール",
|
||||
"members.roles": "ロール",
|
||||
"members.selectRole": "ロールを選択してください",
|
||||
"members.sendInvite": "招待を送る",
|
||||
"members.transferModal.codeLabel": "認証コード",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "拡張機能",
|
||||
"settings.integrations": "統合",
|
||||
"settings.members": "メンバー",
|
||||
"settings.permissionSet": "権限セット",
|
||||
"settings.permissionSetDescription": "アプリとナレッジベースで使用する権限セットを設定します。権限セットは、特定のリソースに対してメンバーに割り当てられる、リソース操作権限の再利用可能なコレクションです。",
|
||||
"settings.preferences": "ユーザー設定",
|
||||
"settings.provider": "モデルプロバイダー",
|
||||
"settings.resourceAccess": "リソースアクセス",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "ロール権限を持つすべてのメンバー",
|
||||
"accessRule.allPermittedMembersDescription": "一致するロール権限を持つメンバーは、このリソースにアクセスできます。",
|
||||
"accessRule.appDescription": "このアプリを公開する対象を制御します。メンバーが表示または操作するには、引き続きロール権限が必要です。",
|
||||
"accessRule.appTitle": "アプリアクセスルール",
|
||||
"accessRule.appTitle": "アプリ権限セット",
|
||||
"accessRule.changeOpenScopeDescription": "公開範囲を変更すると、このリソースのすべての個別権限設定がリセットされます。切り替え後、メンバー固有の権限を再度追加する必要があります。",
|
||||
"accessRule.changeOpenScopeTitle": "リソースの公開範囲を変更しますか?",
|
||||
"accessRule.collapseSection": "{{title}} を折りたたむ",
|
||||
"accessRule.copied": "アクセスルールをコピーしました",
|
||||
"accessRule.created": "アクセスルールを作成しました",
|
||||
"accessRule.datasetDescription": "このナレッジベースを公開する対象を制御します。メンバーが表示または操作するには、引き続きロール権限が必要です。",
|
||||
"accessRule.datasetTitle": "ナレッジベースアクセスルール",
|
||||
"accessRule.datasetTitle": "ナレッジベース権限セット",
|
||||
"accessRule.defaultPermission": "ロール権限に従う",
|
||||
"accessRule.deleteDescription": "このアクセスルールは完全に削除され、リソースの認可リストから削除されます。",
|
||||
"accessRule.deleteTitle": "「{{name}}」を削除しますか?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "MCP 서버를 연결하고 관리하여 앱이 외부 도구와 서비스에 접근할 수 있도록 하세요.",
|
||||
"members.adminTip": "앱 빌드 및 팀 설정 관리 가능",
|
||||
"members.alreadyInTeam": "이미 팀에 있습니다",
|
||||
"members.alreadyInTeamTip": "이 사용자들은 이미 이 작업 공간에 액세스할 수 있습니다.",
|
||||
"members.assignRoles": "역할 할당",
|
||||
"members.assignRolesModal.description": "이 멤버에게 할당할 역할을 선택하세요. 선택한 역할의 모든 권한이 결합됩니다.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}}개 선택됨",
|
||||
"members.assignRolesModal.singleDescription": "이 멤버에게 할당할 역할 하나를 선택하세요.",
|
||||
"members.assignRolesModal.title": "역할 할당",
|
||||
"members.datasetOperatorTip": "기술 자료만 관리할 수 있습니다.",
|
||||
"members.editRole": "역할 편집",
|
||||
"members.editorTip": "앱을 만들고 편집할 수 있음",
|
||||
"members.email": "이메일",
|
||||
"members.emailInvalid": "유효하지 않은 이메일 형식",
|
||||
"members.emailNotSetup": "이메일 서버가 설정되지 않아 초대 이메일을 보낼 수 없습니다. 대신 초대 후 발급되는 초대 링크를 사용자에게 알려주세요.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "최근 활동",
|
||||
"members.memberActions": "멤버 작업",
|
||||
"members.memberDetails.assign": "할당",
|
||||
"members.memberDetails.assignedRole": "할당된 역할",
|
||||
"members.memberDetails.assignedRoles": "할당된 역할",
|
||||
"members.memberDetails.customGroup": "사용자 지정",
|
||||
"members.memberDetails.generalGroup": "일반",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "멤버 세부 정보",
|
||||
"members.name": "이름",
|
||||
"members.noNewInvitationsSent": "새 초대가 전송되지 않았습니다",
|
||||
"members.normalTip": "앱 사용만 가능하고 앱 빌드는 불가능",
|
||||
"members.ok": "확인",
|
||||
"members.pending": "대기 중...",
|
||||
"members.removeFromTeam": "팀에서 제거",
|
||||
"members.removeFromTeamConfirmDescription": "이 멤버를 제거하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
|
||||
"members.removeFromTeamConfirmTitle": "{{memberName}}님을 팀에서 제거",
|
||||
"members.role": "역할",
|
||||
"members.roles": "역할",
|
||||
"members.selectRole": "역할 선택",
|
||||
"members.sendInvite": "초대 보내기",
|
||||
"members.transferModal.codeLabel": "검증 코드",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "통합",
|
||||
"settings.members": "멤버",
|
||||
"settings.permissionSet": "권한 세트",
|
||||
"settings.permissionSetDescription": "앱과 지식 베이스에서 사용할 권한 세트를 구성합니다. 권한 세트는 특정 리소스에 대해 멤버에게 할당할 수 있는 재사용 가능한 리소스 작업 권한 모음입니다.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "모델 제공자",
|
||||
"settings.resourceAccess": "리소스 접근",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "역할 권한이 있는 모든 멤버",
|
||||
"accessRule.allPermittedMembersDescription": "일치하는 역할 권한이 있는 멤버가 이 리소스에 접근할 수 있습니다.",
|
||||
"accessRule.appDescription": "이 앱을 누구에게 공개할지 제어합니다. 멤버가 앱을 보거나 조작하려면 여전히 역할 권한이 필요합니다.",
|
||||
"accessRule.appTitle": "앱 접근 규칙",
|
||||
"accessRule.appTitle": "앱 권한 집합",
|
||||
"accessRule.changeOpenScopeDescription": "공개 범위를 변경하면 이 리소스의 모든 개별 권한 설정이 초기화됩니다. 전환 후 멤버별 권한을 다시 추가해야 합니다.",
|
||||
"accessRule.changeOpenScopeTitle": "리소스 공개 범위를 변경하시겠습니까?",
|
||||
"accessRule.collapseSection": "{{title}} 접기",
|
||||
"accessRule.copied": "접근 규칙이 성공적으로 복사되었습니다",
|
||||
"accessRule.created": "접근 규칙이 성공적으로 생성되었습니다",
|
||||
"accessRule.datasetDescription": "이 지식 베이스를 누구에게 공개할지 제어합니다. 멤버가 지식 베이스를 보거나 조작하려면 여전히 역할 권한이 필요합니다.",
|
||||
"accessRule.datasetTitle": "지식 베이스 접근 규칙",
|
||||
"accessRule.datasetTitle": "지식 베이스 권한 집합",
|
||||
"accessRule.defaultPermission": "역할 권한 기준",
|
||||
"accessRule.deleteDescription": "이 접근 규칙은 영구적으로 삭제되며 리소스 권한 부여 목록에서 제거됩니다.",
|
||||
"accessRule.deleteTitle": "\"{{name}}\"을(를) 삭제하시겠습니까?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Verbind en beheer MCP-servers zodat je apps toegang krijgen tot externe tools en services.",
|
||||
"members.adminTip": "Can build apps & manage team settings",
|
||||
"members.alreadyInTeam": "Al in het team",
|
||||
"members.alreadyInTeamTip": "Deze gebruikers hebben al toegang tot deze werkruimte.",
|
||||
"members.assignRoles": "Rollen toewijzen",
|
||||
"members.assignRolesModal.description": "Selecteer rollen om aan dit lid toe te wijzen. Alle rechten van de geselecteerde rollen worden gecombineerd.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} geselecteerd",
|
||||
"members.assignRolesModal.singleDescription": "Selecteer één rol om aan dit lid toe te wijzen.",
|
||||
"members.assignRolesModal.title": "Rollen toewijzen",
|
||||
"members.datasetOperatorTip": "Only can manage the knowledge base",
|
||||
"members.editRole": "Rol bewerken",
|
||||
"members.editorTip": "Kan apps maken en bewerken",
|
||||
"members.email": "Email",
|
||||
"members.emailInvalid": "Invalid Email Format",
|
||||
"members.emailNotSetup": "Email server is not set up, so invitation emails cannot be sent. Please notify users of the invitation link that will be issued after invitation instead.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "LAST ACTIVE",
|
||||
"members.memberActions": "Ledenacties",
|
||||
"members.memberDetails.assign": "Toewijzen",
|
||||
"members.memberDetails.assignedRole": "Toegewezen rol",
|
||||
"members.memberDetails.assignedRoles": "Toegewezen rollen",
|
||||
"members.memberDetails.customGroup": "AANGEPAST",
|
||||
"members.memberDetails.generalGroup": "ALGEMEEN",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Ledendetails",
|
||||
"members.name": "NAME",
|
||||
"members.noNewInvitationsSent": "Geen nieuwe uitnodigingen verzonden",
|
||||
"members.normalTip": "Only can use apps, can not build apps",
|
||||
"members.ok": "OK",
|
||||
"members.pending": "Pending...",
|
||||
"members.removeFromTeam": "Remove from team",
|
||||
"members.removeFromTeamConfirmDescription": "Bevestig dat je dit lid wilt verwijderen. Deze actie kan niet ongedaan worden gemaakt.",
|
||||
"members.removeFromTeamConfirmTitle": "{{memberName}} uit team verwijderen",
|
||||
"members.role": "ROLES",
|
||||
"members.roles": "ROLES",
|
||||
"members.selectRole": "Selecteer een rol",
|
||||
"members.sendInvite": "Send Invite",
|
||||
"members.transferModal.codeLabel": "Verification code",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Integrations",
|
||||
"settings.members": "Members",
|
||||
"settings.permissionSet": "Set met rechten",
|
||||
"settings.permissionSetDescription": "Configureer sets met rechten voor gebruik met apps en kennisbanken. Een set met rechten is een herbruikbare verzameling rechten voor resourcebewerkingen die aan leden kan worden toegewezen voor specifieke resources.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Model Provider",
|
||||
"settings.resourceAccess": "Toegang tot bronnen",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Alle leden met rolrechten",
|
||||
"accessRule.allPermittedMembersDescription": "Leden met overeenkomende rolrechten hebben toegang tot deze resource.",
|
||||
"accessRule.appDescription": "Bepaal voor wie deze app toegankelijk is. Leden hebben nog steeds rolrechten nodig om deze te bekijken of te bedienen.",
|
||||
"accessRule.appTitle": "Toegangsregels voor app",
|
||||
"accessRule.appTitle": "App-rechtenset",
|
||||
"accessRule.changeOpenScopeDescription": "Het wijzigen van het toegangsbereik stelt alle individuele rechteninstellingen voor deze resource opnieuw in. Je moet ledenspecifieke rechten opnieuw toevoegen na het wisselen.",
|
||||
"accessRule.changeOpenScopeTitle": "Toegangsbereik van resource wijzigen?",
|
||||
"accessRule.collapseSection": "{{title}} samenvouwen",
|
||||
"accessRule.copied": "Toegangsregel succesvol gekopieerd",
|
||||
"accessRule.created": "Toegangsregel succesvol gemaakt",
|
||||
"accessRule.datasetDescription": "Bepaal voor wie deze kennisbank toegankelijk is. Leden hebben nog steeds rolrechten nodig om deze te bekijken of te bedienen.",
|
||||
"accessRule.datasetTitle": "Toegangsregels voor kennisbank",
|
||||
"accessRule.datasetTitle": "Kennisbank-rechtenset",
|
||||
"accessRule.defaultPermission": "Op basis van rolrechten",
|
||||
"accessRule.deleteDescription": "Deze toegangsregel wordt permanent verwijderd en uit de autorisatielijst van de resource gehaald.",
|
||||
"accessRule.deleteTitle": "\"{{name}}\" verwijderen?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Łącz i zarządzaj serwerami MCP, aby aplikacje mogły korzystać z zewnętrznych narzędzi i usług.",
|
||||
"members.adminTip": "Może tworzyć aplikacje i zarządzać ustawieniami zespołu",
|
||||
"members.alreadyInTeam": "Już w zespole",
|
||||
"members.alreadyInTeamTip": "Ci użytkownicy mają już dostęp do tego obszaru roboczego.",
|
||||
"members.assignRoles": "Przypisz role",
|
||||
"members.assignRolesModal.description": "Wybierz role do przypisania temu członkowi. Wszystkie uprawnienia z wybranych ról zostaną połączone.",
|
||||
"members.assignRolesModal.selectedCount": "Wybrano: {{count}}",
|
||||
"members.assignRolesModal.singleDescription": "Wybierz jedną rolę do przypisania temu członkowi.",
|
||||
"members.assignRolesModal.title": "Przypisz role",
|
||||
"members.datasetOperatorTip": "Może zarządzać tylko bazą wiedzy",
|
||||
"members.editRole": "Edytuj rolę",
|
||||
"members.editorTip": "Może tworzyć i edytować aplikacje",
|
||||
"members.email": "Email",
|
||||
"members.emailInvalid": "Nieprawidłowy format e-maila",
|
||||
"members.emailNotSetup": "Serwer poczty nie jest skonfigurowany, więc nie można wysyłać zaproszeń e-mail. Proszę powiadomić użytkowników o linku do zaproszenia, który zostanie wydany po zaproszeniu.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "OSTATNIA AKTYWNOŚĆ",
|
||||
"members.memberActions": "Akcje członka",
|
||||
"members.memberDetails.assign": "Przypisz",
|
||||
"members.memberDetails.assignedRole": "Przypisana rola",
|
||||
"members.memberDetails.assignedRoles": "Przypisane role",
|
||||
"members.memberDetails.customGroup": "NIESTANDARDOWE",
|
||||
"members.memberDetails.generalGroup": "OGÓLNE",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Szczegóły członka",
|
||||
"members.name": "NAZWA",
|
||||
"members.noNewInvitationsSent": "Nie wysłano nowych zaproszeń",
|
||||
"members.normalTip": "Może tylko korzystać z aplikacji, nie może tworzyć aplikacji",
|
||||
"members.ok": "OK",
|
||||
"members.pending": "Oczekujący...",
|
||||
"members.removeFromTeam": "Usuń z zespołu",
|
||||
"members.removeFromTeamConfirmDescription": "Potwierdź usunięcie tego członka. Tej czynności nie można cofnąć.",
|
||||
"members.removeFromTeamConfirmTitle": "Usuń {{memberName}} z zespołu",
|
||||
"members.role": "ROLE",
|
||||
"members.roles": "ROLE",
|
||||
"members.selectRole": "Wybierz rolę",
|
||||
"members.sendInvite": "Wyślij zaproszenie",
|
||||
"members.transferModal.codeLabel": "Kod weryfikacyjny",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Integracje",
|
||||
"settings.members": "Członkowie",
|
||||
"settings.permissionSet": "Zestaw uprawnień",
|
||||
"settings.permissionSetDescription": "Skonfiguruj zestawy uprawnień do użycia z aplikacjami i bazami wiedzy. Zestaw uprawnień to wielokrotnego użytku zbiór uprawnień do operacji na zasobach, który można przypisywać członkom dla konkretnych zasobów.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Dostawca modelu",
|
||||
"settings.resourceAccess": "Dostęp do zasobów",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Wszyscy członkowie z uprawnieniami ról",
|
||||
"accessRule.allPermittedMembersDescription": "Członkowie z pasującymi uprawnieniami ról mogą uzyskać dostęp do tego zasobu.",
|
||||
"accessRule.appDescription": "Kontroluj, dla kogo ta aplikacja jest otwarta. Członkowie nadal potrzebują uprawnień ról, aby ją przeglądać lub obsługiwać.",
|
||||
"accessRule.appTitle": "Reguły dostępu do aplikacji",
|
||||
"accessRule.appTitle": "Zestaw uprawnień aplikacji",
|
||||
"accessRule.changeOpenScopeDescription": "Zmiana zakresu otwarcia zresetuje wszystkie indywidualne ustawienia uprawnień dla tego zasobu. Po zmianie będziesz musiał ponownie dodać uprawnienia specyficzne dla członków.",
|
||||
"accessRule.changeOpenScopeTitle": "Zmienić zakres otwarcia zasobu?",
|
||||
"accessRule.collapseSection": "Zwiń {{title}}",
|
||||
"accessRule.copied": "Pomyślnie skopiowano regułę dostępu",
|
||||
"accessRule.created": "Pomyślnie utworzono regułę dostępu",
|
||||
"accessRule.datasetDescription": "Kontroluj, dla kogo ta baza wiedzy jest otwarta. Członkowie nadal potrzebują uprawnień ról, aby ją przeglądać lub obsługiwać.",
|
||||
"accessRule.datasetTitle": "Reguły dostępu do bazy wiedzy",
|
||||
"accessRule.datasetTitle": "Zestaw uprawnień bazy wiedzy",
|
||||
"accessRule.defaultPermission": "Według uprawnień ról",
|
||||
"accessRule.deleteDescription": "Ta reguła dostępu zostanie trwale usunięta i wykreślona z listy autoryzacji zasobu.",
|
||||
"accessRule.deleteTitle": "Usunąć \"{{name}}\"?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Conecte e gerencie servidores MCP para dar aos seus apps acesso a ferramentas e serviços externos.",
|
||||
"members.adminTip": "Pode criar aplicativos e gerenciar configurações da equipe",
|
||||
"members.alreadyInTeam": "Já está na equipe",
|
||||
"members.alreadyInTeamTip": "Estes usuários já têm acesso a este espaço de trabalho.",
|
||||
"members.assignRoles": "Atribuir Funções",
|
||||
"members.assignRolesModal.description": "Selecione as funções para atribuir a este membro. Todas as permissões das funções selecionadas serão combinadas.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} selecionada(s)",
|
||||
"members.assignRolesModal.singleDescription": "Selecione uma função para atribuir a este membro.",
|
||||
"members.assignRolesModal.title": "Atribuir Funções",
|
||||
"members.datasetOperatorTip": "Só pode gerenciar a base de dados de conhecimento",
|
||||
"members.editRole": "Editar Função",
|
||||
"members.editorTip": "Pode criar e editar apps",
|
||||
"members.email": "E-mail",
|
||||
"members.emailInvalid": "Formato de e-mail inválido",
|
||||
"members.emailNotSetup": "O servidor de e-mail não está configurado, então os e-mails de convite não podem ser enviados. Por favor, notifique os usuários sobre o link de convite que será emitido após o convite.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "ÚLTIMA ATIVIDADE",
|
||||
"members.memberActions": "Ações do membro",
|
||||
"members.memberDetails.assign": "Atribuir",
|
||||
"members.memberDetails.assignedRole": "Função atribuída",
|
||||
"members.memberDetails.assignedRoles": "Funções Atribuídas",
|
||||
"members.memberDetails.customGroup": "PERSONALIZADO",
|
||||
"members.memberDetails.generalGroup": "GERAL",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Detalhes do Membro",
|
||||
"members.name": "NOME",
|
||||
"members.noNewInvitationsSent": "Nenhum novo convite enviado",
|
||||
"members.normalTip": "Só pode usar aplicativos, não pode criar aplicativos",
|
||||
"members.ok": "OK",
|
||||
"members.pending": "Pendente...",
|
||||
"members.removeFromTeam": "Remover da equipe",
|
||||
"members.removeFromTeamConfirmDescription": "Confirme a remoção deste membro. Esta ação não pode ser desfeita.",
|
||||
"members.removeFromTeamConfirmTitle": "Remover {{memberName}} da equipe",
|
||||
"members.role": "FUNÇÕES",
|
||||
"members.roles": "FUNÇÕES",
|
||||
"members.selectRole": "Selecione uma função",
|
||||
"members.sendInvite": "Enviar Convite",
|
||||
"members.transferModal.codeLabel": "Código de verificação",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Integrações",
|
||||
"settings.members": "Membros",
|
||||
"settings.permissionSet": "Conjunto de permissões",
|
||||
"settings.permissionSetDescription": "Configure conjuntos de permissões para uso com apps e bases de conhecimento. Um conjunto de permissões é uma coleção reutilizável de permissões de operações de recursos que pode ser atribuída a membros para recursos específicos.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Fornecedor de modelo",
|
||||
"settings.resourceAccess": "Acesso a Recursos",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Todos os membros com permissões de função",
|
||||
"accessRule.allPermittedMembersDescription": "Membros com permissões de função correspondentes podem acessar este recurso.",
|
||||
"accessRule.appDescription": "Controle para quem este aplicativo está aberto. Os membros ainda precisam de permissões de função para visualizá-lo ou operá-lo.",
|
||||
"accessRule.appTitle": "Regras de Acesso ao Aplicativo",
|
||||
"accessRule.appTitle": "Conjunto de permissões de Aplicativo",
|
||||
"accessRule.changeOpenScopeDescription": "Alterar o escopo de abertura redefinirá todas as configurações de permissão individuais para este recurso. Você precisará adicionar permissões específicas de membro novamente após a alteração.",
|
||||
"accessRule.changeOpenScopeTitle": "Alterar escopo de abertura do recurso?",
|
||||
"accessRule.collapseSection": "Recolher {{title}}",
|
||||
"accessRule.copied": "Regra de acesso copiada com sucesso",
|
||||
"accessRule.created": "Regra de acesso criada com sucesso",
|
||||
"accessRule.datasetDescription": "Controle para quem este Conhecimento está aberto. Os membros ainda precisam de permissões de função para visualizá-lo ou operá-lo.",
|
||||
"accessRule.datasetTitle": "Regras de Acesso ao Conhecimento",
|
||||
"accessRule.datasetTitle": "Conjunto de permissões de Conhecimento",
|
||||
"accessRule.defaultPermission": "Por permissões de função",
|
||||
"accessRule.deleteDescription": "Esta regra de acesso será excluída permanentemente e removida da lista de autorização do recurso.",
|
||||
"accessRule.deleteTitle": "Excluir \"{{name}}\"?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Conectează și administrează servere MCP pentru a oferi aplicațiilor acces la instrumente și servicii externe.",
|
||||
"members.adminTip": "Poate construi aplicații și gestiona setările echipei",
|
||||
"members.alreadyInTeam": "Deja în echipă",
|
||||
"members.alreadyInTeamTip": "Acești utilizatori au deja acces la acest spațiu de lucru.",
|
||||
"members.assignRoles": "Atribuie roluri",
|
||||
"members.assignRolesModal.description": "Selectați rolurile pe care doriți să le atribuiți acestui membru. Toate permisiunile din rolurile selectate vor fi combinate.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} selectate",
|
||||
"members.assignRolesModal.singleDescription": "Selectează un rol de atribuit acestui membru.",
|
||||
"members.assignRolesModal.title": "Atribuie roluri",
|
||||
"members.datasetOperatorTip": "Numai poate gestiona baza de cunoștințe",
|
||||
"members.editRole": "Editează rol",
|
||||
"members.editorTip": "Poate crea și edita aplicații",
|
||||
"members.email": "Email",
|
||||
"members.emailInvalid": "Format de email invalid",
|
||||
"members.emailNotSetup": "Serverul de e-mail nu este configurat, astfel încât e-mailurile de invitație nu pot fi trimise. Vă rugăm să notificați utilizatorii despre linkul de invitație care va fi emis după invitație.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "ULTIMA ACTIVITATE",
|
||||
"members.memberActions": "Acțiuni membru",
|
||||
"members.memberDetails.assign": "Atribuie",
|
||||
"members.memberDetails.assignedRole": "Rol atribuit",
|
||||
"members.memberDetails.assignedRoles": "Roluri atribuite",
|
||||
"members.memberDetails.customGroup": "PERSONALIZAT",
|
||||
"members.memberDetails.generalGroup": "GENERAL",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Detalii membru",
|
||||
"members.name": "NUME",
|
||||
"members.noNewInvitationsSent": "Nu au fost trimise invitații noi",
|
||||
"members.normalTip": "Poate doar utiliza aplicații, nu poate construi aplicații",
|
||||
"members.ok": "OK",
|
||||
"members.pending": "În așteptare...",
|
||||
"members.removeFromTeam": "Elimină din echipă",
|
||||
"members.removeFromTeamConfirmDescription": "Confirmă eliminarea acestui membru. Această acțiune nu poate fi anulată.",
|
||||
"members.removeFromTeamConfirmTitle": "Elimină {{memberName}} din echipă",
|
||||
"members.role": "ROLURI",
|
||||
"members.roles": "ROLURI",
|
||||
"members.selectRole": "Selectați un rol",
|
||||
"members.sendInvite": "Trimite invitație",
|
||||
"members.transferModal.codeLabel": "Cod de verificare",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Integrări",
|
||||
"settings.members": "Membri",
|
||||
"settings.permissionSet": "Set de permisiuni",
|
||||
"settings.permissionSetDescription": "Configurați seturi de permisiuni pentru utilizare cu aplicații și baze de cunoștințe. Un set de permisiuni este o colecție reutilizabilă de permisiuni pentru operațiuni asupra resurselor, care poate fi atribuită membrilor pentru resurse specifice.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Furnizor de modele",
|
||||
"settings.resourceAccess": "Acces la resurse",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Toți membrii cu permisiuni de rol",
|
||||
"accessRule.allPermittedMembersDescription": "Membrii cu permisiuni de rol corespunzătoare pot accesa această resursă.",
|
||||
"accessRule.appDescription": "Controlează cui îi este deschisă această aplicație. Membrii au în continuare nevoie de permisiuni de rol pentru a o vizualiza sau opera.",
|
||||
"accessRule.appTitle": "Reguli de acces ale aplicației",
|
||||
"accessRule.appTitle": "Set de permisiuni pentru aplicație",
|
||||
"accessRule.changeOpenScopeDescription": "Modificarea domeniului de deschidere va reseta toate setările individuale de permisiuni pentru această resursă. Va trebui să adaugi din nou permisiunile specifice membrilor după comutare.",
|
||||
"accessRule.changeOpenScopeTitle": "Schimbi domeniul de deschidere al resursei?",
|
||||
"accessRule.collapseSection": "Restrânge {{title}}",
|
||||
"accessRule.copied": "Regula de acces a fost copiată cu succes",
|
||||
"accessRule.created": "Regula de acces a fost creată cu succes",
|
||||
"accessRule.datasetDescription": "Controlează cui îi este deschisă această bază de cunoștințe. Membrii au în continuare nevoie de permisiuni de rol pentru a o vizualiza sau opera.",
|
||||
"accessRule.datasetTitle": "Reguli de acces ale bazei de cunoștințe",
|
||||
"accessRule.datasetTitle": "Set de permisiuni pentru baza de cunoștințe",
|
||||
"accessRule.defaultPermission": "După permisiunile de rol",
|
||||
"accessRule.deleteDescription": "Această regulă de acces va fi ștearsă definitiv și eliminată din lista de autorizare a resursei.",
|
||||
"accessRule.deleteTitle": "Ștergi \"{{name}}\"?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Подключайте и управляйте MCP-серверами, чтобы приложения могли обращаться к внешним инструментам и сервисам.",
|
||||
"members.adminTip": "Может создавать приложения и управлять настройками команды",
|
||||
"members.alreadyInTeam": "Уже в команде",
|
||||
"members.alreadyInTeamTip": "Эти пользователи уже имеют доступ к этому рабочему пространству.",
|
||||
"members.assignRoles": "Назначить роли",
|
||||
"members.assignRolesModal.description": "Выберите роли для назначения этому участнику. Все разрешения из выбранных ролей будут объединены.",
|
||||
"members.assignRolesModal.selectedCount": "Выбрано: {{count}}",
|
||||
"members.assignRolesModal.singleDescription": "Выберите одну роль для назначения этому участнику.",
|
||||
"members.assignRolesModal.title": "Назначить роли",
|
||||
"members.datasetOperatorTip": "Может управлять только базой знаний",
|
||||
"members.editRole": "Редактировать роль",
|
||||
"members.editorTip": "Может создавать и редактировать приложения",
|
||||
"members.email": "Электронная почта",
|
||||
"members.emailInvalid": "Неверный формат электронной почты",
|
||||
"members.emailNotSetup": "Почтовый сервер не настроен, поэтому приглашения по электронной почте не могут быть отправлены. Пожалуйста, уведомите пользователей о ссылке для приглашения, которая будет выдана после приглашения.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "ПОСЛЕДНЯЯ АКТИВНОСТЬ",
|
||||
"members.memberActions": "Действия с участником",
|
||||
"members.memberDetails.assign": "Назначить",
|
||||
"members.memberDetails.assignedRole": "Назначенная роль",
|
||||
"members.memberDetails.assignedRoles": "Назначенные роли",
|
||||
"members.memberDetails.customGroup": "НАСТРАИВАЕМЫЕ",
|
||||
"members.memberDetails.generalGroup": "ОБЩИЕ",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Сведения об участнике",
|
||||
"members.name": "ИМЯ",
|
||||
"members.noNewInvitationsSent": "Новые приглашения не отправлены",
|
||||
"members.normalTip": "Может только использовать приложения, не может создавать приложения",
|
||||
"members.ok": "ОК",
|
||||
"members.pending": "Ожидание...",
|
||||
"members.removeFromTeam": "Удалить из команды",
|
||||
"members.removeFromTeamConfirmDescription": "Подтвердите удаление этого участника. Это действие нельзя отменить.",
|
||||
"members.removeFromTeamConfirmTitle": "Удалить {{memberName}} из команды",
|
||||
"members.role": "РОЛИ",
|
||||
"members.roles": "РОЛИ",
|
||||
"members.selectRole": "Выберите роль",
|
||||
"members.sendInvite": "Отправить приглашение",
|
||||
"members.transferModal.codeLabel": "Код подтверждения",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Интеграции",
|
||||
"settings.members": "Участники",
|
||||
"settings.permissionSet": "Набор разрешений",
|
||||
"settings.permissionSetDescription": "Настройте наборы разрешений для использования с приложениями и базами знаний. Набор разрешений — это многократно используемая коллекция разрешений на операции с ресурсами, которую можно назначать участникам для конкретных ресурсов.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Поставщик модели",
|
||||
"settings.resourceAccess": "Доступ к ресурсам",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Все участники с правами роли",
|
||||
"accessRule.allPermittedMembersDescription": "Участники с соответствующими правами роли могут получить доступ к этому ресурсу.",
|
||||
"accessRule.appDescription": "Управляйте тем, кому открыто это приложение. Участникам по-прежнему нужны права роли для просмотра или работы с ним.",
|
||||
"accessRule.appTitle": "Правила доступа к приложению",
|
||||
"accessRule.appTitle": "Набор прав приложения",
|
||||
"accessRule.changeOpenScopeDescription": "Изменение области открытости сбросит все индивидуальные настройки прав для этого ресурса. После переключения вам потребуется снова добавить права для отдельных участников.",
|
||||
"accessRule.changeOpenScopeTitle": "Изменить область открытости ресурса?",
|
||||
"accessRule.collapseSection": "Свернуть {{title}}",
|
||||
"accessRule.copied": "Правило доступа успешно скопировано",
|
||||
"accessRule.created": "Правило доступа успешно создано",
|
||||
"accessRule.datasetDescription": "Управляйте тем, кому открыта эта база знаний. Участникам по-прежнему нужны права роли для просмотра или работы с ней.",
|
||||
"accessRule.datasetTitle": "Правила доступа к базе знаний",
|
||||
"accessRule.datasetTitle": "Набор прав базы знаний",
|
||||
"accessRule.defaultPermission": "По правам роли",
|
||||
"accessRule.deleteDescription": "Это правило доступа будет безвозвратно удалено и исключено из списка авторизации ресурса.",
|
||||
"accessRule.deleteTitle": "Удалить \"{{name}}\"?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Povežite in upravljajte strežnike MCP, da aplikacijam omogočite dostop do zunanjih orodij in storitev.",
|
||||
"members.adminTip": "Lahko ustvarja aplikacije in upravlja nastavitve ekipe",
|
||||
"members.alreadyInTeam": "Že v ekipi",
|
||||
"members.alreadyInTeamTip": "Ti uporabniki že imajo dostop do tega delovnega prostora.",
|
||||
"members.assignRoles": "Dodeli vloge",
|
||||
"members.assignRolesModal.description": "Izberite vloge za dodelitev temu članu. Vsa dovoljenja izbranih vlog bodo združena.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} izbranih",
|
||||
"members.assignRolesModal.singleDescription": "Izberite eno vlogo, ki jo želite dodeliti temu članu.",
|
||||
"members.assignRolesModal.title": "Dodeli vloge",
|
||||
"members.datasetOperatorTip": "Lahko upravlja samo bazo znanja",
|
||||
"members.editRole": "Uredi vlogo",
|
||||
"members.editorTip": "Lahko ustvarja in ureja aplikacije",
|
||||
"members.email": "E-pošta",
|
||||
"members.emailInvalid": "Neveljaven format e-pošte",
|
||||
"members.emailNotSetup": "E-poštni strežnik ni nastavljen, zato vabil po e-pošti ni mogoče poslati. Prosimo, obvestite uporabnike o povezavi za povabilo, ki bo izdana po povabilu.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "NAZADNJE AKTIVEN",
|
||||
"members.memberActions": "Dejanja člana",
|
||||
"members.memberDetails.assign": "Dodeli",
|
||||
"members.memberDetails.assignedRole": "Dodeljena vloga",
|
||||
"members.memberDetails.assignedRoles": "Dodeljene vloge",
|
||||
"members.memberDetails.customGroup": "PRILAGOJENO",
|
||||
"members.memberDetails.generalGroup": "SPLOŠNO",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Podrobnosti člana",
|
||||
"members.name": "IME",
|
||||
"members.noNewInvitationsSent": "Nova povabila niso bila poslana",
|
||||
"members.normalTip": "Lahko uporablja samo aplikacije, ne more ustvarjati aplikacij",
|
||||
"members.ok": "V redu",
|
||||
"members.pending": "V teku...",
|
||||
"members.removeFromTeam": "Odstrani iz ekipe",
|
||||
"members.removeFromTeamConfirmDescription": "Potrdite odstranitev tega člana. Tega dejanja ni mogoče razveljaviti.",
|
||||
"members.removeFromTeamConfirmTitle": "Odstrani {{memberName}} iz ekipe",
|
||||
"members.role": "VLOGE",
|
||||
"members.roles": "VLOGE",
|
||||
"members.selectRole": "Izberite vlogo",
|
||||
"members.sendInvite": "Pošlji povabilo",
|
||||
"members.transferModal.codeLabel": "Koda za potrditev",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Integracije",
|
||||
"settings.members": "Člani",
|
||||
"settings.permissionSet": "Nabor dovoljenj",
|
||||
"settings.permissionSetDescription": "Konfigurirajte nabore dovoljenj za uporabo z aplikacijami in bazami znanja. Nabor dovoljenj je ponovno uporabna zbirka dovoljenj za operacije nad viri, ki jo je mogoče dodeliti članom za določene vire.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Ponudnik modelov",
|
||||
"settings.resourceAccess": "Dostop do virov",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Vsi člani z dovoljenji vlog",
|
||||
"accessRule.allPermittedMembersDescription": "Člani z ustreznimi dovoljenji vlog lahko dostopajo do tega vira.",
|
||||
"accessRule.appDescription": "Nadzorujte, komu je ta aplikacija na voljo. Člani še vedno potrebujejo dovoljenja vlog za ogled ali upravljanje.",
|
||||
"accessRule.appTitle": "Pravila za dostop do aplikacije",
|
||||
"accessRule.appTitle": "Nabor dovoljenj za aplikacijo",
|
||||
"accessRule.changeOpenScopeDescription": "Sprememba obsega odprtosti bo ponastavila vse individualne nastavitve dovoljenj za ta vir. Po preklopu boste morali znova dodati dovoljenja, specifična za člane.",
|
||||
"accessRule.changeOpenScopeTitle": "Spremeni obseg odprtosti vira?",
|
||||
"accessRule.collapseSection": "Strni {{title}}",
|
||||
"accessRule.copied": "Pravilo za dostop uspešno kopirano",
|
||||
"accessRule.created": "Pravilo za dostop uspešno ustvarjeno",
|
||||
"accessRule.datasetDescription": "Nadzorujte, komu je ta baza znanja na voljo. Člani še vedno potrebujejo dovoljenja vlog za ogled ali upravljanje.",
|
||||
"accessRule.datasetTitle": "Pravila za dostop do baze znanja",
|
||||
"accessRule.datasetTitle": "Nabor dovoljenj za bazo znanja",
|
||||
"accessRule.defaultPermission": "Po dovoljenjih vlog",
|
||||
"accessRule.deleteDescription": "To pravilo za dostop bo trajno izbrisano in odstranjeno s seznama avtorizacij vira.",
|
||||
"accessRule.deleteTitle": "Izbrisati \"{{name}}\"?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "เชื่อมต่อและจัดการเซิร์ฟเวอร์ MCP เพื่อให้แอปของคุณเข้าถึงเครื่องมือและบริการภายนอกได้",
|
||||
"members.adminTip": "สามารถสร้างแอพและจัดการการตั้งค่าทีมได้",
|
||||
"members.alreadyInTeam": "อยู่ในทีมแล้ว",
|
||||
"members.alreadyInTeamTip": "ผู้ใช้เหล่านี้มีสิทธิ์เข้าถึงพื้นที่ทำงานนี้อยู่แล้ว",
|
||||
"members.assignRoles": "กำหนดบทบาท",
|
||||
"members.assignRolesModal.description": "เลือกบทบาทที่จะกำหนดให้กับสมาชิกคนนี้ สิทธิ์ทั้งหมดจากบทบาทที่เลือกจะถูกรวมเข้าด้วยกัน",
|
||||
"members.assignRolesModal.selectedCount": "เลือกแล้ว {{count}} รายการ",
|
||||
"members.assignRolesModal.singleDescription": "เลือกหนึ่งบทบาทเพื่อมอบหมายให้สมาชิกนี้",
|
||||
"members.assignRolesModal.title": "กำหนดบทบาท",
|
||||
"members.datasetOperatorTip": "สามารถจัดการฐานความรู้ได้เท่านั้น",
|
||||
"members.editRole": "แก้ไขบทบาท",
|
||||
"members.editorTip": "สามารถสร้างและแก้ไขแอปได้",
|
||||
"members.email": "อีเมล",
|
||||
"members.emailInvalid": "รูปแบบอีเมลไม่ถูกต้อง",
|
||||
"members.emailNotSetup": "เซิร์ฟเวอร์อีเมลไม่ได้ตั้งค่าไว้ จึงไม่สามารถส่งอีเมลเชิญได้ กรุณาแจ้งผู้ใช้เกี่ยวกับลิงก์เชิญที่จะออกหลังจากการเชิญแทน",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "ใช้งานล่าสุด",
|
||||
"members.memberActions": "การดำเนินการกับสมาชิก",
|
||||
"members.memberDetails.assign": "กำหนด",
|
||||
"members.memberDetails.assignedRole": "บทบาทที่กำหนด",
|
||||
"members.memberDetails.assignedRoles": "บทบาทที่กำหนด",
|
||||
"members.memberDetails.customGroup": "กำหนดเอง",
|
||||
"members.memberDetails.generalGroup": "ทั่วไป",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "รายละเอียดสมาชิก",
|
||||
"members.name": "ชื่อ",
|
||||
"members.noNewInvitationsSent": "ไม่มีการส่งคำเชิญใหม่",
|
||||
"members.normalTip": "ใช้ได้เฉพาะแอพ สร้างแอพไม่ได้",
|
||||
"members.ok": "ตกลง, ได้",
|
||||
"members.pending": "รอ ",
|
||||
"members.removeFromTeam": "ลบออกจากทีม",
|
||||
"members.removeFromTeamConfirmDescription": "ยืนยันการลบสมาชิกนี้ การดำเนินการนี้ไม่สามารถย้อนกลับได้",
|
||||
"members.removeFromTeamConfirmTitle": "ลบ {{memberName}} ออกจากทีม",
|
||||
"members.role": "บทบาท",
|
||||
"members.roles": "บทบาท",
|
||||
"members.selectRole": "เลือกบทบาท",
|
||||
"members.sendInvite": "ส่งคําเชิญ",
|
||||
"members.transferModal.codeLabel": "รหัสยืนยัน",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "บูรณาการ",
|
||||
"settings.members": "สมาชิก",
|
||||
"settings.permissionSet": "ชุดสิทธิ์",
|
||||
"settings.permissionSetDescription": "กำหนดค่าชุดสิทธิ์สำหรับใช้กับแอปและฐานความรู้ ชุดสิทธิ์คือชุดสิทธิ์การดำเนินการกับทรัพยากรที่นำกลับมาใช้ซ้ำได้ และสามารถกำหนดให้สมาชิกสำหรับทรัพยากรเฉพาะได้",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "ผู้ให้บริการโมเดล",
|
||||
"settings.resourceAccess": "การเข้าถึงทรัพยากร",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "สมาชิกทั้งหมดที่มีสิทธิ์ตามบทบาท",
|
||||
"accessRule.allPermittedMembersDescription": "สมาชิกที่มีสิทธิ์ตามบทบาทที่ตรงกันสามารถเข้าถึงทรัพยากรนี้ได้",
|
||||
"accessRule.appDescription": "ควบคุมว่าแอปนี้เปิดให้ใครเข้าถึง สมาชิกยังคงต้องมีสิทธิ์ตามบทบาทเพื่อดูหรือใช้งาน",
|
||||
"accessRule.appTitle": "กฎการเข้าถึงแอป",
|
||||
"accessRule.appTitle": "ชุดสิทธิ์แอป",
|
||||
"accessRule.changeOpenScopeDescription": "การเปลี่ยนขอบเขตการเปิดจะรีเซ็ตการตั้งค่าสิทธิ์เฉพาะบุคคลทั้งหมดสําหรับทรัพยากรนี้ คุณจะต้องเพิ่มสิทธิ์เฉพาะสมาชิกอีกครั้งหลังจากเปลี่ยน",
|
||||
"accessRule.changeOpenScopeTitle": "เปลี่ยนขอบเขตการเปิดของทรัพยากรหรือไม่",
|
||||
"accessRule.collapseSection": "ยุบ {{title}}",
|
||||
"accessRule.copied": "คัดลอกกฎการเข้าถึงสําเร็จ",
|
||||
"accessRule.created": "สร้างกฎการเข้าถึงสําเร็จ",
|
||||
"accessRule.datasetDescription": "ควบคุมว่าฐานความรู้นี้เปิดให้ใครเข้าถึง สมาชิกยังคงต้องมีสิทธิ์ตามบทบาทเพื่อดูหรือใช้งาน",
|
||||
"accessRule.datasetTitle": "กฎการเข้าถึงฐานความรู้",
|
||||
"accessRule.datasetTitle": "ชุดสิทธิ์ฐานความรู้",
|
||||
"accessRule.defaultPermission": "ตามสิทธิ์บทบาท",
|
||||
"accessRule.deleteDescription": "กฎการเข้าถึงนี้จะถูกลบอย่างถาวรและนําออกจากรายการการอนุญาตทรัพยากร",
|
||||
"accessRule.deleteTitle": "ลบ \"{{name}}\" หรือไม่",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Uygulamalarınıza harici araç ve servislere erişim vermek için MCP sunucularını bağlayın ve yönetin.",
|
||||
"members.adminTip": "Uygulama oluşturabilir ve takım ayarlarını yönetebilir",
|
||||
"members.alreadyInTeam": "Zaten ekipte",
|
||||
"members.alreadyInTeamTip": "Bu kullanıcıların bu çalışma alanına zaten erişimi var.",
|
||||
"members.assignRoles": "Rolleri Ata",
|
||||
"members.assignRolesModal.description": "Bu üyeye atanacak rolleri seçin. Seçilen rollerdeki tüm izinler birleştirilecektir.",
|
||||
"members.assignRolesModal.selectedCount": "{{count}} seçildi",
|
||||
"members.assignRolesModal.singleDescription": "Bu üyeye atanacak bir rol seçin.",
|
||||
"members.assignRolesModal.title": "Rolleri Ata",
|
||||
"members.datasetOperatorTip": "Sadece bilgi tabanını yönetebilir",
|
||||
"members.editRole": "Rolü Düzenle",
|
||||
"members.editorTip": "Uygulama oluşturup düzenleyebilir",
|
||||
"members.email": "E-posta",
|
||||
"members.emailInvalid": "Geçersiz E-posta Formatı",
|
||||
"members.emailNotSetup": "E-posta sunucusu kurulu değil, bu nedenle davet e-postaları gönderilemiyor. Lütfen kullanıcıları davetten sonra verilecek davet bağlantısı hakkında bilgilendirin.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "SON AKTİF",
|
||||
"members.memberActions": "Üye işlemleri",
|
||||
"members.memberDetails.assign": "Ata",
|
||||
"members.memberDetails.assignedRole": "Atanan rol",
|
||||
"members.memberDetails.assignedRoles": "Atanan Roller",
|
||||
"members.memberDetails.customGroup": "ÖZELLEŞTİRİLMİŞ",
|
||||
"members.memberDetails.generalGroup": "GENEL",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Üye Ayrıntıları",
|
||||
"members.name": "İSİM",
|
||||
"members.noNewInvitationsSent": "Yeni davet gönderilmedi",
|
||||
"members.normalTip": "Sadece uygulamaları kullanabilir, uygulama oluşturamaz",
|
||||
"members.ok": "Tamam",
|
||||
"members.pending": "Beklemede...",
|
||||
"members.removeFromTeam": "Takımdan Kaldır",
|
||||
"members.removeFromTeamConfirmDescription": "Bu üyeyi kaldırmayı onaylayın. Bu işlem geri alınamaz.",
|
||||
"members.removeFromTeamConfirmTitle": "{{memberName}} adlı üyeyi takımdan kaldır",
|
||||
"members.role": "ROLLER",
|
||||
"members.roles": "ROLLER",
|
||||
"members.selectRole": "Bir rol seçin",
|
||||
"members.sendInvite": "Davet Gönder",
|
||||
"members.transferModal.codeLabel": "Doğrulama kodu",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Entegrasyonlar",
|
||||
"settings.members": "Üyeler",
|
||||
"settings.permissionSet": "İzin Seti",
|
||||
"settings.permissionSetDescription": "Uygulamalar ve bilgi tabanlarıyla kullanılacak izin setlerini yapılandırın. İzin seti, belirli kaynaklar için üyelere atanabilen yeniden kullanılabilir kaynak işlem izinleri koleksiyonudur.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Model Sağlayıcı",
|
||||
"settings.resourceAccess": "Kaynak Erişimi",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Rol izinlerine sahip tüm üyeler",
|
||||
"accessRule.allPermittedMembersDescription": "Eşleşen rol izinlerine sahip üyeler bu kaynağa erişebilir.",
|
||||
"accessRule.appDescription": "Bu uygulamanın kime açık olduğunu kontrol edin. Üyelerin yine de onu görüntülemek veya işletmek için rol izinlerine ihtiyacı vardır.",
|
||||
"accessRule.appTitle": "Uygulama Erişim Kuralları",
|
||||
"accessRule.appTitle": "Uygulama izin kümesi",
|
||||
"accessRule.changeOpenScopeDescription": "Açık kapsamı değiştirmek bu kaynak için tüm bireysel izin ayarlarını sıfırlar. Geçiş yaptıktan sonra üyeye özel izinleri yeniden eklemeniz gerekir.",
|
||||
"accessRule.changeOpenScopeTitle": "Kaynak açık kapsamı değiştirilsin mi?",
|
||||
"accessRule.collapseSection": "{{title}} daralt",
|
||||
"accessRule.copied": "Erişim kuralı başarıyla kopyalandı",
|
||||
"accessRule.created": "Erişim kuralı başarıyla oluşturuldu",
|
||||
"accessRule.datasetDescription": "Bu bilgi tabanının kime açık olduğunu kontrol edin. Üyelerin yine de onu görüntülemek veya işletmek için rol izinlerine ihtiyacı vardır.",
|
||||
"accessRule.datasetTitle": "Bilgi Tabanı Erişim Kuralları",
|
||||
"accessRule.datasetTitle": "Bilgi Tabanı izin kümesi",
|
||||
"accessRule.defaultPermission": "Rol izinlerine göre",
|
||||
"accessRule.deleteDescription": "Bu erişim kuralı kalıcı olarak silinecek ve kaynak yetkilendirme listesinden kaldırılacaktır.",
|
||||
"accessRule.deleteTitle": "\"{{name}}\" silinsin mi?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Підключайте й керуйте MCP-серверами, щоб ваші застосунки мали доступ до зовнішніх інструментів і сервісів.",
|
||||
"members.adminTip": "Може створювати програми та керувати налаштуваннями команди",
|
||||
"members.alreadyInTeam": "Уже в команді",
|
||||
"members.alreadyInTeamTip": "Ці користувачі вже мають доступ до цього робочого простору.",
|
||||
"members.assignRoles": "Призначити ролі",
|
||||
"members.assignRolesModal.description": "Виберіть ролі для призначення цьому учаснику. Усі дозволи з вибраних ролей буде об'єднано.",
|
||||
"members.assignRolesModal.selectedCount": "Вибрано: {{count}}",
|
||||
"members.assignRolesModal.singleDescription": "Виберіть одну роль, яку потрібно призначити цьому учаснику.",
|
||||
"members.assignRolesModal.title": "Призначити ролі",
|
||||
"members.datasetOperatorTip": "Тільки може управляти базою знань",
|
||||
"members.editRole": "Редагувати роль",
|
||||
"members.editorTip": "Може створювати й редагувати застосунки",
|
||||
"members.email": "Електронна пошта",
|
||||
"members.emailInvalid": "Недійсний формат електронної пошти",
|
||||
"members.emailNotSetup": "Поштовий сервер не налаштований, тому запрошення електронною поштою не можуть бути надіслані. Будь ласка, повідомте користувачів про посилання для запрошення, яке буде видано після запрошення.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "ОСТАННЯ АКТИВНІСТЬ",
|
||||
"members.memberActions": "Дії учасника",
|
||||
"members.memberDetails.assign": "Призначити",
|
||||
"members.memberDetails.assignedRole": "Призначена роль",
|
||||
"members.memberDetails.assignedRoles": "Призначені ролі",
|
||||
"members.memberDetails.customGroup": "НАЛАШТОВАНІ",
|
||||
"members.memberDetails.generalGroup": "ЗАГАЛЬНІ",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Деталі учасника",
|
||||
"members.name": "ІМ'Я",
|
||||
"members.noNewInvitationsSent": "Нові запрошення не надіслано",
|
||||
"members.normalTip": "Може лише використовувати програми, не може створювати програми",
|
||||
"members.ok": "ОК",
|
||||
"members.pending": "В очікуванні...",
|
||||
"members.removeFromTeam": "Видалити з команди",
|
||||
"members.removeFromTeamConfirmDescription": "Підтвердьте видалення цього учасника. Цю дію не можна скасувати.",
|
||||
"members.removeFromTeamConfirmTitle": "Видалити {{memberName}} з команди",
|
||||
"members.role": "РОЛІ",
|
||||
"members.roles": "РОЛІ",
|
||||
"members.selectRole": "Виберіть роль",
|
||||
"members.sendInvite": "Надіслати запрошення",
|
||||
"members.transferModal.codeLabel": "Код перевірки",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Інтеграції",
|
||||
"settings.members": "Учасники",
|
||||
"settings.permissionSet": "Набір дозволів",
|
||||
"settings.permissionSetDescription": "Налаштуйте набори дозволів для використання із застосунками та базами знань. Набір дозволів — це багаторазова колекція дозволів на операції з ресурсами, яку можна призначати учасникам для конкретних ресурсів.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Постачальник моделі",
|
||||
"settings.resourceAccess": "Доступ до ресурсів",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Усі учасники з дозволами ролей",
|
||||
"accessRule.allPermittedMembersDescription": "Учасники з відповідними дозволами ролей можуть отримати доступ до цього ресурсу.",
|
||||
"accessRule.appDescription": "Контролюйте, кому відкрито цей застосунок. Учасникам усе одно потрібні дозволи ролей, щоб переглядати його чи працювати з ним.",
|
||||
"accessRule.appTitle": "Правила доступу до застосунку",
|
||||
"accessRule.appTitle": "Набір дозволів застосунку",
|
||||
"accessRule.changeOpenScopeDescription": "Зміна сфери відкритості скине всі індивідуальні налаштування дозволів для цього ресурсу. Після перемикання вам потрібно буде знову додати дозволи для конкретних учасників.",
|
||||
"accessRule.changeOpenScopeTitle": "Змінити сферу відкритості ресурсу?",
|
||||
"accessRule.collapseSection": "Згорнути {{title}}",
|
||||
"accessRule.copied": "Правило доступу успішно скопійовано",
|
||||
"accessRule.created": "Правило доступу успішно створено",
|
||||
"accessRule.datasetDescription": "Контролюйте, кому відкрито цю базу знань. Учасникам усе одно потрібні дозволи ролей, щоб переглядати її чи працювати з нею.",
|
||||
"accessRule.datasetTitle": "Правила доступу до бази знань",
|
||||
"accessRule.datasetTitle": "Набір дозволів бази знань",
|
||||
"accessRule.defaultPermission": "За дозволами ролей",
|
||||
"accessRule.deleteDescription": "Це правило доступу буде остаточно видалено та вилучено зі списку авторизації ресурсу.",
|
||||
"accessRule.deleteTitle": "Видалити \"{{name}}\"?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "Last opened",
|
||||
"mainNav.workspace.sort.openMenu": "Sort workspaces",
|
||||
"mcpPage.description": "Kết nối và quản lý máy chủ MCP để ứng dụng của bạn truy cập các công cụ và dịch vụ bên ngoài.",
|
||||
"members.adminTip": "Có thể xây dựng ứng dụng và quản lý cài đặt nhóm",
|
||||
"members.alreadyInTeam": "Đã ở trong nhóm",
|
||||
"members.alreadyInTeamTip": "Những người dùng này đã có quyền truy cập vào không gian làm việc này.",
|
||||
"members.assignRoles": "Gán vai trò",
|
||||
"members.assignRolesModal.description": "Chọn vai trò để gán cho thành viên này. Tất cả quyền từ các vai trò đã chọn sẽ được kết hợp.",
|
||||
"members.assignRolesModal.selectedCount": "Đã chọn {{count}}",
|
||||
"members.assignRolesModal.singleDescription": "Chọn một vai trò để gán cho thành viên này.",
|
||||
"members.assignRolesModal.title": "Gán vai trò",
|
||||
"members.datasetOperatorTip": "Chỉ có thể quản lý cơ sở kiến thức",
|
||||
"members.editRole": "Chỉnh sửa vai trò",
|
||||
"members.editorTip": "Có thể tạo và chỉnh sửa ứng dụng",
|
||||
"members.email": "Email",
|
||||
"members.emailInvalid": "Định dạng Email không hợp lệ",
|
||||
"members.emailNotSetup": "Máy chủ email chưa được thiết lập, vì vậy không thể gửi email mời. Vui lòng thông báo cho người dùng về liên kết mời sẽ được phát hành sau khi mời.",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "HOẠT ĐỘNG GẦN ĐÂY",
|
||||
"members.memberActions": "Hành động của thành viên",
|
||||
"members.memberDetails.assign": "Gán",
|
||||
"members.memberDetails.assignedRole": "Vai trò được chỉ định",
|
||||
"members.memberDetails.assignedRoles": "Vai trò đã gán",
|
||||
"members.memberDetails.customGroup": "TÙY CHỈNH",
|
||||
"members.memberDetails.generalGroup": "CHUNG",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "Chi tiết thành viên",
|
||||
"members.name": "TÊN",
|
||||
"members.noNewInvitationsSent": "Không có lời mời mới nào được gửi",
|
||||
"members.normalTip": "Chỉ có thể sử dụng ứng dụng, không thể xây dựng ứng dụng",
|
||||
"members.ok": "OK",
|
||||
"members.pending": "Đang chờ...",
|
||||
"members.removeFromTeam": "Xóa khỏi nhóm",
|
||||
"members.removeFromTeamConfirmDescription": "Xác nhận xóa thành viên này. Không thể hoàn tác thao tác này.",
|
||||
"members.removeFromTeamConfirmTitle": "Xóa {{memberName}} khỏi nhóm",
|
||||
"members.role": "VAI TRÒ",
|
||||
"members.roles": "VAI TRÒ",
|
||||
"members.selectRole": "Chọn một vai trò",
|
||||
"members.sendInvite": "Gửi Lời mời",
|
||||
"members.transferModal.codeLabel": "Mã xác thực",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "Extension",
|
||||
"settings.integrations": "Tích hợp",
|
||||
"settings.members": "Thành viên",
|
||||
"settings.permissionSet": "Bộ quyền",
|
||||
"settings.permissionSetDescription": "Định cấu hình các bộ quyền để sử dụng với ứng dụng và cơ sở kiến thức. Bộ quyền là tập hợp có thể tái sử dụng các quyền thao tác trên tài nguyên, có thể được gán cho thành viên đối với các tài nguyên cụ thể.",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.provider": "Nhà cung cấp mô hình",
|
||||
"settings.resourceAccess": "Quyền truy cập tài nguyên",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "Tất cả thành viên có quyền vai trò",
|
||||
"accessRule.allPermittedMembersDescription": "Các thành viên có quyền vai trò phù hợp có thể truy cập tài nguyên này.",
|
||||
"accessRule.appDescription": "Kiểm soát ứng dụng này được mở cho ai. Các thành viên vẫn cần quyền vai trò để xem hoặc vận hành nó.",
|
||||
"accessRule.appTitle": "Quy tắc truy cập ứng dụng",
|
||||
"accessRule.appTitle": "Bộ quyền ứng dụng",
|
||||
"accessRule.changeOpenScopeDescription": "Việc thay đổi phạm vi mở sẽ đặt lại tất cả cài đặt quyền riêng lẻ cho tài nguyên này. Bạn sẽ cần thêm lại các quyền dành riêng cho thành viên sau khi chuyển đổi.",
|
||||
"accessRule.changeOpenScopeTitle": "Thay đổi phạm vi mở của tài nguyên?",
|
||||
"accessRule.collapseSection": "Thu gọn {{title}}",
|
||||
"accessRule.copied": "Đã sao chép quy tắc truy cập thành công",
|
||||
"accessRule.created": "Đã tạo quy tắc truy cập thành công",
|
||||
"accessRule.datasetDescription": "Kiểm soát cơ sở tri thức này được mở cho ai. Các thành viên vẫn cần quyền vai trò để xem hoặc vận hành nó.",
|
||||
"accessRule.datasetTitle": "Quy tắc truy cập cơ sở tri thức",
|
||||
"accessRule.datasetTitle": "Bộ quyền cơ sở tri thức",
|
||||
"accessRule.defaultPermission": "Theo quyền vai trò",
|
||||
"accessRule.deleteDescription": "Quy tắc truy cập này sẽ bị xóa vĩnh viễn và bị loại bỏ khỏi danh sách ủy quyền tài nguyên.",
|
||||
"accessRule.deleteTitle": "Xóa \"{{name}}\"?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "上次打开",
|
||||
"mainNav.workspace.sort.openMenu": "排序工作空间",
|
||||
"mcpPage.description": "连接并管理 MCP 服务器,让你的应用可以访问外部工具与服务。",
|
||||
"members.adminTip": "能够建立应用程序和管理团队设置",
|
||||
"members.alreadyInTeam": "已在团队中",
|
||||
"members.alreadyInTeamTip": "以下用户已经可以访问此工作空间。",
|
||||
"members.assignRoles": "分配角色",
|
||||
"members.assignRolesModal.description": "为该成员选择要分配的角色,所选角色的权限将被合并。",
|
||||
"members.assignRolesModal.selectedCount": "已选 {{count}} 项",
|
||||
"members.assignRolesModal.singleDescription": "为该成员选择一个要分配的角色。",
|
||||
"members.assignRolesModal.title": "分配角色",
|
||||
"members.datasetOperatorTip": "只能管理知识库",
|
||||
"members.editRole": "编辑角色",
|
||||
"members.editorTip": "能够建立并编辑应用程序,不能管理团队设置",
|
||||
"members.email": "邮箱",
|
||||
"members.emailInvalid": "邮箱格式无效",
|
||||
"members.emailNotSetup": "由于邮件服务器未设置,无法发送邀请邮件。请将邀请后生成的邀请链接通知用户。",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "上次活动时间",
|
||||
"members.memberActions": "成员操作",
|
||||
"members.memberDetails.assign": "分配",
|
||||
"members.memberDetails.assignedRole": "已分配角色",
|
||||
"members.memberDetails.assignedRoles": "已分配角色",
|
||||
"members.memberDetails.customGroup": "自定义",
|
||||
"members.memberDetails.generalGroup": "通用",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "成员详情",
|
||||
"members.name": "姓名",
|
||||
"members.noNewInvitationsSent": "没有新的邀请发送",
|
||||
"members.normalTip": "只能使用应用程序,不能建立应用程序",
|
||||
"members.ok": "好的",
|
||||
"members.pending": "待定...",
|
||||
"members.removeFromTeam": "移出团队",
|
||||
"members.removeFromTeamConfirmDescription": "确认将该成员移出,该操作不可逆",
|
||||
"members.removeFromTeamConfirmTitle": "将{{memberName}}移出团队",
|
||||
"members.role": "角色",
|
||||
"members.roles": "角色",
|
||||
"members.selectRole": "请选择角色",
|
||||
"members.sendInvite": "发送邀请",
|
||||
"members.transferModal.codeLabel": "验证码",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "扩展",
|
||||
"settings.integrations": "集成",
|
||||
"settings.members": "成员",
|
||||
"settings.permissionSet": "权限集",
|
||||
"settings.permissionSetDescription": "配置可供应用和知识库使用的权限集。权限集是一组可复用的资源操作权限,可针对特定资源分配给成员。",
|
||||
"settings.preferences": "偏好设置",
|
||||
"settings.provider": "模型供应商",
|
||||
"settings.resourceAccess": "访问权限",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "全部成员",
|
||||
"accessRule.allPermittedMembersDescription": "所有具备对应角色权限的成员均可访问",
|
||||
"accessRule.appDescription": "设置谁可以访问此应用,以及个别成员的权限例外",
|
||||
"accessRule.appTitle": "应用访问规则",
|
||||
"accessRule.appTitle": "应用权限集",
|
||||
"accessRule.changeOpenScopeDescription": "更改开放范围会重置此资源的所有个人权限设置。切换后,你需要重新添加成员专属权限。",
|
||||
"accessRule.changeOpenScopeTitle": "更改资源开放范围?",
|
||||
"accessRule.collapseSection": "折叠 {{title}}",
|
||||
"accessRule.copied": "访问规则已复制",
|
||||
"accessRule.created": "访问规则已创建",
|
||||
"accessRule.datasetDescription": "控制此知识库对哪些成员开放。成员仍需具备角色权限才能查看或操作此知识库。",
|
||||
"accessRule.datasetTitle": "知识库访问规则",
|
||||
"accessRule.datasetTitle": "知识库权限集",
|
||||
"accessRule.defaultPermission": "按角色权限",
|
||||
"accessRule.deleteDescription": "此访问规则将被永久删除,并从资源授权列表中移除。",
|
||||
"accessRule.deleteTitle": "删除“{{name}}”?",
|
||||
|
||||
@ -213,12 +213,17 @@
|
||||
"mainNav.workspace.sort.lastOpened": "上次開啟",
|
||||
"mainNav.workspace.sort.openMenu": "排序工作區",
|
||||
"mcpPage.description": "連接並管理 MCP 伺服器,讓你的應用可以存取外部工具和服務。",
|
||||
"members.adminTip": "能夠建立應用程式和管理團隊設定",
|
||||
"members.alreadyInTeam": "已在團隊中",
|
||||
"members.alreadyInTeamTip": "以下使用者已經可以存取此工作區。",
|
||||
"members.assignRoles": "分配角色",
|
||||
"members.assignRolesModal.description": "為該成員選擇要分配的角色,所選角色的權限將被合併。",
|
||||
"members.assignRolesModal.selectedCount": "已選 {{count}} 項",
|
||||
"members.assignRolesModal.singleDescription": "為該成員選擇一個要分配的角色。",
|
||||
"members.assignRolesModal.title": "分配角色",
|
||||
"members.datasetOperatorTip": "只能管理知識庫",
|
||||
"members.editRole": "編輯角色",
|
||||
"members.editorTip": "能夠建立並編輯應用程式,不能管理團隊設定",
|
||||
"members.email": "郵箱",
|
||||
"members.emailInvalid": "郵箱格式無效",
|
||||
"members.emailNotSetup": "由於郵件伺服器未設置,無法發送邀請郵件。請將邀請後生成的邀請連結通知用戶。",
|
||||
@ -234,6 +239,7 @@
|
||||
"members.lastActive": "上次活動時間",
|
||||
"members.memberActions": "成員操作",
|
||||
"members.memberDetails.assign": "分配",
|
||||
"members.memberDetails.assignedRole": "已分配角色",
|
||||
"members.memberDetails.assignedRoles": "已分配角色",
|
||||
"members.memberDetails.customGroup": "自訂",
|
||||
"members.memberDetails.generalGroup": "通用",
|
||||
@ -244,10 +250,14 @@
|
||||
"members.memberDetails.title": "成員詳情",
|
||||
"members.name": "姓名",
|
||||
"members.noNewInvitationsSent": "沒有新的邀請送出",
|
||||
"members.normalTip": "只能使用應用程式,不能建立應用程式",
|
||||
"members.ok": "好的",
|
||||
"members.pending": "待定...",
|
||||
"members.removeFromTeam": "移出團隊",
|
||||
"members.removeFromTeamConfirmDescription": "確認將該成員移出,此操作不可逆",
|
||||
"members.removeFromTeamConfirmTitle": "將{{memberName}}移出團隊",
|
||||
"members.role": "角色",
|
||||
"members.roles": "角色",
|
||||
"members.selectRole": "請選擇角色",
|
||||
"members.sendInvite": "傳送邀請",
|
||||
"members.transferModal.codeLabel": "驗證碼",
|
||||
@ -510,6 +520,8 @@
|
||||
"settings.extension": "擴充",
|
||||
"settings.integrations": "集成",
|
||||
"settings.members": "成員",
|
||||
"settings.permissionSet": "權限集",
|
||||
"settings.permissionSetDescription": "配置可供應用和知識庫使用的權限集。權限集是一組可重複使用的資源操作權限,可針對特定資源分配給成員。",
|
||||
"settings.preferences": "偏好設定",
|
||||
"settings.provider": "模型供應商",
|
||||
"settings.resourceAccess": "訪問權限",
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"accessRule.allPermittedMembers": "全部成員",
|
||||
"accessRule.allPermittedMembersDescription": "所有具備對應角色權限的成員均可訪問",
|
||||
"accessRule.appDescription": "設定誰可以訪問此應用,以及個別成員的權限例外",
|
||||
"accessRule.appTitle": "應用訪問規則",
|
||||
"accessRule.appTitle": "應用權限集",
|
||||
"accessRule.changeOpenScopeDescription": "更改開放範圍會重置此資源的所有個人權限設定。切換後,你需要重新新增成員專屬權限。",
|
||||
"accessRule.changeOpenScopeTitle": "更改資源開放範圍?",
|
||||
"accessRule.collapseSection": "折疊 {{title}}",
|
||||
"accessRule.copied": "訪問規則已複製",
|
||||
"accessRule.created": "訪問規則已建立",
|
||||
"accessRule.datasetDescription": "控制此知識庫對哪些成員開放。成員仍需具備角色權限才能查看或操作此知識庫。",
|
||||
"accessRule.datasetTitle": "知識庫訪問規則",
|
||||
"accessRule.datasetTitle": "知識庫權限集",
|
||||
"accessRule.defaultPermission": "按角色權限",
|
||||
"accessRule.deleteDescription": "此訪問規則將被永久刪除,並從資源授權列表中移除。",
|
||||
"accessRule.deleteTitle": "刪除「{{name}}」?",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user