mirror of
https://github.com/langgenius/dify.git
synced 2026-05-09 12:59:18 +08:00
feat: add app and dataset access configuration modals for managing access rules
This commit is contained in:
parent
5f4b086e39
commit
7b97789fd2
175
web/app/components/access-config-modal/index.tsx
Normal file
175
web/app/components/access-config-modal/index.tsx
Normal file
@ -0,0 +1,175 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
AccessRule,
|
||||
AssignedRole,
|
||||
} from '@/app/components/header/account-setting/access-rules-page/access-rule-row'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogCloseButton,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogTitle,
|
||||
} from '@langgenius/dify-ui/dialog'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import { useCallback, useState } from 'react'
|
||||
import AccessRuleRow from '@/app/components/header/account-setting/access-rules-page/access-rule-row'
|
||||
import AddRuleTargetsModal from '@/app/components/header/account-setting/access-rules-page/add-rule-targets-modal'
|
||||
|
||||
export type AccessConfigModalProps = {
|
||||
open: boolean
|
||||
title: string
|
||||
description: string
|
||||
initialRules: AccessRule[]
|
||||
/**
|
||||
* Optional override label for the primary action. Defaults to "Save".
|
||||
*/
|
||||
saveLabel?: string
|
||||
/**
|
||||
* Optional override label for the cancel action. Defaults to "Cancel".
|
||||
*/
|
||||
cancelLabel?: string
|
||||
onClose: () => void
|
||||
onSave?: (rules: AccessRule[]) => void
|
||||
}
|
||||
|
||||
type AccessConfigModalBodyProps = Omit<AccessConfigModalProps, 'open'>
|
||||
|
||||
const AccessConfigModalBody = ({
|
||||
title,
|
||||
description,
|
||||
initialRules,
|
||||
saveLabel = 'Save',
|
||||
cancelLabel = 'Cancel',
|
||||
onClose,
|
||||
onSave,
|
||||
}: AccessConfigModalBodyProps) => {
|
||||
const [rules, setRules] = useState<AccessRule[]>(initialRules)
|
||||
const [addingRule, setAddingRule] = useState<AccessRule | null>(null)
|
||||
|
||||
const handleAddRole = useCallback((rule: AccessRule) => {
|
||||
setAddingRule(rule)
|
||||
}, [])
|
||||
|
||||
const handleCloseAddModal = useCallback(() => {
|
||||
setAddingRule(null)
|
||||
}, [])
|
||||
|
||||
const handleAddSubmit = useCallback(
|
||||
(_selection: { roleIds: string[], memberIds: string[] }) => {
|
||||
// TODO: wire up to API when backend is ready.
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const handleRemoveRole = useCallback(
|
||||
(target: AccessRule, role: AssignedRole) => {
|
||||
setRules(prev =>
|
||||
prev.map(rule =>
|
||||
rule.id === target.id
|
||||
? {
|
||||
...rule,
|
||||
assignedRoles: rule.assignedRoles.filter(r => r.id !== role.id),
|
||||
}
|
||||
: rule,
|
||||
),
|
||||
)
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
onSave?.(rules)
|
||||
onClose()
|
||||
}, [onClose, onSave, rules])
|
||||
|
||||
return (
|
||||
<DialogContent
|
||||
className="flex max-h-[85vh] w-[520px] flex-col overflow-hidden p-0"
|
||||
backdropProps={{ forceRender: true }}
|
||||
>
|
||||
<div className="relative shrink-0 px-6 pt-6 pb-4">
|
||||
<DialogCloseButton />
|
||||
<div className="pr-8">
|
||||
<DialogTitle className="system-xl-semibold text-text-primary">
|
||||
{title}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="mt-1 system-sm-regular text-text-tertiary">
|
||||
{description}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea
|
||||
className="min-h-0 flex-1"
|
||||
slotClassNames={{ viewport: 'px-6 overscroll-contain' }}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
{rules.map(rule => (
|
||||
<AccessRuleRow
|
||||
key={rule.id}
|
||||
rule={rule}
|
||||
showMenu={false}
|
||||
onAddRole={handleAddRole}
|
||||
onRemoveRole={handleRemoveRole}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
<div className="flex shrink-0 items-center justify-end gap-2 border-t border-divider-subtle px-6 py-4">
|
||||
<Button variant="secondary" onClick={onClose}>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSave}>
|
||||
{saveLabel}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{addingRule && (
|
||||
<AddRuleTargetsModal
|
||||
open
|
||||
ruleName={addingRule.name}
|
||||
initialRoleIds={addingRule.assignedRoles.map(role => role.id)}
|
||||
initialMemberIds={[]}
|
||||
onClose={handleCloseAddModal}
|
||||
onSubmit={handleAddSubmit}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
)
|
||||
}
|
||||
|
||||
const AccessConfigModal = ({
|
||||
open,
|
||||
title,
|
||||
description,
|
||||
initialRules,
|
||||
saveLabel,
|
||||
cancelLabel,
|
||||
onClose,
|
||||
onSave,
|
||||
}: AccessConfigModalProps) => {
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(nextOpen) => {
|
||||
if (!nextOpen)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<AccessConfigModalBody
|
||||
title={title}
|
||||
description={description}
|
||||
initialRules={initialRules}
|
||||
saveLabel={saveLabel}
|
||||
cancelLabel={cancelLabel}
|
||||
onClose={onClose}
|
||||
onSave={onSave}
|
||||
/>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccessConfigModal
|
||||
108
web/app/components/apps/app-access-config-modal/index.tsx
Normal file
108
web/app/components/apps/app-access-config-modal/index.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
'use client'
|
||||
|
||||
import type { AccessRule } from '@/app/components/header/account-setting/access-rules-page/access-rule-row'
|
||||
import type { App } from '@/types/app'
|
||||
import AccessConfigModal from '@/app/components/access-config-modal'
|
||||
|
||||
// TODO: replace with the per-app access rules fetched from the access-rules API
|
||||
// once available. The catalog mirrors the workspace-level App access rules and
|
||||
// adds app-specific rules that can only be assigned per-app.
|
||||
const DEFAULT_APP_ACCESS_RULES: AccessRule[] = [
|
||||
{
|
||||
id: 'app-full-access',
|
||||
name: 'Full access',
|
||||
description: 'Highest level. Can edit, publish, delete apps, and manage access for this app.',
|
||||
assignedRoles: [
|
||||
{ id: 'owner', name: 'Owner' },
|
||||
{ id: 'admin', name: 'Admin' },
|
||||
{ id: 'marketing-lead', name: 'Marketing Lead' },
|
||||
{ id: 'kb-admin', name: 'KB Admin' },
|
||||
{ id: 'app-admin', name: 'App Admin' },
|
||||
{ id: 'executive', name: 'Executive' },
|
||||
],
|
||||
permissions: [],
|
||||
},
|
||||
{
|
||||
id: 'app-can-edit',
|
||||
name: 'Can edit',
|
||||
description: 'Modify Prompts, adjust workflows, change variables. Test and publish updates.',
|
||||
assignedRoles: [
|
||||
{ id: 'owner', name: 'Owner' },
|
||||
{ id: 'admin', name: 'Admin' },
|
||||
{ id: 'marketing-lead', name: 'Marketing Lead' },
|
||||
{ id: 'kb-admin', name: 'KB Admin' },
|
||||
{ id: 'app-admin', name: 'App Admin' },
|
||||
{ id: 'executive', name: 'Executive' },
|
||||
],
|
||||
permissions: [],
|
||||
},
|
||||
{
|
||||
id: 'app-can-view-and-use',
|
||||
name: 'Can view & use',
|
||||
description: 'View and use the app. Access Prompt and workflow logs. Cannot modify.',
|
||||
assignedRoles: [
|
||||
{ id: 'owner', name: 'Owner' },
|
||||
{ id: 'admin', name: 'Admin' },
|
||||
{ id: 'marketing-lead', name: 'Marketing Lead' },
|
||||
{ id: 'kb-admin', name: 'KB Admin' },
|
||||
{ id: 'app-admin', name: 'App Admin' },
|
||||
{ id: 'executive', name: 'Executive' },
|
||||
],
|
||||
permissions: [],
|
||||
},
|
||||
{
|
||||
id: 'app-can-preview',
|
||||
name: 'Can preview',
|
||||
description: 'View the app in the list only. Cannot open the editor or use the app.',
|
||||
assignedRoles: [
|
||||
{ id: 'owner', name: 'Owner' },
|
||||
{ id: 'admin', name: 'Admin' },
|
||||
{ id: 'marketing-lead', name: 'Marketing Lead' },
|
||||
{ id: 'kb-admin', name: 'KB Admin' },
|
||||
{ id: 'app-admin', name: 'App Admin' },
|
||||
{ id: 'executive', name: 'Executive' },
|
||||
],
|
||||
permissions: [],
|
||||
},
|
||||
{
|
||||
id: 'app-can-optimize-prompt',
|
||||
name: 'Can optimize prompt',
|
||||
description: 'Dedicated prompt optimization access.',
|
||||
assignedRoles: [
|
||||
{ id: 'owner', name: 'Owner' },
|
||||
{ id: 'admin', name: 'Admin' },
|
||||
{ id: 'marketing-lead', name: 'Marketing Lead' },
|
||||
{ id: 'kb-admin', name: 'KB Admin' },
|
||||
{ id: 'app-admin', name: 'App Admin' },
|
||||
{ id: 'executive', name: 'Executive' },
|
||||
],
|
||||
permissions: [],
|
||||
},
|
||||
]
|
||||
|
||||
export type AppAccessConfigModalProps = {
|
||||
open: boolean
|
||||
app: Pick<App, 'id' | 'name'>
|
||||
onClose: () => void
|
||||
onSave?: (rules: AccessRule[]) => void
|
||||
}
|
||||
|
||||
const AppAccessConfigModal = ({
|
||||
open,
|
||||
app: _app,
|
||||
onClose,
|
||||
onSave,
|
||||
}: AppAccessConfigModalProps) => {
|
||||
return (
|
||||
<AccessConfigModal
|
||||
open={open}
|
||||
title="App Access Config"
|
||||
description="Configure access levels for this specific app."
|
||||
initialRules={DEFAULT_APP_ACCESS_RULES}
|
||||
onClose={onClose}
|
||||
onSave={onSave}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default AppAccessConfigModal
|
||||
@ -68,6 +68,9 @@ const DSLExportConfirmModal = dynamic(() => import('@/app/components/workflow/ds
|
||||
const AccessControl = dynamic(() => import('@/app/components/app/app-access-control'), {
|
||||
ssr: false,
|
||||
})
|
||||
const AppAccessConfigModal = dynamic(() => import('@/app/components/apps/app-access-config-modal'), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
type AppCardProps = {
|
||||
app: App
|
||||
@ -86,6 +89,7 @@ type AppCardOperationsMenuProps = {
|
||||
onSwitch: () => void
|
||||
onDelete: () => void
|
||||
onAccessControl: () => void
|
||||
onAccessConfig: () => void
|
||||
}
|
||||
|
||||
const AppCardOperationsMenu: React.FC<AppCardOperationsMenuProps> = ({
|
||||
@ -99,6 +103,7 @@ const AppCardOperationsMenu: React.FC<AppCardOperationsMenuProps> = ({
|
||||
onSwitch,
|
||||
onDelete,
|
||||
onAccessControl,
|
||||
onAccessConfig,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const openAsyncWindow = useAsyncWindowOpen()
|
||||
@ -167,6 +172,10 @@ const AppCardOperationsMenu: React.FC<AppCardOperationsMenuProps> = ({
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuItem className="gap-2 px-3" onClick={e => handleMenuAction(e, onAccessConfig)}>
|
||||
<span className="text-sm leading-5 text-text-secondary">Access Config</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
className="gap-2 px-3"
|
||||
@ -217,6 +226,7 @@ const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||
const [confirmDeleteInput, setConfirmDeleteInput] = useState('')
|
||||
const [showAccessControl, setShowAccessControl] = useState(false)
|
||||
const [showAccessConfig, setShowAccessConfig] = useState(false)
|
||||
const [isOperationsMenuOpen, setIsOperationsMenuOpen] = useState(false)
|
||||
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
|
||||
const { mutateAsync: mutateDeleteApp, isPending: isDeleting } = useDeleteAppMutation()
|
||||
@ -288,6 +298,13 @@ const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleShowAccessConfig = useCallback(() => {
|
||||
setIsOperationsMenuOpen(false)
|
||||
queueMicrotask(() => {
|
||||
setShowAccessConfig(true)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({
|
||||
name,
|
||||
icon_type,
|
||||
@ -550,6 +567,7 @@ const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {
|
||||
onSwitch={handleShowSwitchModal}
|
||||
onDelete={handleShowDeleteConfirm}
|
||||
onAccessControl={handleShowAccessControl}
|
||||
onAccessConfig={handleShowAccessConfig}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
@ -564,6 +582,7 @@ const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {
|
||||
onSwitch={handleShowSwitchModal}
|
||||
onDelete={handleShowDeleteConfirm}
|
||||
onAccessControl={handleShowAccessControl}
|
||||
onAccessConfig={handleShowAccessConfig}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
@ -670,6 +689,13 @@ const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {
|
||||
{showAccessControl && (
|
||||
<AccessControl app={app} onConfirm={onUpdateAccessControl} onClose={() => setShowAccessControl(false)} />
|
||||
)}
|
||||
{showAccessConfig && (
|
||||
<AppAccessConfigModal
|
||||
open
|
||||
app={app}
|
||||
onClose={() => setShowAccessConfig(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ describe('Operations', () => {
|
||||
openRenameModal: vi.fn(),
|
||||
handleExportPipeline: vi.fn(),
|
||||
detectIsUsedByApp: vi.fn(),
|
||||
openAccessConfig: vi.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -80,6 +81,14 @@ describe('Operations', () => {
|
||||
fireEvent.click(screen.getByText(/operation\.delete/))
|
||||
expect(detectIsUsedByApp).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should call openAccessConfig when access config is clicked', () => {
|
||||
const openAccessConfig = vi.fn()
|
||||
renderInMenu(<Operations {...defaultProps} openAccessConfig={openAccessConfig} />)
|
||||
|
||||
fireEvent.click(screen.getByText('Access Config'))
|
||||
expect(openAccessConfig).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
|
||||
@ -71,10 +71,12 @@ describe('DatasetCardModals', () => {
|
||||
modalState: {
|
||||
showRenameModal: false,
|
||||
showConfirmDelete: false,
|
||||
showAccessConfig: false,
|
||||
confirmMessage: '',
|
||||
},
|
||||
onCloseRename: vi.fn(),
|
||||
onCloseConfirm: vi.fn(),
|
||||
onCloseAccessConfig: vi.fn(),
|
||||
onConfirmDelete: vi.fn(),
|
||||
onSuccess: vi.fn(),
|
||||
}
|
||||
@ -209,6 +211,7 @@ describe('DatasetCardModals', () => {
|
||||
modalState={{
|
||||
showRenameModal: true,
|
||||
showConfirmDelete: true,
|
||||
showAccessConfig: false,
|
||||
confirmMessage: 'Delete this dataset?',
|
||||
}}
|
||||
/>,
|
||||
|
||||
@ -34,6 +34,7 @@ describe('OperationsDropdown', () => {
|
||||
openRenameModal: vi.fn(),
|
||||
handleExportPipeline: vi.fn(),
|
||||
detectIsUsedByApp: vi.fn(),
|
||||
openAccessConfig: vi.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@ -10,11 +10,17 @@ import {
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import dynamic from '@/next/dynamic'
|
||||
import RenameDatasetModal from '../../../rename-modal'
|
||||
|
||||
const DatasetAccessConfigModal = dynamic(() => import('../dataset-access-config-modal'), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
type ModalState = {
|
||||
showRenameModal: boolean
|
||||
showConfirmDelete: boolean
|
||||
showAccessConfig: boolean
|
||||
confirmMessage: string
|
||||
}
|
||||
|
||||
@ -23,6 +29,7 @@ type DatasetCardModalsProps = {
|
||||
modalState: ModalState
|
||||
onCloseRename: () => void
|
||||
onCloseConfirm: () => void
|
||||
onCloseAccessConfig: () => void
|
||||
onConfirmDelete: () => void
|
||||
onSuccess?: () => void
|
||||
}
|
||||
@ -32,6 +39,7 @@ const DatasetCardModals = ({
|
||||
modalState,
|
||||
onCloseRename,
|
||||
onCloseConfirm,
|
||||
onCloseAccessConfig,
|
||||
onConfirmDelete,
|
||||
onSuccess,
|
||||
}: DatasetCardModalsProps) => {
|
||||
@ -47,6 +55,13 @@ const DatasetCardModals = ({
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
)}
|
||||
{modalState.showAccessConfig && (
|
||||
<DatasetAccessConfigModal
|
||||
open
|
||||
dataset={dataset}
|
||||
onClose={onCloseAccessConfig}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={modalState.showConfirmDelete} onOpenChange={open => !open && onCloseConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
|
||||
@ -14,6 +14,7 @@ type OperationsDropdownProps = {
|
||||
openRenameModal: () => void
|
||||
handleExportPipeline: (include?: boolean) => void
|
||||
detectIsUsedByApp: () => void
|
||||
openAccessConfig: () => void
|
||||
}
|
||||
|
||||
const OperationsDropdown = ({
|
||||
@ -22,6 +23,7 @@ const OperationsDropdown = ({
|
||||
openRenameModal,
|
||||
handleExportPipeline,
|
||||
detectIsUsedByApp,
|
||||
openAccessConfig,
|
||||
}: OperationsDropdownProps) => {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
@ -58,6 +60,7 @@ const OperationsDropdown = ({
|
||||
openRenameModal={openRenameModal}
|
||||
handleExportPipeline={handleExportPipeline}
|
||||
detectIsUsedByApp={detectIsUsedByApp}
|
||||
openAccessConfig={openAccessConfig}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@ -0,0 +1,108 @@
|
||||
'use client'
|
||||
|
||||
import type { AccessRule } from '@/app/components/header/account-setting/access-rules-page/access-rule-row'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import AccessConfigModal from '@/app/components/access-config-modal'
|
||||
|
||||
// TODO: replace with the per-knowledge-base access rules fetched from the
|
||||
// access-rules API once available. The catalog mirrors the workspace-level
|
||||
// Knowledge Base access rules.
|
||||
const DEFAULT_KB_ACCESS_RULES: AccessRule[] = [
|
||||
{
|
||||
id: 'kb-full-access',
|
||||
name: 'Full access',
|
||||
description: 'Highest level. Can edit, publish, delete, and manage access for this knowledge base.',
|
||||
assignedRoles: [
|
||||
{ id: 'owner', name: 'Owner' },
|
||||
{ id: 'admin', name: 'Admin' },
|
||||
{ id: 'marketing-lead', name: 'Marketing Lead' },
|
||||
{ id: 'kb-admin', name: 'KB Admin' },
|
||||
{ id: 'app-admin', name: 'App Admin' },
|
||||
{ id: 'executive', name: 'Executive' },
|
||||
],
|
||||
permissions: [],
|
||||
},
|
||||
{
|
||||
id: 'kb-can-edit',
|
||||
name: 'Can edit',
|
||||
description: 'Edit knowledge base content, modify settings, and run tests.',
|
||||
assignedRoles: [
|
||||
{ id: 'owner', name: 'Owner' },
|
||||
{ id: 'admin', name: 'Admin' },
|
||||
{ id: 'marketing-lead', name: 'Marketing Lead' },
|
||||
{ id: 'kb-admin', name: 'KB Admin' },
|
||||
{ id: 'app-admin', name: 'App Admin' },
|
||||
{ id: 'executive', name: 'Executive' },
|
||||
],
|
||||
permissions: [],
|
||||
},
|
||||
{
|
||||
id: 'kb-can-view-and-use',
|
||||
name: 'Can view & use',
|
||||
description: 'View knowledge base sources, configs, and logs. Cannot modify content.',
|
||||
assignedRoles: [
|
||||
{ id: 'owner', name: 'Owner' },
|
||||
{ id: 'admin', name: 'Admin' },
|
||||
{ id: 'marketing-lead', name: 'Marketing Lead' },
|
||||
{ id: 'kb-admin', name: 'KB Admin' },
|
||||
{ id: 'app-admin', name: 'App Admin' },
|
||||
{ id: 'executive', name: 'Executive' },
|
||||
],
|
||||
permissions: [],
|
||||
},
|
||||
{
|
||||
id: 'kb-can-preview',
|
||||
name: 'Can preview',
|
||||
description: 'View in the list only. Cannot access the detail page.',
|
||||
assignedRoles: [
|
||||
{ id: 'owner', name: 'Owner' },
|
||||
{ id: 'admin', name: 'Admin' },
|
||||
{ id: 'marketing-lead', name: 'Marketing Lead' },
|
||||
{ id: 'kb-admin', name: 'KB Admin' },
|
||||
{ id: 'app-admin', name: 'App Admin' },
|
||||
{ id: 'executive', name: 'Executive' },
|
||||
],
|
||||
permissions: [],
|
||||
},
|
||||
{
|
||||
id: 'kb-can-test',
|
||||
name: 'Can test',
|
||||
description: 'Test knowledge base retrieval efficiency in sandbox.',
|
||||
assignedRoles: [
|
||||
{ id: 'owner', name: 'Owner' },
|
||||
{ id: 'admin', name: 'Admin' },
|
||||
{ id: 'marketing-lead', name: 'Marketing Lead' },
|
||||
{ id: 'kb-admin', name: 'KB Admin' },
|
||||
{ id: 'app-admin', name: 'App Admin' },
|
||||
{ id: 'executive', name: 'Executive' },
|
||||
],
|
||||
permissions: [],
|
||||
},
|
||||
]
|
||||
|
||||
export type DatasetAccessConfigModalProps = {
|
||||
open: boolean
|
||||
dataset: Pick<DataSet, 'id' | 'name'>
|
||||
onClose: () => void
|
||||
onSave?: (rules: AccessRule[]) => void
|
||||
}
|
||||
|
||||
const DatasetAccessConfigModal = ({
|
||||
open,
|
||||
dataset: _dataset,
|
||||
onClose,
|
||||
onSave,
|
||||
}: DatasetAccessConfigModalProps) => {
|
||||
return (
|
||||
<AccessConfigModal
|
||||
open={open}
|
||||
title="Knowledge Base Access Config"
|
||||
description="Configure access levels for this specific knowledge base."
|
||||
initialRules={DEFAULT_KB_ACCESS_RULES}
|
||||
onClose={onClose}
|
||||
onSave={onSave}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default DatasetAccessConfigModal
|
||||
@ -10,6 +10,7 @@ import { downloadBlob } from '@/utils/download'
|
||||
type ModalState = {
|
||||
showRenameModal: boolean
|
||||
showConfirmDelete: boolean
|
||||
showAccessConfig: boolean
|
||||
confirmMessage: string
|
||||
}
|
||||
|
||||
@ -30,6 +31,7 @@ export const useDatasetCardState = ({ dataset, onSuccess }: UseDatasetCardStateO
|
||||
const [modalState, setModalState] = useState<ModalState>({
|
||||
showRenameModal: false,
|
||||
showConfirmDelete: false,
|
||||
showAccessConfig: false,
|
||||
confirmMessage: '',
|
||||
})
|
||||
|
||||
@ -49,6 +51,14 @@ export const useDatasetCardState = ({ dataset, onSuccess }: UseDatasetCardStateO
|
||||
setModalState(prev => ({ ...prev, showConfirmDelete: false }))
|
||||
}, [])
|
||||
|
||||
const openAccessConfig = useCallback(() => {
|
||||
setModalState(prev => ({ ...prev, showAccessConfig: true }))
|
||||
}, [])
|
||||
|
||||
const closeAccessConfig = useCallback(() => {
|
||||
setModalState(prev => ({ ...prev, showAccessConfig: false }))
|
||||
}, [])
|
||||
|
||||
// API mutations
|
||||
const { mutateAsync: checkUsage } = useCheckDatasetUsage()
|
||||
const { mutateAsync: deleteDatasetMutation } = useDeleteDataset()
|
||||
@ -122,6 +132,8 @@ export const useDatasetCardState = ({ dataset, onSuccess }: UseDatasetCardStateO
|
||||
openRenameModal,
|
||||
closeRenameModal,
|
||||
closeConfirmDelete,
|
||||
openAccessConfig,
|
||||
closeAccessConfig,
|
||||
|
||||
// Export state
|
||||
exporting,
|
||||
|
||||
@ -37,6 +37,8 @@ const DatasetCard = ({
|
||||
openRenameModal,
|
||||
closeRenameModal,
|
||||
closeConfirmDelete,
|
||||
openAccessConfig,
|
||||
closeAccessConfig,
|
||||
handleExportPipeline,
|
||||
detectIsUsedByApp,
|
||||
onConfirmDelete,
|
||||
@ -88,6 +90,7 @@ const DatasetCard = ({
|
||||
openRenameModal={openRenameModal}
|
||||
handleExportPipeline={handleExportPipeline}
|
||||
detectIsUsedByApp={detectIsUsedByApp}
|
||||
openAccessConfig={openAccessConfig}
|
||||
/>
|
||||
</div>
|
||||
<DatasetCardModals
|
||||
@ -95,6 +98,7 @@ const DatasetCard = ({
|
||||
modalState={modalState}
|
||||
onCloseRename={closeRenameModal}
|
||||
onCloseConfirm={closeConfirmDelete}
|
||||
onCloseAccessConfig={closeAccessConfig}
|
||||
onConfirmDelete={onConfirmDelete}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
|
||||
@ -11,6 +11,7 @@ type OperationsProps = {
|
||||
openRenameModal: () => void
|
||||
handleExportPipeline: () => void
|
||||
detectIsUsedByApp: () => void
|
||||
openAccessConfig: () => void
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
@ -20,6 +21,7 @@ const Operations = ({
|
||||
openRenameModal,
|
||||
handleExportPipeline,
|
||||
detectIsUsedByApp,
|
||||
openAccessConfig,
|
||||
onClose,
|
||||
}: OperationsProps) => {
|
||||
const { t } = useTranslation()
|
||||
@ -39,23 +41,32 @@ const Operations = ({
|
||||
detectIsUsedByApp()
|
||||
}
|
||||
|
||||
const handleAccessConfig = () => {
|
||||
onClose?.()
|
||||
openAccessConfig()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuItem onClick={handleRename}>
|
||||
<span aria-hidden className="i-ri-edit-line size-4 text-text-tertiary" />
|
||||
<span aria-hidden className="mr-1 i-ri-edit-line size-4 text-text-tertiary" />
|
||||
{t('operation.edit', { ns: 'common' })}
|
||||
</DropdownMenuItem>
|
||||
{showExportPipeline && (
|
||||
<DropdownMenuItem onClick={handleExport}>
|
||||
<span aria-hidden className="i-ri-file-download-line size-4 text-text-tertiary" />
|
||||
<span aria-hidden className="mr-1 i-ri-file-download-line size-4 text-text-tertiary" />
|
||||
{t('operations.exportPipeline', { ns: 'datasetPipeline' })}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={handleAccessConfig}>
|
||||
<span aria-hidden className="mr-1 i-ri-user-settings-line size-4 text-text-tertiary" />
|
||||
Access Config
|
||||
</DropdownMenuItem>
|
||||
{showDelete && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem variant="destructive" onClick={handleDelete}>
|
||||
<span aria-hidden className="i-ri-delete-bin-line size-4" />
|
||||
<span aria-hidden className="mr-1 i-ri-delete-bin-line size-4" />
|
||||
{t('operation.delete', { ns: 'common' })}
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
|
||||
@ -21,6 +21,7 @@ export type AccessRule = {
|
||||
export type AccessRuleRowProps = {
|
||||
rule: AccessRule
|
||||
className?: string
|
||||
showMenu?: boolean
|
||||
onEdit?: (rule: AccessRule) => void
|
||||
onCopy?: (rule: AccessRule) => void
|
||||
onDelete?: (rule: AccessRule) => void
|
||||
@ -31,6 +32,7 @@ export type AccessRuleRowProps = {
|
||||
const AccessRuleRow = ({
|
||||
rule,
|
||||
className,
|
||||
showMenu = true,
|
||||
onEdit,
|
||||
onCopy,
|
||||
onDelete,
|
||||
@ -70,11 +72,13 @@ const AccessRuleRow = ({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<AccessRuleRowMenu
|
||||
onEdit={handleEdit}
|
||||
onCopy={handleCopy}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
{showMenu && (
|
||||
<AccessRuleRowMenu
|
||||
onEdit={handleEdit}
|
||||
onCopy={handleCopy}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user