diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx index d1dad5f258..cbcf0518f1 100644 --- a/web/app/(commonLayout)/apps/AppCard.tsx +++ b/web/app/(commonLayout)/apps/AppCard.tsx @@ -437,7 +437,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { /> )} {showAccessControl && ( - setShowAccessControl(false)} /> + setShowAccessControl(false)} /> )} ) diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 5bfe209724..f3621d3018 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -480,7 +480,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => { /> )} { - showAccessControl && + showAccessControl && { setShowAccessControl(false) }} /> } diff --git a/web/app/components/app/app-access-control/access-control-item.tsx b/web/app/components/app/app-access-control/access-control-item.tsx index fe38b99a57..178e6e196b 100644 --- a/web/app/components/app/app-access-control/access-control-item.tsx +++ b/web/app/components/app/app-access-control/access-control-item.tsx @@ -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 = ({ type, children }) => { diff --git a/web/app/components/app/app-access-control/access-control-store.ts b/web/app/components/app/app-access-control/access-control-store.ts index e14307caba..ebf78670e0 100644 --- a/web/app/components/app/app-access-control/access-control-store.ts +++ b/web/app/components/app/app-access-control/access-control-store.ts @@ -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((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 }), } }) 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 28e668a5c9..9c061d395e 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 @@ -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
@@ -125,7 +128,7 @@ function MemberItem({ member }: MemberItemProps) {

{member.name}

-

You

+ {currentUser.email === member.email &&

({t('common.you')})

}

{member.email}

diff --git a/web/app/components/app/app-access-control/index.tsx b/web/app/components/app/app-access-control/index.tsx index db9b876fd0..56ad8ec7c1 100644 --- a/web/app/components/app/app-access-control/index.tsx +++ b/web/app/components/app/app-access-control/index.tsx @@ -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
@@ -28,7 +54,7 @@ export default function AccessControl(props: AccessControlProps) {

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

- +
@@ -37,10 +63,10 @@ export default function AccessControl(props: AccessControlProps) { {!hideTip && }
- + - +

{t('app.accessControlDialog.accessItems.anyone')}

@@ -49,7 +75,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 42fd384d7a..c0cd36bacb 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 @@ -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
@@ -40,7 +54,7 @@ export default function SpecificGroupsOrMembers() {
- + {isPending ? : }
@@ -65,21 +79,21 @@ function RenderGroupsAndMembers() { } type GroupItemProps = { - group: string + group: AccessControlGroup } function GroupItem({ group }: GroupItemProps) { return }> -

Group Name

-

7

+

{group.name}

+

{group.groupSize}

} type MemberItemProps = { - member: string + member: AccessControlAccount } function MemberItem({ member }: MemberItemProps) { - return }> -

Member Name

+ return }> +

{member.name}

} diff --git a/web/app/components/app/overview/settings/index.tsx b/web/app/components/app/overview/settings/index.tsx index 357d7c0196..f7e2d964ed 100644 --- a/web/app/components/app/overview/settings/index.tsx +++ b/web/app/components/app/overview/settings/index.tsx @@ -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 = ({ onClose, onSave, }) => { - const { systemFeatures } = useGlobalPublicStore() - const { isCurrentWorkspaceEditor } = useAppContext() const { notify } = useToastContext() const [isShowMore, setIsShowMore] = useState(false) const { @@ -139,7 +135,7 @@ const SettingsModal: FC = ({ 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 = ({

{t(`${prefixSettings}.workflow.showDesc`)}

- {/* SSO */} - {systemFeatures.enable_web_sso_switch_component && ( - <> - -
-

{t(`${prefixSettings}.sso.label`)}

-
-
{t(`${prefixSettings}.sso.title`)}
- {t(`${prefixSettings}.sso.tooltip`)}
- } - asChild={false} - > - setInputInfo({ ...inputInfo, enable_sso: v })}> - -
-

{t(`${prefixSettings}.sso.description`)}

- - - )} {/* more settings switch */} {!isShowMore && ( diff --git a/web/i18n/en-US/app.ts b/web/i18n/en-US/app.ts index 49017150a3..c3cd439c25 100644 --- a/web/i18n/en-US/app.ts +++ b/web/i18n/en-US/app.ts @@ -196,6 +196,7 @@ const translation = { expand: 'Expand', noResult: 'No result', }, + updateSuccess: 'Update successfully', }, } diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 144b32f74e..71173e2449 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -621,6 +621,7 @@ const translation = { pagination: { perPage: 'Items per page', }, + your: 'You', } export default translation diff --git a/web/i18n/zh-Hans/app.ts b/web/i18n/zh-Hans/app.ts index f718e03c2b..2c68497d5b 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -197,6 +197,7 @@ const translation = { expand: '展开', noResult: '没有结果', }, + updateSuccess: '更新成功', }, } diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index b4d80736ec..55c554efd1 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -621,6 +621,7 @@ const translation = { pagination: { perPage: '每页显示', }, + you: '你', } export default translation diff --git a/web/models/access-control.ts b/web/models/access-control.ts index 9f02a0a6f8..bfbd228fa5 100644 --- a/web/models/access-control.ts +++ b/web/models/access-control.ts @@ -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', } diff --git a/web/service/access-control.ts b/web/service/access-control.ts index 01edd3d839..48673d0cdd 100644 --- a/web/service/access-control.ts +++ b/web/service/access-control.ts @@ -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 }) + }, + }) +}