diff --git a/web/features/deployments/components/create-instance-modal.tsx b/web/features/deployments/components/create-instance-modal.tsx index 326f61fdf5..5ab55a74d8 100644 --- a/web/features/deployments/components/create-instance-modal.tsx +++ b/web/features/deployments/components/create-instance-modal.tsx @@ -209,11 +209,10 @@ function AppPicker({ apps, isLoading, value, onChange }: AppPickerProps) { ) } -function CreateInstanceForm({ onClose }: { - onClose: () => void -}) { +function CreateInstanceForm() { const { t } = useTranslation('deployments') const router = useRouter() + const closeModal = useSetAtom(closeCreateInstanceModalAtom) const createInstance = useMutation(consoleQuery.enterprise.appDeploy.createAppInstance.mutationOptions()) const { data: appList, isLoading } = useQuery(consoleQuery.apps.list.queryOptions({ input: { @@ -251,7 +250,7 @@ function CreateInstanceForm({ onClose }: { }) if (!result.appInstanceId) throw new Error('Create app instance did not return an appInstanceId.') - onClose() + closeModal() router.push(`/deployments/${result.appInstanceId}/overview`) } catch { @@ -313,7 +312,7 @@ function CreateInstanceForm({ onClose }: {
-
- {createdToken && ( + {visibleCreatedApiToken && (
@@ -78,7 +89,7 @@ export function DeveloperApiSection({
)} diff --git a/web/features/deployments/detail/access-tab/permissions-section.tsx b/web/features/deployments/detail/access-tab/permissions-section.tsx index 2e4abce92e..5b937f27dd 100644 --- a/web/features/deployments/detail/access-tab/permissions-section.tsx +++ b/web/features/deployments/detail/access-tab/permissions-section.tsx @@ -1,6 +1,6 @@ 'use client' -import type { AccessSubject, ConsoleEnvironment, EnvironmentAccessRow } from '@dify/contracts/enterprise/types.gen' +import type { ConsoleEnvironment, EnvironmentAccessRow } from '@dify/contracts/enterprise/types.gen' import { useTranslation } from 'react-i18next' import { Section } from './common' import { EnvironmentPermissionRow } from './permissions' @@ -9,18 +9,12 @@ type AccessPermissionsSectionProps = { appId: string environments: ConsoleEnvironment[] policies: EnvironmentAccessRow[] - onSetPolicy: ( - environmentId: string, - accessMode: string, - subjects: AccessSubject[], - ) => Promise } export function AccessPermissionsSection({ appId, environments, policies, - onSetPolicy, }: AccessPermissionsSectionProps) { const { t } = useTranslation('deployments') @@ -45,7 +39,6 @@ export function AccessPermissionsSection({ appId={appId} environment={env} summaryPolicy={policy} - onSetPolicy={onSetPolicy} /> ) })} diff --git a/web/features/deployments/detail/access-tab/permissions.tsx b/web/features/deployments/detail/access-tab/permissions.tsx index d5b58399e8..6f9db826dc 100644 --- a/web/features/deployments/detail/access-tab/permissions.tsx +++ b/web/features/deployments/detail/access-tab/permissions.tsx @@ -17,7 +17,7 @@ import { } from '@langgenius/dify-ui/dropdown-menu' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import { toast } from '@langgenius/dify-ui/toast' -import { skipToken, useQuery } from '@tanstack/react-query' +import { skipToken, useMutation, useQuery } from '@tanstack/react-query' import { useDebounce } from 'ahooks' import { useState } from 'react' import { useTranslation } from 'react-i18next' @@ -295,21 +295,16 @@ type EnvironmentPermissionRowProps = { appId: string environment: ConsoleEnvironment summaryPolicy?: EnvironmentAccessRow - onSetPolicy: ( - environmentId: string, - accessMode: string, - subjects: AccessSubject[], - ) => Promise } export function EnvironmentPermissionRow({ appId, environment, summaryPolicy, - onSetPolicy, }: EnvironmentPermissionRowProps) { const { t } = useTranslation('deployments') const environmentId = environment.id + const setEnvironmentAccessPolicy = useMutation(consoleQuery.enterprise.appDeploy.updateEnvironmentAccessPolicy.mutationOptions()) const policyQuery = useQuery(consoleQuery.enterprise.appDeploy.getEnvironmentAccessPolicy.queryOptions({ input: environmentId ? { @@ -348,11 +343,16 @@ export function EnvironmentPermissionRow({ setIsSaving(true) try { - await onSetPolicy( - environmentId, - permissionKeyToAccessMode(nextKind), - nextKind === 'specific' ? policySubjects(nextSubjects) : [], - ) + await setEnvironmentAccessPolicy.mutateAsync({ + params: { + appInstanceId: appId, + environmentId, + }, + body: { + accessMode: permissionKeyToAccessMode(nextKind), + subjects: nextKind === 'specific' ? policySubjects(nextSubjects) : [], + }, + }) setDraft({}) } catch { diff --git a/web/features/deployments/detail/deployment-sidebar.tsx b/web/features/deployments/detail/deployment-sidebar.tsx index b6bde9a0a8..6bd1f379bc 100644 --- a/web/features/deployments/detail/deployment-sidebar.tsx +++ b/web/features/deployments/detail/deployment-sidebar.tsx @@ -8,6 +8,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { useHover, useKeyPress } from 'ahooks' import { useRef } from 'react' import { useTranslation } from 'react-i18next' +import { getAppModeLabel } from '@/app/components/app-sidebar/app-info/app-mode-labels' import NavLink from '@/app/components/app-sidebar/nav-link' import ToggleButton from '@/app/components/app-sidebar/toggle-button' import AppIcon from '@/app/components/base/app-icon' @@ -15,6 +16,7 @@ import Divider from '@/app/components/base/divider' import { getKeyboardKeyCodeBySystem } from '@/app/components/workflow/utils' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { useLocalStorage } from '@/hooks/use-local-storage' +import { toAppMode } from '../utils' type TabDef = { key: InstanceDetailTabKey @@ -98,27 +100,23 @@ function useDeploymentSidebarMode(isMobile: boolean) { } type DeploymentSidebarProps = { - instanceId: string - instanceName: string - instanceDescription?: string - appModeLabel: string - app?: AppInstanceBasicInfo + app: AppInstanceBasicInfo } export function DeploymentSidebar({ - instanceId, - instanceName, - instanceDescription, - appModeLabel, app, }: DeploymentSidebarProps) { const { t } = useTranslation('deployments') + const { t: tCommon } = useTranslation() const sidebarRef = useRef(null) const isHoveringSidebar = useHover(sidebarRef) const media = useBreakpoints() const isMobile = media === MediaType.mobile const { sidebarMode, toggleSidebarMode } = useDeploymentSidebarMode(isMobile) const expand = sidebarMode === 'expand' + const instanceId = app.id ?? '' + const instanceName = app.name ?? instanceId + const appModeLabel = getAppModeLabel(toAppMode(app.mode), tCommon) useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.b`, (e) => { if (isShortcutFromInputArea(e.target)) @@ -139,24 +137,12 @@ export function DeploymentSidebar({
- {app - ? ( - - ) - : ( -
- -
- )} +
{expand && (
@@ -168,12 +154,12 @@ export function DeploymentSidebar({
{appModeLabel}
- {instanceDescription && ( + {app.description && (
- {instanceDescription} + {app.description}
)}
diff --git a/web/features/deployments/detail/index.tsx b/web/features/deployments/detail/index.tsx index 720c23b1e5..dbb8deb2b8 100644 --- a/web/features/deployments/detail/index.tsx +++ b/web/features/deployments/detail/index.tsx @@ -5,7 +5,6 @@ import type { InstanceDetailTabKey } from './tabs' import { Button } from '@langgenius/dify-ui/button' import { useQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' -import { getAppModeLabel } from '@/app/components/app-sidebar/app-info/app-mode-labels' import useDocumentTitle from '@/hooks/use-document-title' import Link from '@/next/link' import { useSelectedLayoutSegment } from '@/next/navigation' @@ -20,7 +19,6 @@ export function InstanceDetail({ instanceId, children }: { children: ReactNode }) { const { t } = useTranslation('deployments') - const { t: tCommon } = useTranslation() const selectedSegment = useSelectedLayoutSegment() const selectedTab = selectedSegment ?? undefined const activeTab: InstanceDetailTabKey = isInstanceDetailTabKey(selectedTab) ? selectedTab : 'overview' @@ -54,17 +52,11 @@ export function InstanceDetail({ instanceId, children }: {
) } - const appName = app.name ?? appId - const appModeLabel = getAppModeLabel(app.mode ?? 'workflow', tCommon) return ( <>
diff --git a/web/features/deployments/detail/overview-tab.tsx b/web/features/deployments/detail/overview-tab.tsx index 4b41dd5b2f..47e6b30d20 100644 --- a/web/features/deployments/detail/overview-tab.tsx +++ b/web/features/deployments/detail/overview-tab.tsx @@ -13,6 +13,7 @@ import { DEPLOYMENT_PAGE_SIZE } from '../data' import { openDeployDrawerAtom } from '../store' import { releaseLabel, + toAppMode, webappUrl, } from '../utils' @@ -119,7 +120,7 @@ export function OverviewTab({ instanceId }: { const appId = overviewApp.id const appName = overviewApp.name ?? appId - const appModeLabel = getAppModeLabel(overviewApp.mode ?? 'workflow', tCommon) + const appModeLabel = getAppModeLabel(toAppMode(overviewApp.mode), tCommon) const webappAccessUrl = webappUrl(overview?.access?.webappUrl) const cliUrl = overview?.access?.cliUrl const apiUrl = overview?.access?.apiUrl ?? accessConfig?.developerApi?.apiUrl diff --git a/web/features/deployments/detail/settings-tab.tsx b/web/features/deployments/detail/settings-tab.tsx index d039df3931..52ca4926d8 100644 --- a/web/features/deployments/detail/settings-tab.tsx +++ b/web/features/deployments/detail/settings-tab.tsx @@ -21,19 +21,16 @@ import { deployedRows } from '../utils' type SettingsFormProps = { app: AppInstanceBasicInfo settings?: GetAppInstanceSettingsReply - onSave: (patch: Pick) => Promise } type DeleteInstanceControlProps = { - appId: string - appName: string + app: AppInstanceBasicInfo settings?: GetAppInstanceSettingsReply hasDeployments: boolean } function DeleteInstanceControl({ - appId, - appName, + app, settings, hasDeployments, }: DeleteInstanceControlProps) { @@ -42,9 +39,14 @@ function DeleteInstanceControl({ const deleteInstance = useMutation(consoleQuery.enterprise.appDeploy.deleteAppInstance.mutationOptions()) const [isDeleting, setIsDeleting] = useState(false) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) + const appId = app.id + const appName = app.name ?? appId ?? '' const canDelete = !hasDeployments && Boolean(settings) && settings?.deleteGuard?.canDelete !== false const handleDelete = () => { + if (!appId) + return + void (async () => { setIsDeleting(true) try { @@ -114,8 +116,9 @@ function DeleteInstanceControl({ ) } -function SettingsForm({ app, settings, onSave }: SettingsFormProps) { +function SettingsForm({ app, settings }: SettingsFormProps) { const { t } = useTranslation('deployments') + const updateInstance = useMutation(consoleQuery.enterprise.appDeploy.updateAppInstance.mutationOptions()) const appName = app.name ?? app.id ?? '' const [name, setName] = useState(settings?.name ?? appName) const [description, setDescription] = useState(settings?.description ?? app.description ?? '') @@ -125,14 +128,20 @@ function SettingsForm({ app, settings, onSave }: SettingsFormProps) { const canSave = Boolean(name.trim() && (name !== initialName || description !== initialDescription) && !isSaving) const handleSave = () => { - if (!canSave) + const appId = app.id + if (!canSave || !appId) return void (async () => { setIsSaving(true) try { - await onSave({ - name: name.trim(), - description: description.trim() || undefined, + await updateInstance.mutateAsync({ + params: { + appInstanceId: appId, + }, + body: { + name: name.trim(), + description: description.trim() || undefined, + }, }) toast.success(t('settings.updated')) } @@ -194,7 +203,6 @@ function SettingsForm({ app, settings, onSave }: SettingsFormProps) { export function SettingsTab({ instanceId }: { instanceId: string }) { - const updateInstance = useMutation(consoleQuery.enterprise.appDeploy.updateAppInstance.mutationOptions()) const appInput = { params: { appInstanceId: instanceId } } const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({ input: appInput, @@ -220,18 +228,9 @@ export function SettingsTab({ instanceId }: { key={formKey} app={app} settings={settingsQuery.data} - onSave={async (patch) => { - await updateInstance.mutateAsync({ - params: { - appInstanceId: instanceId, - }, - body: patch, - }) - }} /> diff --git a/web/features/deployments/list/instance-card.tsx b/web/features/deployments/list/instance-card.tsx index 97ee9deef4..0baac1ade5 100644 --- a/web/features/deployments/list/instance-card.tsx +++ b/web/features/deployments/list/instance-card.tsx @@ -1,7 +1,6 @@ 'use client' import type { AppInstanceCard } from '@dify/contracts/enterprise/types.gen' -import type { AppModeEnum } from '@/types/app' import { cn } from '@langgenius/dify-ui/cn' import { DropdownMenu, @@ -20,6 +19,7 @@ import AppIcon from '@/app/components/base/app-icon' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import Link from '@/next/link' import { openDeployDrawerAtom } from '../store' +import { toAppMode } from '../utils' export function InstanceCard({ app }: { app: AppInstanceCard @@ -32,7 +32,7 @@ export function InstanceCard({ app }: { const appId = app.id const appName = app.name ?? appId - const appMode = app.mode ?? 'workflow' + const appMode = toAppMode(app.mode) const detailHref = `/deployments/${appId}/overview` const statusCount = (status: string) => @@ -128,7 +128,7 @@ export function InstanceCard({ app }: { background={app.iconBackground} /> diff --git a/web/features/deployments/nav/index.tsx b/web/features/deployments/nav/index.tsx index 07977cddd3..18165e58ea 100644 --- a/web/features/deployments/nav/index.tsx +++ b/web/features/deployments/nav/index.tsx @@ -2,7 +2,6 @@ import type { AppInstanceBasicInfo, AppInstanceCard } from '@dify/contracts/enterprise/types.gen' import type { NavItem } from '@/app/components/header/nav/nav-selector' -import type { AppModeEnum } from '@/types/app' import { skipToken, useQuery } from '@tanstack/react-query' import { useSetAtom } from 'jotai' import { useTranslation } from 'react-i18next' @@ -11,6 +10,7 @@ import { useParams, useRouter, useSelectedLayoutSegment } from '@/next/navigatio import { consoleQuery } from '@/service/client' import { SOURCE_APPS_PAGE_SIZE } from '../data' import { openCreateInstanceModalAtom } from '../store' +import { toAppMode } from '../utils' function navItemFromListApp(app: AppInstanceCard): NavItem[] { if (!app.id || !app.name) @@ -24,7 +24,7 @@ function navItemFromListApp(app: AppInstanceCard): NavItem[] { icon: app.icon ?? '', icon_background: app.iconBackground ?? null, icon_url: null, - mode: app.mode as AppModeEnum | undefined, + mode: toAppMode(app.mode), }] } @@ -42,7 +42,7 @@ function navItemFromOverview(instance?: AppInstanceBasicInfo): NavItem | undefin icon: instance.icon ?? '', icon_background: instance.iconBackground ?? null, icon_url: null, - mode: instance.mode as AppModeEnum | undefined, + mode: toAppMode(instance.mode), } } diff --git a/web/features/deployments/store.ts b/web/features/deployments/store.ts index faf8e63a1e..59fe2e41d3 100644 --- a/web/features/deployments/store.ts +++ b/web/features/deployments/store.ts @@ -13,6 +13,11 @@ type OpenRollbackParams = { deploymentId?: string } +type CreatedDeveloperApiToken = { + appId: string + token: string +} + export const deployDrawerOpenAtom = atom(false) export const deployDrawerAppInstanceIdAtom = atom(undefined) export const deployDrawerEnvironmentIdAtom = atom(undefined) @@ -25,6 +30,7 @@ export const rollbackModalDeploymentIdAtom = atom(undefined) export const rollbackModalTargetReleaseIdAtom = atom(undefined) export const createInstanceModalOpenAtom = atom(false) +export const createdDeveloperApiTokenAtom = atom(undefined) export const openDeployDrawerAtom = atom(null, (_get, set, params: OpenDeployDrawerParams) => { set(deployDrawerAppInstanceIdAtom, params.appInstanceId) diff --git a/web/features/deployments/utils.ts b/web/features/deployments/utils.ts index c492bdadf5..67b8d170ca 100644 --- a/web/features/deployments/utils.ts +++ b/web/features/deployments/utils.ts @@ -11,9 +11,16 @@ import type { EnvironmentOption, } from './types' import { PUBLIC_API_PREFIX } from '@/config' +import { AppModeEnum } from '@/types/app' export type DeploymentUiStatus = 'ready' | 'deploying' | 'deploy_failed' +const appModeValues = new Set(Object.values(AppModeEnum)) + +export function toAppMode(mode?: string): AppModeEnum { + return appModeValues.has(mode ?? '') ? (mode as AppModeEnum) : AppModeEnum.WORKFLOW +} + export function formatDate(value?: string) { if (!value) return '—'