diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx index dce704e1bc..bf6c562cf9 100644 --- a/web/app/(commonLayout)/apps/AppCard.tsx +++ b/web/app/(commonLayout)/apps/AppCard.tsx @@ -179,6 +179,13 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { setShowSwitchModal(false) } + const onUpdateAccessControl = useCallback(() => { + if (onRefresh) + onRefresh() + mutateApps() + setShowAccessControl(false) + }, [onRefresh, mutateApps, setShowAccessControl]) + const Operations = (props: HtmlContentProps) => { const onMouseLeave = async () => { props.onClose?.() @@ -316,13 +323,13 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
- {app.accessMode === AccessMode.PUBLIC && + {app.access_mode === AccessMode.PUBLIC && } - {app.accessMode === AccessMode.SPECIFIC_GROUPS_MEMBERS && + {app.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS && } - {app.accessMode === AccessMode.ORGANIZATION && + {app.access_mode === AccessMode.ORGANIZATION && }
@@ -444,7 +451,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { /> )} {showAccessControl && ( - setShowAccessControl(false)} /> + setShowAccessControl(false)} /> )} ) diff --git a/web/app/components/app/app-access-control/add-member-or-group-pop.tsx b/web/app/components/app/app-access-control/add-member-or-group-pop.tsx index 59a0a055d3..ad61ef814e 100644 --- a/web/app/components/app/app-access-control/add-member-or-group-pop.tsx +++ b/web/app/components/app/app-access-control/add-member-or-group-pop.tsx @@ -1,7 +1,7 @@ 'use client' import { RiAddCircleFill, RiArrowRightSLine, RiOrganizationChart } from '@remixicon/react' import { useTranslation } from 'react-i18next' -import { useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useDebounce } from 'ahooks' import Avatar from '../../base/avatar' import Button from '../../base/button' @@ -9,6 +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 classNames from '@/utils/classnames' import { useSearchForWhiteListCandidates } from '@/service/access-control' import type { AccessControlAccount, AccessControlGroup, Subject, SubjectAccount, SubjectGroup } from '@/models/access-control' @@ -78,7 +79,7 @@ function renderGroupOrMember(data: GroupOrMemberData) { return data?.map((page) => { return
{page.subjects?.map((item, index) => { - if (item.subjectType === SubjectType.Group) + if (item.subjectType === SubjectType.GROUP) return return })} @@ -91,8 +92,21 @@ type GroupItemProps = { } function GroupItem({ group }: GroupItemProps) { const { t } = useTranslation() + const specificGroups = useAccessControlStore(s => s.specificGroups) + const setSpecificGroups = useAccessControlStore(s => s.setSpecificGroups) + const isChecked = specificGroups.some(g => g.id === group.id) + const handleCheckChange = useCallback(() => { + if (!isChecked) { + const newGroups = [...specificGroups, group] + setSpecificGroups(newGroups) + } + else { + const newGroups = specificGroups.filter(g => g.id !== group.id) + setSpecificGroups(newGroups) + } + }, [specificGroups, setSpecificGroups, group, isChecked]) return - +
@@ -115,8 +129,21 @@ type MemberItemProps = { function MemberItem({ member }: MemberItemProps) { const currentUser = useSelector(s => s.userProfile) const { t } = useTranslation() + const specificMembers = useAccessControlStore(s => s.specificMembers) + const setSpecificMembers = useAccessControlStore(s => s.setSpecificMembers) + const isChecked = specificMembers.some(m => m.id === member.id) + const handleCheckChange = useCallback(() => { + if (!isChecked) { + const newMembers = [...specificMembers, member] + setSpecificMembers(newMembers) + } + else { + const newMembers = specificMembers.filter(m => m.id !== member.id) + setSpecificMembers(newMembers) + } + }, [specificMembers, setSpecificMembers, member, isChecked]) return - +
diff --git a/web/app/components/app/app-access-control/index.tsx b/web/app/components/app/app-access-control/index.tsx index 56ad8ec7c1..53f60f2481 100644 --- a/web/app/components/app/app-access-control/index.tsx +++ b/web/app/components/app/app-access-control/index.tsx @@ -11,40 +11,57 @@ import SpecificGroupsOrMembers, { WebAppSSONotEnabledTip } from './specific-grou 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 type { Subject } from '@/models/access-control' +import { AccessMode, SubjectType } from '@/models/access-control' import { useUpdateAccessMode } from '@/service/access-control' type AccessControlProps = { app: App onClose: () => void + onConfirm?: () => void } export default function AccessControl(props: AccessControlProps) { + const { app, onClose, onConfirm } = props 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 currentMenu = useAccessControlStore(s => s.currentMenu) + const setCurrentMenu = useAccessControlStore(s => s.setCurrentMenu) const hideTip = systemFeatures.enable_web_sso_switch_component && systemFeatures.sso_enforced_for_web useEffect(() => { - setAppId(props.app.id) - }, [props.app, setAppId]) + setAppId(app.id) + setCurrentMenu(app.access_mode ?? AccessMode.SPECIFIC_GROUPS_MEMBERS) + }, [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 }) + const submitData: { + appId: string + accessMode: AccessMode + subjects?: Pick[] + } = { appId: app.id, accessMode: currentMenu } + if (currentMenu === AccessMode.SPECIFIC_GROUPS_MEMBERS) { + const subjects: Pick[] = [] + specificGroups.forEach((group) => { + subjects.push({ subjectId: group.id, subjectType: SubjectType.GROUP }) + }) + specificMembers.forEach((member) => { + subjects.push({ + subjectId: member.id, + subjectType: SubjectType.ACCOUNT, + }) + }) + submitData.subjects = subjects + } + await updateAccessMode(submitData) Toast.notify({ type: 'success', message: t('app.accessControlDialog.updateSuccess') }) - }, [updateAccessMode, props.app, specificGroups, specificMembers, t]) - return + onConfirm?.() + }, [updateAccessMode, app, specificGroups, specificMembers, t, onConfirm, currentMenu]) + return
{t('app.accessControlDialog.title')} @@ -74,7 +91,7 @@ export default function AccessControl(props: AccessControlProps) {
- +
diff --git a/web/app/components/app/app-access-control/specific-groups-or-members.tsx b/web/app/components/app/app-access-control/specific-groups-or-members.tsx index c0cd36bacb..414d8cce00 100644 --- a/web/app/components/app/app-access-control/specific-groups-or-members.tsx +++ b/web/app/components/app/app-access-control/specific-groups-or-members.tsx @@ -53,7 +53,7 @@ export default function SpecificGroupsOrMembers() {
-
+
{isPending ? : }
@@ -67,11 +67,11 @@ function RenderGroupsAndMembers() { if (specificGroups.length <= 0 && specificMembers.length <= 0) return

{t('app.accessControlDialog.noGroupsOrMembers')}

return <> -

{t('app.accessControlDialog.groups', { count: specificGroups.length ?? 0 })}

+

{t('app.accessControlDialog.groups', { count: specificGroups.length ?? 0 })}

{specificGroups.map((group, index) => )}
-

{t('app.accessControlDialog.members', { count: specificMembers.length ?? 0 })}

+

{t('app.accessControlDialog.members', { count: specificMembers.length ?? 0 })}

{specificMembers.map((member, index) => )}
diff --git a/web/models/access-control.ts b/web/models/access-control.ts index bfbd228fa5..b0e4a097e9 100644 --- a/web/models/access-control.ts +++ b/web/models/access-control.ts @@ -1,12 +1,12 @@ export enum SubjectType { - Group = 'group', - Account = 'account', + GROUP = 'group', + ACCOUNT = 'account', } export enum AccessMode { - PUBLIC = 'PUBLIC', - SPECIFIC_GROUPS_MEMBERS = 'SPECIFIC_GROUPS_MEMBERS', - ORGANIZATION = 'ORGANIZATION', + PUBLIC = 'public', + SPECIFIC_GROUPS_MEMBERS = 'private', + ORGANIZATION = 'private_all', } export type AccessControlGroup = { diff --git a/web/service/access-control.ts b/web/service/access-control.ts index 549ba43d7b..dbb72a3ae7 100644 --- a/web/service/access-control.ts +++ b/web/service/access-control.ts @@ -1,6 +1,6 @@ import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query' import { get, post } from './base' -import type { AccessControlAccount, AccessControlGroup, Subject } from '@/models/access-control' +import type { AccessControlAccount, AccessControlGroup, AccessMode, Subject } from '@/models/access-control' import type { App } from '@/types/app' const NAME_SPACE = 'access-control' @@ -45,7 +45,8 @@ export const useSearchForWhiteListCandidates = (query: { keyword?: string; resul type UpdateAccessModeParams = { appId: App['id'] - subjects: Subject['subjectId'][] + subjects?: Pick[] + accessMode: AccessMode } export const useUpdateAccessMode = () => { diff --git a/web/types/app.ts b/web/types/app.ts index 101a31c09d..720f97134c 100644 --- a/web/types/app.ts +++ b/web/types/app.ts @@ -353,7 +353,7 @@ export type App = { api_base_url: string tags: Tag[] /** access control */ - accessMode: AccessMode + access_mode: AccessMode } export type AppSSO = {