mirror of
https://github.com/langgenius/dify.git
synced 2026-05-10 05:56:31 +08:00
tweaks
This commit is contained in:
parent
e7e6ccd11a
commit
a223869a23
@ -1,74 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
ConsoleEnvironment,
|
||||
EnvironmentAccessRow,
|
||||
} from '@dify/contracts/enterprise/types.gen'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import {
|
||||
deployedRows,
|
||||
} from '../utils'
|
||||
import { AccessChannelsSection } from './access-tab/channels-section'
|
||||
import { DeveloperApiSection } from './access-tab/developer-api-section'
|
||||
import { AccessPermissionsSection } from './access-tab/permissions-section'
|
||||
import { getUrlOrigin } from './access-tab/url'
|
||||
|
||||
const EMPTY_ACCESS_PERMISSIONS: EnvironmentAccessRow[] = []
|
||||
|
||||
function uniqueEnvironments(environments: (ConsoleEnvironment | undefined)[]) {
|
||||
return environments.filter((environment, index): environment is ConsoleEnvironment => {
|
||||
if (!environment?.id)
|
||||
return false
|
||||
return environments.findIndex(candidate => candidate?.id === environment.id) === index
|
||||
})
|
||||
}
|
||||
|
||||
export function AccessTab({ instanceId: appId }: {
|
||||
instanceId: string
|
||||
}) {
|
||||
const appInput = { params: { appInstanceId: appId } }
|
||||
const { data: accessConfig } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceAccess.queryOptions({
|
||||
input: appInput,
|
||||
}))
|
||||
const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
|
||||
input: appInput,
|
||||
}))
|
||||
const deploymentRows = deployedRows(environmentDeployments?.data)
|
||||
const policies = accessConfig?.permissions ?? EMPTY_ACCESS_PERMISSIONS
|
||||
const deployedEnvs = uniqueEnvironments([
|
||||
...deploymentRows.map(row => row.environment),
|
||||
...policies.map(policy => policy.environment),
|
||||
...(accessConfig?.accessChannels?.webappRows?.map(row => row.environment) ?? []),
|
||||
])
|
||||
const apiEnabled = accessConfig?.developerApi?.enabled ?? false
|
||||
const apiKeys = accessConfig?.developerApi?.apiKeys ?? []
|
||||
const webappRows = accessConfig?.accessChannels?.webappRows?.filter(row => row.url) ?? []
|
||||
const runEnabled = accessConfig?.accessChannels?.enabled ?? false
|
||||
const cliDomain = getUrlOrigin(accessConfig?.accessChannels?.cli?.url)
|
||||
const cliDocsUrl = cliDomain ? `${cliDomain}/cli` : undefined
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-[960px] flex-col gap-5 p-6">
|
||||
<AccessPermissionsSection
|
||||
appId={appId}
|
||||
environments={deployedEnvs}
|
||||
policies={policies}
|
||||
/>
|
||||
<AccessChannelsSection
|
||||
appId={appId}
|
||||
runEnabled={runEnabled}
|
||||
webappRows={webappRows}
|
||||
cliDomain={cliDomain}
|
||||
cliDocsUrl={cliDocsUrl}
|
||||
/>
|
||||
<DeveloperApiSection
|
||||
appId={appId}
|
||||
apiEnabled={apiEnabled}
|
||||
apiUrl={accessConfig?.developerApi?.apiUrl}
|
||||
environments={deployedEnvs}
|
||||
apiKeys={apiKeys}
|
||||
/>
|
||||
<AccessPermissionsSection appId={appId} />
|
||||
<AccessChannelsSection appId={appId} />
|
||||
<DeveloperApiSection appId={appId} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,19 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import type { WebAppAccessRow } from '@dify/contracts/enterprise/types.gen'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { environmentName, webappUrl } from '../../utils'
|
||||
import { CopyPill, EndpointRow, Section } from './common'
|
||||
import { getUrlOrigin } from './url'
|
||||
|
||||
type AccessChannelsSectionProps = {
|
||||
appId: string
|
||||
runEnabled: boolean
|
||||
webappRows: WebAppAccessRow[]
|
||||
cliDomain?: string
|
||||
cliDocsUrl?: string
|
||||
}
|
||||
|
||||
function AccessChannelsSwitch({ appId, checked }: {
|
||||
@ -37,12 +33,17 @@ function AccessChannelsSwitch({ appId, checked }: {
|
||||
|
||||
export function AccessChannelsSection({
|
||||
appId,
|
||||
runEnabled,
|
||||
webappRows,
|
||||
cliDomain,
|
||||
cliDocsUrl,
|
||||
}: AccessChannelsSectionProps) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const { data: accessConfig } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceAccess.queryOptions({
|
||||
input: {
|
||||
params: { appInstanceId: appId },
|
||||
},
|
||||
}))
|
||||
const runEnabled = accessConfig?.accessChannels?.enabled ?? false
|
||||
const webappRows = accessConfig?.accessChannels?.webappRows?.filter(row => row.url) ?? []
|
||||
const cliDomain = getUrlOrigin(accessConfig?.accessChannels?.cli?.url)
|
||||
const cliDocsUrl = cliDomain ? `${cliDomain}/cli` : undefined
|
||||
|
||||
return (
|
||||
<Section
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import type { ConsoleEnvironment, DeveloperApiKeyRow } from '@dify/contracts/enterprise/types.gen'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
@ -9,13 +8,10 @@ import { consoleQuery } from '@/service/client'
|
||||
import { createdDeveloperApiTokenAtom } from '../../store'
|
||||
import { ApiKeyGenerateMenu, ApiKeyList } from './api-keys'
|
||||
import { CopyPill, Section } from './common'
|
||||
import { useAccessEnvironmentScope } from './use-access-environment-scope'
|
||||
|
||||
type DeveloperApiSectionProps = {
|
||||
appId: string
|
||||
apiEnabled: boolean
|
||||
apiUrl?: string
|
||||
environments: ConsoleEnvironment[]
|
||||
apiKeys: DeveloperApiKeyRow[]
|
||||
}
|
||||
|
||||
function DeveloperApiSwitch({ appId, checked }: {
|
||||
@ -80,12 +76,12 @@ function CreatedApiTokenCard({ appId }: {
|
||||
|
||||
export function DeveloperApiSection({
|
||||
appId,
|
||||
apiEnabled,
|
||||
apiUrl,
|
||||
environments,
|
||||
apiKeys,
|
||||
}: DeveloperApiSectionProps) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const { accessConfig, environments } = useAccessEnvironmentScope(appId)
|
||||
const apiEnabled = accessConfig?.developerApi?.enabled ?? false
|
||||
const apiUrl = accessConfig?.developerApi?.apiUrl
|
||||
const apiKeys = accessConfig?.developerApi?.apiKeys ?? []
|
||||
|
||||
return (
|
||||
<Section
|
||||
|
||||
@ -1,22 +1,19 @@
|
||||
'use client'
|
||||
|
||||
import type { ConsoleEnvironment, EnvironmentAccessRow } from '@dify/contracts/enterprise/types.gen'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Section } from './common'
|
||||
import { EnvironmentPermissionRow } from './permissions'
|
||||
import { useAccessEnvironmentScope } from './use-access-environment-scope'
|
||||
|
||||
type AccessPermissionsSectionProps = {
|
||||
appId: string
|
||||
environments: ConsoleEnvironment[]
|
||||
policies: EnvironmentAccessRow[]
|
||||
}
|
||||
|
||||
export function AccessPermissionsSection({
|
||||
appId,
|
||||
environments,
|
||||
policies,
|
||||
}: AccessPermissionsSectionProps) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const { environments, policies } = useAccessEnvironmentScope(appId)
|
||||
|
||||
return (
|
||||
<Section
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
import type {
|
||||
ConsoleEnvironment,
|
||||
EnvironmentAccessRow,
|
||||
} from '@dify/contracts/enterprise/types.gen'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { deployedRows } from '../../utils'
|
||||
|
||||
const EMPTY_ACCESS_PERMISSIONS: EnvironmentAccessRow[] = []
|
||||
|
||||
function uniqueEnvironments(environments: (ConsoleEnvironment | undefined)[]) {
|
||||
return environments.filter((environment, index): environment is ConsoleEnvironment => {
|
||||
if (!environment?.id)
|
||||
return false
|
||||
return environments.findIndex(candidate => candidate?.id === environment.id) === index
|
||||
})
|
||||
}
|
||||
|
||||
export function useAccessEnvironmentScope(appId: string) {
|
||||
const appInput = { params: { appInstanceId: appId } }
|
||||
const { data: accessConfig } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceAccess.queryOptions({
|
||||
input: appInput,
|
||||
}))
|
||||
const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
|
||||
input: appInput,
|
||||
}))
|
||||
const deploymentRows = deployedRows(environmentDeployments?.data)
|
||||
const policies = accessConfig?.permissions ?? EMPTY_ACCESS_PERMISSIONS
|
||||
const environments = uniqueEnvironments([
|
||||
...deploymentRows.map(row => row.environment),
|
||||
...policies.map(policy => policy.environment),
|
||||
...(accessConfig?.accessChannels?.webappRows?.map(row => row.environment) ?? []),
|
||||
])
|
||||
|
||||
return {
|
||||
accessConfig,
|
||||
environments,
|
||||
policies,
|
||||
}
|
||||
}
|
||||
@ -101,12 +101,41 @@ function DeployFromOverviewButton({ appId }: {
|
||||
)
|
||||
}
|
||||
|
||||
export function OverviewTab({ instanceId }: {
|
||||
instanceId: string
|
||||
function BasicInfoSection({ appId }: {
|
||||
appId: string
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const { t: tCommon } = useTranslation()
|
||||
const input = { params: { appInstanceId: instanceId } }
|
||||
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
|
||||
input: {
|
||||
params: { appInstanceId: appId },
|
||||
},
|
||||
}))
|
||||
const overviewApp = overview?.instance
|
||||
|
||||
if (!overviewApp?.id)
|
||||
return null
|
||||
|
||||
const appName = overviewApp.name ?? overviewApp.id
|
||||
const appModeLabel = getAppModeLabel(toAppMode(overviewApp.mode), tCommon)
|
||||
|
||||
return (
|
||||
<Section title={t('overview.basicInfo')}>
|
||||
<div className="flex flex-col divide-y divide-divider-subtle">
|
||||
<InfoRow label={t('overview.name')} value={appName} />
|
||||
<InfoRow label={t('overview.description')} value={overviewApp.description ?? t('overview.emptyValue')} />
|
||||
<InfoRow label={t('overview.sourceApp')} value={overviewApp.sourceAppName ?? appName} />
|
||||
<InfoRow label={t('overview.appMode')} value={appModeLabel} />
|
||||
</div>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
|
||||
function DeploymentStatusSection({ appId }: {
|
||||
appId: string
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const input = { params: { appInstanceId: appId } }
|
||||
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
|
||||
input,
|
||||
}))
|
||||
@ -119,9 +148,6 @@ export function OverviewTab({ instanceId }: {
|
||||
},
|
||||
},
|
||||
}))
|
||||
const { data: accessConfig } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceAccess.queryOptions({
|
||||
input,
|
||||
}))
|
||||
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) ?? []
|
||||
@ -130,112 +156,124 @@ export function OverviewTab({ instanceId }: {
|
||||
if (!overviewApp?.id)
|
||||
return null
|
||||
|
||||
const appId = overviewApp.id
|
||||
const appName = overviewApp.name ?? appId
|
||||
const appModeLabel = getAppModeLabel(toAppMode(overviewApp.mode), tCommon)
|
||||
return (
|
||||
<Section
|
||||
title={t('overview.deploymentStatus')}
|
||||
action={(
|
||||
<Button nativeButton={false} size="small" variant="secondary" render={<Link href={`/deployments/${appId}/deploy`} />}>
|
||||
{t('overview.viewDeployments')}
|
||||
<span className="i-ri-arrow-right-up-line h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
>
|
||||
{deployments.length === 0
|
||||
? (
|
||||
<div className="flex flex-col items-center gap-3 rounded-lg border border-dashed border-components-panel-border bg-components-panel-bg-blur px-4 py-8 text-center">
|
||||
<span className="i-ri-rocket-line h-5 w-5 text-text-quaternary" />
|
||||
<div className="system-sm-regular text-text-tertiary">
|
||||
{releaseRows.length === 0
|
||||
? t(canCreateRelease ? 'overview.noReleaseYet' : 'overview.noReleaseSourceUnavailable')
|
||||
: t('overview.notDeployedYet')}
|
||||
</div>
|
||||
{releaseRows.length === 0
|
||||
? canCreateRelease
|
||||
? (
|
||||
<Button nativeButton={false} size="small" variant="primary" render={<Link href={`/deployments/${appId}/versions`} />}>
|
||||
{t('overview.createRelease')}
|
||||
</Button>
|
||||
)
|
||||
: (
|
||||
<Button size="small" variant="primary" disabled>
|
||||
{t('overview.createRelease')}
|
||||
</Button>
|
||||
)
|
||||
: (
|
||||
<DeployFromOverviewButton appId={appId} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className="flex flex-col divide-y divide-divider-subtle">
|
||||
{deployments.map((row) => {
|
||||
const status = overviewDeploymentStatus(row.status)
|
||||
return (
|
||||
<div key={row.environment?.id} className="flex items-center justify-between gap-3 py-2">
|
||||
<div className="flex min-w-0 flex-col">
|
||||
<span className="system-sm-medium text-text-primary">{row.environment?.name || row.environment?.id}</span>
|
||||
<span className="system-xs-regular text-text-tertiary">
|
||||
{releaseLabel(row.release) || t('overview.emptyValue')}
|
||||
</span>
|
||||
</div>
|
||||
<StatusBadge status={status} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
|
||||
function AccessStatusSection({ appId }: {
|
||||
appId: string
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const input = { params: { appInstanceId: appId } }
|
||||
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
|
||||
input,
|
||||
}))
|
||||
const { data: accessConfig } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceAccess.queryOptions({
|
||||
input,
|
||||
}))
|
||||
const webappAccessUrl = webappUrl(overview?.access?.webappUrl)
|
||||
const cliUrl = overview?.access?.cliUrl
|
||||
const apiUrl = overview?.access?.apiUrl ?? accessConfig?.developerApi?.apiUrl
|
||||
const apiKeysCount = overview?.access?.apiKeyCount ?? accessConfig?.developerApi?.apiKeys?.length ?? 0
|
||||
|
||||
return (
|
||||
<Section
|
||||
title={t('overview.accessStatus')}
|
||||
action={(
|
||||
<Button nativeButton={false} size="small" variant="secondary" render={<Link href={`/deployments/${appId}/access`} />}>
|
||||
{t('overview.configureAccess')}
|
||||
<span className="i-ri-arrow-right-up-line h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col divide-y divide-divider-subtle">
|
||||
<AccessOverviewRow
|
||||
label={t('overview.webapp')}
|
||||
enabled={overview?.access?.accessChannelsEnabled ?? false}
|
||||
hint={webappAccessUrl || t('overview.notConfigured')}
|
||||
/>
|
||||
<AccessOverviewRow
|
||||
label={t('overview.cli')}
|
||||
enabled={overview?.access?.accessChannelsEnabled ?? false}
|
||||
hint={cliUrl ?? t('overview.notConfigured')}
|
||||
/>
|
||||
<AccessOverviewRow
|
||||
label={t('overview.api')}
|
||||
enabled={overview?.access?.developerApiEnabled ?? false}
|
||||
hint={overview?.access?.developerApiEnabled
|
||||
? apiUrl || t('overview.notConfigured')
|
||||
: t('overview.notConfigured')}
|
||||
meta={overview?.access?.developerApiEnabled
|
||||
? t('overview.apiKeysCount', { count: apiKeysCount })
|
||||
: undefined}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
|
||||
export function OverviewTab({ instanceId: appId }: {
|
||||
instanceId: string
|
||||
}) {
|
||||
return (
|
||||
<div className="flex w-full max-w-[960px] flex-col gap-5 p-6">
|
||||
<Section title={t('overview.basicInfo')}>
|
||||
<div className="flex flex-col divide-y divide-divider-subtle">
|
||||
<InfoRow label={t('overview.name')} value={appName} />
|
||||
<InfoRow label={t('overview.description')} value={overviewApp.description ?? t('overview.emptyValue')} />
|
||||
<InfoRow label={t('overview.sourceApp')} value={overviewApp.sourceAppName ?? appName} />
|
||||
<InfoRow label={t('overview.appMode')} value={appModeLabel} />
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title={t('overview.deploymentStatus')}
|
||||
action={(
|
||||
<Button nativeButton={false} size="small" variant="secondary" render={<Link href={`/deployments/${appId}/deploy`} />}>
|
||||
{t('overview.viewDeployments')}
|
||||
<span className="i-ri-arrow-right-up-line h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
>
|
||||
{deployments.length === 0
|
||||
? (
|
||||
<div className="flex flex-col items-center gap-3 rounded-lg border border-dashed border-components-panel-border bg-components-panel-bg-blur px-4 py-8 text-center">
|
||||
<span className="i-ri-rocket-line h-5 w-5 text-text-quaternary" />
|
||||
<div className="system-sm-regular text-text-tertiary">
|
||||
{releaseRows.length === 0
|
||||
? t(canCreateRelease ? 'overview.noReleaseYet' : 'overview.noReleaseSourceUnavailable')
|
||||
: t('overview.notDeployedYet')}
|
||||
</div>
|
||||
{releaseRows.length === 0
|
||||
? canCreateRelease
|
||||
? (
|
||||
<Button nativeButton={false} size="small" variant="primary" render={<Link href={`/deployments/${appId}/versions`} />}>
|
||||
{t('overview.createRelease')}
|
||||
</Button>
|
||||
)
|
||||
: (
|
||||
<Button size="small" variant="primary" disabled>
|
||||
{t('overview.createRelease')}
|
||||
</Button>
|
||||
)
|
||||
: (
|
||||
<DeployFromOverviewButton appId={appId} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className="flex flex-col divide-y divide-divider-subtle">
|
||||
{deployments.map((row) => {
|
||||
const status = overviewDeploymentStatus(row.status)
|
||||
return (
|
||||
<div key={row.environment?.id} className="flex items-center justify-between gap-3 py-2">
|
||||
<div className="flex min-w-0 flex-col">
|
||||
<span className="system-sm-medium text-text-primary">{row.environment?.name || row.environment?.id}</span>
|
||||
<span className="system-xs-regular text-text-tertiary">
|
||||
{releaseLabel(row.release) || t('overview.emptyValue')}
|
||||
</span>
|
||||
</div>
|
||||
<StatusBadge status={status} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title={t('overview.accessStatus')}
|
||||
action={(
|
||||
<Button nativeButton={false} size="small" variant="secondary" render={<Link href={`/deployments/${appId}/access`} />}>
|
||||
{t('overview.configureAccess')}
|
||||
<span className="i-ri-arrow-right-up-line h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col divide-y divide-divider-subtle">
|
||||
<AccessOverviewRow
|
||||
label={t('overview.webapp')}
|
||||
enabled={overview?.access?.accessChannelsEnabled ?? false}
|
||||
hint={webappAccessUrl || t('overview.notConfigured')}
|
||||
/>
|
||||
<AccessOverviewRow
|
||||
label={t('overview.cli')}
|
||||
enabled={overview?.access?.accessChannelsEnabled ?? false}
|
||||
hint={cliUrl ?? t('overview.notConfigured')}
|
||||
/>
|
||||
<AccessOverviewRow
|
||||
label={t('overview.api')}
|
||||
enabled={overview?.access?.developerApiEnabled ?? false}
|
||||
hint={overview?.access?.developerApiEnabled
|
||||
? apiUrl || t('overview.notConfigured')
|
||||
: t('overview.notConfigured')}
|
||||
meta={overview?.access?.developerApiEnabled
|
||||
? t('overview.apiKeysCount', { count: apiKeysCount })
|
||||
: undefined}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
<BasicInfoSection appId={appId} />
|
||||
<DeploymentStatusSection appId={appId} />
|
||||
<AccessStatusSection appId={appId} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -216,16 +216,13 @@ function SettingsForm({ app, settings }: SettingsFormProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsTab({ instanceId }: {
|
||||
instanceId: string
|
||||
function SettingsFormSection({ appId }: {
|
||||
appId: string
|
||||
}) {
|
||||
const appInput = { params: { appInstanceId: instanceId } }
|
||||
const appInput = { params: { appInstanceId: appId } }
|
||||
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
|
||||
input: appInput,
|
||||
}))
|
||||
const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
|
||||
input: appInput,
|
||||
}))
|
||||
const app = overview?.instance
|
||||
const settingsQuery = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceSettings.queryOptions({
|
||||
input: appInput,
|
||||
@ -234,22 +231,54 @@ export function SettingsTab({ instanceId }: {
|
||||
if (!app?.id)
|
||||
return null
|
||||
|
||||
const hasDeployments = deployedRows(environmentDeployments?.data).length > 0
|
||||
const appName = app.name ?? app.id
|
||||
const formKey = `${app.id}-${settingsQuery.data?.name ?? appName}-${settingsQuery.data?.description ?? app.description ?? ''}`
|
||||
|
||||
return (
|
||||
<SettingsForm
|
||||
key={formKey}
|
||||
app={app}
|
||||
settings={settingsQuery.data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DeleteInstanceControlSection({ appId }: {
|
||||
appId: string
|
||||
}) {
|
||||
const appInput = { params: { appInstanceId: appId } }
|
||||
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
|
||||
input: appInput,
|
||||
}))
|
||||
const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
|
||||
input: appInput,
|
||||
}))
|
||||
const settingsQuery = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceSettings.queryOptions({
|
||||
input: appInput,
|
||||
}))
|
||||
const app = overview?.instance
|
||||
|
||||
if (!app?.id)
|
||||
return null
|
||||
|
||||
const hasDeployments = deployedRows(environmentDeployments?.data).length > 0
|
||||
|
||||
return (
|
||||
<DeleteInstanceControl
|
||||
app={app}
|
||||
settings={settingsQuery.data}
|
||||
hasDeployments={hasDeployments}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsTab({ instanceId: appId }: {
|
||||
instanceId: string
|
||||
}) {
|
||||
return (
|
||||
<div className="flex max-w-[640px] flex-col gap-5 p-6">
|
||||
<SettingsForm
|
||||
key={formKey}
|
||||
app={app}
|
||||
settings={settingsQuery.data}
|
||||
/>
|
||||
<DeleteInstanceControl
|
||||
app={app}
|
||||
settings={settingsQuery.data}
|
||||
hasDeployments={hasDeployments}
|
||||
/>
|
||||
<SettingsFormSection appId={appId} />
|
||||
<DeleteInstanceControlSection appId={appId} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,26 +1,15 @@
|
||||
'use client'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { DEPLOYMENT_PAGE_SIZE } from '../data'
|
||||
import {
|
||||
deployedRows,
|
||||
formatDate,
|
||||
releaseCommit,
|
||||
releaseLabel,
|
||||
} from '../utils'
|
||||
import { DeployReleaseMenu } from './versions-tab/deploy-release-menu'
|
||||
import { DeployedToBadge } from './versions-tab/deployed-to-badge'
|
||||
import { getReleaseDeployments } from './versions-tab/release-deployments'
|
||||
|
||||
const GRID_TEMPLATE = 'grid-cols-[minmax(0,0.9fr)_minmax(0,1fr)_minmax(0,0.8fr)_minmax(0,1.5fr)_96px]'
|
||||
import { deployedRows } from '../utils'
|
||||
import { ReleaseHistoryTable } from './versions-tab/release-history-table'
|
||||
|
||||
function CreateReleaseControl({ appId, canCreateRelease }: {
|
||||
appId: string
|
||||
@ -212,104 +201,11 @@ export function VersionsTab({ instanceId: appId }: {
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className="overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg">
|
||||
<div className={cn(
|
||||
'hidden items-center gap-4 border-b border-divider-subtle px-4 py-3 system-xs-medium-uppercase text-text-tertiary pc:grid',
|
||||
GRID_TEMPLATE,
|
||||
)}
|
||||
>
|
||||
<div>{t('versions.col.release')}</div>
|
||||
<div>{t('versions.col.createdAt')}</div>
|
||||
<div>{t('versions.col.author')}</div>
|
||||
<div>{t('versions.col.deployedTo')}</div>
|
||||
<div className="text-right">{t('versions.col.action')}</div>
|
||||
</div>
|
||||
|
||||
{releaseRows.map((row) => {
|
||||
const release = row
|
||||
const releaseDeployments = getReleaseDeployments(row, deploymentRows)
|
||||
return (
|
||||
<div key={release.id} className="border-b border-divider-subtle last:border-b-0">
|
||||
<div className="flex flex-col gap-3 p-4 pc:hidden">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<span className="inline-flex max-w-full cursor-default truncate font-mono system-sm-medium text-text-primary">
|
||||
{releaseLabel(release)}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{t('versions.commitTooltip', { commit: releaseCommit(release) })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 system-xs-regular text-text-secondary">
|
||||
<span>{formatDate(release.createdAt)}</span>
|
||||
<span aria-hidden>·</span>
|
||||
<span>{row.createdBy?.name ?? '—'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 justify-end gap-1">
|
||||
<DeployReleaseMenu releaseId={release.id!} appInstanceId={appId} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<div className="shrink-0 system-xs-medium-uppercase text-text-tertiary">
|
||||
{t('versions.col.deployedTo')}
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-wrap gap-1">
|
||||
{releaseDeployments.length === 0
|
||||
? <span className="system-sm-regular text-text-quaternary">—</span>
|
||||
: releaseDeployments.map(item => (
|
||||
<DeployedToBadge
|
||||
key={`${item.environmentId}-${item.state}`}
|
||||
item={item}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn(
|
||||
'hidden items-center gap-4 px-4 py-3 pc:grid',
|
||||
GRID_TEMPLATE,
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<span className="inline-flex cursor-default font-mono system-sm-medium text-text-primary">
|
||||
{releaseLabel(release)}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{t('versions.commitTooltip', { commit: releaseCommit(release) })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="system-sm-regular text-text-secondary">{formatDate(release.createdAt)}</div>
|
||||
<div className="system-sm-regular text-text-secondary">{row.createdBy?.name ?? '—'}</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{releaseDeployments.length === 0
|
||||
? <span className="system-sm-regular text-text-quaternary">—</span>
|
||||
: releaseDeployments.map(item => (
|
||||
<DeployedToBadge
|
||||
key={`${item.environmentId}-${item.state}`}
|
||||
item={item}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-end gap-1">
|
||||
<DeployReleaseMenu releaseId={release.id!} appInstanceId={appId} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<ReleaseHistoryTable
|
||||
appId={appId}
|
||||
releaseRows={releaseRows}
|
||||
deploymentRows={deploymentRows}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -0,0 +1,125 @@
|
||||
'use client'
|
||||
|
||||
import type { ReleaseRow, RuntimeInstanceRow } from '@dify/contracts/enterprise/types.gen'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
formatDate,
|
||||
releaseCommit,
|
||||
releaseLabel,
|
||||
} from '../../utils'
|
||||
import { DeployReleaseMenu } from './deploy-release-menu'
|
||||
import { DeployedToBadge } from './deployed-to-badge'
|
||||
import { getReleaseDeployments } from './release-deployments'
|
||||
|
||||
const GRID_TEMPLATE = 'grid-cols-[minmax(0,0.9fr)_minmax(0,1fr)_minmax(0,0.8fr)_minmax(0,1.5fr)_96px]'
|
||||
|
||||
export function ReleaseHistoryTable({ appId, releaseRows, deploymentRows }: {
|
||||
appId: string
|
||||
releaseRows: ReleaseRow[]
|
||||
deploymentRows: RuntimeInstanceRow[]
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg">
|
||||
<div className={cn(
|
||||
'hidden items-center gap-4 border-b border-divider-subtle px-4 py-3 system-xs-medium-uppercase text-text-tertiary pc:grid',
|
||||
GRID_TEMPLATE,
|
||||
)}
|
||||
>
|
||||
<div>{t('versions.col.release')}</div>
|
||||
<div>{t('versions.col.createdAt')}</div>
|
||||
<div>{t('versions.col.author')}</div>
|
||||
<div>{t('versions.col.deployedTo')}</div>
|
||||
<div className="text-right">{t('versions.col.action')}</div>
|
||||
</div>
|
||||
|
||||
{releaseRows.map((row) => {
|
||||
const release = row
|
||||
const releaseDeployments = getReleaseDeployments(row, deploymentRows)
|
||||
return (
|
||||
<div key={release.id} className="border-b border-divider-subtle last:border-b-0">
|
||||
<div className="flex flex-col gap-3 p-4 pc:hidden">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<span className="inline-flex max-w-full cursor-default truncate font-mono system-sm-medium text-text-primary">
|
||||
{releaseLabel(release)}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{t('versions.commitTooltip', { commit: releaseCommit(release) })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 system-xs-regular text-text-secondary">
|
||||
<span>{formatDate(release.createdAt)}</span>
|
||||
<span aria-hidden>·</span>
|
||||
<span>{row.createdBy?.name ?? '—'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 justify-end gap-1">
|
||||
<DeployReleaseMenu releaseId={release.id!} appInstanceId={appId} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<div className="shrink-0 system-xs-medium-uppercase text-text-tertiary">
|
||||
{t('versions.col.deployedTo')}
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-wrap gap-1">
|
||||
{releaseDeployments.length === 0
|
||||
? <span className="system-sm-regular text-text-quaternary">—</span>
|
||||
: releaseDeployments.map(item => (
|
||||
<DeployedToBadge
|
||||
key={`${item.environmentId}-${item.state}`}
|
||||
item={item}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn(
|
||||
'hidden items-center gap-4 px-4 py-3 pc:grid',
|
||||
GRID_TEMPLATE,
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<span className="inline-flex cursor-default font-mono system-sm-medium text-text-primary">
|
||||
{releaseLabel(release)}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{t('versions.commitTooltip', { commit: releaseCommit(release) })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="system-sm-regular text-text-secondary">{formatDate(release.createdAt)}</div>
|
||||
<div className="system-sm-regular text-text-secondary">{row.createdBy?.name ?? '—'}</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{releaseDeployments.length === 0
|
||||
? <span className="system-sm-regular text-text-quaternary">—</span>
|
||||
: releaseDeployments.map(item => (
|
||||
<DeployedToBadge
|
||||
key={`${item.environmentId}-${item.state}`}
|
||||
item={item}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-end gap-1">
|
||||
<DeployReleaseMenu releaseId={release.id!} appInstanceId={appId} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user