mirror of
https://github.com/langgenius/dify.git
synced 2026-05-09 04:36:31 +08:00
tweaks
This commit is contained in:
parent
64fc1e8281
commit
04124edd70
@ -4,6 +4,7 @@ import type {
|
||||
AccessPermission,
|
||||
AccessSubject,
|
||||
ConsoleEnvironmentSummary,
|
||||
DeveloperAPIKeySummary,
|
||||
} from '@/features/deployments/types'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
@ -26,44 +27,39 @@ function uniqueEnvironments(environments: (ConsoleEnvironmentSummary | undefined
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
}))
|
||||
type DeveloperApiAccessSectionProps = {
|
||||
appId: string
|
||||
apiEnabled: boolean
|
||||
apiUrl?: string
|
||||
environments: ConsoleEnvironmentSummary[]
|
||||
apiKeys: DeveloperAPIKeySummary[]
|
||||
}
|
||||
|
||||
function DeveloperApiAccessSection({
|
||||
appId,
|
||||
apiEnabled,
|
||||
apiUrl,
|
||||
environments,
|
||||
apiKeys,
|
||||
}: DeveloperApiAccessSectionProps) {
|
||||
const [createdApiToken, setCreatedApiToken] = useState<{
|
||||
appId: string
|
||||
token: string
|
||||
}>()
|
||||
const generateApiKey = useMutation(consoleQuery.enterprise.appDeploy.createDeveloperApiKey.mutationOptions())
|
||||
const revokeApiKey = useMutation(consoleQuery.enterprise.appDeploy.deleteDeveloperApiKey.mutationOptions())
|
||||
const toggleAccessChannel = useMutation(consoleQuery.enterprise.appDeploy.updateAccessChannels.mutationOptions())
|
||||
const toggleDeveloperAPI = useMutation(consoleQuery.enterprise.appDeploy.updateDeveloperApi.mutationOptions())
|
||||
const setEnvironmentAccessPolicy = useMutation(consoleQuery.enterprise.appDeploy.updateEnvironmentAccessPolicy.mutationOptions())
|
||||
|
||||
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 createApiKeyLabel = (environmentId: string) => {
|
||||
function createApiKeyLabel(environmentId: string) {
|
||||
const existingCount = apiKeys.filter(key =>
|
||||
key.environment?.id === environmentId,
|
||||
).length
|
||||
const name = deployedEnvs.find(env => env.id === environmentId)?.name ?? 'env'
|
||||
const name = environments.find(env => env.id === environmentId)?.name ?? 'env'
|
||||
|
||||
return `${name}-key-${String(existingCount + 1).padStart(3, '0')}`
|
||||
}
|
||||
const handleGenerateApiKey = (environmentId: string) => {
|
||||
|
||||
function handleGenerateApiKey(environmentId: string) {
|
||||
generateApiKey.mutate(
|
||||
{
|
||||
params: {
|
||||
@ -82,7 +78,8 @@ export function AccessTab({ instanceId: appId }: {
|
||||
},
|
||||
)
|
||||
}
|
||||
const handleRevokeApiKey = (_environmentId: string, apiKeyId: string) => {
|
||||
|
||||
function handleRevokeApiKey(_environmentId: string, apiKeyId: string) {
|
||||
revokeApiKey.mutate({
|
||||
params: {
|
||||
appInstanceId: appId,
|
||||
@ -90,7 +87,8 @@ export function AccessTab({ instanceId: appId }: {
|
||||
},
|
||||
})
|
||||
}
|
||||
const handleCopyApiKey = async (apiKeyId: string) => {
|
||||
|
||||
async function handleCopyApiKey(apiKeyId: string) {
|
||||
const response = await consoleClient.enterprise.appDeploy.revealDeveloperApiKey({
|
||||
params: {
|
||||
appInstanceId: appId,
|
||||
@ -101,6 +99,52 @@ export function AccessTab({ instanceId: appId }: {
|
||||
throw new Error('Reveal developer API key did not return a token.')
|
||||
return response.token
|
||||
}
|
||||
|
||||
const visibleCreatedApiToken = createdApiToken?.appId === appId
|
||||
? createdApiToken.token
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<DeveloperApiSection
|
||||
apiEnabled={apiEnabled}
|
||||
apiUrl={apiUrl}
|
||||
environments={environments}
|
||||
apiKeys={apiKeys}
|
||||
createdToken={visibleCreatedApiToken}
|
||||
onToggle={enabled => toggleDeveloperAPI.mutate({
|
||||
params: { appInstanceId: appId },
|
||||
body: { enabled },
|
||||
})}
|
||||
onGenerate={handleGenerateApiKey}
|
||||
onCopyApiKey={handleCopyApiKey}
|
||||
onRevoke={handleRevokeApiKey}
|
||||
onClearCreatedToken={() => setCreatedApiToken(undefined)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
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 toggleAccessChannel = useMutation(consoleQuery.enterprise.appDeploy.updateAccessChannels.mutationOptions())
|
||||
const setEnvironmentAccessPolicy = useMutation(consoleQuery.enterprise.appDeploy.updateEnvironmentAccessPolicy.mutationOptions())
|
||||
|
||||
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 handleSetEnvironmentAccessPolicy = async (
|
||||
appId: string,
|
||||
environmentId: string,
|
||||
@ -120,9 +164,6 @@ export function AccessTab({ instanceId: appId }: {
|
||||
}
|
||||
const webappRows = accessConfig?.accessChannels?.webappRows?.filter(row => row.url) ?? []
|
||||
const runEnabled = accessConfig?.accessChannels?.enabled ?? false
|
||||
const visibleCreatedApiToken = createdApiToken?.appId === appId
|
||||
? createdApiToken.token
|
||||
: undefined
|
||||
const cliDomain = getUrlOrigin(accessConfig?.accessChannels?.cli?.url)
|
||||
const cliDocsUrl = cliDomain ? `${cliDomain}/cli` : undefined
|
||||
|
||||
@ -144,20 +185,12 @@ export function AccessTab({ instanceId: appId }: {
|
||||
body: { enabled },
|
||||
})}
|
||||
/>
|
||||
<DeveloperApiSection
|
||||
<DeveloperApiAccessSection
|
||||
appId={appId}
|
||||
apiEnabled={apiEnabled}
|
||||
apiUrl={accessConfig?.developerApi?.apiUrl}
|
||||
environments={deployedEnvs}
|
||||
apiKeys={apiKeys}
|
||||
createdToken={visibleCreatedApiToken}
|
||||
onToggle={enabled => toggleDeveloperAPI.mutate({
|
||||
params: { appInstanceId: appId },
|
||||
body: { enabled },
|
||||
})}
|
||||
onGenerate={handleGenerateApiKey}
|
||||
onCopyApiKey={handleCopyApiKey}
|
||||
onRevoke={handleRevokeApiKey}
|
||||
onClearCreatedToken={() => setCreatedApiToken(undefined)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
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 {
|
||||
@ -32,6 +33,66 @@ 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]'
|
||||
|
||||
function NewDeploymentMenu({ appInstanceId, availableEnvs }: {
|
||||
appInstanceId: string
|
||||
availableEnvs: EnvironmentOption[]
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer)
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<DropdownMenu modal={false} open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger
|
||||
className={cn(
|
||||
'inline-flex h-8 shrink-0 items-center gap-1 rounded-lg px-3 system-sm-medium',
|
||||
'border border-components-button-primary-border bg-components-button-primary-bg text-components-button-primary-text',
|
||||
'hover:bg-components-button-primary-bg-hover',
|
||||
)}
|
||||
>
|
||||
<span className="i-ri-rocket-line h-3.5 w-3.5" />
|
||||
{t('deployTab.newDeployment')}
|
||||
<span className="i-ri-arrow-down-s-line h-3.5 w-3.5" />
|
||||
</DropdownMenuTrigger>
|
||||
{open && (
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-[220px]">
|
||||
<DropdownMenuItem
|
||||
className="gap-2 px-3"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
openDeployDrawer({ appInstanceId })
|
||||
}}
|
||||
>
|
||||
<span className="system-sm-regular text-text-secondary">{t('deployTab.deployToNewEnv')}</span>
|
||||
</DropdownMenuItem>
|
||||
{availableEnvs.length > 0 && (
|
||||
<>
|
||||
<div className="px-3 py-1 system-xs-medium-uppercase text-text-quaternary">{t('deployTab.shortcut')}</div>
|
||||
{availableEnvs.map(env => (
|
||||
<DropdownMenuItem
|
||||
key={env.id}
|
||||
className="gap-2 px-3"
|
||||
disabled={env.disabled}
|
||||
onClick={() => {
|
||||
if (env.disabled)
|
||||
return
|
||||
setOpen(false)
|
||||
openDeployDrawer({ appInstanceId, environmentId: env.id })
|
||||
}}
|
||||
>
|
||||
<span className="system-sm-regular text-text-secondary">
|
||||
{t('deployTab.deployToEnv', { name: environmentName(env) })}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
export function DeployTab({ instanceId: appInstanceId }: {
|
||||
instanceId: string
|
||||
}) {
|
||||
@ -64,7 +125,6 @@ export function DeployTab({ instanceId: appInstanceId }: {
|
||||
return current === id ? null : id
|
||||
})
|
||||
}
|
||||
const [deployMenuOpen, setDeployMenuOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-[960px] flex-col gap-4 p-6">
|
||||
@ -78,54 +138,7 @@ export function DeployTab({ instanceId: appInstanceId }: {
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
<DropdownMenu modal={false} open={deployMenuOpen} onOpenChange={setDeployMenuOpen}>
|
||||
<DropdownMenuTrigger
|
||||
className={cn(
|
||||
'inline-flex h-8 shrink-0 items-center gap-1 rounded-lg px-3 system-sm-medium',
|
||||
'border border-components-button-primary-border bg-components-button-primary-bg text-components-button-primary-text',
|
||||
'hover:bg-components-button-primary-bg-hover',
|
||||
)}
|
||||
>
|
||||
<span className="i-ri-rocket-line h-3.5 w-3.5" />
|
||||
{t('deployTab.newDeployment')}
|
||||
<span className="i-ri-arrow-down-s-line h-3.5 w-3.5" />
|
||||
</DropdownMenuTrigger>
|
||||
{deployMenuOpen && (
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-[220px]">
|
||||
<DropdownMenuItem
|
||||
className="gap-2 px-3"
|
||||
onClick={() => {
|
||||
setDeployMenuOpen(false)
|
||||
openDeployDrawer({ appInstanceId })
|
||||
}}
|
||||
>
|
||||
<span className="system-sm-regular text-text-secondary">{t('deployTab.deployToNewEnv')}</span>
|
||||
</DropdownMenuItem>
|
||||
{availableEnvs.length > 0 && (
|
||||
<>
|
||||
<div className="px-3 py-1 system-xs-medium-uppercase text-text-quaternary">{t('deployTab.shortcut')}</div>
|
||||
{availableEnvs.map(env => (
|
||||
<DropdownMenuItem
|
||||
key={env.id}
|
||||
className="gap-2 px-3"
|
||||
disabled={env.disabled}
|
||||
onClick={() => {
|
||||
if (env.disabled)
|
||||
return
|
||||
setDeployMenuOpen(false)
|
||||
openDeployDrawer({ appInstanceId, environmentId: env.id })
|
||||
}}
|
||||
>
|
||||
<span className="system-sm-regular text-text-secondary">
|
||||
{t('deployTab.deployToEnv', { name: environmentName(env) })}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
<NewDeploymentMenu appInstanceId={appInstanceId} availableEnvs={availableEnvs} />
|
||||
</div>
|
||||
|
||||
{rows.length === 0
|
||||
|
||||
@ -23,37 +23,19 @@ 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]'
|
||||
|
||||
export function VersionsTab({ instanceId: appId }: {
|
||||
instanceId: string
|
||||
function CreateReleaseControl({ appId, canCreateRelease }: {
|
||||
appId: string
|
||||
canCreateRelease: boolean
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const input = { params: { appInstanceId: appId } }
|
||||
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
|
||||
input,
|
||||
}))
|
||||
const { data: releaseHistory } = useQuery(consoleQuery.enterprise.appDeploy.listReleases.queryOptions({
|
||||
input: {
|
||||
...input,
|
||||
query: {
|
||||
pageNumber: 1,
|
||||
resultsPerPage: DEPLOYMENT_PAGE_SIZE,
|
||||
},
|
||||
},
|
||||
}))
|
||||
const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
|
||||
input,
|
||||
}))
|
||||
const createRelease = useMutation(consoleQuery.enterprise.appDeploy.createRelease.mutationOptions())
|
||||
const [isCreating, setIsCreating] = useState(false)
|
||||
const [releaseName, setReleaseName] = useState('')
|
||||
const [releaseDescription, setReleaseDescription] = useState('')
|
||||
const releaseRows = releaseHistory?.data?.filter(row => row.id) ?? []
|
||||
const deploymentRows = deployedRows(environmentDeployments?.data)
|
||||
const canCreateRelease = overview?.instance?.canCreateRelease ?? true
|
||||
const trimmedReleaseName = releaseName.trim()
|
||||
const canSubmitRelease = Boolean(canCreateRelease && trimmedReleaseName && !createRelease.isPending)
|
||||
|
||||
const handleCreateRelease = async () => {
|
||||
async function handleCreateRelease() {
|
||||
if (!canSubmitRelease)
|
||||
return
|
||||
|
||||
@ -79,33 +61,16 @@ export function VersionsTab({ instanceId: appId }: {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-[960px] flex-col gap-4 p-6">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="system-sm-semibold text-text-primary">
|
||||
{t('versions.releaseHistory')}
|
||||
{' '}
|
||||
<span className="system-sm-regular text-text-tertiary">
|
||||
(
|
||||
{releaseRows.length}
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
size="small"
|
||||
variant="primary"
|
||||
disabled={!canCreateRelease}
|
||||
onClick={() => setIsCreating(true)}
|
||||
>
|
||||
<span className="i-ri-add-line h-3.5 w-3.5" />
|
||||
{t('versions.createRelease')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{!canCreateRelease && (
|
||||
<div className="rounded-lg border border-divider-subtle bg-background-default-subtle px-3 py-2 system-sm-regular text-text-tertiary">
|
||||
{t('versions.sourceAppUnavailable')}
|
||||
</div>
|
||||
)}
|
||||
<>
|
||||
<Button
|
||||
size="small"
|
||||
variant="primary"
|
||||
disabled={!canCreateRelease}
|
||||
onClick={() => setIsCreating(true)}
|
||||
>
|
||||
<span className="i-ri-add-line h-3.5 w-3.5" />
|
||||
{t('versions.createRelease')}
|
||||
</Button>
|
||||
|
||||
<Dialog open={isCreating} onOpenChange={setIsCreating}>
|
||||
<DialogContent className="w-[560px] overflow-hidden p-0">
|
||||
@ -192,6 +157,54 @@ export function VersionsTab({ instanceId: appId }: {
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function VersionsTab({ instanceId: appId }: {
|
||||
instanceId: string
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const input = { params: { appInstanceId: appId } }
|
||||
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
|
||||
input,
|
||||
}))
|
||||
const { data: releaseHistory } = useQuery(consoleQuery.enterprise.appDeploy.listReleases.queryOptions({
|
||||
input: {
|
||||
...input,
|
||||
query: {
|
||||
pageNumber: 1,
|
||||
resultsPerPage: DEPLOYMENT_PAGE_SIZE,
|
||||
},
|
||||
},
|
||||
}))
|
||||
const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({
|
||||
input,
|
||||
}))
|
||||
const releaseRows = releaseHistory?.data?.filter(row => row.id) ?? []
|
||||
const deploymentRows = deployedRows(environmentDeployments?.data)
|
||||
const canCreateRelease = overview?.instance?.canCreateRelease ?? true
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-[960px] flex-col gap-4 p-6">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="system-sm-semibold text-text-primary">
|
||||
{t('versions.releaseHistory')}
|
||||
{' '}
|
||||
<span className="system-sm-regular text-text-tertiary">
|
||||
(
|
||||
{releaseRows.length}
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
<CreateReleaseControl appId={appId} canCreateRelease={canCreateRelease} />
|
||||
</div>
|
||||
|
||||
{!canCreateRelease && (
|
||||
<div className="rounded-lg border border-divider-subtle bg-background-default-subtle px-3 py-2 system-sm-regular text-text-tertiary">
|
||||
{t('versions.sourceAppUnavailable')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{releaseRows.length === 0
|
||||
? (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user