diff --git a/web/features/deployments/detail/access-tab/channels-section.tsx b/web/features/deployments/detail/access-tab/channels-section.tsx index 21078b37aa..842ee329cd 100644 --- a/web/features/deployments/detail/access-tab/channels-section.tsx +++ b/web/features/deployments/detail/access-tab/channels-section.tsx @@ -16,6 +16,25 @@ type AccessChannelsSectionProps = { cliDocsUrl?: string } +function AccessChannelsSwitch({ appId, checked }: { + appId: string + checked: boolean +}) { + const toggleAccessChannel = useMutation(consoleQuery.enterprise.appDeploy.updateAccessChannels.mutationOptions()) + + return ( + { + toggleAccessChannel.mutate({ + params: { appInstanceId: appId }, + body: { enabled }, + }) + }} + /> + ) +} + export function AccessChannelsSection({ appId, runEnabled, @@ -24,23 +43,15 @@ export function AccessChannelsSection({ cliDocsUrl, }: AccessChannelsSectionProps) { const { t } = useTranslation('deployments') - const toggleAccessChannel = useMutation(consoleQuery.enterprise.appDeploy.updateAccessChannels.mutationOptions()) - - function handleToggle(enabled: boolean) { - toggleAccessChannel.mutate({ - params: { appInstanceId: appId }, - body: { enabled }, - }) - } return (
)} > diff --git a/web/features/deployments/detail/access-tab/developer-api-section.tsx b/web/features/deployments/detail/access-tab/developer-api-section.tsx index ed262d7dea..a0a44a7943 100644 --- a/web/features/deployments/detail/access-tab/developer-api-section.tsx +++ b/web/features/deployments/detail/access-tab/developer-api-section.tsx @@ -18,6 +18,66 @@ type DeveloperApiSectionProps = { apiKeys: DeveloperApiKeyRow[] } +function DeveloperApiSwitch({ appId, checked }: { + appId: string + checked: boolean +}) { + const toggleDeveloperAPI = useMutation(consoleQuery.enterprise.appDeploy.updateDeveloperApi.mutationOptions()) + + return ( + { + toggleDeveloperAPI.mutate({ + params: { appInstanceId: appId }, + body: { enabled }, + }) + }} + /> + ) +} + +function CreatedApiTokenCard({ appId }: { + appId: string +}) { + const { t } = useTranslation('deployments') + const createdApiToken = useAtomValue(createdDeveloperApiTokenAtom) + const setCreatedApiToken = useSetAtom(createdDeveloperApiTokenAtom) + const visibleCreatedApiToken = createdApiToken?.appId === appId + ? createdApiToken.token + : undefined + + if (!visibleCreatedApiToken) + return null + + return ( +
+
+
+ + {t('access.api.newTokenTitle')} + + + {t('access.api.newTokenDescription')} + +
+ +
+ +
+ ) +} + export function DeveloperApiSection({ appId, apiEnabled, @@ -26,29 +86,15 @@ export function DeveloperApiSection({ apiKeys, }: DeveloperApiSectionProps) { const { t } = useTranslation('deployments') - const createdApiToken = useAtomValue(createdDeveloperApiTokenAtom) - const setCreatedApiToken = useSetAtom(createdDeveloperApiTokenAtom) - const toggleDeveloperAPI = useMutation(consoleQuery.enterprise.appDeploy.updateDeveloperApi.mutationOptions()) - - function handleToggle(enabled: boolean) { - toggleDeveloperAPI.mutate({ - params: { appInstanceId: appId }, - body: { enabled }, - }) - } - - const visibleCreatedApiToken = createdApiToken?.appId === appId - ? createdApiToken.token - : undefined return (
)} > @@ -76,32 +122,7 @@ export function DeveloperApiSection({ apiKeys={apiKeys} /> - {visibleCreatedApiToken && ( -
-
-
- - {t('access.api.newTokenTitle')} - - - {t('access.api.newTokenDescription')} - -
- -
- -
- )} + {apiKeys.length === 0 ? (
diff --git a/web/features/deployments/detail/deploy-tab.tsx b/web/features/deployments/detail/deploy-tab.tsx index 93c177ab91..dd3b457ef5 100644 --- a/web/features/deployments/detail/deploy-tab.tsx +++ b/web/features/deployments/detail/deploy-tab.tsx @@ -1,8 +1,5 @@ 'use client' -import type { RuntimeInstanceRow } from '@dify/contracts/enterprise/types.gen' -import type { KeyboardEvent } from 'react' import type { EnvironmentOption } from '../types' -import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { DropdownMenu, @@ -10,30 +7,19 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@langgenius/dify-ui/dropdown-menu' -import { useMutation, useQuery } from '@tanstack/react-query' +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 { openDeployDrawerAtom } from '../store' import { - activeRelease, deployedRows, - deploymentId, - deploymentStatus, - environmentBackend, environmentId, - environmentMode, environmentName, environmentOptionsFromOptionsReply, - isUndeployedDeploymentRow, - releaseCommit, - releaseLabel, } from '../utils' -import { DeploymentPanel } from './deploy-tab/deployment-panel' -import { DeploymentStatusSummary } from './deploy-tab/deployment-status-summary' - -const GRID_TEMPLATE = 'lg:grid-cols-[minmax(180px,1fr)_minmax(140px,0.75fr)_minmax(180px,0.85fr)_240px]' +import { DeploymentEnvironmentList } from './deploy-tab/deployment-environment-list' function NewDeploymentMenu({ appInstanceId, availableEnvs }: { appInstanceId: string @@ -95,90 +81,6 @@ function NewDeploymentMenu({ appInstanceId, availableEnvs }: { ) } -function DeploymentRowActions({ appInstanceId, envId, row }: { - appInstanceId: string - envId: string - row: RuntimeInstanceRow -}) { - const { t } = useTranslation('deployments') - const [menuOpen, setMenuOpen] = useState(false) - const openDeployDrawer = useSetAtom(openDeployDrawerAtom) - const cancelDeployment = useMutation(consoleQuery.enterprise.appDeploy.cancelRuntimeDeployment.mutationOptions()) - const undeployDeployment = useMutation(consoleQuery.enterprise.appDeploy.undeployRuntimeInstance.mutationOptions()) - const isUndeployed = isUndeployedDeploymentRow(row) - const status = deploymentStatus(row) - - function handleRuntimeAction() { - const runtimeInstanceId = deploymentId(row) - setMenuOpen(false) - - if (status === 'deploying') { - cancelDeployment.mutate({ - params: { - appInstanceId, - runtimeInstanceId, - }, - body: { - appInstanceId, - runtimeInstanceId, - }, - }) - return - } - - undeployDeployment.mutate({ - params: { - appInstanceId, - runtimeInstanceId, - }, - body: { - appInstanceId, - runtimeInstanceId, - }, - }) - } - - return ( -
e.stopPropagation()} - onKeyDown={e => e.stopPropagation()} - > - - {!isUndeployed && ( - - - - - {menuOpen && ( - - - - {status === 'deploying' ? t('deployTab.cancelDeployment') : t('deployTab.undeploy')} - - - - )} - - )} -
- ) -} - export function DeployTab({ instanceId: appInstanceId }: { instanceId: string }) { @@ -195,19 +97,6 @@ export function DeployTab({ instanceId: appInstanceId }: { const deployedEnvIds = new Set(deployedRuntimeRows.map(row => environmentId(row.environment))) const availableEnvs = environmentOptions.filter(env => env.id && !deployedEnvIds.has(env.id)) - const expandableEnvIds = rows.filter(row => !isUndeployedDeploymentRow(row)).map(row => environmentId(row.environment)) - const [expanded, setExpanded] = useState() - const activeExpanded = expanded === undefined - ? expandableEnvIds[0] ?? null - : expanded !== null && expandableEnvIds.includes(expanded) - ? expanded - : null - const toggle = (id: string) => { - setExpanded((prev) => { - const current = prev === undefined ? expandableEnvIds[0] ?? null : prev - return current === id ? null : id - }) - } return (
@@ -231,90 +120,7 @@ export function DeployTab({ instanceId: appInstanceId }: {
) : ( -
-
-
{t('deployTab.col.environment')}
-
{t('deployTab.col.currentRelease')}
-
{t('deployTab.col.status')}
-
{t('deployTab.col.actions')}
-
- {rows.map((row) => { - const envId = environmentId(row.environment) - const isUndeployed = isUndeployedDeploymentRow(row) - const isExpanded = !isUndeployed && activeExpanded === envId - const release = activeRelease(row) - const chevron = !isUndeployed && ( - - ) - const handleRowToggle = () => { - if (!isUndeployed) - toggle(envId) - } - const handleRowKeyDown = (event: KeyboardEvent) => { - if (isUndeployed) - return - - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault() - toggle(envId) - } - } - return ( -
-
-
-
- {environmentName(row.environment)} -
- {environmentBackend(row.environment)} - · - {t(environmentMode(row.environment) === 'isolated' ? 'mode.isolated' : 'mode.shared')} -
-
-
- - {chevron} -
-
-
- {isUndeployed ? '—' : releaseLabel(release)} - {!isUndeployed && ( - {releaseCommit(release)} - )} -
-
- -
-
- - {chevron} -
-
- {isExpanded && } -
- ) - })} -
+ )}
) diff --git a/web/features/deployments/detail/deploy-tab/deployment-environment-list.tsx b/web/features/deployments/detail/deploy-tab/deployment-environment-list.tsx new file mode 100644 index 0000000000..8d7beb4b87 --- /dev/null +++ b/web/features/deployments/detail/deploy-tab/deployment-environment-list.tsx @@ -0,0 +1,248 @@ +'use client' + +import type { RuntimeInstanceRow } from '@dify/contracts/enterprise/types.gen' +import type { KeyboardEvent } from 'react' +import { Button } from '@langgenius/dify-ui/button' +import { cn } from '@langgenius/dify-ui/cn' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@langgenius/dify-ui/dropdown-menu' +import { useMutation } from '@tanstack/react-query' +import { useSetAtom } from 'jotai' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { consoleQuery } from '@/service/client' +import { openDeployDrawerAtom } from '../../store' +import { + activeRelease, + deploymentId, + deploymentStatus, + environmentBackend, + environmentId, + environmentMode, + environmentName, + isUndeployedDeploymentRow, + releaseCommit, + releaseLabel, +} from '../../utils' +import { DeploymentPanel } from './deployment-panel' +import { DeploymentStatusSummary } from './deployment-status-summary' + +const GRID_TEMPLATE = 'lg:grid-cols-[minmax(180px,1fr)_minmax(140px,0.75fr)_minmax(180px,0.85fr)_240px]' + +function DeploymentRowActions({ appInstanceId, envId, row }: { + appInstanceId: string + envId: string + row: RuntimeInstanceRow +}) { + const { t } = useTranslation('deployments') + const [menuOpen, setMenuOpen] = useState(false) + const openDeployDrawer = useSetAtom(openDeployDrawerAtom) + const cancelDeployment = useMutation(consoleQuery.enterprise.appDeploy.cancelRuntimeDeployment.mutationOptions()) + const undeployDeployment = useMutation(consoleQuery.enterprise.appDeploy.undeployRuntimeInstance.mutationOptions()) + const isUndeployed = isUndeployedDeploymentRow(row) + const status = deploymentStatus(row) + + function handleRuntimeAction() { + const runtimeInstanceId = deploymentId(row) + setMenuOpen(false) + + if (status === 'deploying') { + cancelDeployment.mutate({ + params: { + appInstanceId, + runtimeInstanceId, + }, + body: { + appInstanceId, + runtimeInstanceId, + }, + }) + return + } + + undeployDeployment.mutate({ + params: { + appInstanceId, + runtimeInstanceId, + }, + body: { + appInstanceId, + runtimeInstanceId, + }, + }) + } + + return ( +
e.stopPropagation()} + onKeyDown={e => e.stopPropagation()} + > + + {!isUndeployed && ( + + + + + {menuOpen && ( + + + + {status === 'deploying' ? t('deployTab.cancelDeployment') : t('deployTab.undeploy')} + + + + )} + + )} +
+ ) +} + +function DeploymentEnvironmentRow({ appInstanceId, row, isExpanded, onToggle }: { + appInstanceId: string + row: RuntimeInstanceRow + isExpanded: boolean + onToggle: (envId: string) => void +}) { + const { t } = useTranslation('deployments') + const envId = environmentId(row.environment) + const isUndeployed = isUndeployedDeploymentRow(row) + const release = activeRelease(row) + const chevron = !isUndeployed && ( + + ) + + function handleRowToggle() { + if (!isUndeployed) + onToggle(envId) + } + + function handleRowKeyDown(event: KeyboardEvent) { + if (isUndeployed) + return + + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + onToggle(envId) + } + } + + return ( +
+
+
+
+ {environmentName(row.environment)} +
+ {environmentBackend(row.environment)} + · + {t(environmentMode(row.environment) === 'isolated' ? 'mode.isolated' : 'mode.shared')} +
+
+
+ + {chevron} +
+
+
+ {isUndeployed ? '—' : releaseLabel(release)} + {!isUndeployed && ( + {releaseCommit(release)} + )} +
+
+ +
+
+ + {chevron} +
+
+ {isExpanded && } +
+ ) +} + +export function DeploymentEnvironmentList({ appInstanceId, rows }: { + appInstanceId: string + rows: RuntimeInstanceRow[] +}) { + const { t } = useTranslation('deployments') + const expandableEnvIds = rows.filter(row => !isUndeployedDeploymentRow(row)).map(row => environmentId(row.environment)) + const [expanded, setExpanded] = useState() + const activeExpanded = expanded === undefined + ? expandableEnvIds[0] ?? null + : expanded !== null && expandableEnvIds.includes(expanded) + ? expanded + : null + + function toggleExpandedEnv(envId: string) { + setExpanded((prev) => { + const current = prev === undefined ? expandableEnvIds[0] ?? null : prev + return current === envId ? null : envId + }) + } + + return ( +
+
+
{t('deployTab.col.environment')}
+
{t('deployTab.col.currentRelease')}
+
{t('deployTab.col.status')}
+
{t('deployTab.col.actions')}
+
+ {rows.map((row) => { + const envId = environmentId(row.environment) + const isExpanded = !isUndeployedDeploymentRow(row) && activeExpanded === envId + return ( + + ) + })} +
+ ) +} diff --git a/web/features/deployments/detail/overview-tab.tsx b/web/features/deployments/detail/overview-tab.tsx index 47e6b30d20..25e9a722e6 100644 --- a/web/features/deployments/detail/overview-tab.tsx +++ b/web/features/deployments/detail/overview-tab.tsx @@ -88,6 +88,19 @@ function overviewDeploymentStatus(status?: string): 'deploying' | 'deploy_failed return 'ready' } +function DeployFromOverviewButton({ appId }: { + appId: string +}) { + const { t } = useTranslation('deployments') + const openDeployDrawer = useSetAtom(openDeployDrawerAtom) + + return ( + + ) +} + export function OverviewTab({ instanceId }: { instanceId: string }) { @@ -109,7 +122,6 @@ export function OverviewTab({ instanceId }: { const { data: accessConfig } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceAccess.queryOptions({ input, })) - 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) ?? [] @@ -168,9 +180,7 @@ export function OverviewTab({ instanceId }: { ) : ( - + )} ) diff --git a/web/features/deployments/detail/settings-tab.tsx b/web/features/deployments/detail/settings-tab.tsx index 52ca4926d8..d87552d781 100644 --- a/web/features/deployments/detail/settings-tab.tsx +++ b/web/features/deployments/detail/settings-tab.tsx @@ -29,7 +29,7 @@ type DeleteInstanceControlProps = { hasDeployments: boolean } -function DeleteInstanceControl({ +function DeleteInstanceButton({ app, settings, hasDeployments, @@ -70,27 +70,14 @@ function DeleteInstanceControl({ return ( <> -
-
{t('settings.danger')}
-
- {t('settings.dangerDesc')} -
-
-
- {hasDeployments - ? t('settings.undeployFirst') - : settings?.deleteGuard?.disabledReason || t('settings.safeToDelete')} -
- -
-
+ !open && setShowDeleteConfirm(false)}> @@ -116,6 +103,35 @@ function DeleteInstanceControl({ ) } +function DeleteInstanceControl({ + app, + settings, + hasDeployments, +}: DeleteInstanceControlProps) { + const { t } = useTranslation('deployments') + + return ( +
+
{t('settings.danger')}
+
+ {t('settings.dangerDesc')} +
+
+
+ {hasDeployments + ? t('settings.undeployFirst') + : settings?.deleteGuard?.disabledReason || t('settings.safeToDelete')} +
+ +
+
+ ) +} + function SettingsForm({ app, settings }: SettingsFormProps) { const { t } = useTranslation('deployments') const updateInstance = useMutation(consoleQuery.enterprise.appDeploy.updateAppInstance.mutationOptions()) diff --git a/web/features/deployments/list/new-instance-card.tsx b/web/features/deployments/list/new-instance-card.tsx index 3e08e155cd..e11a1a6469 100644 --- a/web/features/deployments/list/new-instance-card.tsx +++ b/web/features/deployments/list/new-instance-card.tsx @@ -39,21 +39,29 @@ function NewInstanceAction({ icon, label, disabled, onClick }: NewInstanceAction ) } -export function NewInstanceCard() { +function CreateFromStudioAction() { const { t } = useTranslation('deployments') const openCreateInstanceModal = useSetAtom(openCreateInstanceModalAtom) + return ( + + ) +} + +export function NewInstanceCard() { + const { t } = useTranslation('deployments') + return (
{t('newInstance.title')}
- +