/console/api/enterprise/deployment-environment-options

This commit is contained in:
Stephen Zhou 2026-04-30 11:34:16 +08:00
parent 2c34f9849d
commit 2459b88114
No known key found for this signature in database
10 changed files with 82 additions and 56 deletions

View File

@ -46,12 +46,6 @@ export type DeploymentStatusCount = {
count?: number
}
export type AppInstanceFilter = {
id?: string
name?: string
kind?: 'all' | 'environment' | 'not_deployed' | (string & {})
}
export type AppDeploymentSummary = {
id?: string
name?: string
@ -73,7 +67,6 @@ export type Pagination = {
}
export type ListAppDeploymentsReply = {
filters?: AppInstanceFilter[]
data?: AppDeploymentSummary[]
pagination?: Pagination
}
@ -144,11 +137,20 @@ export type ListEnvironmentDeploymentsReply = {
data?: EnvironmentDeploymentRow[]
}
export type EnvironmentOption = ConsoleEnvironmentSummary & {
disabled?: boolean
export type DeploymentEnvironmentOption = ConsoleEnvironmentSummary & {
managedBy?: string
deployable?: boolean
disabledReason?: string
}
export type ListDeploymentEnvironmentOptionsReply = {
environments?: DeploymentEnvironmentOption[]
}
export type EnvironmentOption = DeploymentEnvironmentOption & {
disabled?: boolean
}
export type ReleaseRuntimePreviewReply = {
release?: ConsoleReleaseSummary
bindings?: RuntimeBindingDisplay[]
@ -370,6 +372,13 @@ export const runtimeInstancesContract = base
}>())
.output(type<ListEnvironmentDeploymentsReply>())
export const deploymentEnvironmentOptionsContract = base
.route({
path: '/enterprise/deployment-environment-options',
method: 'GET',
})
.output(type<ListDeploymentEnvironmentOptionsReply>())
export const previewReleaseContract = base
.route({
path: '/enterprise/app-instances/{appInstanceId}/releases:preview',

View File

@ -12,6 +12,7 @@ import {
createReleaseContract,
deleteAppInstanceContract,
deleteEnvironmentAPITokenContract,
deploymentEnvironmentOptionsContract,
deploymentOverviewContract,
environmentAccessPolicyContract,
listAppDeploymentsContract,
@ -119,6 +120,7 @@ export const consoleRouterContract = {
createInstance: createAppInstanceContract,
overview: deploymentOverviewContract,
environmentDeployments: runtimeInstancesContract,
deploymentEnvironmentOptions: deploymentEnvironmentOptionsContract,
previewRelease: previewReleaseContract,
releaseHistory: releaseHistoryContract,
accessConfig: accessConfigContract,

View File

@ -10,9 +10,8 @@ import {
DEPLOYMENT_PAGE_SIZE,
} from '../data'
import { useStartDeployment } from '../hooks/use-deployment-mutations'
import { deploymentEnvironmentDeploymentsQueryOptions } from '../queries'
import { useDeploymentsStore } from '../store'
import { environmentOptionsFromDeploymentRows } from '../utils'
import { environmentOptionsFromOptionsReply } from '../utils'
import { DeployForm } from './deploy-drawer/form'
const DeployDrawer: FC = () => {
@ -34,14 +33,14 @@ const DeployDrawer: FC = () => {
: skipToken,
enabled: open && Boolean(drawerAppId),
}))
const { data: environmentDeployments } = useQuery({
...deploymentEnvironmentDeploymentsQueryOptions(drawerAppId),
enabled: open && Boolean(drawerAppId),
const { data: environmentOptionsReply } = useQuery({
...consoleQuery.deployments.deploymentEnvironmentOptions.queryOptions(),
enabled: open,
})
const environmentOptions = useMemo(
() => environmentOptionsFromDeploymentRows(environmentDeployments?.data),
[environmentDeployments?.data],
() => environmentOptionsFromOptionsReply(environmentOptionsReply),
[environmentOptionsReply],
)
const environments = environmentOptions
const releases = releaseHistory?.data?.map(row => row.release ?? row).filter(release => release.id) ?? []
@ -57,7 +56,7 @@ const DeployDrawer: FC = () => {
<DialogCloseButton />
{!drawerAppId
? <div className="p-4 text-text-tertiary">{t('deployDrawer.notFound')}</div>
: (!releaseHistory || !environmentDeployments)
: (!releaseHistory || !environmentOptionsReply)
? (
<div className="flex items-center gap-2 p-4 system-sm-regular text-text-tertiary">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-components-panel-border border-t-transparent" />

View File

@ -97,11 +97,12 @@ export const DeployForm: FC<DeployFormProps> = ({
const isPromote = Boolean(presetReleaseId)
const [selectedEnvId, setSelectedEnvId] = useState<string>(
() => lockedEnvId ?? environments[0]?.id ?? '',
() => lockedEnvId ?? environments.find(env => !env.disabled)?.id ?? environments[0]?.id ?? '',
)
const selectedEnvironmentId = selectedEnvId || lockedEnvId || environments[0]?.id || ''
const selectedEnvironment = environments.find(env => env.id === selectedEnvironmentId)
const [releaseNote, setReleaseNote] = useState<string>('')
const canDeploy = Boolean(selectedEnvironmentId && (!isPromote || displayedRelease?.id || defaultReleaseId))
const canDeploy = Boolean(selectedEnvironmentId && selectedEnvironment && !selectedEnvironment.disabled && (!isPromote || displayedRelease?.id || defaultReleaseId))
const previewReleaseId = isPromote ? displayedRelease?.id ?? defaultReleaseId : undefined
const releasePreview = useQuery(consoleQuery.deployments.previewRelease.queryOptions({
input: appId && (!isPromote || previewReleaseId)
@ -193,6 +194,8 @@ export const DeployForm: FC<DeployFormProps> = ({
options={environments.filter(env => env.id).map(env => ({
value: env.id!,
label: `${environmentName(env)} · ${t(environmentMode(env) === 'isolated' ? 'mode.isolated' : 'mode.shared')} · ${(env.type ?? 'env').toUpperCase()}`,
disabled: env.disabled,
disabledReason: env.disabledReason,
}))}
placeholder={t('deployDrawer.selectEnv')}
/>

View File

@ -24,7 +24,12 @@ export const Field: FC<FieldProps> = ({ label, hint, children }) => (
</div>
)
type SelectOption = { value: string, label: string }
type SelectOption = {
value: string
label: string
disabled?: boolean
disabledReason?: string
}
type SelectProps = {
value: string
@ -57,7 +62,12 @@ export const DeploymentSelect: FC<SelectProps> = ({ value, onChange, options, pl
</SelectTrigger>
<SelectContent popupClassName="w-(--anchor-width)">
{options.map(opt => (
<SelectItem key={opt.value} value={opt.value}>
<SelectItem
key={opt.value}
value={opt.value}
disabled={opt.disabled}
title={opt.disabled ? opt.disabledReason : undefined}
>
<SelectItemText>{opt.label}</SelectItemText>
<SelectItemIndicator />
</SelectItem>

View File

@ -27,7 +27,7 @@ import {
deployedRows,
environmentId,
environmentName,
environmentOptionsFromDeploymentRows,
environmentOptionsFromOptionsReply,
releaseCommit,
releaseLabel,
toAppInfoFromOverview,
@ -67,13 +67,17 @@ const RollbackModal: FC = () => {
...deploymentEnvironmentDeploymentsQueryOptions(modal.appId),
enabled: modal.open && Boolean(modal.appId),
})
const { data: environmentOptionsReply } = useQuery({
...consoleQuery.deployments.deploymentEnvironmentOptions.queryOptions(),
enabled: modal.open,
})
const { data: releaseHistory } = useQuery(consoleQuery.deployments.releaseHistory.queryOptions({
input: pagedInput ?? skipToken,
enabled: modal.open && Boolean(modal.appId),
}))
const environmentOptions = useMemo(
() => environmentOptionsFromDeploymentRows(environmentDeployments?.data),
[environmentDeployments?.data],
() => environmentOptionsFromOptionsReply(environmentOptionsReply),
[environmentOptionsReply],
)
const currentRow = deployedRows(environmentDeployments?.data)

View File

@ -11,6 +11,7 @@ import {
import { useQuery } from '@tanstack/react-query'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { consoleQuery } from '@/service/client'
import { useUndeployDeployment } from '../hooks/use-deployment-mutations'
import { deploymentEnvironmentDeploymentsQueryOptions } from '../queries'
import { useDeploymentsStore } from '../store'
@ -23,7 +24,7 @@ import {
environmentId,
environmentMode,
environmentName,
environmentOptionsFromDeploymentRows,
environmentOptionsFromOptionsReply,
isUndeployedDeploymentRow,
releaseCommit,
releaseLabel,
@ -40,11 +41,12 @@ type DeployTabProps = {
const DeployTab: FC<DeployTabProps> = ({ instanceId: appId }) => {
const { t } = useTranslation('deployments')
const { data: environmentDeployments } = useQuery(deploymentEnvironmentDeploymentsQueryOptions(appId))
const { data: environmentOptionsReply } = useQuery(consoleQuery.deployments.deploymentEnvironmentOptions.queryOptions())
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
const undeployDeployment = useUndeployDeployment()
const environmentOptions = useMemo(
() => environmentOptionsFromDeploymentRows(environmentDeployments?.data),
[environmentDeployments?.data],
() => environmentOptionsFromOptionsReply(environmentOptionsReply),
[environmentOptionsReply],
)
const rows = useMemo(
@ -118,7 +120,10 @@ const DeployTab: FC<DeployTabProps> = ({ instanceId: appId }) => {
<DropdownMenuItem
key={env.id}
className="gap-2 px-3"
disabled={env.disabled}
onClick={() => {
if (env.disabled)
return
setDeployMenuOpen(false)
openDeployDrawer({ appId, environmentId: env.id })
}}

View File

@ -11,6 +11,7 @@ import {
import { useQuery } from '@tanstack/react-query'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { consoleQuery } from '@/service/client'
import { deploymentEnvironmentDeploymentsQueryOptions } from '../../queries'
import { useDeploymentsStore } from '../../store'
import {
@ -20,7 +21,7 @@ import {
deploymentStatus,
environmentId,
environmentName,
environmentOptionsFromDeploymentRows,
environmentOptionsFromOptionsReply,
} from '../../utils'
type DeployReleaseMenuProps = {
@ -37,10 +38,14 @@ export const DeployReleaseMenu: FC<DeployReleaseMenuProps> = ({ appId, releaseId
...deploymentEnvironmentDeploymentsQueryOptions(appId),
enabled: open,
})
const { data: environmentOptionsReply } = useQuery({
...consoleQuery.deployments.deploymentEnvironmentOptions.queryOptions(),
enabled: open,
})
const environmentOptions = useMemo(
() => environmentOptionsFromDeploymentRows(environmentDeployments?.data),
[environmentDeployments?.data],
() => environmentOptionsFromOptionsReply(environmentOptionsReply),
[environmentOptionsReply],
)
const environments = environmentOptions.filter(env => env.id)
const deploymentRows = deployedRows(environmentDeployments?.data)

View File

@ -7,6 +7,7 @@ import { debounce, parseAsString, useQueryState } from 'nuqs'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '@/app/components/base/input'
import { consoleQuery } from '@/service/client'
import CreateInstanceModal from '../components/create-instance-modal'
import DeployDrawer from '../components/deploy-drawer'
import RollbackModal from '../components/rollback-modal'
@ -16,7 +17,7 @@ import {
deploymentSummariesFromList,
environmentId,
environmentName,
environmentOptionsFromList,
environmentOptionsFromOptionsReply,
sourceAppsFromList,
} from '../utils'
import { EnvironmentFilter } from './environment-filter'
@ -52,9 +53,13 @@ const DeploymentsMain: FC = () => {
...(envFilter === 'not-deployed' ? { notDeployed: true } : {}),
...(queryKeywords.trim() ? { query: queryKeywords.trim() } : {}),
}))
const { data: environmentOptionsReply } = useQuery(consoleQuery.deployments.deploymentEnvironmentOptions.queryOptions())
const apps = useMemo(() => sourceAppsFromList(listQuery.data), [listQuery.data])
const summaries = useMemo(() => deploymentSummariesFromList(listQuery.data), [listQuery.data])
const environmentOptions = useMemo(() => environmentOptionsFromList(listQuery.data), [listQuery.data])
const environmentOptions = useMemo(
() => environmentOptionsFromOptionsReply(environmentOptionsReply),
[environmentOptionsReply],
)
const environments = useMemo(() => {
return environmentOptions

View File

@ -7,6 +7,7 @@ import type {
EnvironmentDeploymentRow,
EnvironmentOption,
ListAppDeploymentsReply,
ListDeploymentEnvironmentOptionsReply,
RuntimeBindingDisplay,
} from '@/contract/console/deployments'
import { PUBLIC_API_PREFIX } from '@/config'
@ -107,14 +108,6 @@ export const deployedRows = (rows?: EnvironmentDeploymentRow[]) =>
&& (row.id || runtimeStatus || row.currentRelease || row.detail)
}) ?? []
type DeploymentEnvironmentFilter = {
id?: string
name?: string
kind?: string
disabled?: boolean
disabledReason?: string
}
export function toAppInfoFromSummary(summary: AppDeploymentSummary): AppInfo | undefined {
if (!summary.id || !summary.name)
return undefined
@ -158,22 +151,13 @@ export const deploymentSummariesFromList = (response?: ListAppDeploymentsReply):
)
}
export const environmentOptionsFromList = (response?: ListAppDeploymentsReply): EnvironmentOption[] => {
return ((response?.filters ?? []) as DeploymentEnvironmentFilter[])
.filter(filter => filter.kind === 'environment' && filter.id)
.map(filter => ({
id: filter.id,
name: filter.name,
disabled: filter.disabled,
disabledReason: filter.disabledReason,
}))
}
export const environmentOptionsFromDeploymentRows = (rows?: EnvironmentDeploymentRow[]): EnvironmentOption[] => {
return rows
?.map(row => row.environment)
.filter((environment): environment is ConsoleEnvironmentSummary => Boolean(environment?.id))
.map(environment => ({ ...environment })) ?? []
export const environmentOptionsFromOptionsReply = (response?: ListDeploymentEnvironmentOptionsReply): EnvironmentOption[] => {
return response?.environments
?.filter(environment => environment.id)
.map(environment => ({
...environment,
disabled: environment.deployable === false,
})) ?? []
}
export const accessModeToPermissionKey = (mode?: string): AccessPermissionKind => {