mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-15 01:45:58 +08:00
fix: add api
This commit is contained in:
parent
d1de872d86
commit
ab262b8506
@ -437,7 +437,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
/>
|
||||
)}
|
||||
{showAccessControl && (
|
||||
<AccessControl onClose={() => setShowAccessControl(false)} />
|
||||
<AccessControl app={app} onClose={() => setShowAccessControl(false)} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
@ -480,7 +480,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
/>
|
||||
)}
|
||||
{
|
||||
showAccessControl && <AccessControl />
|
||||
showAccessControl && <AccessControl app={appDetail} onClose={() => { setShowAccessControl(false) }} />
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElem>
|
||||
|
@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
import type { FC, PropsWithChildren } from 'react'
|
||||
import type { AccessControlType } from './access-control-store'
|
||||
import useAccessControlStore from './access-control-store'
|
||||
import type { AccessMode } from '@/models/access-control'
|
||||
|
||||
type AccessControlItemProps = PropsWithChildren<{
|
||||
type: AccessControlType
|
||||
type: AccessMode
|
||||
}>
|
||||
|
||||
const AccessControlItem: FC<AccessControlItemProps> = ({ type, children }) => {
|
||||
|
@ -1,26 +1,28 @@
|
||||
import { create } from 'zustand'
|
||||
export enum AccessControlType {
|
||||
PUBLIC = 'PUBLIC',
|
||||
SPECIFIC_GROUPS_MEMBERS = 'SPECIFIC_GROUPS_MEMBERS',
|
||||
ORGANIZATION = 'ORGANIZATION',
|
||||
}
|
||||
import type { AccessControlAccount, AccessControlGroup } from '@/models/access-control'
|
||||
import { AccessMode } from '@/models/access-control'
|
||||
import type { App } from '@/types/app'
|
||||
|
||||
type AccessControlStore = {
|
||||
specificGroups: []
|
||||
setSpecificGroups: (specificGroups: []) => void
|
||||
specificMembers: []
|
||||
setSpecificMembers: (specificMembers: []) => void
|
||||
currentMenu: AccessControlType
|
||||
setCurrentMenu: (currentMenu: AccessControlType) => void
|
||||
appId: App['id']
|
||||
setAppId: (appId: App['id']) => void
|
||||
specificGroups: AccessControlGroup[]
|
||||
setSpecificGroups: (specificGroups: AccessControlGroup[]) => void
|
||||
specificMembers: AccessControlAccount[]
|
||||
setSpecificMembers: (specificMembers: AccessControlAccount[]) => void
|
||||
currentMenu: AccessMode
|
||||
setCurrentMenu: (currentMenu: AccessMode) => void
|
||||
}
|
||||
|
||||
const useAccessControlStore = create<AccessControlStore>((set) => {
|
||||
return {
|
||||
appId: '',
|
||||
setAppId: appId => set({ appId }),
|
||||
specificGroups: [],
|
||||
setSpecificGroups: specificGroups => set({ specificGroups }),
|
||||
specificMembers: [],
|
||||
setSpecificMembers: specificMembers => set({ specificMembers }),
|
||||
currentMenu: AccessControlType.SPECIFIC_GROUPS_MEMBERS,
|
||||
currentMenu: AccessMode.SPECIFIC_GROUPS_MEMBERS,
|
||||
setCurrentMenu: currentMenu => set({ currentMenu }),
|
||||
}
|
||||
})
|
||||
|
@ -13,6 +13,7 @@ import classNames from '@/utils/classnames'
|
||||
import { useSearchForWhiteListCandidates } from '@/service/access-control'
|
||||
import type { AccessControlAccount, AccessControlGroup, Subject, SubjectAccount, SubjectGroup } from '@/models/access-control'
|
||||
import { SubjectType } from '@/models/access-control'
|
||||
import { useSelector } from '@/context/app-context'
|
||||
|
||||
export default function AddMemberOrGroupDialog() {
|
||||
const { t } = useTranslation()
|
||||
@ -116,6 +117,8 @@ type MemberItemProps = {
|
||||
member: AccessControlAccount
|
||||
}
|
||||
function MemberItem({ member }: MemberItemProps) {
|
||||
const currentUser = useSelector(s => s.userProfile)
|
||||
const { t } = useTranslation()
|
||||
return <BaseItem className='pr-3'>
|
||||
<Checkbox className='w-4 h-4 shrink-0' />
|
||||
<div className='flex items-center grow'>
|
||||
@ -125,7 +128,7 @@ function MemberItem({ member }: MemberItemProps) {
|
||||
</div>
|
||||
</div>
|
||||
<p className='system-sm-medium text-text-secondary mr-1'>{member.name}</p>
|
||||
<p className='system-xs-regular text-text-tertiary'>You</p>
|
||||
{currentUser.email === member.email && <p className='system-xs-regular text-text-tertiary'>({t('common.you')})</p>}
|
||||
</div>
|
||||
<p className='system-xs-regular text-text-quaternary'>{member.email}</p>
|
||||
</BaseItem>
|
||||
|
@ -2,22 +2,48 @@
|
||||
import { Dialog } from '@headlessui/react'
|
||||
import { RiBuildingLine, RiGlobalLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import Button from '../../base/button'
|
||||
import Toast from '../../base/toast'
|
||||
import AccessControlDialog from './access-control-dialog'
|
||||
import AccessControlItem from './access-control-item'
|
||||
import SpecificGroupsOrMembers, { WebAppSSONotEnabledTip } from './specific-groups-or-members'
|
||||
import { AccessControlType } from './access-control-store'
|
||||
import useAccessControlStore from './access-control-store'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import type { App } from '@/types/app'
|
||||
import { AccessMode } from '@/models/access-control'
|
||||
import { useUpdateAccessMode } from '@/service/access-control'
|
||||
|
||||
type AccessControlProps = {
|
||||
app: App
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export default function AccessControl(props: AccessControlProps) {
|
||||
const { t } = useTranslation()
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const setAppId = useAccessControlStore(s => s.setAppId)
|
||||
const specificGroups = useAccessControlStore(s => s.specificGroups)
|
||||
const specificMembers = useAccessControlStore(s => s.specificMembers)
|
||||
const hideTip = systemFeatures.enable_web_sso_switch_component && systemFeatures.sso_enforced_for_web
|
||||
|
||||
useEffect(() => {
|
||||
setAppId(props.app.id)
|
||||
}, [props.app, setAppId])
|
||||
|
||||
const { isPending, mutateAsync: updateAccessMode } = useUpdateAccessMode()
|
||||
const handleConfirm = useCallback(async () => {
|
||||
const subjectIds: string[] = []
|
||||
specificGroups.forEach((group) => {
|
||||
subjectIds.push(group.id)
|
||||
})
|
||||
specificMembers.forEach((member) => {
|
||||
subjectIds.push(member.id)
|
||||
},
|
||||
)
|
||||
await updateAccessMode({ appId: props.app.id, subjects: subjectIds })
|
||||
Toast.notify({ type: 'success', message: t('app.accessControlDialog.updateSuccess') })
|
||||
}, [updateAccessMode, props.app, specificGroups, specificMembers, t])
|
||||
return <AccessControlDialog show onClose={props.onClose}>
|
||||
<div className='flex flex-col gap-y-3'>
|
||||
<div className='pt-6 pr-14 pb-3 pl-6'>
|
||||
@ -28,7 +54,7 @@ export default function AccessControl(props: AccessControlProps) {
|
||||
<div className='leading-6'>
|
||||
<p className='system-sm-medium'>{t('app.accessControlDialog.accessLabel')}</p>
|
||||
</div>
|
||||
<AccessControlItem type={AccessControlType.ORGANIZATION}>
|
||||
<AccessControlItem type={AccessMode.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' />
|
||||
@ -37,10 +63,10 @@ export default function AccessControl(props: AccessControlProps) {
|
||||
{!hideTip && <WebAppSSONotEnabledTip />}
|
||||
</div>
|
||||
</AccessControlItem>
|
||||
<AccessControlItem type={AccessControlType.SPECIFIC_GROUPS_MEMBERS}>
|
||||
<AccessControlItem type={AccessMode.SPECIFIC_GROUPS_MEMBERS}>
|
||||
<SpecificGroupsOrMembers />
|
||||
</AccessControlItem>
|
||||
<AccessControlItem type={AccessControlType.PUBLIC}>
|
||||
<AccessControlItem type={AccessMode.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>
|
||||
@ -49,7 +75,7 @@ export default function AccessControl(props: AccessControlProps) {
|
||||
</div>
|
||||
<div className='flex items-center justify-end p-6 pt-5 gap-x-2'>
|
||||
<Button onClick={props.onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary'>{t('common.operation.confirm')}</Button>
|
||||
<Button disabled={isPending} loading={isPending} variant='primary' onClick={handleConfirm}>{t('common.operation.confirm')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AccessControlDialog>
|
||||
|
@ -1,20 +1,34 @@
|
||||
'use client'
|
||||
import { RiAlertFill, RiCloseCircleFill, RiLockLine, RiOrganizationChart } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEffect } from 'react'
|
||||
import Avatar from '../../base/avatar'
|
||||
import Divider from '../../base/divider'
|
||||
import Tooltip from '../../base/tooltip'
|
||||
import Loading from '../../base/loading'
|
||||
import AddMemberOrGroupDialog from './add-member-or-group-pop'
|
||||
import useAccessControlStore, { AccessControlType } from './access-control-store'
|
||||
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'
|
||||
import { useAppWhiteListSubjects } from '@/service/access-control'
|
||||
|
||||
export default function SpecificGroupsOrMembers() {
|
||||
const currentMenu = useAccessControlStore(s => s.currentMenu)
|
||||
const appId = useAccessControlStore(s => s.appId)
|
||||
const setSpecificGroups = useAccessControlStore(s => s.setSpecificGroups)
|
||||
const setSpecificMembers = useAccessControlStore(s => s.setSpecificMembers)
|
||||
const { t } = useTranslation()
|
||||
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) {
|
||||
const { isPending, data } = useAppWhiteListSubjects(appId, Boolean(appId) && currentMenu === AccessMode.SPECIFIC_GROUPS_MEMBERS)
|
||||
useEffect(() => {
|
||||
setSpecificGroups(data?.groups ?? [])
|
||||
setSpecificMembers(data?.members ?? [])
|
||||
}, [data, setSpecificGroups, setSpecificMembers])
|
||||
|
||||
if (currentMenu !== AccessMode.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' />
|
||||
@ -40,7 +54,7 @@ export default function SpecificGroupsOrMembers() {
|
||||
</div>
|
||||
<div className='px-1 pb-1'>
|
||||
<div className='bg-background-section rounded-lg p-2 flex flex-col gap-y-2'>
|
||||
<RenderGroupsAndMembers />
|
||||
{isPending ? <Loading /> : <RenderGroupsAndMembers />}
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
@ -65,21 +79,21 @@ function RenderGroupsAndMembers() {
|
||||
}
|
||||
|
||||
type GroupItemProps = {
|
||||
group: string
|
||||
group: AccessControlGroup
|
||||
}
|
||||
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>
|
||||
<p className='system-xs-regular text-text-primary'>{group.name}</p>
|
||||
<p className='system-xs-regular text-text-tertiary'>{group.groupSize}</p>
|
||||
</BaseItem>
|
||||
}
|
||||
|
||||
type MemberItemProps = {
|
||||
member: string
|
||||
member: AccessControlAccount
|
||||
}
|
||||
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>
|
||||
return <BaseItem icon={<Avatar className='w-[14px] h-[14px]' textClassName='text-[12px]' avatar={null} name={member.name} />}>
|
||||
<p className='system-xs-regular text-text-primary'>{member.name}</p>
|
||||
</BaseItem>
|
||||
}
|
||||
|
||||
|
@ -21,14 +21,12 @@ import type { AppIconType, AppSSO, Language } from '@/types/app'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { LanguagesSupported, languages } from '@/i18n/language'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
|
||||
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
||||
import I18n from '@/context/i18n'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
|
||||
export type ISettingsModalProps = {
|
||||
isChat: boolean
|
||||
@ -66,8 +64,6 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
onClose,
|
||||
onSave,
|
||||
}) => {
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const { isCurrentWorkspaceEditor } = useAppContext()
|
||||
const { notify } = useToastContext()
|
||||
const [isShowMore, setIsShowMore] = useState(false)
|
||||
const {
|
||||
@ -139,7 +135,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
setAppIcon(icon_type === 'image'
|
||||
? { type: 'image', url: icon_url!, fileId: icon }
|
||||
: { type: 'emoji', icon, background: icon_background! })
|
||||
}, [appInfo])
|
||||
}, [appInfo, chat_color_theme, chat_color_theme_inverted, copyright, custom_disclaimer, default_language, description, icon, icon_background, icon_type, icon_url, privacy_policy, show_workflow_steps, title, use_icon_as_answer_icon])
|
||||
|
||||
const onHide = () => {
|
||||
onClose()
|
||||
@ -325,28 +321,6 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
</div>
|
||||
<p className='pb-0.5 text-text-tertiary body-xs-regular'>{t(`${prefixSettings}.workflow.showDesc`)}</p>
|
||||
</div>
|
||||
{/* SSO */}
|
||||
{systemFeatures.enable_web_sso_switch_component && (
|
||||
<>
|
||||
<Divider className="h-px my-0" />
|
||||
<div className='w-full'>
|
||||
<p className='mb-1 system-xs-medium-uppercase text-text-tertiary'>{t(`${prefixSettings}.sso.label`)}</p>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.sso.title`)}</div>
|
||||
<Tooltip
|
||||
disabled={systemFeatures.sso_enforced_for_web}
|
||||
popupContent={
|
||||
<div className='w-[180px]'>{t(`${prefixSettings}.sso.tooltip`)}</div>
|
||||
}
|
||||
asChild={false}
|
||||
>
|
||||
<Switch disabled={!systemFeatures.sso_enforced_for_web || !isCurrentWorkspaceEditor} defaultValue={systemFeatures.sso_enforced_for_web && inputInfo.enable_sso} onChange={v => setInputInfo({ ...inputInfo, enable_sso: v })}></Switch>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<p className='pb-0.5 body-xs-regular text-text-tertiary'>{t(`${prefixSettings}.sso.description`)}</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{/* more settings switch */}
|
||||
<Divider className="h-px my-0" />
|
||||
{!isShowMore && (
|
||||
|
@ -196,6 +196,7 @@ const translation = {
|
||||
expand: 'Expand',
|
||||
noResult: 'No result',
|
||||
},
|
||||
updateSuccess: 'Update successfully',
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -621,6 +621,7 @@ const translation = {
|
||||
pagination: {
|
||||
perPage: 'Items per page',
|
||||
},
|
||||
your: 'You',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
@ -197,6 +197,7 @@ const translation = {
|
||||
expand: '展开',
|
||||
noResult: '没有结果',
|
||||
},
|
||||
updateSuccess: '更新成功',
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -621,6 +621,7 @@ const translation = {
|
||||
pagination: {
|
||||
perPage: '每页显示',
|
||||
},
|
||||
you: '你',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
@ -3,9 +3,9 @@ export enum SubjectType {
|
||||
Account = 'account',
|
||||
}
|
||||
|
||||
export enum AccessModel {
|
||||
export enum AccessMode {
|
||||
PUBLIC = 'PUBLIC',
|
||||
PRIVATE = 'PRIVATE',
|
||||
SPECIFIC_GROUPS_MEMBERS = 'SPECIFIC_GROUPS_MEMBERS',
|
||||
ORGANIZATION = 'ORGANIZATION',
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { get } from './base'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { get, post } from './base'
|
||||
import type { AccessControlAccount, AccessControlGroup, Subject } from '@/models/access-control'
|
||||
import type { App } from '@/types/app'
|
||||
|
||||
const NAME_SPACE = 'access-control'
|
||||
|
||||
export const useAppWhiteListSubjects = (appId: string) => {
|
||||
export const useAppWhiteListSubjects = (appId: string, enabled: boolean) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'app-whitelist-subjects', appId],
|
||||
queryFn: () => get<{ groups: AccessControlGroup[]; members: AccessControlAccount[] }>(`/enterprise/webapp/app/subjects?appId=${appId}`),
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
@ -33,3 +35,17 @@ export const useSearchForWhiteListCandidates = (query: { appId?: string; keyword
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
type UpdateAccessModeParams = {
|
||||
appId: App['id']
|
||||
subjects: Subject['subjectId'][]
|
||||
}
|
||||
|
||||
export const useUpdateAccessMode = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update-access-mode'],
|
||||
mutationFn: (params: UpdateAccessModeParams) => {
|
||||
return post('/enterprise/webapp/app/access-mode', { body: params })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user