This commit is contained in:
Stephen Zhou 2026-05-07 22:52:19 +08:00
parent b1822a06d2
commit 19a76cb49e
No known key found for this signature in database
10 changed files with 120 additions and 88 deletions

View File

@ -6,6 +6,7 @@ import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitl
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { toast } from '@langgenius/dify-ui/toast'
import { useMutation, useQuery } from '@tanstack/react-query'
import { useAtomValue, useSetAtom } from 'jotai'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { AppTypeIcon } from '@/app/components/app/type-selector'
@ -14,7 +15,7 @@ import Input from '@/app/components/base/input'
import { useRouter } from '@/next/navigation'
import { consoleQuery } from '@/service/client'
import { AppModeEnum } from '@/types/app'
import { useDeploymentsStore } from '../store'
import { closeCreateInstanceModalAtom, createInstanceModalOpenAtom } from '../store'
const MAX_STUDIO_SOURCE_APPS = 100
@ -324,17 +325,17 @@ function CreateInstanceForm({ onClose }: {
}
export function CreateInstanceModal() {
const modal = useDeploymentsStore(state => state.createInstanceModal)
const closeModal = useDeploymentsStore(state => state.closeCreateInstanceModal)
const open = useAtomValue(createInstanceModalOpenAtom)
const closeModal = useSetAtom(closeCreateInstanceModalAtom)
return (
<Dialog
open={modal.open}
open={open}
onOpenChange={next => !next && closeModal()}
>
<DialogContent className="w-[520px] max-w-[90vw]">
<DialogCloseButton />
{modal.open && <CreateInstanceForm onClose={closeModal} />}
{open && <CreateInstanceForm onClose={closeModal} />}
</DialogContent>
</Dialog>
)

View File

@ -3,20 +3,28 @@
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
import { toast } from '@langgenius/dify-ui/toast'
import { skipToken, useMutation, useQuery } from '@tanstack/react-query'
import { useAtomValue, useSetAtom } from 'jotai'
import { useTranslation } from 'react-i18next'
import { consoleQuery } from '@/service/client'
import { DEPLOYMENT_PAGE_SIZE } from '../data'
import { useDeploymentsStore } from '../store'
import {
closeDeployDrawerAtom,
deployDrawerAppInstanceIdAtom,
deployDrawerEnvironmentIdAtom,
deployDrawerOpenAtom,
deployDrawerReleaseIdAtom,
} from '../store'
import { environmentOptionsFromOptionsReply } from '../utils'
import { DeployForm } from './deploy-drawer/form'
export function DeployDrawer() {
const { t } = useTranslation('deployments')
const drawer = useDeploymentsStore(state => state.deployDrawer)
const drawerAppInstanceId = drawer.appInstanceId
const closeDeployDrawer = useDeploymentsStore(state => state.closeDeployDrawer)
const open = useAtomValue(deployDrawerOpenAtom)
const drawerAppInstanceId = useAtomValue(deployDrawerAppInstanceIdAtom)
const drawerEnvironmentId = useAtomValue(deployDrawerEnvironmentIdAtom)
const drawerReleaseId = useAtomValue(deployDrawerReleaseIdAtom)
const closeDeployDrawer = useSetAtom(closeDeployDrawerAtom)
const startDeploy = useMutation(consoleQuery.enterprise.appDeploy.createDeployment.mutationOptions())
const open = drawer.open
const { data: releaseHistory } = useQuery(consoleQuery.enterprise.appDeploy.listReleases.queryOptions({
input: drawerAppInstanceId
? {
@ -36,7 +44,7 @@ export function DeployDrawer() {
const environments = environmentOptionsFromOptionsReply(environmentOptionsReply)
const releases = releaseHistory?.data?.filter(release => release.id) ?? []
const defaultReleaseId = releases[0]?.id
const formKey = `${drawer.appInstanceId ?? 'none'}-${drawer.environmentId ?? 'any'}-${drawer.releaseId ?? 'new'}-${open ? '1' : '0'}`
const formKey = `${drawerAppInstanceId ?? 'none'}-${drawerEnvironmentId ?? 'any'}-${drawerReleaseId ?? 'new'}-${open ? '1' : '0'}`
return (
<Dialog
@ -61,8 +69,8 @@ export function DeployDrawer() {
environments={environments}
releases={releases}
defaultReleaseId={defaultReleaseId}
lockedEnvId={drawer.environmentId}
presetReleaseId={drawer.releaseId}
lockedEnvId={drawerEnvironmentId}
presetReleaseId={drawerReleaseId}
isSubmitting={startDeploy.isPending}
onCancel={closeDeployDrawer}
onSubmit={async ({ environmentId, releaseId, bindings }) => {

View File

@ -9,10 +9,17 @@ import {
AlertDialogTitle,
} from '@langgenius/dify-ui/alert-dialog'
import { skipToken, useMutation, useQuery } from '@tanstack/react-query'
import { useAtomValue, useSetAtom } from 'jotai'
import { useTranslation } from 'react-i18next'
import { consoleQuery } from '@/service/client'
import { DEPLOYMENT_PAGE_SIZE } from '../data'
import { useDeploymentsStore } from '../store'
import {
closeRollbackModalAtom,
rollbackModalAppInstanceIdAtom,
rollbackModalEnvironmentIdAtom,
rollbackModalOpenAtom,
rollbackModalTargetReleaseIdAtom,
} from '../store'
import {
activeRelease,
deployedRows,
@ -37,60 +44,63 @@ function InfoRow({ label, value }: {
export function RollbackModal() {
const { t } = useTranslation('deployments')
const modal = useDeploymentsStore(state => state.rollbackModal)
const closeRollbackModal = useDeploymentsStore(state => state.closeRollbackModal)
const open = useAtomValue(rollbackModalOpenAtom)
const appInstanceId = useAtomValue(rollbackModalAppInstanceIdAtom)
const modalEnvironmentId = useAtomValue(rollbackModalEnvironmentIdAtom)
const targetReleaseId = useAtomValue(rollbackModalTargetReleaseIdAtom)
const closeRollbackModal = useSetAtom(closeRollbackModalAtom)
const rollbackDeployment = useMutation(consoleQuery.enterprise.appDeploy.createDeployment.mutationOptions())
const appInput = modal.appInstanceId
? { params: { appInstanceId: modal.appInstanceId } }
const appInput = appInstanceId
? { params: { appInstanceId } }
: skipToken
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
input: appInput,
enabled: modal.open && Boolean(modal.appInstanceId),
enabled: open && Boolean(appInstanceId),
}))
const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
input: appInput,
enabled: modal.open && Boolean(modal.appInstanceId),
enabled: open && Boolean(appInstanceId),
}))
const { data: environmentOptionsReply } = useQuery(consoleQuery.enterprise.appDeploy.listDeploymentEnvironmentOptions.queryOptions({
enabled: modal.open,
enabled: open,
}))
const { data: releaseHistory } = useQuery(consoleQuery.enterprise.appDeploy.listReleases.queryOptions({
input: modal.appInstanceId
input: appInstanceId
? {
params: { appInstanceId: modal.appInstanceId },
params: { appInstanceId },
query: {
pageNumber: 1,
resultsPerPage: DEPLOYMENT_PAGE_SIZE,
},
}
: skipToken,
enabled: modal.open && Boolean(modal.appInstanceId),
enabled: open && Boolean(appInstanceId),
}))
const environmentOptions = environmentOptionsFromOptionsReply(environmentOptionsReply)
const currentRow = deployedRows(environmentDeployments?.data)
.find(row => environmentId(row.environment) === modal.environmentId)
.find(row => environmentId(row.environment) === modalEnvironmentId)
const targetRelease = [
...(releaseHistory?.data?.filter(release => !!release.id) ?? []),
].find(release => release.id === modal.targetReleaseId)
].find(release => release.id === targetReleaseId)
const currentRelease = activeRelease(currentRow)
const environment = currentRow?.environment
?? environmentOptions.find(env => env.id === modal.environmentId)
?? environmentOptions.find(env => env.id === modalEnvironmentId)
const app = overview?.instance
const appName = app?.name ?? '-'
const sourceAppName = app?.sourceAppName ?? appName
const confirm = () => {
if (!modal.appInstanceId || !modal.environmentId || !modal.targetReleaseId)
if (!appInstanceId || !modalEnvironmentId || !targetReleaseId)
return
closeRollbackModal()
rollbackDeployment.mutate({
params: {
appInstanceId: modal.appInstanceId,
appInstanceId,
},
body: {
environmentId: modal.environmentId,
releaseId: modal.targetReleaseId,
environmentId: modalEnvironmentId,
releaseId: targetReleaseId,
bindings: [],
},
})
@ -98,7 +108,7 @@ export function RollbackModal() {
return (
<AlertDialog
open={modal.open}
open={open}
onOpenChange={next => !next && closeRollbackModal()}
>
<AlertDialogContent className="w-[520px]">

View File

@ -10,10 +10,11 @@ import {
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { useMutation, useQuery } from '@tanstack/react-query'
import { useSetAtom } from 'jotai'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { consoleQuery } from '@/service/client'
import { useDeploymentsStore } from '../store'
import { openDeployDrawerAtom } from '../store'
import {
activeRelease,
deployedRows,
@ -38,7 +39,7 @@ function NewDeploymentMenu({ appInstanceId, availableEnvs }: {
availableEnvs: EnvironmentOption[]
}) {
const { t } = useTranslation('deployments')
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
const openDeployDrawer = useSetAtom(openDeployDrawerAtom)
const [open, setOpen] = useState(false)
return (
@ -100,7 +101,7 @@ function DeploymentRowActions({ appInstanceId, envId, row }: {
}) {
const { t } = useTranslation('deployments')
const [menuOpen, setMenuOpen] = useState(false)
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
const openDeployDrawer = useSetAtom(openDeployDrawerAtom)
const cancelDeployment = useMutation(consoleQuery.enterprise.appDeploy.cancelRuntimeDeployment.mutationOptions())
const undeployDeployment = useMutation(consoleQuery.enterprise.appDeploy.undeployRuntimeInstance.mutationOptions())
const isUndeployed = isUndeployedDeploymentRow(row)

View File

@ -3,13 +3,14 @@ import type { ReactNode } from 'react'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { useQuery } from '@tanstack/react-query'
import { useSetAtom } from 'jotai'
import { useTranslation } from 'react-i18next'
import { getAppModeLabel } from '@/app/components/app-sidebar/app-info/app-mode-labels'
import Link from '@/next/link'
import { consoleQuery } from '@/service/client'
import { StatusBadge } from '../components/status-badge'
import { DEPLOYMENT_PAGE_SIZE } from '../data'
import { useDeploymentsStore } from '../store'
import { openDeployDrawerAtom } from '../store'
import {
releaseLabel,
webappUrl,
@ -107,7 +108,7 @@ export function OverviewTab({ instanceId }: {
const { data: accessConfig } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceAccess.queryOptions({
input,
}))
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
const openDeployDrawer = useSetAtom(openDeployDrawerAtom)
const overviewApp = overview?.instance
const deployments = overview?.deployments?.filter(row => row.environment?.id && row.status?.toLowerCase() !== 'undeployed') ?? []
const releaseRows = releaseHistory?.data?.filter(row => row.id) ?? []

View File

@ -8,10 +8,11 @@ import {
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { useQuery } from '@tanstack/react-query'
import { useSetAtom } from 'jotai'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { consoleQuery } from '@/service/client'
import { useDeploymentsStore } from '../../store'
import { openDeployDrawerAtom } from '../../store'
import {
activeRelease,
deployedRows,
@ -26,7 +27,7 @@ export function DeployReleaseMenu({ appInstanceId, releaseId }: {
releaseId: string
}) {
const { t } = useTranslation('deployments')
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
const openDeployDrawer = useSetAtom(openDeployDrawerAtom)
const [open, setOpen] = useState(false)
const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
input: {

View File

@ -12,13 +12,14 @@ import {
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useSetAtom } from 'jotai'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { AppTypeIcon } from '@/app/components/app/type-selector'
import AppIcon from '@/app/components/base/app-icon'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import Link from '@/next/link'
import { useDeploymentsStore } from '../store'
import { openDeployDrawerAtom } from '../store'
export function InstanceCard({ app }: {
app: AppInstanceCard
@ -194,7 +195,7 @@ function InstanceCardActions({ appId, detailHref }: {
}) {
const { t } = useTranslation('deployments')
const [menuOpen, setMenuOpen] = useState(false)
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
const openDeployDrawer = useSetAtom(openDeployDrawerAtom)
return (
<div className="pointer-events-none absolute right-0 bottom-1 left-0 flex h-[42px] shrink-0 items-center pt-1 pr-[6px] pb-[6px] pl-[14px]">

View File

@ -1,8 +1,9 @@
'use client'
import { cn } from '@langgenius/dify-ui/cn'
import { useSetAtom } from 'jotai'
import { useTranslation } from 'react-i18next'
import { useDeploymentsStore } from '../store'
import { openCreateInstanceModalAtom } from '../store'
type NewInstanceActionProps = {
icon: string
@ -40,7 +41,7 @@ function NewInstanceAction({ icon, label, disabled, onClick }: NewInstanceAction
export function NewInstanceCard() {
const { t } = useTranslation('deployments')
const openCreateInstanceModal = useDeploymentsStore(state => state.openCreateInstanceModal)
const openCreateInstanceModal = useSetAtom(openCreateInstanceModalAtom)
return (
<div className="relative col-span-1 inline-flex h-[160px] flex-col justify-between rounded-xl border-[0.5px] border-components-card-border bg-components-card-bg">

View File

@ -4,12 +4,13 @@ import type { AppInstanceBasicInfo, AppInstanceCard } from '@dify/contracts/ente
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'
import Nav from '@/app/components/header/nav'
import { useParams, useRouter, useSelectedLayoutSegment } from '@/next/navigation'
import { consoleQuery } from '@/service/client'
import { SOURCE_APPS_PAGE_SIZE } from '../data'
import { useDeploymentsStore } from '../store'
import { openCreateInstanceModalAtom } from '../store'
function navItemFromListApp(app: AppInstanceCard): NavItem[] {
if (!app.id || !app.name)
@ -53,7 +54,7 @@ export function DeploymentsNav() {
const params = useParams<{ instanceId?: string }>()
const instanceId = params?.instanceId
const openCreateInstanceModal = useDeploymentsStore(state => state.openCreateInstanceModal)
const openCreateInstanceModal = useSetAtom(openCreateInstanceModalAtom)
const { data: currentInstance } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
input: instanceId
? { params: { appInstanceId: instanceId } }

View File

@ -1,4 +1,4 @@
import { create } from 'zustand'
import { atom } from 'jotai'
type OpenDeployDrawerParams = {
appInstanceId: string
@ -13,48 +13,55 @@ type OpenRollbackParams = {
deploymentId?: string
}
type DeploymentsStore = {
deployDrawer: {
open: boolean
appInstanceId?: string
environmentId?: string
releaseId?: string
}
rollbackModal: {
open: boolean
appInstanceId?: string
environmentId?: string
deploymentId?: string
targetReleaseId?: string
}
createInstanceModal: { open: boolean }
export const deployDrawerOpenAtom = atom(false)
export const deployDrawerAppInstanceIdAtom = atom<string | undefined>(undefined)
export const deployDrawerEnvironmentIdAtom = atom<string | undefined>(undefined)
export const deployDrawerReleaseIdAtom = atom<string | undefined>(undefined)
openDeployDrawer: (params: OpenDeployDrawerParams) => void
closeDeployDrawer: () => void
openRollbackModal: (params: OpenRollbackParams) => void
closeRollbackModal: () => void
openCreateInstanceModal: () => void
closeCreateInstanceModal: () => void
}
export const rollbackModalOpenAtom = atom(false)
export const rollbackModalAppInstanceIdAtom = atom<string | undefined>(undefined)
export const rollbackModalEnvironmentIdAtom = atom<string | undefined>(undefined)
export const rollbackModalDeploymentIdAtom = atom<string | undefined>(undefined)
export const rollbackModalTargetReleaseIdAtom = atom<string | undefined>(undefined)
export const useDeploymentsStore = create<DeploymentsStore>()(set => ({
deployDrawer: { open: false },
rollbackModal: { open: false },
createInstanceModal: { open: false },
export const createInstanceModalOpenAtom = atom(false)
openDeployDrawer: params => set({
deployDrawer: {
open: true,
appInstanceId: params.appInstanceId,
environmentId: params.environmentId,
releaseId: params.releaseId,
},
}),
closeDeployDrawer: () => set({ deployDrawer: { open: false } }),
openRollbackModal: ({ appInstanceId, environmentId, deploymentId, targetReleaseId }) => set({
rollbackModal: { open: true, appInstanceId, environmentId, deploymentId, targetReleaseId },
}),
closeRollbackModal: () => set({ rollbackModal: { open: false } }),
openCreateInstanceModal: () => set({ createInstanceModal: { open: true } }),
closeCreateInstanceModal: () => set({ createInstanceModal: { open: false } }),
}))
export const openDeployDrawerAtom = atom(null, (_get, set, params: OpenDeployDrawerParams) => {
set(deployDrawerAppInstanceIdAtom, params.appInstanceId)
set(deployDrawerEnvironmentIdAtom, params.environmentId)
set(deployDrawerReleaseIdAtom, params.releaseId)
set(deployDrawerOpenAtom, true)
})
export const closeDeployDrawerAtom = atom(null, (_get, set) => {
set(deployDrawerOpenAtom, false)
set(deployDrawerAppInstanceIdAtom, undefined)
set(deployDrawerEnvironmentIdAtom, undefined)
set(deployDrawerReleaseIdAtom, undefined)
})
export const openRollbackModalAtom = atom(null, (_get, set, {
appInstanceId,
environmentId,
deploymentId,
targetReleaseId,
}: OpenRollbackParams) => {
set(rollbackModalAppInstanceIdAtom, appInstanceId)
set(rollbackModalEnvironmentIdAtom, environmentId)
set(rollbackModalDeploymentIdAtom, deploymentId)
set(rollbackModalTargetReleaseIdAtom, targetReleaseId)
set(rollbackModalOpenAtom, true)
})
export const closeRollbackModalAtom = atom(null, (_get, set) => {
set(rollbackModalOpenAtom, false)
set(rollbackModalAppInstanceIdAtom, undefined)
set(rollbackModalEnvironmentIdAtom, undefined)
set(rollbackModalDeploymentIdAtom, undefined)
set(rollbackModalTargetReleaseIdAtom, undefined)
})
export const openCreateInstanceModalAtom = atom(null, (_get, set) => {
set(createInstanceModalOpenAtom, true)
})
export const closeCreateInstanceModalAtom = atom(null, (_get, set) => {
set(createInstanceModalOpenAtom, false)
})