Skip to content

Commit 40defc2

Browse files
WTW0313fatelei
authored andcommitted
feat: add access configuration pages and editor components for app and dataset management
1 parent faf79b7 commit 40defc2

8 files changed

Lines changed: 368 additions & 114 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { Locale } from '@/i18n-config'
2+
import AppAccessConfigPage from '@/app/components/app/access-config'
3+
4+
export type AccessConfigPageProps = {
5+
params: Promise<{ locale: Locale, appId: string }>
6+
}
7+
8+
const AccessConfig = async (props: AccessConfigPageProps) => {
9+
const params = await props.params
10+
11+
const { appId } = params
12+
13+
return <AppAccessConfigPage appId={appId} />
14+
}
15+
16+
export default AccessConfig

web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx

Lines changed: 54 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
RiTerminalBoxLine,
1313
RiTerminalWindowFill,
1414
RiTerminalWindowLine,
15+
RiUserSettingsFill,
16+
RiUserSettingsLine,
1517
} from '@remixicon/react'
1618
import { useUnmount } from 'ahooks'
1719
import * as React from 'react'
@@ -73,51 +75,58 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
7375
}>>([])
7476

7577
const getNavigationConfig = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: AppModeEnum) => {
76-
const navConfig = []
77-
78-
if (isCurrentWorkspaceEditor) {
79-
navConfig.push({
80-
name: t('appMenus.promptEng', { ns: 'common' }),
81-
href: `/app/${appId}/${(mode === AppModeEnum.WORKFLOW || mode === AppModeEnum.ADVANCED_CHAT) ? 'workflow' : 'configuration'}`,
82-
icon: RiTerminalWindowLine,
83-
selectedIcon: RiTerminalWindowFill,
84-
})
85-
}
86-
87-
navConfig.push({
88-
name: t('appMenus.apiAccess', { ns: 'common' }),
89-
href: `/app/${appId}/develop`,
90-
icon: RiTerminalBoxLine,
91-
selectedIcon: RiTerminalBoxFill,
92-
})
93-
94-
if (isCurrentWorkspaceEditor) {
95-
navConfig.push({
96-
name: mode !== AppModeEnum.WORKFLOW
97-
? t('appMenus.logAndAnn', { ns: 'common' })
98-
: t('appMenus.logs', { ns: 'common' }),
99-
href: `/app/${appId}/logs`,
100-
icon: RiFileList3Line,
101-
selectedIcon: RiFileList3Fill,
102-
})
103-
}
104-
105-
navConfig.push({
106-
name: t('appMenus.overview', { ns: 'common' }),
107-
href: `/app/${appId}/overview`,
108-
icon: RiDashboard2Line,
109-
selectedIcon: RiDashboard2Fill,
110-
})
111-
112-
if (isCurrentWorkspaceEditor && canAccessSnippetsAndEvaluation) {
113-
navConfig.push({
114-
name: t('appMenus.evaluation', { ns: 'common' }),
115-
href: `/app/${appId}/evaluation`,
116-
icon: EvaluationIcon,
117-
selectedIcon: EvaluationIcon,
118-
})
119-
}
120-
78+
const navConfig = [
79+
...(isCurrentWorkspaceEditor
80+
? [{
81+
name: t('appMenus.promptEng', { ns: 'common' }),
82+
href: `/app/${appId}/${(mode === AppModeEnum.WORKFLOW || mode === AppModeEnum.ADVANCED_CHAT) ? 'workflow' : 'configuration'}`,
83+
icon: RiTerminalWindowLine,
84+
selectedIcon: RiTerminalWindowFill,
85+
}]
86+
: []
87+
),
88+
{
89+
name: t('appMenus.apiAccess', { ns: 'common' }),
90+
href: `/app/${appId}/develop`,
91+
icon: RiTerminalBoxLine,
92+
selectedIcon: RiTerminalBoxFill,
93+
},
94+
...(isCurrentWorkspaceEditor
95+
? [{
96+
name: mode !== AppModeEnum.WORKFLOW
97+
? t('appMenus.logAndAnn', { ns: 'common' })
98+
: t('appMenus.logs', { ns: 'common' }),
99+
href: `/app/${appId}/logs`,
100+
icon: RiFileList3Line,
101+
selectedIcon: RiFileList3Fill,
102+
}]
103+
: []
104+
),
105+
{
106+
name: t('appMenus.overview', { ns: 'common' }),
107+
href: `/app/${appId}/overview`,
108+
icon: RiDashboard2Line,
109+
selectedIcon: RiDashboard2Fill,
110+
},
111+
...(isCurrentWorkspaceEditor && canAccessSnippetsAndEvaluation
112+
? [{
113+
name: t('appMenus.evaluation', { ns: 'common' }),
114+
href: `/app/${appId}/evaluation`,
115+
icon: EvaluationIcon,
116+
selectedIcon: EvaluationIcon,
117+
}]
118+
: []
119+
),
120+
...(isCurrentWorkspaceEditor
121+
? [{
122+
name: 'Access Config',
123+
href: `/app/${appId}/access-config`,
124+
icon: RiUserSettingsLine,
125+
selectedIcon: RiUserSettingsFill,
126+
}]
127+
: []
128+
),
129+
]
121130
return navConfig
122131
}, [canAccessSnippetsAndEvaluation, t])
123132

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import DatasetAccessConfigPage from '@/app/components/datasets/access-config'
2+
3+
type Props = {
4+
params: Promise<{ datasetId: string }>
5+
}
6+
7+
const AccessConfig = async (props: Props) => {
8+
const params = await props.params
9+
10+
const { datasetId } = params
11+
12+
return <DatasetAccessConfigPage datasetId={datasetId} />
13+
}
14+
15+
export default AccessConfig

