mirror of
https://mirror.skon.top/github.com/langgenius/dify.git
synced 2026-04-20 15:20:15 +08:00
Some checks failed
autofix.ci / autofix (push) Has been cancelled
Build and Push API & Web / build (api, {{defaultContext}}:api, Dockerfile, DIFY_API_IMAGE_NAME, linux/amd64, ubuntu-latest, build-api-amd64) (push) Has been cancelled
Build and Push API & Web / build (api, {{defaultContext}}:api, Dockerfile, DIFY_API_IMAGE_NAME, linux/arm64, ubuntu-24.04-arm, build-api-arm64) (push) Has been cancelled
Build and Push API & Web / build (web, {{defaultContext}}, web/Dockerfile, DIFY_WEB_IMAGE_NAME, linux/amd64, ubuntu-latest, build-web-amd64) (push) Has been cancelled
Build and Push API & Web / build (web, {{defaultContext}}, web/Dockerfile, DIFY_WEB_IMAGE_NAME, linux/arm64, ubuntu-24.04-arm, build-web-arm64) (push) Has been cancelled
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Has been cancelled
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Has been cancelled
Main CI Pipeline / Skip Duplicate Checks (push) Has been cancelled
Main CI Pipeline / Check Changed Files (push) Has been cancelled
Main CI Pipeline / Run API Tests (push) Has been cancelled
Main CI Pipeline / Skip API Tests (push) Has been cancelled
Main CI Pipeline / API Tests (push) Has been cancelled
Main CI Pipeline / Run Web Tests (push) Has been cancelled
Main CI Pipeline / Skip Web Tests (push) Has been cancelled
Main CI Pipeline / Web Tests (push) Has been cancelled
Main CI Pipeline / Run Web Full-Stack E2E (push) Has been cancelled
Main CI Pipeline / Skip Web Full-Stack E2E (push) Has been cancelled
Main CI Pipeline / Web Full-Stack E2E (push) Has been cancelled
Main CI Pipeline / Style Check (push) Has been cancelled
Main CI Pipeline / Run VDB Tests (push) Has been cancelled
Main CI Pipeline / Skip VDB Tests (push) Has been cancelled
Main CI Pipeline / VDB Tests (push) Has been cancelled
Main CI Pipeline / Run DB Migration Test (push) Has been cancelled
Main CI Pipeline / Skip DB Migration Test (push) Has been cancelled
Main CI Pipeline / DB Migration Test (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
146 lines
6.0 KiB
TypeScript
146 lines
6.0 KiB
TypeScript
'use client'
|
|
import type { AccessControlAccount, AccessControlGroup } from '@/models/access-control'
|
|
import { RiAlertFill, RiCloseCircleFill, RiLockLine, RiOrganizationChart } from '@remixicon/react'
|
|
import { useCallback, useEffect } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { Avatar } from '@/app/components/base/ui/avatar'
|
|
import { AccessMode } from '@/models/access-control'
|
|
import { useAppWhiteListSubjects } from '@/service/access-control'
|
|
import useAccessControlStore from '../../../../context/access-control-store'
|
|
import Loading from '../../base/loading'
|
|
import Tooltip from '../../base/tooltip'
|
|
import AddMemberOrGroupDialog from './add-member-or-group-pop'
|
|
|
|
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 { 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="flex grow items-center gap-x-2">
|
|
<RiLockLine className="h-4 w-4 text-text-primary" />
|
|
<p className="system-sm-medium text-text-primary">{t('accessControlDialog.accessItems.specific', { ns: 'app' })}</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div className="flex items-center gap-x-1 p-3">
|
|
<div className="flex grow items-center gap-x-1">
|
|
<RiLockLine className="h-4 w-4 text-text-primary" />
|
|
<p className="system-sm-medium text-text-primary">{t('accessControlDialog.accessItems.specific', { ns: 'app' })}</p>
|
|
</div>
|
|
<div className="flex items-center gap-x-1">
|
|
<AddMemberOrGroupDialog />
|
|
</div>
|
|
</div>
|
|
<div className="px-1 pb-1">
|
|
<div className="flex max-h-[400px] flex-col gap-y-2 overflow-y-auto rounded-lg bg-background-section p-2">
|
|
{isPending ? <Loading /> : <RenderGroupsAndMembers />}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
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="text-center system-xs-regular text-text-tertiary">{t('accessControlDialog.noGroupsOrMembers', { ns: 'app' })}</p></div>
|
|
return (
|
|
<>
|
|
<p className="sticky top-0 system-2xs-medium-uppercase text-text-tertiary">{t('accessControlDialog.groups', { ns: 'app', 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="sticky top-0 system-2xs-medium-uppercase text-text-tertiary">{t('accessControlDialog.members', { ns: 'app', 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: AccessControlGroup
|
|
}
|
|
function GroupItem({ group }: GroupItemProps) {
|
|
const specificGroups = useAccessControlStore(s => s.specificGroups)
|
|
const setSpecificGroups = useAccessControlStore(s => s.setSpecificGroups)
|
|
const handleRemoveGroup = useCallback(() => {
|
|
setSpecificGroups(specificGroups.filter(g => g.id !== group.id))
|
|
}, [group, setSpecificGroups, specificGroups])
|
|
return (
|
|
<BaseItem
|
|
icon={<RiOrganizationChart className="h-[14px] w-[14px] text-components-avatar-shape-fill-stop-0" />}
|
|
onRemove={handleRemoveGroup}
|
|
>
|
|
<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: AccessControlAccount
|
|
}
|
|
function MemberItem({ member }: MemberItemProps) {
|
|
const specificMembers = useAccessControlStore(s => s.specificMembers)
|
|
const setSpecificMembers = useAccessControlStore(s => s.setSpecificMembers)
|
|
const handleRemoveMember = useCallback(() => {
|
|
setSpecificMembers(specificMembers.filter(m => m.id !== member.id))
|
|
}, [member, setSpecificMembers, specificMembers])
|
|
return (
|
|
<BaseItem
|
|
icon={<Avatar size="xxs" avatar={null} name={member.name} />}
|
|
onRemove={handleRemoveMember}
|
|
>
|
|
<p className="system-xs-regular text-text-primary">{member.name}</p>
|
|
</BaseItem>
|
|
)
|
|
}
|
|
|
|
type BaseItemProps = {
|
|
icon: React.ReactNode
|
|
children: React.ReactNode
|
|
onRemove?: () => void
|
|
}
|
|
function BaseItem({ icon, onRemove, children }: BaseItemProps) {
|
|
return (
|
|
<div className="group flex flex-row items-center gap-x-1 rounded-full border-[0.5px] bg-components-badge-white-to-dark p-1 pr-1.5 shadow-xs">
|
|
<div className="h-5 w-5 overflow-hidden rounded-full bg-components-icon-bg-blue-solid">
|
|
<div className="bg-access-app-icon-mask-bg flex h-full w-full items-center justify-center">
|
|
{icon}
|
|
</div>
|
|
</div>
|
|
{children}
|
|
<div className="flex h-4 w-4 cursor-pointer items-center justify-center" onClick={onRemove}>
|
|
<RiCloseCircleFill className="h-[14px] w-[14px] text-text-quaternary" />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function WebAppSSONotEnabledTip() {
|
|
const { t } = useTranslation()
|
|
return (
|
|
<Tooltip asChild={false} popupContent={t('accessControlDialog.webAppSSONotEnabledTip', { ns: 'app' })}>
|
|
<RiAlertFill className="h-4 w-4 shrink-0 text-text-warning-secondary" />
|
|
</Tooltip>
|
|
)
|
|
}
|