fix: web app access-control setting supports filter by group

This commit is contained in:
NFish 2025-04-15 14:31:10 +08:00
parent 22a0453102
commit a3bb48bf98
9 changed files with 53 additions and 18 deletions

View File

@ -76,14 +76,6 @@ const WebSSOForm: FC = () => {
}
}
const goWebApp = () => {
if (!redirectUrl) {
showErrorToast('redirect url is invalid.')
return
}
router.push(redirectUrl)
}
useEffect(() => {
const init = async () => {
if (message) {

View File

@ -1,6 +1,6 @@
'use client'
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'
type AccessControlItemProps = PropsWithChildren<{

View File

@ -9,7 +9,7 @@ import Checkbox from '../../base/checkbox'
import Input from '../../base/input'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
import Loading from '../../base/loading'
import useAccessControlStore from './access-control-store'
import useAccessControlStore from '../../../../context/access-control-store'
import classNames from '@/utils/classnames'
import { useSearchForWhiteListCandidates } from '@/service/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 [open, setOpen] = useState(false)
const [keyword, setKeyword] = useState('')
const selectedGroupsForBreadcrumb = useAccessControlStore(s => s.selectedGroupsForBreadcrumb)
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>) => {
setKeyword(e.target.value)
}
@ -59,7 +61,7 @@ export default function AddMemberOrGroupDialog() {
: (data?.pages?.length ?? 0) > 0
? <>
<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>
{renderGroupOrMember(data?.pages ?? [])}
{isFetchingNextPage && <div className='p-1'><Loading /></div>}
@ -87,6 +89,29 @@ function renderGroupOrMember(data: GroupOrMemberData) {
}) ?? 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 = {
group: AccessControlGroup
}
@ -94,6 +119,8 @@ function GroupItem({ group }: GroupItemProps) {
const { t } = useTranslation()
const specificGroups = useAccessControlStore(s => s.specificGroups)
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 handleCheckChange = useCallback(() => {
if (!isChecked) {
@ -105,6 +132,10 @@ function GroupItem({ group }: GroupItemProps) {
setSpecificGroups(newGroups)
}
}, [specificGroups, setSpecificGroups, group, isChecked])
const handleExpandClick = useCallback(() => {
setSelectedGroupsForBreadcrumb([...selectedGroupsForBreadcrumb, group])
}, [selectedGroupsForBreadcrumb, setSelectedGroupsForBreadcrumb, group])
return <BaseItem>
<Checkbox checked={isChecked} className='w-4 h-4 shrink-0' onCheck={handleCheckChange} />
<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-xs-regular text-text-tertiary'>{group.groupSize}</p>
</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>
<RiArrowRightSLine className='w-4 h-4' />
</Button>

View File

@ -5,10 +5,10 @@ import { useTranslation } from 'react-i18next'
import { useCallback, useEffect } from 'react'
import Button from '../../base/button'
import Toast from '../../base/toast'
import useAccessControlStore from '../../../../context/access-control-store'
import AccessControlDialog from './access-control-dialog'
import AccessControlItem from './access-control-item'
import SpecificGroupsOrMembers, { WebAppSSONotEnabledTip } from './specific-groups-or-members'
import useAccessControlStore from './access-control-store'
import { useGlobalPublicStore } from '@/context/global-public-context'
import type { App } from '@/types/app'
import type { Subject } from '@/models/access-control'

View File

@ -6,8 +6,8 @@ import Avatar from '../../base/avatar'
import Divider from '../../base/divider'
import Tooltip from '../../base/tooltip'
import Loading from '../../base/loading'
import useAccessControlStore from '../../../../context/access-control-store'
import AddMemberOrGroupDialog from './add-member-or-group-pop'
import useAccessControlStore from './access-control-store'
import { useGlobalPublicStore } from '@/context/global-public-context'
import type { AccessControlAccount, AccessControlGroup } from '@/models/access-control'
import { AccessMode } from '@/models/access-control'

View File

@ -12,6 +12,8 @@ type AccessControlStore = {
setSpecificMembers: (specificMembers: AccessControlAccount[]) => void
currentMenu: AccessMode
setCurrentMenu: (currentMenu: AccessMode) => void
selectedGroupsForBreadcrumb: AccessControlGroup[]
setSelectedGroupsForBreadcrumb: (selectedGroupsForBreadcrumb: AccessControlGroup[]) => void
}
const useAccessControlStore = create<AccessControlStore>((set) => {
@ -24,6 +26,8 @@ const useAccessControlStore = create<AccessControlStore>((set) => {
setSpecificMembers: specificMembers => set({ specificMembers }),
currentMenu: AccessMode.SPECIFIC_GROUPS_MEMBERS,
setCurrentMenu: currentMenu => set({ currentMenu }),
selectedGroupsForBreadcrumb: [],
setSelectedGroupsForBreadcrumb: selectedGroupsForBreadcrumb => set({ selectedGroupsForBreadcrumb }),
}
})

View File

@ -621,7 +621,7 @@ const translation = {
pagination: {
perPage: 'Items per page',
},
your: 'You',
you: 'You',
}
export default translation

View File

@ -622,6 +622,7 @@ const translation = {
pagination: {
perPage: 'ページあたりのアイテム数',
},
you: 'あなた',
}
export default translation

View File

@ -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 type { AccessControlAccount, AccessControlGroup, AccessMode, Subject } from '@/models/access-control'
import type { App } from '@/types/app'
@ -20,7 +20,7 @@ type SearchResults = {
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({
queryKey: [NAME_SPACE, 'app-whitelist-candidates', query],
queryFn: ({ pageParam }) => {
@ -50,10 +50,16 @@ type UpdateAccessModeParams = {
}
export const useUpdateAccessMode = () => {
const queryClient = useQueryClient()
return useMutation({
mutationKey: [NAME_SPACE, 'update-access-mode'],
mutationFn: (params: UpdateAccessModeParams) => {
return post('/enterprise/webapp/app/access-mode', { body: params })
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [NAME_SPACE, 'app-whitelist-subjects'],
})
},
})
}