web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
RiFileTextLine,
1010
RiFocus2Fill,
1111
RiFocus2Line,
12+
RiUserSettingsFill,
13+
RiUserSettingsLine,
1214
} from '@remixicon/react'
1315
import * as React from 'react'
1416
import { useEffect, useMemo, useState } from 'react'
@@ -90,6 +92,13 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
9092
selectedIcon: RiEqualizer2Fill,
9193
disabled: false,
9294
},
95+
{
96+
name: 'Access Config',
97+
href: `/datasets/${datasetId}/access-config`,
98+
icon: RiUserSettingsLine,
99+
selectedIcon: RiUserSettingsFill,
100+
disabled: false,
101+
},
93102
]
94103

95104
if (datasetRes?.provider !== 'external') {

web/app/components/access-config-modal/index.tsx

Lines changed: 14 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
'use client'
22

3-
import type {
4-
AccessRule,
5-
AssignedRole,
6-
} from '@/app/components/header/account-setting/access-rules-page/access-rule-row'
3+
import type { AccessRule } from '@/app/components/header/account-setting/access-rules-page/access-rule-row'
74
import { Button } from '@langgenius/dify-ui/button'
85
import {
96
Dialog,
@@ -14,8 +11,7 @@ import {
1411
} from '@langgenius/dify-ui/dialog'
1512
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
1613
import { useCallback, useState } from 'react'
17-
import AccessRuleRow from '@/app/components/header/account-setting/access-rules-page/access-rule-row'
18-
import AddRuleTargetsModal from '@/app/components/header/account-setting/access-rules-page/add-rule-targets-modal'
14+
import AccessRulesEditor from '@/app/components/access-rules-editor'
1915

2016
export type AccessConfigModalProps = {
2117
open: boolean
@@ -46,38 +42,6 @@ const AccessConfigModalBody = ({
4642
onSave,
4743
}: AccessConfigModalBodyProps) => {
4844
const [rules, setRules] = useState<AccessRule[]>(initialRules)
49-
const [addingRule, setAddingRule] = useState<AccessRule | null>(null)
50-
51-
const handleAddRole = useCallback((rule: AccessRule) => {
52-
setAddingRule(rule)
53-
}, [])
54-
55-
const handleCloseAddModal = useCallback(() => {
56-
setAddingRule(null)
57-
}, [])
58-
59-
const handleAddSubmit = useCallback(
60-
(_selection: { roleIds: string[], memberIds: string[] }) => {
61-
// TODO: wire up to API when backend is ready.
62-
},
63-
[],
64-
)
65-
66-
const handleRemoveRole = useCallback(
67-
(target: AccessRule, role: AssignedRole) => {
68-
setRules(prev =>
69-
prev.map(rule =>
70-
rule.id === target.id
71-
? {
72-
...rule,
73-
assignedRoles: rule.assignedRoles.filter(r => r.id !== role.id),
74-
}
75-
: rule,
76-
),
77-
)
78-
},
79-
[],
80-
)
8145

8246
const handleSave = useCallback(() => {
8347
onSave?.(rules)
@@ -105,17 +69,7 @@ const AccessConfigModalBody = ({
10569
className="min-h-0 flex-1"
10670
slotClassNames={{ viewport: 'px-6 overscroll-contain' }}
10771
>
108-
<div className="flex flex-col">
109-
{rules.map(rule => (
110-
<AccessRuleRow
111-
key={rule.id}
112-
rule={rule}
113-
showMenu={false}
114-
onAddRole={handleAddRole}
115-
onRemoveRole={handleRemoveRole}
116-
/>
117-
))}
118-
</div>
72+
<AccessRulesEditor rules={rules} onRulesChange={setRules} />
11973
</ScrollArea>
12074

12175
<div className="flex shrink-0 items-center justify-end gap-2 border-t border-divider-subtle px-6 py-4">
@@ -126,17 +80,6 @@ const AccessConfigModalBody = ({
12680
{saveLabel}
12781
</Button>
12882
</div>
129-
130-
{addingRule && (
131-
<AddRuleTargetsModal
132-
open
133-
ruleName={addingRule.name}
134-
initialRoleIds={addingRule.assignedRoles.map(role => role.id)}
135-
initialMemberIds={[]}
136-
onClose={handleCloseAddModal}
137-
onSubmit={handleAddSubmit}
138-
/>
139-
)}
14083
</DialogContent>
14184
)
14285
}
@@ -159,15 +102,17 @@ const AccessConfigModal = ({
159102
onClose()
160103
}}
161104
>
162-
<AccessConfigModalBody
163-
title={title}
164-
description={description}
165-
initialRules={initialRules}
166-
saveLabel={saveLabel}
167-
cancelLabel={cancelLabel}
168-
onClose={onClose}
169-
onSave={onSave}
170-
/>
105+
{open && (
106+
<AccessConfigModalBody
107+
title={title}
108+
description={description}
109+
initialRules={initialRules}
110+
saveLabel={saveLabel}
111+
cancelLabel={cancelLabel}
112+
onClose={onClose}
113+
onSave={onSave}
114+
/>
115+
)}
171116
</Dialog>
172117
)
173118
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
'use client'
2+
3+
import type {
4+
AccessRule,
5+
AssignedRole,
6+
} from '@/app/components/header/account-setting/access-rules-page/access-rule-row'
7+
import { cn } from '@langgenius/dify-ui/cn'
8+
import { useCallback, useState } from 'react'
9+
import AccessRuleRow from '@/app/components/header/account-setting/access-rules-page/access-rule-row'
10+
import AddRuleTargetsModal from '@/app/components/header/account-setting/access-rules-page/add-rule-targets-modal'
11+
12+
export type AccessRulesEditorProps = {
13+
rules: AccessRule[]
14+
/**
15+
* Called whenever assigned roles/members are mutated. The editor is
16+
* controlled when this callback is provided, uncontrolled (with internal
17+
* state seeded from `rules`) otherwise.
18+
*/
19+
onRulesChange?: (rules: AccessRule[]) => void
20+
className?: string
21+
}
22+
23+
const AccessRulesEditor = ({
24+
rules: rulesProp,
25+
onRulesChange,
26+
className,
27+
}: AccessRulesEditorProps) => {
28+
const isControlled = typeof onRulesChange === 'function'
29+
const [internalRules, setInternalRules] = useState<AccessRule[]>(rulesProp)
30+
const rules = isControlled ? rulesProp : internalRules
31+
32+
const updateRules = useCallback(
33+
(updater: (prev: AccessRule[]) => AccessRule[]) => {
34+
if (isControlled) {
35+
onRulesChange(updater(rulesProp))
36+
return
37+
}
38+
setInternalRules(prev => updater(prev))
39+
},
40+
[isControlled, onRulesChange, rulesProp],
41+
)
42+
43+
const [addingRule, setAddingRule] = useState<AccessRule | null>(null)
44+
45+
const handleAddRole = useCallback((rule: AccessRule) => {
46+
setAddingRule(rule)
47+
}, [])
48+
49+
const handleCloseAddModal = useCallback(() => {
50+
setAddingRule(null)
51+
}, [])
52+
53+
const handleAddSubmit = useCallback(
54+
(_selection: { roleIds: string[], memberIds: string[] }) => {
55+
// TODO: wire up to API when backend is ready.
56+
},
57+
[],
58+
)
59+
60+
const handleRemoveRole = useCallback(
61+
(target: AccessRule, role: AssignedRole) => {
62+
updateRules(prev =>
63+
prev.map(rule =>
64+
rule.id === target.id
65+
? {
66+
...rule,
67+
assignedRoles: rule.assignedRoles.filter(r => r.id !== role.id),
68+
}
69+
: rule,
70+
),
71+
)
72+
},
73+
[updateRules],
74+
)
75+
76+
return (
77+
<div className={cn('flex flex-col', className)}>
78+
{rules.map((rule, index) => (
79+
<AccessRuleRow
80+
key={rule.id}
81+
rule={rule}
82+
showMenu={false}
83+
onAddRole={handleAddRole}
84+
onRemoveRole={handleRemoveRole}
85+
className={cn(index > 0 && 'border-t border-divider-subtle')}
86+
/>
87+
))}
88+
89+
{addingRule && (
90+
<AddRuleTargetsModal
91+
open
92+
ruleName={addingRule.name}
93+
initialRoleIds={addingRule.assignedRoles.map(role => role.id)}
94+
initialMemberIds={[]}
95+
onClose={handleCloseAddModal}
96+
onSubmit={handleAddSubmit}
97+
/>
98+
)}
99+
</div>
100+
)
101+
}
102+
103+
export default AccessRulesEditor

0 commit comments

Comments
 (0)