mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-18 05:45:59 +08:00
fix: web app access-control setting supports filter by group
This commit is contained in:
parent
22a0453102
commit
a3bb48bf98
@ -76,14 +76,6 @@ const WebSSOForm: FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const goWebApp = () => {
|
|
||||||
if (!redirectUrl) {
|
|
||||||
showErrorToast('redirect url is invalid.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
router.push(redirectUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
if (message) {
|
if (message) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC, PropsWithChildren } from 'react'
|
import type { FC, PropsWithChildren } from 'react'
|
||||||
import useAccessControlStore from './access-control-store'
|
import useAccessControlStore from '../../../../context/access-control-store'
|
||||||
import type { AccessMode } from '@/models/access-control'
|
import type { AccessMode } from '@/models/access-control'
|
||||||
|
|
||||||
type AccessControlItemProps = PropsWithChildren<{
|
type AccessControlItemProps = PropsWithChildren<{
|
||||||
|
@ -9,7 +9,7 @@ import Checkbox from '../../base/checkbox'
|
|||||||
import Input from '../../base/input'
|
import Input from '../../base/input'
|
||||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
|
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
|
||||||
import Loading from '../../base/loading'
|
import Loading from '../../base/loading'
|
||||||
import useAccessControlStore from './access-control-store'
|
import useAccessControlStore from '../../../../context/access-control-store'
|
||||||
import classNames from '@/utils/classnames'
|
import classNames from '@/utils/classnames'
|
||||||
import { useSearchForWhiteListCandidates } from '@/service/access-control'
|
import { useSearchForWhiteListCandidates } from '@/service/access-control'
|
||||||
import type { AccessControlAccount, AccessControlGroup, Subject, SubjectAccount, SubjectGroup } from '@/models/access-control'
|
import type { AccessControlAccount, AccessControlGroup, Subject, SubjectAccount, SubjectGroup } from '@/models/access-control'
|
||||||
@ -20,9 +20,11 @@ export default function AddMemberOrGroupDialog() {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [keyword, setKeyword] = useState('')
|
const [keyword, setKeyword] = useState('')
|
||||||
|
const selectedGroupsForBreadcrumb = useAccessControlStore(s => s.selectedGroupsForBreadcrumb)
|
||||||
const debouncedKeyword = useDebounce(keyword, { wait: 500 })
|
const debouncedKeyword = useDebounce(keyword, { wait: 500 })
|
||||||
|
|
||||||
const { isPending, isFetchingNextPage, fetchNextPage, data } = useSearchForWhiteListCandidates({ keyword: debouncedKeyword, resultsPerPage: 10 }, open)
|
const lastAvailableGroup = selectedGroupsForBreadcrumb[selectedGroupsForBreadcrumb.length - 1]
|
||||||
|
const { isPending, isFetchingNextPage, fetchNextPage, data } = useSearchForWhiteListCandidates({ keyword: debouncedKeyword, groupId: lastAvailableGroup?.id, resultsPerPage: 10 }, open)
|
||||||
const handleKeywordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleKeywordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setKeyword(e.target.value)
|
setKeyword(e.target.value)
|
||||||
}
|
}
|
||||||
@ -59,7 +61,7 @@ export default function AddMemberOrGroupDialog() {
|
|||||||
: (data?.pages?.length ?? 0) > 0
|
: (data?.pages?.length ?? 0) > 0
|
||||||
? <>
|
? <>
|
||||||
<div className='flex items-center h-7 px-2 py-0.5'>
|
<div className='flex items-center h-7 px-2 py-0.5'>
|
||||||
<span className='system-xs-regular text-text-tertiary'>{t('app.accessControlDialog.operateGroupAndMember.allMembers')}</span>
|
<SelectedGroupsBreadCrumb />
|
||||||
</div>
|
</div>
|
||||||
{renderGroupOrMember(data?.pages ?? [])}
|
{renderGroupOrMember(data?.pages ?? [])}
|
||||||
{isFetchingNextPage && <div className='p-1'><Loading /></div>}
|
{isFetchingNextPage && <div className='p-1'><Loading /></div>}
|
||||||
@ -87,6 +89,29 @@ function renderGroupOrMember(data: GroupOrMemberData) {
|
|||||||
}) ?? null
|
}) ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SelectedGroupsBreadCrumb() {
|
||||||
|
const selectedGroupsForBreadcrumb = useAccessControlStore(s => s.selectedGroupsForBreadcrumb)
|
||||||
|
const setSelectedGroupsForBreadcrumb = useAccessControlStore(s => s.setSelectedGroupsForBreadcrumb)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const handleBreadCrumbClick = useCallback((index: number) => {
|
||||||
|
const newGroups = selectedGroupsForBreadcrumb.slice(0, index + 1)
|
||||||
|
setSelectedGroupsForBreadcrumb(newGroups)
|
||||||
|
}, [setSelectedGroupsForBreadcrumb, selectedGroupsForBreadcrumb])
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
setSelectedGroupsForBreadcrumb([])
|
||||||
|
}, [setSelectedGroupsForBreadcrumb])
|
||||||
|
return <div className='flex items-center h-7 px-2 py-0.5 gap-x-0.5'>
|
||||||
|
<span className={classNames('system-xs-regular text-text-tertiary', selectedGroupsForBreadcrumb.length > 0 && 'text-text-accent cursor-pointer')} onClick={handleReset}>{t('app.accessControlDialog.operateGroupAndMember.allMembers')}</span>
|
||||||
|
{selectedGroupsForBreadcrumb.map((group, index) => {
|
||||||
|
return <div key={index} className='flex items-center gap-x-0.5 text-text-tertiary system-xs-regular'>
|
||||||
|
<span>/</span>
|
||||||
|
<span className={index === selectedGroupsForBreadcrumb.length - 1 ? '' : 'text-text-accent cursor-pointer'} onClick={() => handleBreadCrumbClick(index)}>{group.name}</span>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
type GroupItemProps = {
|
type GroupItemProps = {
|
||||||
group: AccessControlGroup
|
group: AccessControlGroup
|
||||||
}
|
}
|
||||||
@ -94,6 +119,8 @@ function GroupItem({ group }: GroupItemProps) {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const specificGroups = useAccessControlStore(s => s.specificGroups)
|
const specificGroups = useAccessControlStore(s => s.specificGroups)
|
||||||
const setSpecificGroups = useAccessControlStore(s => s.setSpecificGroups)
|
const setSpecificGroups = useAccessControlStore(s => s.setSpecificGroups)
|
||||||
|
const selectedGroupsForBreadcrumb = useAccessControlStore(s => s.selectedGroupsForBreadcrumb)
|
||||||
|
const setSelectedGroupsForBreadcrumb = useAccessControlStore(s => s.setSelectedGroupsForBreadcrumb)
|
||||||
const isChecked = specificGroups.some(g => g.id === group.id)
|
const isChecked = specificGroups.some(g => g.id === group.id)
|
||||||
const handleCheckChange = useCallback(() => {
|
const handleCheckChange = useCallback(() => {
|
||||||
if (!isChecked) {
|
if (!isChecked) {
|
||||||
@ -105,6 +132,10 @@ function GroupItem({ group }: GroupItemProps) {
|
|||||||
setSpecificGroups(newGroups)
|
setSpecificGroups(newGroups)
|
||||||
}
|
}
|
||||||
}, [specificGroups, setSpecificGroups, group, isChecked])
|
}, [specificGroups, setSpecificGroups, group, isChecked])
|
||||||
|
|
||||||
|
const handleExpandClick = useCallback(() => {
|
||||||
|
setSelectedGroupsForBreadcrumb([...selectedGroupsForBreadcrumb, group])
|
||||||
|
}, [selectedGroupsForBreadcrumb, setSelectedGroupsForBreadcrumb, group])
|
||||||
return <BaseItem>
|
return <BaseItem>
|
||||||
<Checkbox checked={isChecked} className='w-4 h-4 shrink-0' onCheck={handleCheckChange} />
|
<Checkbox checked={isChecked} className='w-4 h-4 shrink-0' onCheck={handleCheckChange} />
|
||||||
<div className='flex item-center grow'>
|
<div className='flex item-center grow'>
|
||||||
@ -116,7 +147,8 @@ function GroupItem({ group }: GroupItemProps) {
|
|||||||
<p className='system-sm-medium text-text-secondary mr-1'>{group.name}</p>
|
<p className='system-sm-medium text-text-secondary mr-1'>{group.name}</p>
|
||||||
<p className='system-xs-regular text-text-tertiary'>{group.groupSize}</p>
|
<p className='system-xs-regular text-text-tertiary'>{group.groupSize}</p>
|
||||||
</div>
|
</div>
|
||||||
<Button size="small" variant='ghost-accent' className='py-1 px-1.5 shrink-0 flex items-center justify-between'>
|
<Button size="small" disabled={isChecked} variant='ghost-accent'
|
||||||
|
className='py-1 px-1.5 shrink-0 flex items-center justify-between' onClick={handleExpandClick}>
|
||||||
<span className='px-[3px]'>{t('app.accessControlDialog.operateGroupAndMember.expand')}</span>
|
<span className='px-[3px]'>{t('app.accessControlDialog.operateGroupAndMember.expand')}</span>
|
||||||
<RiArrowRightSLine className='w-4 h-4' />
|
<RiArrowRightSLine className='w-4 h-4' />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -5,10 +5,10 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { useCallback, useEffect } from 'react'
|
import { useCallback, useEffect } from 'react'
|
||||||
import Button from '../../base/button'
|
import Button from '../../base/button'
|
||||||
import Toast from '../../base/toast'
|
import Toast from '../../base/toast'
|
||||||
|
import useAccessControlStore from '../../../../context/access-control-store'
|
||||||
import AccessControlDialog from './access-control-dialog'
|
import AccessControlDialog from './access-control-dialog'
|
||||||
import AccessControlItem from './access-control-item'
|
import AccessControlItem from './access-control-item'
|
||||||
import SpecificGroupsOrMembers, { WebAppSSONotEnabledTip } from './specific-groups-or-members'
|
import SpecificGroupsOrMembers, { WebAppSSONotEnabledTip } from './specific-groups-or-members'
|
||||||
import useAccessControlStore from './access-control-store'
|
|
||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
import type { App } from '@/types/app'
|
import type { App } from '@/types/app'
|
||||||
import type { Subject } from '@/models/access-control'
|
import type { Subject } from '@/models/access-control'
|
||||||
|
@ -6,8 +6,8 @@ import Avatar from '../../base/avatar'
|
|||||||
import Divider from '../../base/divider'
|
import Divider from '../../base/divider'
|
||||||
import Tooltip from '../../base/tooltip'
|
import Tooltip from '../../base/tooltip'
|
||||||
import Loading from '../../base/loading'
|
import Loading from '../../base/loading'
|
||||||
|
import useAccessControlStore from '../../../../context/access-control-store'
|
||||||
import AddMemberOrGroupDialog from './add-member-or-group-pop'
|
import AddMemberOrGroupDialog from './add-member-or-group-pop'
|
||||||
import useAccessControlStore from './access-control-store'
|
|
||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
import type { AccessControlAccount, AccessControlGroup } from '@/models/access-control'
|
import type { AccessControlAccount, AccessControlGroup } from '@/models/access-control'
|
||||||
import { AccessMode } from '@/models/access-control'
|
import { AccessMode } from '@/models/access-control'
|
||||||
|
@ -12,6 +12,8 @@ type AccessControlStore = {
|
|||||||
setSpecificMembers: (specificMembers: AccessControlAccount[]) => void
|
setSpecificMembers: (specificMembers: AccessControlAccount[]) => void
|
||||||
currentMenu: AccessMode
|
currentMenu: AccessMode
|
||||||
setCurrentMenu: (currentMenu: AccessMode) => void
|
setCurrentMenu: (currentMenu: AccessMode) => void
|
||||||
|
selectedGroupsForBreadcrumb: AccessControlGroup[]
|
||||||
|
setSelectedGroupsForBreadcrumb: (selectedGroupsForBreadcrumb: AccessControlGroup[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const useAccessControlStore = create<AccessControlStore>((set) => {
|
const useAccessControlStore = create<AccessControlStore>((set) => {
|
||||||
@ -24,6 +26,8 @@ const useAccessControlStore = create<AccessControlStore>((set) => {
|
|||||||
setSpecificMembers: specificMembers => set({ specificMembers }),
|
setSpecificMembers: specificMembers => set({ specificMembers }),
|
||||||
currentMenu: AccessMode.SPECIFIC_GROUPS_MEMBERS,
|
currentMenu: AccessMode.SPECIFIC_GROUPS_MEMBERS,
|
||||||
setCurrentMenu: currentMenu => set({ currentMenu }),
|
setCurrentMenu: currentMenu => set({ currentMenu }),
|
||||||
|
selectedGroupsForBreadcrumb: [],
|
||||||
|
setSelectedGroupsForBreadcrumb: selectedGroupsForBreadcrumb => set({ selectedGroupsForBreadcrumb }),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -621,7 +621,7 @@ const translation = {
|
|||||||
pagination: {
|
pagination: {
|
||||||
perPage: 'Items per page',
|
perPage: 'Items per page',
|
||||||
},
|
},
|
||||||
your: 'You',
|
you: 'You',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -622,6 +622,7 @@ const translation = {
|
|||||||
pagination: {
|
pagination: {
|
||||||
perPage: 'ページあたりのアイテム数',
|
perPage: 'ページあたりのアイテム数',
|
||||||
},
|
},
|
||||||
|
you: 'あなた',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'
|
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { get, post } from './base'
|
import { get, post } from './base'
|
||||||
import type { AccessControlAccount, AccessControlGroup, AccessMode, Subject } from '@/models/access-control'
|
import type { AccessControlAccount, AccessControlGroup, AccessMode, Subject } from '@/models/access-control'
|
||||||
import type { App } from '@/types/app'
|
import type { App } from '@/types/app'
|
||||||
@ -20,7 +20,7 @@ type SearchResults = {
|
|||||||
hasMore: boolean
|
hasMore: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSearchForWhiteListCandidates = (query: { keyword?: string; resultsPerPage?: number }, enabled: boolean) => {
|
export const useSearchForWhiteListCandidates = (query: { keyword?: string; groupId?: AccessControlGroup['id']; resultsPerPage?: number }, enabled: boolean) => {
|
||||||
return useInfiniteQuery({
|
return useInfiniteQuery({
|
||||||
queryKey: [NAME_SPACE, 'app-whitelist-candidates', query],
|
queryKey: [NAME_SPACE, 'app-whitelist-candidates', query],
|
||||||
queryFn: ({ pageParam }) => {
|
queryFn: ({ pageParam }) => {
|
||||||
@ -50,10 +50,16 @@ type UpdateAccessModeParams = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useUpdateAccessMode = () => {
|
export const useUpdateAccessMode = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: [NAME_SPACE, 'update-access-mode'],
|
mutationKey: [NAME_SPACE, 'update-access-mode'],
|
||||||
mutationFn: (params: UpdateAccessModeParams) => {
|
mutationFn: (params: UpdateAccessModeParams) => {
|
||||||
return post('/enterprise/webapp/app/access-mode', { body: params })
|
return post('/enterprise/webapp/app/access-mode', { body: params })
|
||||||
},
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [NAME_SPACE, 'app-whitelist-subjects'],
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user