mirror of
https://github.com/langgenius/dify.git
synced 2026-05-09 04:36:31 +08:00
tweaks
This commit is contained in:
parent
04124edd70
commit
ea6e7a9ed0
@ -8,9 +8,14 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useQueryState } from 'nuqs'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { envFilterQueryState } from './query-state'
|
||||
|
||||
export type EnvironmentFilterOption = {
|
||||
type EnvironmentFilterOption = {
|
||||
value: string
|
||||
text: string
|
||||
icon: ReactNode
|
||||
@ -18,13 +23,64 @@ export type EnvironmentFilterOption = {
|
||||
disabledReason?: string
|
||||
}
|
||||
|
||||
export function EnvironmentFilter({ value, options, onChange }: {
|
||||
value: string
|
||||
options: EnvironmentFilterOption[]
|
||||
onChange: (value: string) => void
|
||||
}) {
|
||||
type FilterEnvironment = {
|
||||
id: string
|
||||
name: string
|
||||
disabled?: boolean
|
||||
disabledReason?: string
|
||||
}
|
||||
|
||||
function getEnvironmentId(env: FilterEnvironment) {
|
||||
return env.id
|
||||
}
|
||||
|
||||
function getEnvironmentFilterOption(env: FilterEnvironment): EnvironmentFilterOption {
|
||||
return {
|
||||
value: env.id,
|
||||
text: env.name,
|
||||
icon: <span className="i-ri-stack-line h-[14px] w-[14px]" />,
|
||||
disabled: env.disabled,
|
||||
disabledReason: env.disabledReason,
|
||||
}
|
||||
}
|
||||
|
||||
export function EnvironmentFilter() {
|
||||
const { t } = useTranslation('deployments')
|
||||
const [open, setOpen] = useState(false)
|
||||
const selectedOption = options.find(option => option.value === value) ?? options[0]
|
||||
const [envFilter, setEnvFilter] = useQueryState('env', envFilterQueryState)
|
||||
const { data: environmentOptionsReply } = useQuery(consoleQuery.enterprise.appDeploy.listDeploymentEnvironmentOptions.queryOptions())
|
||||
const environmentOptions = environmentOptionsReply?.environments ?? []
|
||||
|
||||
function getFilterEnvironment(env: (typeof environmentOptions)[number]): FilterEnvironment[] {
|
||||
if (!env.id)
|
||||
return []
|
||||
return [{
|
||||
id: env.id,
|
||||
name: env.name || env.id,
|
||||
disabled: env.deployable === false,
|
||||
disabledReason: env.disabledReason,
|
||||
}]
|
||||
}
|
||||
|
||||
const environments = environmentOptions.flatMap(getFilterEnvironment)
|
||||
const envIdSet = new Set(environments.map(getEnvironmentId))
|
||||
const activeFilter = envFilter === 'all' || envFilter === 'not-deployed' || envIdSet.has(envFilter)
|
||||
? envFilter
|
||||
: 'all'
|
||||
const filterOptions: EnvironmentFilterOption[] = [
|
||||
{
|
||||
value: 'all',
|
||||
text: t('filter.allEnvs'),
|
||||
icon: <span className="i-ri-apps-2-line h-[14px] w-[14px]" />,
|
||||
},
|
||||
...environments.map(getEnvironmentFilterOption),
|
||||
{
|
||||
value: 'not-deployed',
|
||||
text: t('filter.notDeployed'),
|
||||
icon: <span className="i-ri-inbox-line h-[14px] w-[14px]" />,
|
||||
},
|
||||
]
|
||||
const selectedOption = filterOptions.find(option => option.value === activeFilter) ?? filterOptions[0]
|
||||
|
||||
return (
|
||||
<DropdownMenu modal={false} open={open} onOpenChange={setOpen}>
|
||||
@ -51,13 +107,13 @@ export function EnvironmentFilter({ value, options, onChange }: {
|
||||
popupClassName="w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px]"
|
||||
>
|
||||
<div className="max-h-72 overflow-auto p-1">
|
||||
{options.map(option => (
|
||||
{filterOptions.map(option => (
|
||||
<DropdownMenuItem
|
||||
key={option.value}
|
||||
onClick={() => {
|
||||
if (option.disabled)
|
||||
return
|
||||
onChange(option.value)
|
||||
void setEnvFilter(option.value)
|
||||
setOpen(false)
|
||||
}}
|
||||
title={option.disabled ? option.disabledReason : undefined}
|
||||
@ -71,7 +127,7 @@ export function EnvironmentFilter({ value, options, onChange }: {
|
||||
>
|
||||
<span className="shrink-0 text-text-tertiary">{option.icon}</span>
|
||||
<span className="grow truncate text-sm leading-5 text-text-tertiary">{option.text}</span>
|
||||
{option.value === value && (
|
||||
{option.value === activeFilter && (
|
||||
<span className="i-custom-vender-line-general-check h-4 w-4 shrink-0 text-text-secondary" />
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useDebounce } from 'ahooks'
|
||||
import { debounce, parseAsString, useQueryState } from 'nuqs'
|
||||
import { debounce, useQueryState } from 'nuqs'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
@ -10,36 +11,59 @@ import { CreateInstanceModal } from '../components/create-instance-modal'
|
||||
import { DeployDrawer } from '../components/deploy-drawer'
|
||||
import { RollbackModal } from '../components/rollback-modal'
|
||||
import { SOURCE_APPS_PAGE_SIZE } from '../data'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import {
|
||||
deploymentSummariesFromList,
|
||||
sourceAppsFromList,
|
||||
} from '../utils'
|
||||
import { EnvironmentFilter } from './environment-filter'
|
||||
import { InstanceCard } from './instance-card'
|
||||
import { NewInstanceCard } from './new-instance-card'
|
||||
import { envFilterQueryState, keywordsQueryState } from './query-state'
|
||||
|
||||
export function DeploymentsMain() {
|
||||
function DeploymentsSearchInput() {
|
||||
const { t } = useTranslation('deployments')
|
||||
const openCreateInstanceModal = useDeploymentsStore(state => state.openCreateInstanceModal)
|
||||
const [keywords, setKeywords] = useQueryState('keywords', keywordsQueryState)
|
||||
|
||||
const [envFilter, setEnvFilter] = useQueryState(
|
||||
'env',
|
||||
parseAsString.withDefault('all').withOptions({ history: 'push' }),
|
||||
)
|
||||
const [keywords, setKeywords] = useQueryState(
|
||||
'keywords',
|
||||
parseAsString.withDefault('').withOptions({ history: 'push' }),
|
||||
)
|
||||
const debouncedKeywords = useDebounce(keywords, { wait: 300 })
|
||||
const queryKeywords = keywords.trim() ? debouncedKeywords : keywords
|
||||
|
||||
const handleKeywordsChange = (next: string) => {
|
||||
function handleKeywordsChange(next: string) {
|
||||
void setKeywords(next.trim() ? next : null, {
|
||||
limitUrlUpdates: next.trim() ? debounce(300) : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
function handleKeywordsInputChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
handleKeywordsChange(e.target.value)
|
||||
}
|
||||
|
||||
function handleKeywordsClear() {
|
||||
handleKeywordsChange('')
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
wrapperClassName="w-[200px]"
|
||||
placeholder={t('filter.searchPlaceholder')}
|
||||
value={keywords}
|
||||
onChange={handleKeywordsInputChange}
|
||||
onClear={handleKeywordsClear}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DeploymentsListControls() {
|
||||
return (
|
||||
<div className="sticky top-0 z-10 flex flex-wrap items-center justify-end gap-y-2 bg-background-body px-12 pt-7 pb-5">
|
||||
<div className="flex items-center gap-2">
|
||||
<EnvironmentFilter />
|
||||
<DeploymentsSearchInput />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DeploymentsList() {
|
||||
const [envFilter] = useQueryState('env', envFilterQueryState)
|
||||
const [keywords] = useQueryState('keywords', keywordsQueryState)
|
||||
const debouncedKeywords = useDebounce(keywords, { wait: 300 })
|
||||
const queryKeywords = keywords.trim() ? debouncedKeywords : keywords
|
||||
|
||||
const requestedEnvironmentId = envFilter !== 'all' && envFilter !== 'not-deployed'
|
||||
? envFilter
|
||||
: undefined
|
||||
@ -54,79 +78,32 @@ export function DeploymentsMain() {
|
||||
},
|
||||
},
|
||||
}))
|
||||
const { data: environmentOptionsReply } = useQuery(consoleQuery.enterprise.appDeploy.listDeploymentEnvironmentOptions.queryOptions())
|
||||
const apps = sourceAppsFromList(listQuery.data)
|
||||
const summaries = deploymentSummariesFromList(listQuery.data)
|
||||
const environments = environmentOptionsReply?.environments?.flatMap((env) => {
|
||||
if (!env.id)
|
||||
return []
|
||||
return [{
|
||||
id: env.id,
|
||||
name: env.name || env.id,
|
||||
disabled: env.deployable === false,
|
||||
disabledReason: env.disabledReason,
|
||||
}]
|
||||
}) ?? []
|
||||
const envIdSet = new Set(environments.map(e => e.id))
|
||||
const activeFilter = envFilter === 'all' || envFilter === 'not-deployed' || envIdSet.has(envFilter)
|
||||
? envFilter
|
||||
: 'all'
|
||||
|
||||
const filterOptions = [
|
||||
{
|
||||
value: 'all',
|
||||
text: t('filter.allEnvs'),
|
||||
icon: <span className="i-ri-apps-2-line h-[14px] w-[14px]" />,
|
||||
},
|
||||
...environments.map(env => ({
|
||||
value: env.id,
|
||||
text: env.name,
|
||||
icon: <span className="i-ri-stack-line h-[14px] w-[14px]" />,
|
||||
disabled: env.disabled,
|
||||
disabledReason: env.disabledReason,
|
||||
})),
|
||||
{
|
||||
value: 'not-deployed',
|
||||
text: t('filter.notDeployed'),
|
||||
icon: <span className="i-ri-inbox-line h-[14px] w-[14px]" />,
|
||||
},
|
||||
]
|
||||
const apps = listQuery.data?.data ?? []
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body">
|
||||
<div className="sticky top-0 z-10 flex flex-wrap items-center justify-end gap-y-2 bg-background-body px-12 pt-7 pb-5">
|
||||
<div className="flex items-center gap-2">
|
||||
<EnvironmentFilter
|
||||
value={activeFilter}
|
||||
onChange={(next) => { void setEnvFilter(next) }}
|
||||
options={filterOptions}
|
||||
/>
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
wrapperClassName="w-[200px]"
|
||||
placeholder={t('filter.searchPlaceholder')}
|
||||
value={keywords}
|
||||
onChange={e => handleKeywordsChange(e.target.value)}
|
||||
onClear={() => handleKeywordsChange('')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative grid grow grid-cols-1 content-start gap-4 px-12 pt-2 2k:grid-cols-6 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5">
|
||||
<NewInstanceCard onOpen={openCreateInstanceModal} />
|
||||
{apps.map(app => (
|
||||
<InstanceCard
|
||||
key={app.id}
|
||||
app={app}
|
||||
summary={summaries[app.id]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="py-4" />
|
||||
<div className="relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body">
|
||||
<DeploymentsListControls />
|
||||
<div className="relative grid grow grid-cols-1 content-start gap-4 px-12 pt-2 2k:grid-cols-6 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5">
|
||||
<NewInstanceCard />
|
||||
{apps.map(app => app.id
|
||||
? (
|
||||
<InstanceCard
|
||||
key={app.id}
|
||||
app={app}
|
||||
/>
|
||||
)
|
||||
: null)}
|
||||
</div>
|
||||
|
||||
<div className="py-4" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function DeploymentsMain() {
|
||||
return (
|
||||
<>
|
||||
<DeploymentsList />
|
||||
<CreateInstanceModal />
|
||||
<DeployDrawer />
|
||||
<RollbackModal />
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import type { AppInstanceCard } from '@dify/contracts/enterprise/types.gen'
|
||||
import type { MouseEvent } from 'react'
|
||||
import type { AppInfo } from '../types'
|
||||
import type { AppDeploymentSummary } from '@/features/deployments/types'
|
||||
import type { AppModeEnum } from '@/types/app'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
@ -21,9 +20,8 @@ import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
|
||||
export function InstanceCard({ app, summary }: {
|
||||
app: AppInfo
|
||||
summary?: AppDeploymentSummary
|
||||
export function InstanceCard({ app }: {
|
||||
app: AppInstanceCard
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const router = useRouter()
|
||||
@ -31,7 +29,13 @@ export function InstanceCard({ app, summary }: {
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
|
||||
|
||||
const navigateToDetail = () => router.push(`/deployments/${app.id}/overview`)
|
||||
if (!app.id)
|
||||
return null
|
||||
|
||||
const appId = app.id
|
||||
const appName = app.name ?? appId
|
||||
const appMode = app.mode ?? 'workflow'
|
||||
const navigateToDetail = () => router.push(`/deployments/${appId}/overview`)
|
||||
|
||||
const handleMenuAction = (e: MouseEvent<HTMLElement>, action: () => void) => {
|
||||
e.stopPropagation()
|
||||
@ -41,14 +45,14 @@ export function InstanceCard({ app, summary }: {
|
||||
}
|
||||
|
||||
const statusCount = (status: string) =>
|
||||
summary?.statuses?.find(item => item.status === status)?.count ?? 0
|
||||
app.statuses?.find(item => item.status === status)?.count ?? 0
|
||||
const failedCount = statusCount('failed') + statusCount('deploy_failed')
|
||||
const deployingCount = statusCount('deploying')
|
||||
const readyCount = statusCount('ready')
|
||||
const envCount = failedCount + deployingCount + readyCount
|
||||
|
||||
const lastDeployedAt = summary?.lastDeployedAt
|
||||
? Date.parse(summary.lastDeployedAt)
|
||||
const lastDeployedAt = app.lastDeployedAt
|
||||
? Date.parse(app.lastDeployedAt)
|
||||
: null
|
||||
|
||||
const primaryStatus: 'none' | 'failed' | 'deploying' | 'ready' = envCount === 0
|
||||
@ -83,7 +87,7 @@ export function InstanceCard({ app, summary }: {
|
||||
return status || 'unknown'
|
||||
}
|
||||
|
||||
const statusSummaryTooltip = summary?.statuses?.filter(item => item.count && item.status !== 'undeployed') ?? []
|
||||
const statusSummaryTooltip = app.statuses?.filter(item => item.count && item.status !== 'undeployed') ?? []
|
||||
const statusTooltip = primaryStatus === 'none'
|
||||
? t('card.tooltip.notDeployed')
|
||||
: (
|
||||
@ -114,7 +118,7 @@ export function InstanceCard({ app, summary }: {
|
||||
? 'bg-util-colors-warning-warning-500 animate-pulse'
|
||||
: 'bg-util-colors-green-green-500'
|
||||
|
||||
const appModeLabel = t(`appMode.${app.mode}`, { defaultValue: app.mode })
|
||||
const appModeLabel = t(`appMode.${appMode}`, { defaultValue: appMode })
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -128,20 +132,19 @@ export function InstanceCard({ app, summary }: {
|
||||
<div className="relative shrink-0">
|
||||
<AppIcon
|
||||
size="large"
|
||||
iconType={app.iconType}
|
||||
iconType="emoji"
|
||||
icon={app.icon}
|
||||
background={app.iconBackground}
|
||||
imageUrl={app.iconUrl}
|
||||
/>
|
||||
<AppTypeIcon
|
||||
type={app.mode as unknown as AppModeEnum}
|
||||
type={appMode as AppModeEnum}
|
||||
wrapperClassName="absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm"
|
||||
className="h-3 w-3"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-0 grow py-px">
|
||||
<div className="flex items-center text-sm leading-5 font-semibold text-text-secondary">
|
||||
<div className="truncate" title={app.name}>{app.name}</div>
|
||||
<div className="truncate" title={appName}>{appName}</div>
|
||||
</div>
|
||||
<div className="truncate text-[10px] leading-[18px] font-medium text-text-tertiary" title={appModeLabel}>
|
||||
{appModeLabel}
|
||||
@ -174,8 +177,8 @@ export function InstanceCard({ app, summary }: {
|
||||
</Tooltip>
|
||||
<div className="flex min-w-0 items-center gap-1.5 system-xs-regular text-text-tertiary">
|
||||
<span aria-hidden className="i-ri-apps-2-line h-3.5 w-3.5 shrink-0 text-text-quaternary" />
|
||||
<span className="truncate" title={app.sourceAppName ?? app.name}>
|
||||
{t('card.fromApp', { name: app.sourceAppName ?? app.name })}
|
||||
<span className="truncate" title={app.sourceAppName ?? appName}>
|
||||
{t('card.fromApp', { name: app.sourceAppName ?? appName })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -214,7 +217,7 @@ export function InstanceCard({ app, summary }: {
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-[216px]">
|
||||
<DropdownMenuItem
|
||||
className="gap-2 px-3"
|
||||
onClick={e => handleMenuAction(e, () => openDeployDrawer({ appInstanceId: app.id }))}
|
||||
onClick={e => handleMenuAction(e, () => openDeployDrawer({ appInstanceId: appId }))}
|
||||
>
|
||||
<span className="system-sm-regular text-text-secondary">{t('card.menu.deploy')}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
|
||||
type NewInstanceActionProps = {
|
||||
icon: string
|
||||
@ -37,10 +38,10 @@ function NewInstanceAction({ icon, label, disabled, onClick }: NewInstanceAction
|
||||
)
|
||||
}
|
||||
|
||||
export function NewInstanceCard({ onOpen }: {
|
||||
onOpen: () => void
|
||||
}) {
|
||||
export function NewInstanceCard() {
|
||||
const { t } = useTranslation('deployments')
|
||||
const openCreateInstanceModal = useDeploymentsStore(state => state.openCreateInstanceModal)
|
||||
|
||||
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">
|
||||
<div className="grow rounded-t-xl p-2">
|
||||
@ -50,7 +51,7 @@ export function NewInstanceCard({ onOpen }: {
|
||||
<NewInstanceAction
|
||||
icon="i-ri-stack-line"
|
||||
label={t('newInstance.fromStudio')}
|
||||
onClick={onOpen}
|
||||
onClick={openCreateInstanceModal}
|
||||
/>
|
||||
<NewInstanceAction
|
||||
icon="i-ri-file-code-line"
|
||||
|
||||
4
web/features/deployments/list/query-state.ts
Normal file
4
web/features/deployments/list/query-state.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { parseAsString } from 'nuqs'
|
||||
|
||||
export const envFilterQueryState = parseAsString.withDefault('all').withOptions({ history: 'push' })
|
||||
export const keywordsQueryState = parseAsString.withDefault('').withOptions({ history: 'push' })
|
||||
@ -1,7 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import type { AppInstanceBasicInfo, AppInstanceCard } from '@dify/contracts/enterprise/types.gen'
|
||||
import type { NavItem } from '@/app/components/header/nav/nav-selector'
|
||||
import type { AppIconType, AppModeEnum } from '@/types/app'
|
||||
import type { AppModeEnum } from '@/types/app'
|
||||
import { skipToken, useQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Nav from '@/app/components/header/nav'
|
||||
@ -9,10 +10,40 @@ import { useParams, useRouter, useSelectedLayoutSegment } from '@/next/navigatio
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { SOURCE_APPS_PAGE_SIZE } from '../data'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import {
|
||||
sourceAppsFromList,
|
||||
toAppInfoFromOverview,
|
||||
} from '../utils'
|
||||
|
||||
function navItemFromListApp(app: AppInstanceCard): NavItem[] {
|
||||
if (!app.id || !app.name)
|
||||
return []
|
||||
|
||||
return [{
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
link: `/deployments/${app.id}/overview`,
|
||||
icon_type: 'emoji',
|
||||
icon: app.icon ?? '',
|
||||
icon_background: app.iconBackground ?? null,
|
||||
icon_url: null,
|
||||
mode: app.mode as AppModeEnum | undefined,
|
||||
}]
|
||||
}
|
||||
|
||||
function navItemFromOverview(instance?: AppInstanceBasicInfo): NavItem | undefined {
|
||||
if (!instance?.id)
|
||||
return undefined
|
||||
|
||||
const name = instance.name ?? instance.id
|
||||
|
||||
return {
|
||||
id: instance.id,
|
||||
name,
|
||||
link: `/deployments/${instance.id}/overview`,
|
||||
icon_type: 'emoji',
|
||||
icon: instance.icon ?? '',
|
||||
icon_background: instance.iconBackground ?? null,
|
||||
icon_url: null,
|
||||
mode: instance.mode as AppModeEnum | undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export function DeploymentsNav() {
|
||||
const { t } = useTranslation()
|
||||
@ -28,7 +59,7 @@ export function DeploymentsNav() {
|
||||
? { params: { appInstanceId: instanceId } }
|
||||
: skipToken,
|
||||
enabled: isActive && Boolean(instanceId),
|
||||
select: data => toAppInfoFromOverview(data.instance),
|
||||
select: data => data.instance,
|
||||
}))
|
||||
|
||||
const listQuery = useQuery(consoleQuery.enterprise.appDeploy.listAppInstances.queryOptions({
|
||||
@ -40,24 +71,13 @@ export function DeploymentsNav() {
|
||||
},
|
||||
enabled: isActive,
|
||||
}))
|
||||
const apps = sourceAppsFromList(listQuery.data)
|
||||
const appNavItems = listQuery.data?.data?.flatMap(navItemFromListApp) ?? []
|
||||
const currentNavItem = navItemFromOverview(currentInstance)
|
||||
|
||||
const navApps = currentInstance && !apps.some(app => app.id === currentInstance.id)
|
||||
? [...apps, currentInstance]
|
||||
: apps
|
||||
const navigationItems: NavItem[] = isActive
|
||||
? navApps.map((app) => {
|
||||
return {
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
link: `/deployments/${app.id}/overview`,
|
||||
icon_type: (app.iconType ?? null) as AppIconType | null,
|
||||
icon: app.icon ?? '',
|
||||
icon_background: app.iconBackground ?? null,
|
||||
icon_url: app.iconUrl ?? null,
|
||||
mode: app.mode as unknown as AppModeEnum | undefined,
|
||||
}
|
||||
})
|
||||
? currentNavItem && !appNavItems.some(item => item.id === currentNavItem.id)
|
||||
? [...appNavItems, currentNavItem]
|
||||
: appNavItems
|
||||
: []
|
||||
|
||||
const curNav = instanceId
|
||||
|
||||
@ -44,16 +44,6 @@ type ConsoleUser = EnterpriseContract.ConsoleUser & {
|
||||
displayName?: string
|
||||
}
|
||||
|
||||
export type AppDeploymentSummary = EnterpriseContract.AppInstanceCard & {
|
||||
createdAt?: Timestamp
|
||||
description?: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
export type ListAppDeploymentsReply = Omit<EnterpriseContract.ListAppInstancesReply, 'data'> & {
|
||||
data?: AppDeploymentSummary[]
|
||||
}
|
||||
|
||||
export type AppInstanceOverview = EnterpriseContract.AppInstanceBasicInfo
|
||||
|
||||
export type RuntimeBindingDisplay = EnterpriseContract.ReleaseRuntimeBinding & {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import type {
|
||||
AccessPermissionKind,
|
||||
AppDeploymentSummary,
|
||||
AppInfo,
|
||||
AppInstanceOverview,
|
||||
AppMode,
|
||||
@ -8,7 +7,6 @@ import type {
|
||||
ConsoleReleaseSummary,
|
||||
EnvironmentDeploymentRow,
|
||||
EnvironmentOption,
|
||||
ListAppDeploymentsReply,
|
||||
ListDeploymentEnvironmentOptionsReply,
|
||||
RuntimeBindingDisplay,
|
||||
} from './types'
|
||||
@ -136,23 +134,6 @@ export function deployedRows(rows?: EnvironmentDeploymentRow[]) {
|
||||
}) ?? []
|
||||
}
|
||||
|
||||
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,
|
||||
iconBackground: summary.iconBackground,
|
||||
sourceAppName: summary.sourceAppName,
|
||||
sourceAppAvailable: summary.sourceAppAvailable,
|
||||
canCreateRelease: summary.canCreateRelease,
|
||||
}
|
||||
}
|
||||
|
||||
export function toAppInfoFromOverview(instance?: AppInstanceOverview): AppInfo | undefined {
|
||||
if (!instance?.id)
|
||||
return undefined
|
||||
@ -172,20 +153,6 @@ export function toAppInfoFromOverview(instance?: AppInstanceOverview): AppInfo |
|
||||
}
|
||||
}
|
||||
|
||||
export function sourceAppsFromList(response?: ListAppDeploymentsReply) {
|
||||
return (response?.data ?? [])
|
||||
.map(toAppInfoFromSummary)
|
||||
.filter((app): app is AppInfo => Boolean(app))
|
||||
}
|
||||
|
||||
export function deploymentSummariesFromList(response?: ListAppDeploymentsReply): Record<string, AppDeploymentSummary> {
|
||||
return Object.fromEntries(
|
||||
(response?.data ?? [])
|
||||
.filter(summary => summary.id)
|
||||
.map(summary => [summary.id!, summary]),
|
||||
)
|
||||
}
|
||||
|
||||
export function environmentOptionsFromOptionsReply(response?: ListDeploymentEnvironmentOptionsReply): EnvironmentOption[] {
|
||||
return response?.environments
|
||||
?.filter(environment => environment.id)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user