mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-14 23:06:15 +08:00
wip: support add groups and members
This commit is contained in:
parent
7199cee622
commit
e8ce7de718
@ -1,15 +1,20 @@
|
||||
'use client'
|
||||
import type { FC, PropsWithChildren } from 'react'
|
||||
import type { AccessControlType } from './access-control-store'
|
||||
import useAccessControlStore from './access-control-store'
|
||||
|
||||
type AccessControlItemProps = PropsWithChildren<{
|
||||
active: boolean
|
||||
type: AccessControlType
|
||||
}>
|
||||
|
||||
const AccessControlItem: FC<AccessControlItemProps> = ({ active, children }) => {
|
||||
if (!active) {
|
||||
return <div className="rounded-[10px] border-[1px] cursor-pointer
|
||||
const AccessControlItem: FC<AccessControlItemProps> = ({ type, children }) => {
|
||||
const { currentMenu, setCurrentMenu } = useAccessControlStore(s => ({ currentMenu: s.currentMenu, setCurrentMenu: s.setCurrentMenu }))
|
||||
if (currentMenu !== type) {
|
||||
return <div
|
||||
className="rounded-[10px] border-[1px] cursor-pointer
|
||||
border-components-option-card-option-border bg-components-option-card-option-bg
|
||||
hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover">
|
||||
hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover"
|
||||
onClick={() => setCurrentMenu(type)} >
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
import { create } from 'zustand'
|
||||
export enum AccessControlType {
|
||||
PUBLIC = 'PUBLIC',
|
||||
SPECIFIC_GROUPS_MEMBERS = 'SPECIFIC_GROUPS_MEMBERS',
|
||||
ORGANIZATION = 'ORGANIZATION',
|
||||
}
|
||||
|
||||
type AccessControlStore = {
|
||||
specificGroups: []
|
||||
setSpecificGroups: (specificGroups: []) => void
|
||||
specificMembers: []
|
||||
setSpecificMembers: (specificMembers: []) => void
|
||||
currentMenu: AccessControlType
|
||||
setCurrentMenu: (currentMenu: AccessControlType) => void
|
||||
}
|
||||
|
||||
const useAccessControlStore = create<AccessControlStore>((set) => {
|
||||
return {
|
||||
specificGroups: [],
|
||||
setSpecificGroups: specificGroups => set({ specificGroups }),
|
||||
specificMembers: [],
|
||||
setSpecificMembers: specificMembers => set({ specificMembers }),
|
||||
currentMenu: AccessControlType.SPECIFIC_GROUPS_MEMBERS,
|
||||
setCurrentMenu: currentMenu => set({ currentMenu }),
|
||||
}
|
||||
})
|
||||
|
||||
export default useAccessControlStore
|
@ -23,21 +23,33 @@ export default function AddMemberOrGroupDialog() {
|
||||
<PortalToFollowElemContent className='z-[25]'>
|
||||
<div className='w-[400px] max-h-[400px] overflow-y-auto flex flex-col border-[0.5px] border-components-panel-border rounded-xl bg-components-panel-bg-blur backdrop-blur-[5px] shadow-lg'>
|
||||
<div className='p-2 pb-0.5'>
|
||||
<Input placeholder='search...' />
|
||||
<Input placeholder={t('app.accessControlDialog.operateGroupAndMember.searchPlaceholder') as string} />
|
||||
</div>
|
||||
<div className='flex items-center h-7 px-2 py-0.5'>
|
||||
<span className='system-xs-regular text-text-tertiary'>All Members</span>
|
||||
</div>
|
||||
<div className='p-1'>
|
||||
<GroupItem />
|
||||
<MemberItem />
|
||||
<span className='system-xs-regular text-text-tertiary'>{t('app.accessControlDialog.operateGroupAndMember.allMembers')}</span>
|
||||
</div>
|
||||
<RenderGroupOrMember data={[]} />
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
}
|
||||
|
||||
type RenderGroupOrMemberProps = {
|
||||
data: any[]
|
||||
}
|
||||
|
||||
function RenderGroupOrMember({ data }: RenderGroupOrMemberProps) {
|
||||
return <div className='p-1'>
|
||||
{data.map((item, index) => {
|
||||
if (item.type === 'group')
|
||||
return <GroupItem key={index} />
|
||||
return <MemberItem key={index} />
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
|
||||
function GroupItem() {
|
||||
const { t } = useTranslation()
|
||||
return <BaseItem>
|
||||
<Checkbox className='w-4 h-4 shrink-0' />
|
||||
<div className='flex item-center grow'>
|
||||
@ -49,8 +61,8 @@ function GroupItem() {
|
||||
<p className='system-sm-medium text-text-secondary mr-1'>Name</p>
|
||||
<p className='system-xs-regular text-text-tertiary'>5</p>
|
||||
</div>
|
||||
<Button size="small" variant='ghost-accent' className='w-[76px] py-1 px-1.5 shrink-0 flex items-center justify-between'>
|
||||
<span>Expand</span>
|
||||
<Button size="small" variant='ghost-accent' className='py-1 px-1.5 shrink-0 flex items-center justify-between'>
|
||||
<span className='px-[3px]'>{t('app.accessControlDialog.operateGroupAndMember.expand')}</span>
|
||||
<RiArrowRightSLine className='w-4 h-4' />
|
||||
</Button>
|
||||
</BaseItem>
|
@ -5,7 +5,9 @@ import { useTranslation } from 'react-i18next'
|
||||
import Button from '../../base/button'
|
||||
import AccessControlDialog from './access-control-dialog'
|
||||
import AccessControlItem from './access-control-item'
|
||||
import SpecificGroupsOrMembers from './specific-groups-or-members'
|
||||
import SpecificGroupsOrMembers, { WebAppSSONotEnabledTip } from './specific-groups-or-members'
|
||||
import { AccessControlType } from './access-control-store'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
|
||||
type AccessControlProps = {
|
||||
onClose: () => void
|
||||
@ -13,6 +15,9 @@ type AccessControlProps = {
|
||||
|
||||
export default function AccessControl(props: AccessControlProps) {
|
||||
const { t } = useTranslation()
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const hideTip = systemFeatures.enable_web_sso_switch_component && systemFeatures.sso_enforced_for_web
|
||||
|
||||
return <AccessControlDialog show onClose={props.onClose}>
|
||||
<div className='flex flex-col gap-y-3'>
|
||||
<div className='pt-6 pr-14 pb-3 pl-6'>
|
||||
@ -23,17 +28,20 @@ export default function AccessControl(props: AccessControlProps) {
|
||||
<div className='leading-6'>
|
||||
<p className='system-sm-medium'>{t('app.accessControlDialog.accessLabel')}</p>
|
||||
</div>
|
||||
<AccessControlItem active={false}>
|
||||
<div className='h-[40px] p-3 flex items-center gap-x-2'>
|
||||
<RiBuildingLine className='w-4 h-4 text-text-primary' />
|
||||
<p className='system-sm-medium text-text-primary'>{t('app.accessControlDialog.accessItems.organization')}</p>
|
||||
<AccessControlItem type={AccessControlType.ORGANIZATION}>
|
||||
<div className='flex items-center p-3'>
|
||||
<div className='grow flex items-center gap-x-2'>
|
||||
<RiBuildingLine className='w-4 h-4 text-text-primary' />
|
||||
<p className='system-sm-medium text-text-primary'>{t('app.accessControlDialog.accessItems.organization')}</p>
|
||||
</div>
|
||||
{!hideTip && <WebAppSSONotEnabledTip />}
|
||||
</div>
|
||||
</AccessControlItem>
|
||||
<AccessControlItem active={true}>
|
||||
<SpecificGroupsOrMembers active={true} />
|
||||
<AccessControlItem type={AccessControlType.SPECIFIC_GROUPS_MEMBERS}>
|
||||
<SpecificGroupsOrMembers />
|
||||
</AccessControlItem>
|
||||
<AccessControlItem active={false}>
|
||||
<div className='h-[40px] p-3 flex items-center gap-x-2'>
|
||||
<AccessControlItem type={AccessControlType.PUBLIC}>
|
||||
<div className='flex items-center p-3 gap-x-2'>
|
||||
<RiGlobalLine className='w-4 h-4 text-text-primary' />
|
||||
<p className='system-sm-medium text-text-primary'>{t('app.accessControlDialog.accessItems.anyone')}</p>
|
||||
</div>
|
||||
|
@ -1,20 +1,26 @@
|
||||
'use client'
|
||||
import { RiCloseCircleFill, RiLockLine, RiOrganizationChart } from '@remixicon/react'
|
||||
import { RiAlertFill, RiCloseCircleFill, RiLockLine, RiOrganizationChart } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Avatar from '../../base/avatar'
|
||||
import AddMemberOrGroupDialog from './add-member-or-group-dialog'
|
||||
import Divider from '../../base/divider'
|
||||
import Tooltip from '../../base/tooltip'
|
||||
import AddMemberOrGroupDialog from './add-member-or-group-pop'
|
||||
import useAccessControlStore, { AccessControlType } from './access-control-store'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
|
||||
type SpecificGroupsOrMembersProps = {
|
||||
active: boolean
|
||||
}
|
||||
|
||||
export default function SpecificGroupsOrMembers(props: SpecificGroupsOrMembersProps) {
|
||||
const { active } = props
|
||||
export default function SpecificGroupsOrMembers() {
|
||||
const currentMenu = useAccessControlStore(s => s.currentMenu)
|
||||
const { t } = useTranslation()
|
||||
if (!active) {
|
||||
return <div className='h-[40px] p-3 flex items-center gap-x-2'>
|
||||
<RiLockLine className='w-4 h-4 text-text-primary' />
|
||||
<p className='system-sm-medium text-text-primary'>{t('app.accessControlDialog.accessItems.specific')}</p>
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const hideTip = systemFeatures.enable_web_sso_switch_component && systemFeatures.sso_enforced_for_web
|
||||
|
||||
if (currentMenu !== AccessControlType.SPECIFIC_GROUPS_MEMBERS) {
|
||||
return <div className='flex items-center p-3'>
|
||||
<div className='grow flex items-center gap-x-2'>
|
||||
<RiLockLine className='w-4 h-4 text-text-primary' />
|
||||
<p className='system-sm-medium text-text-primary'>{t('app.accessControlDialog.accessItems.specific')}</p>
|
||||
</div>
|
||||
{!hideTip && <WebAppSSONotEnabledTip />}
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -24,30 +30,54 @@ export default function SpecificGroupsOrMembers(props: SpecificGroupsOrMembersPr
|
||||
<RiLockLine className='w-4 h-4 text-text-primary' />
|
||||
<p className='system-sm-medium text-text-primary'>{t('app.accessControlDialog.accessItems.specific')}</p>
|
||||
</div>
|
||||
<AddMemberOrGroupDialog />
|
||||
<div className='flex items-center gap-x-1'>
|
||||
{!hideTip && <>
|
||||
<WebAppSSONotEnabledTip />
|
||||
<Divider className='h-[14px] ml-2 mr-0' type="vertical" />
|
||||
</>}
|
||||
<AddMemberOrGroupDialog />
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-1 pb-1'>
|
||||
<div className='bg-background-section rounded-lg p-2 flex flex-col gap-y-2'>
|
||||
<p className='system-2xs-medium-uppercase text-text-tertiary'>{t('app.accessControlDialog.groups', { count: 1 })}</p>
|
||||
<div className='flex flex-row flex-wrap gap-1'>
|
||||
<GroupItem />
|
||||
</div>
|
||||
<p className='system-2xs-medium-uppercase text-text-tertiary'>{t('app.accessControlDialog.members', { count: 4 })}</p>
|
||||
<div className='flex flex-row flex-wrap gap-1'>
|
||||
<MemberItem />
|
||||
</div>
|
||||
<RenderGroupsAndMembers />
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
}
|
||||
function GroupItem() {
|
||||
|
||||
function RenderGroupsAndMembers() {
|
||||
const { t } = useTranslation()
|
||||
const specificGroups = useAccessControlStore(s => s.specificGroups)
|
||||
const specificMembers = useAccessControlStore(s => s.specificMembers)
|
||||
if (specificGroups.length <= 0 && specificMembers.length <= 0)
|
||||
return <div className='px-2 pt-5 pb-1.5'><p className='system-xs-regular text-text-tertiary text-center'>{t('app.accessControlDialog.noGroupsOrMembers')}</p></div>
|
||||
return <>
|
||||
<p className='system-2xs-medium-uppercase text-text-tertiary'>{t('app.accessControlDialog.groups', { count: specificGroups.length ?? 0 })}</p>
|
||||
<div className='flex flex-row flex-wrap gap-1'>
|
||||
{specificGroups.map((group, index) => <GroupItem key={index} group={group} />)}
|
||||
</div>
|
||||
<p className='system-2xs-medium-uppercase text-text-tertiary'>{t('app.accessControlDialog.members', { count: specificMembers.length ?? 0 })}</p>
|
||||
<div className='flex flex-row flex-wrap gap-1'>
|
||||
{specificMembers.map((member, index) => <MemberItem key={index} member={member} />)}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
type GroupItemProps = {
|
||||
group: string
|
||||
}
|
||||
function GroupItem({ group }: GroupItemProps) {
|
||||
return <BaseItem icon={<RiOrganizationChart className='w-[14px] h-[14px] text-components-avatar-shape-fill-stop-0' />}>
|
||||
<p className='system-xs-regular text-text-primary'>Group Name</p>
|
||||
<p className='system-xs-regular text-text-tertiary'>7</p>
|
||||
</BaseItem>
|
||||
}
|
||||
|
||||
function MemberItem() {
|
||||
type MemberItemProps = {
|
||||
member: string
|
||||
}
|
||||
function MemberItem({ member }: MemberItemProps) {
|
||||
return <BaseItem icon={<Avatar className='w-[14px] h-[14px]' textClassName='text-[12px]' avatar={null} name='M' />}>
|
||||
<p className='system-xs-regular text-text-primary'>Member Name</p>
|
||||
</BaseItem>
|
||||
@ -70,3 +100,10 @@ function BaseItem({ icon, children }: BaseItemProps) {
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export function WebAppSSONotEnabledTip() {
|
||||
const { t } = useTranslation()
|
||||
return <Tooltip asChild={false} popupContent={t('app.accessControlDialog.webAppSSONotEnabledTip')}>
|
||||
<RiAlertFill className='w-4 h-4 text-text-warning-secondary shrink-0' />
|
||||
</Tooltip>
|
||||
}
|
||||
|
@ -188,6 +188,13 @@ const translation = {
|
||||
groups_other: '{{count}} GROUPS',
|
||||
members_one: '{{count}} MEMBER',
|
||||
members_other: '{{count}} MEMBERS',
|
||||
noGroupsOrMembers: 'No access members have been added yet',
|
||||
webAppSSONotEnabledTip: 'Please contact the administrator to enable the WebApp identity authentication method.',
|
||||
operateGroupAndMember: {
|
||||
searchPlaceholder: 'Search groups and members',
|
||||
allMembers: 'All members',
|
||||
expand: 'Expand',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -189,6 +189,13 @@ const translation = {
|
||||
groups_other: '{{count}} 个组',
|
||||
members_one: '{{count}} 个人',
|
||||
members_other: '{{count}} 个人',
|
||||
noGroupsOrMembers: '没有添加组或成员',
|
||||
webAppSSONotEnabledTip: '请联系管理员启用 WebApp SSO 身份验证方式。',
|
||||
operateGroupAndMember: {
|
||||
searchPlaceholder: '搜索组或成员',
|
||||
allMembers: '所有成员',
|
||||
expand: '展开',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user