From ddb3e32c1afc612d60b4c6932481a630d1ae7a27 Mon Sep 17 00:00:00 2001 From: NFish Date: Tue, 6 May 2025 11:27:46 +0800 Subject: [PATCH 1/3] fix: disable items in publish dropdown menu panel if web app access not set --- .../components/app/app-publisher/index.tsx | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index 79c8f11c12..5b1adb5717 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -89,7 +89,7 @@ const AppPublisher = ({ const appMode = (appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow') ? 'chat' : appDetail.mode const appURL = `${appBaseURL}${basePath}/${appMode}/${accessToken}` const isChatApp = ['chat', 'agent-chat', 'completion'].includes(appDetail?.mode || '') - const { data: useCanAccessApp, isLoading: isGettingUserCanAccessApp, refetch } = useGetUserCanAccessApp({ appId: appDetail?.id, enabled: false }) + const { data: userCanAccessApp, isLoading: isGettingUserCanAccessApp, refetch } = useGetUserCanAccessApp({ appId: appDetail?.id, enabled: false }) const { data: appAccessSubjects, isLoading: isGettingAppWhiteListSubjects } = useAppWhiteListSubjects(appDetail?.id, open && appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS) useEffect(() => { @@ -287,9 +287,10 @@ const AppPublisher = ({ {!isAppAccessSet &&

{t('app.publishApp.notSetDesc')}

}
- + } > @@ -298,9 +299,10 @@ const AppPublisher = ({ {appDetail?.mode === 'workflow' || appDetail?.mode === 'completion' ? ( - + } > @@ -320,44 +322,41 @@ const AppPublisher = ({ {t('workflow.common.embedIntoSite')} )} - + { publishedAt && handleOpenInExplore() }} - disabled={!publishedAt} + disabled={!publishedAt || !userCanAccessApp?.result} icon={} > {t('workflow.common.openInExplore')} -
- } - > - {t('workflow.common.accessAPIReference')} - -
+ } + > + {t('workflow.common.accessAPIReference')} + {appDetail?.mode === 'workflow' && ( -
- -
+ )}
} From 7237c467cec09865af86811c38251b847d5dd09e Mon Sep 17 00:00:00 2001 From: NFish Date: Tue, 6 May 2025 14:52:07 +0800 Subject: [PATCH 2/3] fix: reopen switch to 'workflow orchestrate' menu in app detail page --- web/app/components/app-sidebar/app-info.tsx | 60 +++++++++++++++++---- web/i18n/en-US/common.ts | 1 + web/i18n/ja-JP/common.ts | 1 + web/i18n/zh-Hans/common.ts | 1 + 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 27a6e352a8..7c41811ebd 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -6,8 +6,11 @@ import { RiDeleteBinLine, RiEditLine, RiEqualizer2Line, + RiExchange2Line, + RiFileCopy2Line, RiFileDownloadLine, RiFileUploadLine, + RiMoreLine, } from '@remixicon/react' import AppIcon from '../base/app-icon' import SwitchAppModal from '../app/switch-app-modal' @@ -33,6 +36,7 @@ import ContentDialog from '@/app/components/base/content-dialog' import Button from '@/app/components/base/button' import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView' import Divider from '../base/divider' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../base/portal-to-follow-elem' export type IAppInfoProps = { expand: boolean @@ -194,6 +198,11 @@ const AppInfo = ({ expand }: IAppInfoProps) => { const { isCurrentWorkspaceEditor } = useAppContext() + const [showMore, setShowMore] = useState(false) + const handleTriggerMore = useCallback(() => { + setShowMore(true) + }, [setShowMore]) + if (!appDetail) return null @@ -278,7 +287,8 @@ const AppInfo = ({ expand }: IAppInfoProps) => { setOpen(false) setShowDuplicateModal(true) }}> - {t('app.duplicate')} + + {t('app.duplicate')} - { - (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && ( + + - ) - } + + +
+ { + (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') + &&
{ + setOpen(false) + setShowImportDSLModal(true) + }}> + + {t('workflow.common.importDSL')} +
+ } + { + (appDetail.mode === 'completion' || appDetail.mode === 'chat') + &&
{ + setOpen(false) + setShowSwitchModal(true) + }}> + + {t('app.switch')} +
+ } +
+
+
diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index ca7bb84883..ce7a347fbb 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -57,6 +57,7 @@ const translation = { submit: 'Submit', skip: 'Skip', format: 'Format', + more: 'More', }, errorMsg: { fieldRequired: '{{field}} is required', diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index b85e17a0a0..30b58b5399 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -57,6 +57,7 @@ const translation = { copied: 'コピーしました', in: '中', format: 'フォーマット', + more: 'もっと', }, errorMsg: { fieldRequired: '{{field}}は必要です', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 3db6f84e20..80c8a6215d 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -57,6 +57,7 @@ const translation = { submit: '提交', skip: '跳过', format: '格式化', + more: '更多', }, errorMsg: { fieldRequired: '{{field}} 为必填项', From b30308e85ba4119a1ae6c5c901d059e973e3f19d Mon Sep 17 00:00:00 2001 From: NFish Date: Tue, 6 May 2025 15:28:23 +0800 Subject: [PATCH 3/3] fix: migrating web app access control features --- web/app/components/app-sidebar/app-info.tsx | 26 +-------- web/app/components/app/overview/appCard.tsx | 62 ++++++++++++++++++++- 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 7c41811ebd..16225dd6b3 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -14,14 +14,13 @@ import { } from '@remixicon/react' import AppIcon from '../base/app-icon' import SwitchAppModal from '../app/switch-app-modal' -import AccessControl from '../app/app-access-control' import cn from '@/utils/classnames' import Confirm from '@/app/components/base/confirm' import { useStore as useAppStore } from '@/app/components/app/store' import { ToastContext } from '@/app/components/base/toast' import AppsContext, { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' -import { copyApp, deleteApp, exportAppConfig, fetchAppDetail, updateAppInfo } from '@/service/apps' +import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps' import DuplicateAppModal from '@/app/components/app/duplicate-modal' import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal' import CreateAppModal from '@/app/components/explore/create-app-modal' @@ -55,7 +54,6 @@ const AppInfo = ({ expand }: IAppInfoProps) => { const [showConfirmDelete, setShowConfirmDelete] = useState(false) const [showSwitchModal, setShowSwitchModal] = useState(false) const [showImportDSLModal, setShowImportDSLModal] = useState(false) - const [showAccessControl, setShowAccessControl] = useState(false) const [secretEnvList, setSecretEnvList] = useState([]) const mutateApps = useContextSelector( @@ -183,19 +181,6 @@ const AppInfo = ({ expand }: IAppInfoProps) => { setShowConfirmDelete(false) }, [appDetail, mutateApps, notify, onPlanInfoChanged, replace, setAppDetail, t]) - const handleClickAccessControl = useCallback(() => { - if (!appDetail) - return - setShowAccessControl(true) - setOpen(false) - }, [appDetail]) - const handleAccessControlUpdate = useCallback(() => { - fetchAppDetail({ url: '/apps', id: appDetail!.id }).then((res) => { - setAppDetail(res) - setShowAccessControl(false) - }) - }, [appDetail, setAppDetail]) - const { isCurrentWorkspaceEditor } = useAppContext() const [showMore, setShowMore] = useState(false) @@ -353,10 +338,6 @@ const AppInfo = ({ expand }: IAppInfoProps) => { />
- {/* TODO update style figma */} -
- {t('app.accessControl')} -
) } diff --git a/web/app/components/app/overview/appCard.tsx b/web/app/components/app/overview/appCard.tsx index 7c12f1edee..42ba5ef36f 100644 --- a/web/app/components/app/overview/appCard.tsx +++ b/web/app/components/app/overview/appCard.tsx @@ -1,11 +1,13 @@ 'use client' -import React, { useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { usePathname, useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import { + RiArrowRightSLine, RiBookOpenLine, RiEqualizer2Line, RiExternalLinkLine, + RiLockLine, RiPaintBrushLine, RiWindowLine, } from '@remixicon/react' @@ -17,6 +19,7 @@ import type { ConfigParams } from './settings' import Tooltip from '@/app/components/base/tooltip' import AppBasic from '@/app/components/app-sidebar/basic' import { asyncRunSafe, randomString } from '@/utils' +import { useStore as useAppStore } from '@/app/components/app/store' import { basePath } from '@/utils/var' import Button from '@/app/components/base/button' import Switch from '@/app/components/base/switch' @@ -29,6 +32,11 @@ import type { AppDetailResponse } from '@/models/app' import { useAppContext } from '@/context/app-context' import type { AppSSO } from '@/types/app' import Indicator from '@/app/components/header/indicator' +import { fetchAppDetail } from '@/service/apps' +import { AccessMode } from '@/models/access-control' +import AccessControl from '../app-access-control' +import { useAppWhiteListSubjects } from '@/service/access-control' +import { useGlobalPublicStore } from '@/context/global-public-context' export type IAppCardProps = { className?: string @@ -54,13 +62,17 @@ function AppCard({ const router = useRouter() const pathname = usePathname() const { isCurrentWorkspaceManager, isCurrentWorkspaceEditor } = useAppContext() + const appDetail = useAppStore(state => state.appDetail) + const setAppDetail = useAppStore(state => state.setAppDetail) const [showSettingsModal, setShowSettingsModal] = useState(false) const [showEmbedded, setShowEmbedded] = useState(false) const [showCustomizeModal, setShowCustomizeModal] = useState(false) const [genLoading, setGenLoading] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false) - + const [showAccessControl, setShowAccessControl] = useState(false) const { t } = useTranslation() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) + const { data: appAccessSubjects } = useAppWhiteListSubjects(appDetail?.id, systemFeatures.webapp_auth.enabled && appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS) const OPERATIONS_MAP = useMemo(() => { const operationsMap = { @@ -128,6 +140,31 @@ function AppCard({ } } + const [isAppAccessSet, setIsAppAccessSet] = useState(true) + useEffect(() => { + if (appDetail && appAccessSubjects) { + if (appDetail.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS && appAccessSubjects.groups?.length === 0 && appAccessSubjects.members?.length === 0) + setIsAppAccessSet(false) + else + setIsAppAccessSet(true) + } + else { + setIsAppAccessSet(true) + } + }, [appAccessSubjects, appDetail]) + + const handleClickAccessControl = useCallback(() => { + if (!appDetail) + return + setShowAccessControl(true) + }, [appDetail]) + const handleAccessControlUpdate = useCallback(() => { + fetchAppDetail({ url: '/apps', id: appDetail!.id }).then((res) => { + setAppDetail(res) + setShowAccessControl(false) + }) + }, [appDetail, setAppDetail]) + return (
+ {isApp && systemFeatures.webapp_auth.enabled && appDetail &&
+
{t('app.publishApp.title')}
+
+
+ + {appDetail?.access_mode === AccessMode.ORGANIZATION &&

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

} + {appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS &&

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

} + {appDetail?.access_mode === AccessMode.PUBLIC &&

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

} +
+ {!isAppAccessSet &&

{t('app.publishApp.notSet')}

} +
+ +
+
+
}
{!isApp && } @@ -264,6 +317,11 @@ function AppCard({ api_base_url={appInfo.api_base_url} mode={appInfo.mode} /> + { + showAccessControl && { setShowAccessControl(false) }} /> + } ) : null}