mirror of
https://github.com/langgenius/dify.git
synced 2026-05-10 05:56:31 +08:00
tweaks
This commit is contained in:
parent
79591ca7bd
commit
f7014fd156
@ -3,19 +3,17 @@
|
||||
import type { FC } from 'react'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { deploymentAppDataQueryOptions } from '../data'
|
||||
import { useSourceApps } from '../hooks/use-source-apps'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import { useDeploymentAppData, useDeploymentsStore } from '../store'
|
||||
import { DeployForm } from './deploy-drawer/form'
|
||||
|
||||
const DeployDrawer: FC = () => {
|
||||
const { t } = useTranslation('deployments')
|
||||
const drawer = useDeploymentsStore(state => state.deployDrawer)
|
||||
const drawerAppId = drawer.appId
|
||||
const storedAppData = useDeploymentsStore(state => drawerAppId ? state.appData[drawerAppId] : undefined)
|
||||
const applyAppData = useDeploymentsStore(state => state.applyAppData)
|
||||
const storedAppData = useDeploymentAppData(drawerAppId)
|
||||
const closeDeployDrawer = useDeploymentsStore(state => state.closeDeployDrawer)
|
||||
const startDeploy = useDeploymentsStore(state => state.startDeploy)
|
||||
const open = drawer.open
|
||||
@ -23,12 +21,9 @@ const DeployDrawer: FC = () => {
|
||||
|
||||
const appDataQuery = useQuery({
|
||||
...deploymentAppDataQueryOptions(drawerAppId ?? ''),
|
||||
queryFn: () => useDeploymentsStore.getState().fetchAppData(drawerAppId!),
|
||||
enabled: open && Boolean(drawerAppId) && !storedAppData,
|
||||
})
|
||||
useEffect(() => {
|
||||
if (appDataQuery.data)
|
||||
applyAppData(appDataQuery.data)
|
||||
}, [appDataQuery.data, applyAppData])
|
||||
|
||||
const appData = storedAppData ?? (appDataQuery.data?.appId === drawerAppId ? appDataQuery.data : undefined)
|
||||
const environments = environmentOptions
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSourceApps } from '../hooks/use-source-apps'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import { useDeploymentAppData, useDeploymentInstance, useDeploymentsStore } from '../store'
|
||||
import {
|
||||
activeRelease,
|
||||
deployedRows,
|
||||
@ -34,7 +34,8 @@ const InfoRow: FC<{ label: string, value: string }> = ({ label, value }) => {
|
||||
const RollbackModal: FC = () => {
|
||||
const { t } = useTranslation('deployments')
|
||||
const modal = useDeploymentsStore(state => state.rollbackModal)
|
||||
const appData = useDeploymentsStore(state => modal.appId ? state.appData[modal.appId] : undefined)
|
||||
const appData = useDeploymentAppData(modal.appId)
|
||||
const storedApp = useDeploymentInstance(modal.appId)
|
||||
const closeRollbackModal = useDeploymentsStore(state => state.closeRollbackModal)
|
||||
const rollbackDeployment = useDeploymentsStore(state => state.rollbackDeployment)
|
||||
const { appMap, environmentOptions } = useSourceApps()
|
||||
@ -47,7 +48,7 @@ const RollbackModal: FC = () => {
|
||||
const currentRelease = activeRelease(currentRow)
|
||||
const environment = currentRow?.environment
|
||||
?? environmentOptions.find(env => env.id === modal.environmentId)
|
||||
const app = modal.appId ? appMap.get(modal.appId) : undefined
|
||||
const app = storedApp ?? (modal.appId ? appMap.get(modal.appId) : undefined)
|
||||
|
||||
const confirm = () => {
|
||||
if (!modal.appId || !modal.environmentId || !modal.targetReleaseId)
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import type { AppInfo, AppMode } from './types'
|
||||
import type {
|
||||
AccessSubject,
|
||||
AppDeploymentSummary,
|
||||
AppInstanceOverview,
|
||||
ConsoleReleaseSummary,
|
||||
CreateAppInstanceReply,
|
||||
GetAccessConfigReply,
|
||||
GetDeploymentOverviewReply,
|
||||
ListAppDeploymentsReply,
|
||||
ListEnvironmentDeploymentsReply,
|
||||
ListReleaseHistoryReply,
|
||||
} from '@/contract/console/deployments'
|
||||
@ -41,8 +45,54 @@ export type UpdateInstanceParams = {
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type ListAppDeploymentsQuery = {
|
||||
environmentId?: string
|
||||
notDeployed?: boolean
|
||||
query?: string
|
||||
pageNumber?: number
|
||||
resultsPerPage?: number
|
||||
}
|
||||
|
||||
export function toAppInfoFromSummary(summary: AppDeploymentSummary): AppInfo | undefined {
|
||||
if (!summary.id || !summary.name)
|
||||
return undefined
|
||||
|
||||
return {
|
||||
id: summary.id,
|
||||
name: summary.name,
|
||||
mode: (summary.mode || 'workflow') as AppMode,
|
||||
iconType: 'emoji',
|
||||
icon: summary.icon,
|
||||
description: summary.description ?? undefined,
|
||||
sourceAppId: summary.sourceAppId,
|
||||
sourceAppName: summary.sourceAppName,
|
||||
}
|
||||
}
|
||||
|
||||
export function toAppInfoFromOverview(instance?: AppInstanceOverview): AppInfo | undefined {
|
||||
if (!instance?.id)
|
||||
return undefined
|
||||
|
||||
return {
|
||||
id: instance.id,
|
||||
name: instance.name ?? instance.id,
|
||||
mode: (instance.mode || 'workflow') as AppMode,
|
||||
iconType: 'emoji',
|
||||
icon: instance.icon,
|
||||
description: instance.description ?? undefined,
|
||||
sourceAppId: instance.sourceAppId,
|
||||
sourceAppName: instance.sourceAppName,
|
||||
}
|
||||
}
|
||||
|
||||
export const deploymentAppDataQueryKey = (appId: string) => ['console', 'deployments', 'app-data', appId] as const
|
||||
|
||||
export const listAppDeployments = async (query: ListAppDeploymentsQuery): Promise<ListAppDeploymentsReply> => {
|
||||
return consoleClient.deployments.list({
|
||||
query,
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchDeploymentAppData = async (appId: string): Promise<DeploymentAppData> => {
|
||||
const input = { params: { appInstanceId: appId } }
|
||||
const [
|
||||
@ -118,7 +168,7 @@ export const refreshDeploymentLists = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
export const waitForAppInstanceInDeploymentList = async (appInstanceId: string) => {
|
||||
export const waitForAppInstanceInDeploymentList = async (appInstanceId: string): Promise<ListAppDeploymentsReply | undefined> => {
|
||||
let lastError: unknown
|
||||
|
||||
for (const delay of DEPLOYMENT_READINESS_RETRY_DELAYS) {
|
||||
@ -126,19 +176,12 @@ export const waitForAppInstanceInDeploymentList = async (appInstanceId: string)
|
||||
await wait(delay)
|
||||
|
||||
try {
|
||||
const response = await getQueryClient().fetchQuery({
|
||||
...consoleQuery.deployments.list.queryOptions({
|
||||
input: {
|
||||
query: {
|
||||
pageNumber: 1,
|
||||
resultsPerPage: DEPLOYMENT_PAGE_SIZE,
|
||||
},
|
||||
},
|
||||
}),
|
||||
staleTime: 0,
|
||||
const response = await listAppDeployments({
|
||||
pageNumber: 1,
|
||||
resultsPerPage: DEPLOYMENT_PAGE_SIZE,
|
||||
})
|
||||
if (response.data?.some(app => app.id === appInstanceId))
|
||||
return
|
||||
return response
|
||||
}
|
||||
catch (error) {
|
||||
lastError = error
|
||||
@ -149,6 +192,8 @@ export const waitForAppInstanceInDeploymentList = async (appInstanceId: string)
|
||||
|
||||
if (lastError)
|
||||
throw lastError
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const createRelease = async (appId: string, releaseNote?: string): Promise<ConsoleReleaseSummary> => {
|
||||
|
||||
@ -5,7 +5,7 @@ import type {
|
||||
ConsoleEnvironmentSummary,
|
||||
} from '@/contract/console/deployments'
|
||||
import { useMemo } from 'react'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import { useDeploymentAppData, useDeploymentsStore } from '../store'
|
||||
import {
|
||||
deployedRows,
|
||||
} from '../utils'
|
||||
@ -27,7 +27,7 @@ type AccessTabProps = {
|
||||
}
|
||||
|
||||
const AccessTab: FC<AccessTabProps> = ({ instanceId: appId }) => {
|
||||
const appData = useDeploymentsStore(state => state.appData[appId])
|
||||
const appData = useDeploymentAppData(appId)
|
||||
const createdApiToken = useDeploymentsStore(state => state.createdApiToken)
|
||||
const clearCreatedApiToken = useDeploymentsStore(state => state.clearCreatedApiToken)
|
||||
const generateApiKey = useDeploymentsStore(state => state.generateApiKey)
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSourceApps } from '../hooks/use-source-apps'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import { useDeploymentAppData, useDeploymentsStore } from '../store'
|
||||
import {
|
||||
activeRelease,
|
||||
deployedRows,
|
||||
@ -36,7 +36,7 @@ type DeployTabProps = {
|
||||
|
||||
const DeployTab: FC<DeployTabProps> = ({ instanceId: appId }) => {
|
||||
const { t } = useTranslation('deployments')
|
||||
const appData = useDeploymentsStore(state => state.appData[appId])
|
||||
const appData = useDeploymentAppData(appId)
|
||||
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
|
||||
const undeployDeployment = useDeploymentsStore(state => state.undeployDeployment)
|
||||
const { environmentOptions } = useSourceApps()
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import type { AppInfo, AppMode } from '../types'
|
||||
import type { AppInfo } from '../types'
|
||||
import type { InstanceDetailTabKey } from './tabs'
|
||||
import type { AppInstanceOverview } from '@/contract/console/deployments'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -14,27 +13,11 @@ import DeployDrawer from '../components/deploy-drawer'
|
||||
import RollbackModal from '../components/rollback-modal'
|
||||
import { useDeploymentData } from '../hooks/use-deployment-data'
|
||||
import { useSourceApps } from '../hooks/use-source-apps'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import { useDeploymentAppData, useDeploymentInstance } from '../store'
|
||||
import { deployedRows, deploymentStatus } from '../utils'
|
||||
import { DeploymentSidebar } from './deployment-sidebar'
|
||||
import { isInstanceDetailTabKey } from './tabs'
|
||||
|
||||
function toAppInfoFromOverview(instance?: AppInstanceOverview): AppInfo | undefined {
|
||||
if (!instance?.id)
|
||||
return undefined
|
||||
|
||||
return {
|
||||
id: instance.id,
|
||||
name: instance.name ?? instance.id,
|
||||
mode: (instance.mode || 'workflow') as AppMode,
|
||||
iconType: 'emoji',
|
||||
icon: instance.icon,
|
||||
description: instance.description ?? undefined,
|
||||
sourceAppId: instance.sourceAppId,
|
||||
sourceAppName: instance.sourceAppName,
|
||||
}
|
||||
}
|
||||
|
||||
type InstanceDetailProps = {
|
||||
instanceId: string
|
||||
children: ReactNode
|
||||
@ -47,19 +30,14 @@ const InstanceDetail: FC<InstanceDetailProps> = ({ instanceId, children }) => {
|
||||
const selectedSegment = useSelectedLayoutSegment()
|
||||
const selectedTab = selectedSegment ?? undefined
|
||||
const activeTab: InstanceDetailTabKey = isInstanceDetailTabKey(selectedTab) ? selectedTab : 'overview'
|
||||
const sourceApps = useDeploymentsStore(state => state.sourceApps)
|
||||
const appData = useDeploymentsStore(state => state.appData)
|
||||
const storedApp = useDeploymentInstance(instanceId)
|
||||
const appData = useDeploymentAppData(instanceId)
|
||||
const { appMap, isLoading: isLoadingApps } = useSourceApps()
|
||||
useDocumentTitle(t('documentTitle.detail'))
|
||||
|
||||
const appDataForInstance = appData[instanceId]
|
||||
const appFromData = useMemo(
|
||||
() => toAppInfoFromOverview(appDataForInstance?.overview.instance),
|
||||
[appDataForInstance?.overview.instance],
|
||||
)
|
||||
const app = useMemo(
|
||||
() => sourceApps.find(item => item.id === instanceId) ?? appMap.get(instanceId) ?? appFromData,
|
||||
[sourceApps, instanceId, appMap, appFromData],
|
||||
() => storedApp ?? appMap.get(instanceId),
|
||||
[storedApp, instanceId, appMap],
|
||||
)
|
||||
const detailApps = useMemo<AppInfo[]>(() => [
|
||||
app ?? {
|
||||
@ -70,8 +48,8 @@ const InstanceDetail: FC<InstanceDetailProps> = ({ instanceId, children }) => {
|
||||
], [app, instanceId])
|
||||
const detailQuery = useDeploymentData(detailApps, { enabled: Boolean(instanceId) })
|
||||
const appDeployments = useMemo(
|
||||
() => deployedRows(appData[instanceId]?.environmentDeployments.data),
|
||||
[appData, instanceId],
|
||||
() => deployedRows(appData?.environmentDeployments.data),
|
||||
[appData?.environmentDeployments.data],
|
||||
)
|
||||
|
||||
if (!app && (isLoadingApps || detailQuery.isLoading || detailQuery.isFetching)) {
|
||||
|
||||
@ -9,7 +9,7 @@ import { getAppModeLabel } from '@/app/components/app-sidebar/app-info/app-mode-
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { StatusBadge } from '../components/status-badge'
|
||||
import { useSourceApps } from '../hooks/use-source-apps'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import { useDeploymentAppData, useDeploymentInstance, useDeploymentsStore } from '../store'
|
||||
import { releaseLabel, webappUrl } from '../utils'
|
||||
|
||||
type OverviewTabProps = {
|
||||
@ -91,10 +91,11 @@ const OverviewTab: FC<OverviewTabProps> = ({ instanceId }) => {
|
||||
const { t } = useTranslation('deployments')
|
||||
const { t: tCommon } = useTranslation()
|
||||
const router = useRouter()
|
||||
const appData = useDeploymentsStore(state => state.appData[instanceId])
|
||||
const appData = useDeploymentAppData(instanceId)
|
||||
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
|
||||
const storedApp = useDeploymentInstance(instanceId)
|
||||
const { appMap } = useSourceApps()
|
||||
const app = appMap.get(instanceId)
|
||||
const app = storedApp ?? appMap.get(instanceId)
|
||||
const overview = appData?.overview
|
||||
const overviewApp = overview?.instance
|
||||
const deployments = useMemo(
|
||||
|
||||
@ -20,7 +20,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { useSourceApps } from '../hooks/use-source-apps'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import { useDeploymentAppData, useDeploymentInstance, useDeploymentsStore } from '../store'
|
||||
import { deployedRows } from '../utils'
|
||||
|
||||
type SettingsTabProps = {
|
||||
@ -177,12 +177,12 @@ const SettingsForm: FC<SettingsFormProps> = ({ app, settings, hasDeployments, on
|
||||
|
||||
const SettingsTab: FC<SettingsTabProps> = ({ instanceId }) => {
|
||||
const router = useRouter()
|
||||
const sourceApps = useDeploymentsStore(state => state.sourceApps)
|
||||
const appData = useDeploymentsStore(state => state.appData[instanceId])
|
||||
const storedApp = useDeploymentInstance(instanceId)
|
||||
const appData = useDeploymentAppData(instanceId)
|
||||
const updateInstance = useDeploymentsStore(state => state.updateInstance)
|
||||
const deleteInstance = useDeploymentsStore(state => state.deleteInstance)
|
||||
const { appMap } = useSourceApps()
|
||||
const app = sourceApps.find(item => item.id === instanceId) ?? appMap.get(instanceId)
|
||||
const app = storedApp ?? appMap.get(instanceId)
|
||||
const settingsQuery = useQuery(consoleQuery.deployments.settings.queryOptions({
|
||||
input: {
|
||||
params: {
|
||||
|
||||
@ -4,7 +4,7 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import { useDeploymentAppData } from '../store'
|
||||
import {
|
||||
deployedRows,
|
||||
formatDate,
|
||||
@ -23,7 +23,7 @@ type VersionsTabProps = {
|
||||
|
||||
const VersionsTab: FC<VersionsTabProps> = ({ instanceId: appId }) => {
|
||||
const { t } = useTranslation('deployments')
|
||||
const appData = useDeploymentsStore(state => state.appData[appId])
|
||||
const appData = useDeploymentAppData(appId)
|
||||
const releaseRows = useMemo(
|
||||
() => appData?.releaseHistory.data?.filter(row => (row.release ?? row).id) ?? [],
|
||||
[appData?.releaseHistory.data],
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSourceApps } from '../../hooks/use-source-apps'
|
||||
import { useDeploymentsStore } from '../../store'
|
||||
import { useDeploymentAppData, useDeploymentsStore } from '../../store'
|
||||
import {
|
||||
activeRelease,
|
||||
deployedRows,
|
||||
@ -28,7 +28,7 @@ type DeployReleaseMenuProps = {
|
||||
|
||||
export const DeployReleaseMenu: FC<DeployReleaseMenuProps> = ({ appId, releaseId }) => {
|
||||
const { t } = useTranslation('deployments')
|
||||
const appData = useDeploymentsStore(state => state.appData[appId])
|
||||
const appData = useDeploymentAppData(appId)
|
||||
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
|
||||
const openRollbackModal = useDeploymentsStore(state => state.openRollbackModal)
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
import type { AppInfo } from '../types'
|
||||
import { useQueries } from '@tanstack/react-query'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { deploymentAppDataQueryOptions } from '../data'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
|
||||
@ -12,26 +11,15 @@ type UseDeploymentDataOptions = {
|
||||
|
||||
export function useDeploymentData(apps: AppInfo[], options: UseDeploymentDataOptions = {}) {
|
||||
const { enabled = true } = options
|
||||
const applyAppData = useDeploymentsStore(state => state.applyAppData)
|
||||
|
||||
const queries = useQueries({
|
||||
queries: apps.map(app => ({
|
||||
...deploymentAppDataQueryOptions(app.id),
|
||||
queryFn: () => useDeploymentsStore.getState().fetchAppData(app.id),
|
||||
enabled: enabled && Boolean(app.id),
|
||||
})),
|
||||
})
|
||||
|
||||
const queriesRef = useRef(queries)
|
||||
queriesRef.current = queries
|
||||
const dataUpdatedAt = queries.map(query => query.dataUpdatedAt).join('|')
|
||||
|
||||
useEffect(() => {
|
||||
queriesRef.current.forEach((query) => {
|
||||
if (query.data)
|
||||
applyAppData(query.data)
|
||||
})
|
||||
}, [applyAppData, dataUpdatedAt])
|
||||
|
||||
return {
|
||||
isLoading: queries.some(query => query.isLoading),
|
||||
isFetching: queries.some(query => query.isFetching),
|
||||
|
||||
@ -1,29 +1,13 @@
|
||||
'use client'
|
||||
import type { AppInfo, AppMode } from '../types'
|
||||
import type { AppInfo } from '../types'
|
||||
import type { AppDeploymentSummary, EnvironmentOption } from '@/contract/console/deployments'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
|
||||
const MAX_SOURCE_APPS = 100
|
||||
|
||||
function toAppInfo(summary: AppDeploymentSummary): AppInfo | undefined {
|
||||
if (!summary.id || !summary.name)
|
||||
return undefined
|
||||
|
||||
return {
|
||||
id: summary.id,
|
||||
name: summary.name,
|
||||
mode: (summary.mode || 'workflow') as AppMode,
|
||||
iconType: 'emoji',
|
||||
icon: summary.icon,
|
||||
description: summary.description ?? undefined,
|
||||
sourceAppId: summary.sourceAppId,
|
||||
sourceAppName: summary.sourceAppName,
|
||||
}
|
||||
}
|
||||
|
||||
type UseSourceAppsOptions = {
|
||||
enabled?: boolean
|
||||
environmentId?: string
|
||||
@ -33,7 +17,7 @@ type UseSourceAppsOptions = {
|
||||
|
||||
export function useSourceApps(options: UseSourceAppsOptions = {}) {
|
||||
const { enabled = true, environmentId, keyword, notDeployed } = options
|
||||
const seedInstancesFromApps = useDeploymentsStore(state => state.seedInstancesFromApps)
|
||||
const instancesById = useDeploymentsStore(state => state.instancesById)
|
||||
|
||||
const query = useMemo(() => ({
|
||||
pageNumber: 1,
|
||||
@ -45,16 +29,23 @@ export function useSourceApps(options: UseSourceAppsOptions = {}) {
|
||||
|
||||
const listQuery = useQuery(consoleQuery.deployments.list.queryOptions({
|
||||
input: { query },
|
||||
queryFn: () => useDeploymentsStore.getState().fetchSourceApps(query),
|
||||
enabled,
|
||||
staleTime: 30 * 1000,
|
||||
}))
|
||||
|
||||
const apps = useMemo<AppInfo[]>(() => {
|
||||
const appIds = useMemo(() => {
|
||||
return (listQuery.data?.data ?? [])
|
||||
.map(toAppInfo)
|
||||
.filter((app): app is AppInfo => Boolean(app))
|
||||
.map(summary => summary.id)
|
||||
.filter((id): id is string => Boolean(id))
|
||||
}, [listQuery.data?.data])
|
||||
|
||||
const apps = useMemo<AppInfo[]>(() => {
|
||||
return appIds
|
||||
.map(id => instancesById[id])
|
||||
.filter((app): app is AppInfo => Boolean(app))
|
||||
}, [appIds, instancesById])
|
||||
|
||||
const appMap = useMemo<Map<string, AppInfo>>(() => {
|
||||
return new Map(apps.map(a => [a.id, a]))
|
||||
}, [apps])
|
||||
@ -76,12 +67,6 @@ export function useSourceApps(options: UseSourceAppsOptions = {}) {
|
||||
})) ?? []
|
||||
}, [listQuery.data?.filters])
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || listQuery.isLoading)
|
||||
return
|
||||
seedInstancesFromApps(apps)
|
||||
}, [apps, enabled, listQuery.isLoading, seedInstancesFromApps])
|
||||
|
||||
return {
|
||||
apps,
|
||||
appMap,
|
||||
|
||||
@ -18,7 +18,6 @@ import { NewInstanceCard } from './new-instance-card'
|
||||
|
||||
const DeploymentsMain: FC = () => {
|
||||
const { t } = useTranslation('deployments')
|
||||
const appData = useDeploymentsStore(state => state.appData)
|
||||
const openCreateInstanceModal = useDeploymentsStore(state => state.openCreateInstanceModal)
|
||||
|
||||
const [envFilter, setEnvFilter] = useQueryState(
|
||||
@ -122,7 +121,6 @@ const DeploymentsMain: FC = () => {
|
||||
<InstanceCard
|
||||
key={app.id}
|
||||
app={app}
|
||||
appData={appData[app.id]}
|
||||
summary={summaries[app.id]}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { FC, MouseEvent } from 'react'
|
||||
import type { DeploymentAppData } from '../data'
|
||||
import type { AppInfo } from '../types'
|
||||
import type { AppDeploymentSummary } from '@/contract/console/deployments'
|
||||
import type { AppModeEnum } from '@/types/app'
|
||||
@ -20,20 +19,20 @@ 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 { useRouter } from '@/next/navigation'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import { useDeploymentAppData, useDeploymentsStore } from '../store'
|
||||
import { deployedRows, deploymentStatus, environmentId, environmentName, releaseLabel } from '../utils'
|
||||
|
||||
type InstanceCardProps = {
|
||||
app: AppInfo
|
||||
appData?: DeploymentAppData
|
||||
summary?: AppDeploymentSummary
|
||||
}
|
||||
|
||||
export const InstanceCard: FC<InstanceCardProps> = ({ app, appData, summary }) => {
|
||||
export const InstanceCard: FC<InstanceCardProps> = ({ app, summary }) => {
|
||||
const { t } = useTranslation('deployments')
|
||||
const router = useRouter()
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const appData = useDeploymentAppData(app.id)
|
||||
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
|
||||
|
||||
const navigateToDetail = () => router.push(`/deployments/${app.id}/overview`)
|
||||
|
||||
@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import Nav from '@/app/components/header/nav'
|
||||
import { useParams, useRouter, useSelectedLayoutSegment } from '@/next/navigation'
|
||||
import { useSourceApps } from '../hooks/use-source-apps'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import { useDeploymentInstance, useDeploymentsStore } from '../store'
|
||||
|
||||
const DeploymentsNav = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -17,19 +17,18 @@ const DeploymentsNav = () => {
|
||||
const params = useParams<{ instanceId?: string }>()
|
||||
const instanceId = params?.instanceId
|
||||
|
||||
const sourceApps = useDeploymentsStore(state => state.sourceApps)
|
||||
const openCreateInstanceModal = useDeploymentsStore(state => state.openCreateInstanceModal)
|
||||
const currentInstance = useDeploymentInstance(instanceId)
|
||||
|
||||
const { appMap } = useSourceApps({ enabled: isActive })
|
||||
const apps = useMemo(
|
||||
() => sourceApps.length > 0 ? sourceApps : [...appMap.values()],
|
||||
[appMap, sourceApps],
|
||||
)
|
||||
const { apps } = useSourceApps({ enabled: isActive })
|
||||
|
||||
const navigationItems = useMemo<NavItem[]>(() => {
|
||||
if (!isActive)
|
||||
return []
|
||||
return apps.map((app) => {
|
||||
const navApps = currentInstance && !apps.some(app => app.id === currentInstance.id)
|
||||
? [...apps, currentInstance]
|
||||
: apps
|
||||
return navApps.map((app) => {
|
||||
return {
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
@ -41,7 +40,7 @@ const DeploymentsNav = () => {
|
||||
mode: app.mode as unknown as AppModeEnum | undefined,
|
||||
}
|
||||
})
|
||||
}, [apps, isActive])
|
||||
}, [apps, currentInstance, isActive])
|
||||
|
||||
const curNav = useMemo(() => {
|
||||
if (!instanceId)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { DeploymentAppData } from './data'
|
||||
import type { DeploymentAppData, ListAppDeploymentsQuery } from './data'
|
||||
import type { AppInfo } from './types'
|
||||
import type { AccessSubject, APIToken, ConsoleReleaseSummary } from '@/contract/console/deployments'
|
||||
import type { AccessSubject, APIToken, ConsoleReleaseSummary, ListAppDeploymentsReply } from '@/contract/console/deployments'
|
||||
import { create } from 'zustand'
|
||||
import {
|
||||
cancelDeployment,
|
||||
@ -9,11 +9,15 @@ import {
|
||||
createDeployment,
|
||||
deleteApiKey,
|
||||
deleteAppInstance,
|
||||
fetchDeploymentAppData,
|
||||
listAppDeployments,
|
||||
patchAccessChannel,
|
||||
patchDeveloperAPI,
|
||||
refreshDeploymentAppData,
|
||||
refreshDeploymentAppDataWhenReady,
|
||||
refreshDeploymentLists,
|
||||
toAppInfoFromOverview,
|
||||
toAppInfoFromSummary,
|
||||
undeployEnvironment,
|
||||
updateAppInstance,
|
||||
updateEnvironmentAccessPolicy,
|
||||
@ -57,7 +61,7 @@ export type CreateInstanceResult = {
|
||||
}
|
||||
|
||||
type DeploymentsState = {
|
||||
sourceApps: AppInfo[]
|
||||
instancesById: Record<string, AppInfo>
|
||||
appData: Record<string, DeploymentAppData>
|
||||
createdApiToken?: CreatedApiToken
|
||||
|
||||
@ -85,8 +89,10 @@ type DeploymentsState = {
|
||||
openCreateInstanceModal: () => void
|
||||
closeCreateInstanceModal: () => void
|
||||
|
||||
seedInstancesFromApps: (apps: AppInfo[]) => void
|
||||
upsertInstances: (apps: AppInfo[]) => void
|
||||
applyAppData: (data: DeploymentAppData) => void
|
||||
fetchSourceApps: (query: ListAppDeploymentsQuery) => Promise<ListAppDeploymentsReply>
|
||||
fetchAppData: (appId: string) => Promise<DeploymentAppData>
|
||||
refreshAppData: (appId: string) => Promise<void>
|
||||
|
||||
createInstance: (params: CreateInstanceParams) => Promise<CreateInstanceResult>
|
||||
@ -115,7 +121,7 @@ type DeploymentsState = {
|
||||
}
|
||||
|
||||
export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
|
||||
sourceApps: [],
|
||||
instancesById: {},
|
||||
appData: {},
|
||||
createdApiToken: undefined,
|
||||
|
||||
@ -141,8 +147,14 @@ export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
|
||||
openCreateInstanceModal: () => set({ createInstanceModal: { open: true } }),
|
||||
closeCreateInstanceModal: () => set({ createInstanceModal: { open: false } }),
|
||||
|
||||
seedInstancesFromApps: apps => set(() => ({
|
||||
sourceApps: apps,
|
||||
upsertInstances: apps => set(state => ({
|
||||
instancesById: apps.reduce((next, app) => {
|
||||
next[app.id] = {
|
||||
...next[app.id],
|
||||
...app,
|
||||
}
|
||||
return next
|
||||
}, { ...state.instancesById }),
|
||||
})),
|
||||
|
||||
applyAppData: data => set(state => ({
|
||||
@ -152,9 +164,30 @@ export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
|
||||
},
|
||||
})),
|
||||
|
||||
fetchSourceApps: async (query) => {
|
||||
const response = await listAppDeployments(query)
|
||||
const apps = response.data
|
||||
?.map(toAppInfoFromSummary)
|
||||
.filter((app): app is AppInfo => Boolean(app)) ?? []
|
||||
get().upsertInstances(apps)
|
||||
return response
|
||||
},
|
||||
|
||||
fetchAppData: async (appId) => {
|
||||
const data = await fetchDeploymentAppData(appId)
|
||||
get().applyAppData(data)
|
||||
const app = toAppInfoFromOverview(data.overview.instance)
|
||||
if (app)
|
||||
get().upsertInstances([app])
|
||||
return data
|
||||
},
|
||||
|
||||
refreshAppData: async (appId) => {
|
||||
const data = await refreshDeploymentAppData(appId)
|
||||
get().applyAppData(data)
|
||||
const app = toAppInfoFromOverview(data.overview.instance)
|
||||
if (app)
|
||||
get().upsertInstances([app])
|
||||
},
|
||||
|
||||
createInstance: async ({ sourceAppId, name, description }) => {
|
||||
@ -164,9 +197,20 @@ export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
|
||||
set({ createInstanceModal: { open: false } })
|
||||
await Promise.allSettled([
|
||||
refreshDeploymentAppDataWhenReady(response.appInstanceId)
|
||||
.then(data => get().applyAppData(data)),
|
||||
waitForAppInstanceInDeploymentList(response.appInstanceId),
|
||||
.then((data) => {
|
||||
get().applyAppData(data)
|
||||
const app = toAppInfoFromOverview(data.overview.instance)
|
||||
if (app)
|
||||
get().upsertInstances([app])
|
||||
}),
|
||||
waitForAppInstanceInDeploymentList(response.appInstanceId).then((list) => {
|
||||
const apps = list?.data
|
||||
?.map(toAppInfoFromSummary)
|
||||
.filter((app): app is AppInfo => Boolean(app)) ?? []
|
||||
get().upsertInstances(apps)
|
||||
}),
|
||||
])
|
||||
await refreshDeploymentLists()
|
||||
return {
|
||||
appInstanceId: response.appInstanceId,
|
||||
initialRelease: response.initialRelease,
|
||||
@ -181,7 +225,16 @@ export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
|
||||
await get().refreshAppData(appId)
|
||||
await refreshDeploymentLists()
|
||||
set(state => ({
|
||||
sourceApps: state.sourceApps.map(app => app.id === appId ? { ...app, ...patch } : app),
|
||||
instancesById: {
|
||||
...state.instancesById,
|
||||
[appId]: {
|
||||
...state.instancesById[appId],
|
||||
id: appId,
|
||||
name: patch.name,
|
||||
mode: state.instancesById[appId]?.mode ?? 'workflow',
|
||||
description: patch.description,
|
||||
},
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
@ -191,8 +244,9 @@ export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
|
||||
await deleteAppInstance(appId)
|
||||
set((state) => {
|
||||
const { [appId]: _removed, ...appData } = state.appData
|
||||
const { [appId]: _removedInstance, ...instancesById } = state.instancesById
|
||||
return {
|
||||
sourceApps: state.sourceApps.filter(app => app.id !== appId),
|
||||
instancesById,
|
||||
appData,
|
||||
}
|
||||
})
|
||||
@ -278,3 +332,11 @@ export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
|
||||
await get().refreshAppData(appId)
|
||||
},
|
||||
}))
|
||||
|
||||
export const useDeploymentInstance = (appId?: string) => {
|
||||
return useDeploymentsStore(state => appId ? state.instancesById[appId] : undefined)
|
||||
}
|
||||
|
||||
export const useDeploymentAppData = (appId?: string) => {
|
||||
return useDeploymentsStore(state => appId ? state.appData[appId] : undefined)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user