mirror of
https://github.com/langgenius/dify.git
synced 2026-05-10 05:56:31 +08:00
tweaks
This commit is contained in:
parent
ac56e38a2a
commit
605dca6431
@ -209,11 +209,10 @@ function AppPicker({ apps, isLoading, value, onChange }: AppPickerProps) {
|
||||
)
|
||||
}
|
||||
|
||||
function CreateInstanceForm({ onClose }: {
|
||||
onClose: () => void
|
||||
}) {
|
||||
function CreateInstanceForm() {
|
||||
const { t } = useTranslation('deployments')
|
||||
const router = useRouter()
|
||||
const closeModal = useSetAtom(closeCreateInstanceModalAtom)
|
||||
const createInstance = useMutation(consoleQuery.enterprise.appDeploy.createAppInstance.mutationOptions())
|
||||
const { data: appList, isLoading } = useQuery(consoleQuery.apps.list.queryOptions({
|
||||
input: {
|
||||
@ -251,7 +250,7 @@ function CreateInstanceForm({ onClose }: {
|
||||
})
|
||||
if (!result.appInstanceId)
|
||||
throw new Error('Create app instance did not return an appInstanceId.')
|
||||
onClose()
|
||||
closeModal()
|
||||
router.push(`/deployments/${result.appInstanceId}/overview`)
|
||||
}
|
||||
catch {
|
||||
@ -313,7 +312,7 @@ function CreateInstanceForm({ onClose }: {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="secondary" onClick={onClose}>
|
||||
<Button variant="secondary" onClick={closeModal}>
|
||||
{t('createModal.cancel')}
|
||||
</Button>
|
||||
<Button type="submit" variant="primary" disabled={!canCreate}>
|
||||
@ -335,7 +334,7 @@ export function CreateInstanceModal() {
|
||||
>
|
||||
<DialogContent className="w-[520px] max-w-[90vw]">
|
||||
<DialogCloseButton />
|
||||
{open && <CreateInstanceForm onClose={closeModal} />}
|
||||
{open && <CreateInstanceForm />}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { skipToken, useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { skipToken, useQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
@ -24,7 +23,6 @@ export function DeployDrawer() {
|
||||
const drawerEnvironmentId = useAtomValue(deployDrawerEnvironmentIdAtom)
|
||||
const drawerReleaseId = useAtomValue(deployDrawerReleaseIdAtom)
|
||||
const closeDeployDrawer = useSetAtom(closeDeployDrawerAtom)
|
||||
const startDeploy = useMutation(consoleQuery.enterprise.appDeploy.createDeployment.mutationOptions())
|
||||
const { data: releaseHistory } = useQuery(consoleQuery.enterprise.appDeploy.listReleases.queryOptions({
|
||||
input: drawerAppInstanceId
|
||||
? {
|
||||
@ -71,26 +69,6 @@ export function DeployDrawer() {
|
||||
defaultReleaseId={defaultReleaseId}
|
||||
lockedEnvId={drawerEnvironmentId}
|
||||
presetReleaseId={drawerReleaseId}
|
||||
isSubmitting={startDeploy.isPending}
|
||||
onCancel={closeDeployDrawer}
|
||||
onSubmit={async ({ environmentId, releaseId, bindings }) => {
|
||||
try {
|
||||
await startDeploy.mutateAsync({
|
||||
params: {
|
||||
appInstanceId: drawerAppInstanceId,
|
||||
},
|
||||
body: {
|
||||
environmentId,
|
||||
releaseId,
|
||||
bindings,
|
||||
},
|
||||
})
|
||||
closeDeployDrawer()
|
||||
}
|
||||
catch {
|
||||
toast.error(t('deployDrawer.deployFailed'))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
@ -4,10 +4,13 @@ import type { DeploymentBindingOptionSlot, DeploymentRuntimeBinding, ReleaseRow
|
||||
import type { EnvironmentOption } from '@/features/deployments/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { skipToken, useQuery } from '@tanstack/react-query'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { skipToken, useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { closeDeployDrawerAtom } from '../../store'
|
||||
import {
|
||||
environmentMode,
|
||||
environmentName,
|
||||
@ -20,12 +23,6 @@ import {
|
||||
Field,
|
||||
} from './select'
|
||||
|
||||
export type DeployFormSubmit = {
|
||||
environmentId: string
|
||||
releaseId: string
|
||||
bindings: DeploymentRuntimeBinding[]
|
||||
}
|
||||
|
||||
type DeployFormProps = {
|
||||
appInstanceId: string
|
||||
environments: EnvironmentOption[]
|
||||
@ -33,9 +30,6 @@ type DeployFormProps = {
|
||||
defaultReleaseId?: string
|
||||
lockedEnvId?: string
|
||||
presetReleaseId?: string
|
||||
isSubmitting?: boolean
|
||||
onCancel: () => void
|
||||
onSubmit: (params: DeployFormSubmit) => void | Promise<void>
|
||||
}
|
||||
|
||||
type BindingSelections = Record<string, string>
|
||||
@ -213,11 +207,10 @@ export function DeployForm({
|
||||
defaultReleaseId,
|
||||
lockedEnvId,
|
||||
presetReleaseId,
|
||||
isSubmitting,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}: DeployFormProps) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const closeDeployDrawer = useSetAtom(closeDeployDrawerAtom)
|
||||
const startDeploy = useMutation(consoleQuery.enterprise.appDeploy.createDeployment.mutationOptions())
|
||||
const presetRelease = presetReleaseId ? releases.find(r => r.id === presetReleaseId) : undefined
|
||||
const displayedRelease: ReleaseRow | undefined = presetRelease ?? (presetReleaseId ? { id: presetReleaseId } : undefined)
|
||||
const isPromote = Boolean(presetReleaseId)
|
||||
@ -249,6 +242,7 @@ export function DeployForm({
|
||||
const bindingOptionsLoading = Boolean(targetReleaseId && (bindingOptions.isLoading || bindingOptions.isFetching))
|
||||
const bindingOptionsReady = Boolean(targetReleaseId && bindingOptions.data && !bindingOptionsLoading && !bindingOptions.isError)
|
||||
const requiredBindingsReady = bindingSlots.every(slot => !hasMissingRequiredBinding(slot, selectedBindings[bindingSlotKey(slot)]))
|
||||
const isSubmitting = startDeploy.isPending
|
||||
const canDeploy = Boolean(
|
||||
selectedEnvironmentId
|
||||
&& selectedEnvironment
|
||||
@ -270,11 +264,24 @@ export function DeployForm({
|
||||
if (!canDeploy || !targetReleaseId)
|
||||
return
|
||||
|
||||
onSubmit({
|
||||
environmentId: selectedEnvironmentId,
|
||||
releaseId: targetReleaseId,
|
||||
bindings: deploymentBindings,
|
||||
})
|
||||
void (async () => {
|
||||
try {
|
||||
await startDeploy.mutateAsync({
|
||||
params: {
|
||||
appInstanceId,
|
||||
},
|
||||
body: {
|
||||
environmentId: selectedEnvironmentId,
|
||||
releaseId: targetReleaseId,
|
||||
bindings: deploymentBindings,
|
||||
},
|
||||
})
|
||||
closeDeployDrawer()
|
||||
}
|
||||
catch {
|
||||
toast.error(t('deployDrawer.deployFailed'))
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
return (
|
||||
@ -356,7 +363,7 @@ export function DeployForm({
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="secondary" onClick={onCancel}>
|
||||
<Button variant="secondary" onClick={closeDeployDrawer}>
|
||||
{t('deployDrawer.cancel')}
|
||||
</Button>
|
||||
<Button variant="primary" disabled={!canDeploy} onClick={handleDeploy}>
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
AccessSubject,
|
||||
ConsoleEnvironment,
|
||||
DeveloperApiKeyRow,
|
||||
EnvironmentAccessRow,
|
||||
} from '@dify/contracts/enterprise/types.gen'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import {
|
||||
deployedRows,
|
||||
@ -27,79 +24,6 @@ function uniqueEnvironments(environments: (ConsoleEnvironment | undefined)[]) {
|
||||
})
|
||||
}
|
||||
|
||||
type DeveloperApiAccessSectionProps = {
|
||||
appId: string
|
||||
apiEnabled: boolean
|
||||
apiUrl?: string
|
||||
environments: ConsoleEnvironment[]
|
||||
apiKeys: DeveloperApiKeyRow[]
|
||||
}
|
||||
|
||||
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 toggleDeveloperAPI = useMutation(consoleQuery.enterprise.appDeploy.updateDeveloperApi.mutationOptions())
|
||||
|
||||
function createApiKeyLabel(environmentId: string) {
|
||||
const existingCount = apiKeys.filter(key =>
|
||||
key.environment?.id === environmentId,
|
||||
).length
|
||||
const name = environments.find(env => env.id === environmentId)?.name ?? 'env'
|
||||
|
||||
return `${name}-key-${String(existingCount + 1).padStart(3, '0')}`
|
||||
}
|
||||
|
||||
function handleGenerateApiKey(environmentId: string) {
|
||||
generateApiKey.mutate(
|
||||
{
|
||||
params: {
|
||||
appInstanceId: appId,
|
||||
},
|
||||
body: {
|
||||
environmentId,
|
||||
name: createApiKeyLabel(environmentId),
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: (response) => {
|
||||
if (response.token)
|
||||
setCreatedApiToken({ appId, token: response.token })
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const visibleCreatedApiToken = createdApiToken?.appId === appId
|
||||
? createdApiToken.token
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<DeveloperApiSection
|
||||
appId={appId}
|
||||
apiEnabled={apiEnabled}
|
||||
apiUrl={apiUrl}
|
||||
environments={environments}
|
||||
apiKeys={apiKeys}
|
||||
createdToken={visibleCreatedApiToken}
|
||||
onToggle={enabled => toggleDeveloperAPI.mutate({
|
||||
params: { appInstanceId: appId },
|
||||
body: { enabled },
|
||||
})}
|
||||
onGenerate={handleGenerateApiKey}
|
||||
onClearCreatedToken={() => setCreatedApiToken(undefined)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function AccessTab({ instanceId: appId }: {
|
||||
instanceId: string
|
||||
}) {
|
||||
@ -110,9 +34,6 @@ export function AccessTab({ instanceId: appId }: {
|
||||
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([
|
||||
@ -122,22 +43,6 @@ export function AccessTab({ instanceId: appId }: {
|
||||
])
|
||||
const apiEnabled = accessConfig?.developerApi?.enabled ?? false
|
||||
const apiKeys = accessConfig?.developerApi?.apiKeys ?? []
|
||||
const handleSetEnvironmentAccessPolicy = async (
|
||||
environmentId: string,
|
||||
accessMode: string,
|
||||
subjects: AccessSubject[],
|
||||
) => {
|
||||
await setEnvironmentAccessPolicy.mutateAsync({
|
||||
params: {
|
||||
appInstanceId: appId,
|
||||
environmentId,
|
||||
},
|
||||
body: {
|
||||
accessMode,
|
||||
subjects,
|
||||
},
|
||||
})
|
||||
}
|
||||
const webappRows = accessConfig?.accessChannels?.webappRows?.filter(row => row.url) ?? []
|
||||
const runEnabled = accessConfig?.accessChannels?.enabled ?? false
|
||||
const cliDomain = getUrlOrigin(accessConfig?.accessChannels?.cli?.url)
|
||||
@ -149,19 +54,15 @@ export function AccessTab({ instanceId: appId }: {
|
||||
appId={appId}
|
||||
environments={deployedEnvs}
|
||||
policies={policies}
|
||||
onSetPolicy={handleSetEnvironmentAccessPolicy}
|
||||
/>
|
||||
<AccessChannelsSection
|
||||
appId={appId}
|
||||
runEnabled={runEnabled}
|
||||
webappRows={webappRows}
|
||||
cliDomain={cliDomain}
|
||||
cliDocsUrl={cliDocsUrl}
|
||||
onToggle={enabled => toggleAccessChannel.mutate({
|
||||
params: { appInstanceId: appId },
|
||||
body: { enabled },
|
||||
})}
|
||||
/>
|
||||
<DeveloperApiAccessSection
|
||||
<DeveloperApiSection
|
||||
appId={appId}
|
||||
apiEnabled={apiEnabled}
|
||||
apiUrl={accessConfig?.developerApi?.apiUrl}
|
||||
|
||||
@ -10,9 +10,11 @@ import {
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { consoleClient, consoleQuery } from '@/service/client'
|
||||
import { createdDeveloperApiTokenAtom } from '../../store'
|
||||
import { environmentName } from '../../utils'
|
||||
import { useCopyFeedback } from './use-copy-feedback'
|
||||
|
||||
@ -115,15 +117,47 @@ export function ApiKeyList({ appId, apiKeys }: {
|
||||
)
|
||||
}
|
||||
|
||||
export function ApiKeyGenerateMenu({ environments, onGenerate }: {
|
||||
export function ApiKeyGenerateMenu({ appId, environments, apiKeys }: {
|
||||
appId: string
|
||||
environments: ConsoleEnvironment[]
|
||||
onGenerate: (environmentId: string) => void
|
||||
apiKeys: DeveloperApiKeyRow[]
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const [open, setOpen] = useState(false)
|
||||
const setCreatedApiToken = useSetAtom(createdDeveloperApiTokenAtom)
|
||||
const generateApiKey = useMutation(consoleQuery.enterprise.appDeploy.createDeveloperApiKey.mutationOptions())
|
||||
const selectableEnvironments = environments.filter(env => env.id)
|
||||
const disabled = selectableEnvironments.length === 0
|
||||
|
||||
function createApiKeyLabel(environmentId: string) {
|
||||
const existingCount = apiKeys.filter(key =>
|
||||
key.environment?.id === environmentId,
|
||||
).length
|
||||
const name = environments.find(env => env.id === environmentId)?.name ?? 'env'
|
||||
|
||||
return `${name}-key-${String(existingCount + 1).padStart(3, '0')}`
|
||||
}
|
||||
|
||||
function handleGenerateApiKey(environmentId: string) {
|
||||
generateApiKey.mutate(
|
||||
{
|
||||
params: {
|
||||
appInstanceId: appId,
|
||||
},
|
||||
body: {
|
||||
environmentId,
|
||||
name: createApiKeyLabel(environmentId),
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: (response) => {
|
||||
if (response.token)
|
||||
setCreatedApiToken({ appId, token: response.token })
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu modal={false} open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger
|
||||
@ -147,7 +181,7 @@ export function ApiKeyGenerateMenu({ environments, onGenerate }: {
|
||||
className="gap-2 px-3"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
onGenerate(env.id!)
|
||||
handleGenerateApiKey(env.id!)
|
||||
}}
|
||||
>
|
||||
<span className="system-sm-regular text-text-secondary">
|
||||
|
||||
@ -2,26 +2,36 @@
|
||||
|
||||
import type { WebAppAccessRow } from '@dify/contracts/enterprise/types.gen'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { useMutation } 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'
|
||||
|
||||
type AccessChannelsSectionProps = {
|
||||
appId: string
|
||||
runEnabled: boolean
|
||||
webappRows: WebAppAccessRow[]
|
||||
cliDomain?: string
|
||||
cliDocsUrl?: string
|
||||
onToggle: (enabled: boolean) => void
|
||||
}
|
||||
|
||||
export function AccessChannelsSection({
|
||||
appId,
|
||||
runEnabled,
|
||||
webappRows,
|
||||
cliDomain,
|
||||
cliDocsUrl,
|
||||
onToggle,
|
||||
}: 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 (
|
||||
<Section
|
||||
@ -30,7 +40,7 @@ export function AccessChannelsSection({
|
||||
action={(
|
||||
<Switch
|
||||
checked={runEnabled}
|
||||
onCheckedChange={onToggle}
|
||||
onCheckedChange={handleToggle}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
|
||||
@ -2,7 +2,11 @@
|
||||
|
||||
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'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { createdDeveloperApiTokenAtom } from '../../store'
|
||||
import { ApiKeyGenerateMenu, ApiKeyList } from './api-keys'
|
||||
import { CopyPill, Section } from './common'
|
||||
|
||||
@ -12,10 +16,6 @@ type DeveloperApiSectionProps = {
|
||||
apiUrl?: string
|
||||
environments: ConsoleEnvironment[]
|
||||
apiKeys: DeveloperApiKeyRow[]
|
||||
createdToken?: string
|
||||
onToggle: (enabled: boolean) => void
|
||||
onGenerate: (environmentId: string) => void
|
||||
onClearCreatedToken: () => void
|
||||
}
|
||||
|
||||
export function DeveloperApiSection({
|
||||
@ -24,12 +24,22 @@ export function DeveloperApiSection({
|
||||
apiUrl,
|
||||
environments,
|
||||
apiKeys,
|
||||
createdToken,
|
||||
onToggle,
|
||||
onGenerate,
|
||||
onClearCreatedToken,
|
||||
}: 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 (
|
||||
<Section
|
||||
@ -38,7 +48,7 @@ export function DeveloperApiSection({
|
||||
action={(
|
||||
<Switch
|
||||
checked={apiEnabled}
|
||||
onCheckedChange={onToggle}
|
||||
onCheckedChange={handleToggle}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
@ -61,11 +71,12 @@ export function DeveloperApiSection({
|
||||
</span>
|
||||
</div>
|
||||
<ApiKeyGenerateMenu
|
||||
appId={appId}
|
||||
environments={environments}
|
||||
onGenerate={onGenerate}
|
||||
apiKeys={apiKeys}
|
||||
/>
|
||||
</div>
|
||||
{createdToken && (
|
||||
{visibleCreatedApiToken && (
|
||||
<div className="flex flex-col gap-2 rounded-lg border border-components-panel-border bg-components-panel-bg-blur p-3">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex min-w-0 flex-col">
|
||||
@ -78,7 +89,7 @@ export function DeveloperApiSection({
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClearCreatedToken}
|
||||
onClick={() => setCreatedApiToken(undefined)}
|
||||
aria-label={t('access.api.dismissToken')}
|
||||
className="flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary"
|
||||
>
|
||||
@ -87,7 +98,7 @@ export function DeveloperApiSection({
|
||||
</div>
|
||||
<CopyPill
|
||||
label={t('access.api.newTokenLabel')}
|
||||
value={createdToken}
|
||||
value={visibleCreatedApiToken}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { AccessSubject, ConsoleEnvironment, EnvironmentAccessRow } from '@dify/contracts/enterprise/types.gen'
|
||||
import type { ConsoleEnvironment, EnvironmentAccessRow } from '@dify/contracts/enterprise/types.gen'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Section } from './common'
|
||||
import { EnvironmentPermissionRow } from './permissions'
|
||||
@ -9,18 +9,12 @@ type AccessPermissionsSectionProps = {
|
||||
appId: string
|
||||
environments: ConsoleEnvironment[]
|
||||
policies: EnvironmentAccessRow[]
|
||||
onSetPolicy: (
|
||||
environmentId: string,
|
||||
accessMode: string,
|
||||
subjects: AccessSubject[],
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
export function AccessPermissionsSection({
|
||||
appId,
|
||||
environments,
|
||||
policies,
|
||||
onSetPolicy,
|
||||
}: AccessPermissionsSectionProps) {
|
||||
const { t } = useTranslation('deployments')
|
||||
|
||||
@ -45,7 +39,6 @@ export function AccessPermissionsSection({
|
||||
appId={appId}
|
||||
environment={env}
|
||||
summaryPolicy={policy}
|
||||
onSetPolicy={onSetPolicy}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { skipToken, useQuery } from '@tanstack/react-query'
|
||||
import { skipToken, useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useDebounce } from 'ahooks'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -295,21 +295,16 @@ type EnvironmentPermissionRowProps = {
|
||||
appId: string
|
||||
environment: ConsoleEnvironment
|
||||
summaryPolicy?: EnvironmentAccessRow
|
||||
onSetPolicy: (
|
||||
environmentId: string,
|
||||
accessMode: string,
|
||||
subjects: AccessSubject[],
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
export function EnvironmentPermissionRow({
|
||||
appId,
|
||||
environment,
|
||||
summaryPolicy,
|
||||
onSetPolicy,
|
||||
}: EnvironmentPermissionRowProps) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const environmentId = environment.id
|
||||
const setEnvironmentAccessPolicy = useMutation(consoleQuery.enterprise.appDeploy.updateEnvironmentAccessPolicy.mutationOptions())
|
||||
const policyQuery = useQuery(consoleQuery.enterprise.appDeploy.getEnvironmentAccessPolicy.queryOptions({
|
||||
input: environmentId
|
||||
? {
|
||||
@ -348,11 +343,16 @@ export function EnvironmentPermissionRow({
|
||||
|
||||
setIsSaving(true)
|
||||
try {
|
||||
await onSetPolicy(
|
||||
environmentId,
|
||||
permissionKeyToAccessMode(nextKind),
|
||||
nextKind === 'specific' ? policySubjects(nextSubjects) : [],
|
||||
)
|
||||
await setEnvironmentAccessPolicy.mutateAsync({
|
||||
params: {
|
||||
appInstanceId: appId,
|
||||
environmentId,
|
||||
},
|
||||
body: {
|
||||
accessMode: permissionKeyToAccessMode(nextKind),
|
||||
subjects: nextKind === 'specific' ? policySubjects(nextSubjects) : [],
|
||||
},
|
||||
})
|
||||
setDraft({})
|
||||
}
|
||||
catch {
|
||||
|
||||
@ -8,6 +8,7 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useHover, useKeyPress } from 'ahooks'
|
||||
import { useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getAppModeLabel } from '@/app/components/app-sidebar/app-info/app-mode-labels'
|
||||
import NavLink from '@/app/components/app-sidebar/nav-link'
|
||||
import ToggleButton from '@/app/components/app-sidebar/toggle-button'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
@ -15,6 +16,7 @@ import Divider from '@/app/components/base/divider'
|
||||
import { getKeyboardKeyCodeBySystem } from '@/app/components/workflow/utils'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { useLocalStorage } from '@/hooks/use-local-storage'
|
||||
import { toAppMode } from '../utils'
|
||||
|
||||
type TabDef = {
|
||||
key: InstanceDetailTabKey
|
||||
@ -98,27 +100,23 @@ function useDeploymentSidebarMode(isMobile: boolean) {
|
||||
}
|
||||
|
||||
type DeploymentSidebarProps = {
|
||||
instanceId: string
|
||||
instanceName: string
|
||||
instanceDescription?: string
|
||||
appModeLabel: string
|
||||
app?: AppInstanceBasicInfo
|
||||
app: AppInstanceBasicInfo
|
||||
}
|
||||
|
||||
export function DeploymentSidebar({
|
||||
instanceId,
|
||||
instanceName,
|
||||
instanceDescription,
|
||||
appModeLabel,
|
||||
app,
|
||||
}: DeploymentSidebarProps) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const { t: tCommon } = useTranslation()
|
||||
const sidebarRef = useRef<HTMLDivElement>(null)
|
||||
const isHoveringSidebar = useHover(sidebarRef)
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
const { sidebarMode, toggleSidebarMode } = useDeploymentSidebarMode(isMobile)
|
||||
const expand = sidebarMode === 'expand'
|
||||
const instanceId = app.id ?? ''
|
||||
const instanceName = app.name ?? instanceId
|
||||
const appModeLabel = getAppModeLabel(toAppMode(app.mode), tCommon)
|
||||
|
||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.b`, (e) => {
|
||||
if (isShortcutFromInputArea(e.target))
|
||||
@ -139,24 +137,12 @@ export function DeploymentSidebar({
|
||||
<div className={cn('shrink-0', expand ? 'p-2' : 'p-1')}>
|
||||
<div className={cn('flex flex-col gap-2 rounded-lg', expand ? 'p-1' : 'items-center p-1')}>
|
||||
<div className="flex items-center gap-1">
|
||||
{app
|
||||
? (
|
||||
<AppIcon
|
||||
size={expand ? 'large' : 'medium'}
|
||||
iconType="emoji"
|
||||
icon={app.icon}
|
||||
background={app.iconBackground}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className={cn(
|
||||
'flex items-center justify-center rounded-xl border border-divider-subtle bg-background-default text-text-tertiary',
|
||||
expand ? 'h-10 w-10' : 'h-8 w-8',
|
||||
)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-apps-2-line h-5 w-5" />
|
||||
</div>
|
||||
)}
|
||||
<AppIcon
|
||||
size={expand ? 'large' : 'medium'}
|
||||
iconType="emoji"
|
||||
icon={app.icon}
|
||||
background={app.iconBackground}
|
||||
/>
|
||||
</div>
|
||||
{expand && (
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
@ -168,12 +154,12 @@ export function DeploymentSidebar({
|
||||
<div className="system-2xs-medium-uppercase whitespace-nowrap text-text-tertiary">
|
||||
{appModeLabel}
|
||||
</div>
|
||||
{instanceDescription && (
|
||||
{app.description && (
|
||||
<div
|
||||
className="line-clamp-2 system-xs-regular text-text-tertiary"
|
||||
title={instanceDescription}
|
||||
title={app.description}
|
||||
>
|
||||
{instanceDescription}
|
||||
{app.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -5,7 +5,6 @@ import type { InstanceDetailTabKey } from './tabs'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getAppModeLabel } from '@/app/components/app-sidebar/app-info/app-mode-labels'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import Link from '@/next/link'
|
||||
import { useSelectedLayoutSegment } from '@/next/navigation'
|
||||
@ -20,7 +19,6 @@ export function InstanceDetail({ instanceId, children }: {
|
||||
children: ReactNode
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const { t: tCommon } = useTranslation()
|
||||
const selectedSegment = useSelectedLayoutSegment()
|
||||
const selectedTab = selectedSegment ?? undefined
|
||||
const activeTab: InstanceDetailTabKey = isInstanceDetailTabKey(selectedTab) ? selectedTab : 'overview'
|
||||
@ -54,17 +52,11 @@ export function InstanceDetail({ instanceId, children }: {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const appName = app.name ?? appId
|
||||
const appModeLabel = getAppModeLabel(app.mode ?? 'workflow', tCommon)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex h-full overflow-hidden rounded-t-2xl shadow-[0_0_5px_rgba(0,0,0,0.05),0_0_2px_-1px_rgba(0,0,0,0.03)]">
|
||||
<DeploymentSidebar
|
||||
instanceId={appId}
|
||||
instanceName={appName}
|
||||
instanceDescription={app.description}
|
||||
appModeLabel={appModeLabel}
|
||||
app={app}
|
||||
/>
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import { DEPLOYMENT_PAGE_SIZE } from '../data'
|
||||
import { openDeployDrawerAtom } from '../store'
|
||||
import {
|
||||
releaseLabel,
|
||||
toAppMode,
|
||||
webappUrl,
|
||||
} from '../utils'
|
||||
|
||||
@ -119,7 +120,7 @@ export function OverviewTab({ instanceId }: {
|
||||
|
||||
const appId = overviewApp.id
|
||||
const appName = overviewApp.name ?? appId
|
||||
const appModeLabel = getAppModeLabel(overviewApp.mode ?? 'workflow', tCommon)
|
||||
const appModeLabel = getAppModeLabel(toAppMode(overviewApp.mode), tCommon)
|
||||
const webappAccessUrl = webappUrl(overview?.access?.webappUrl)
|
||||
const cliUrl = overview?.access?.cliUrl
|
||||
const apiUrl = overview?.access?.apiUrl ?? accessConfig?.developerApi?.apiUrl
|
||||
|
||||
@ -21,19 +21,16 @@ import { deployedRows } from '../utils'
|
||||
type SettingsFormProps = {
|
||||
app: AppInstanceBasicInfo
|
||||
settings?: GetAppInstanceSettingsReply
|
||||
onSave: (patch: Pick<AppInstanceBasicInfo, 'name' | 'description'>) => Promise<void>
|
||||
}
|
||||
|
||||
type DeleteInstanceControlProps = {
|
||||
appId: string
|
||||
appName: string
|
||||
app: AppInstanceBasicInfo
|
||||
settings?: GetAppInstanceSettingsReply
|
||||
hasDeployments: boolean
|
||||
}
|
||||
|
||||
function DeleteInstanceControl({
|
||||
appId,
|
||||
appName,
|
||||
app,
|
||||
settings,
|
||||
hasDeployments,
|
||||
}: DeleteInstanceControlProps) {
|
||||
@ -42,9 +39,14 @@ function DeleteInstanceControl({
|
||||
const deleteInstance = useMutation(consoleQuery.enterprise.appDeploy.deleteAppInstance.mutationOptions())
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
||||
const appId = app.id
|
||||
const appName = app.name ?? appId ?? ''
|
||||
const canDelete = !hasDeployments && Boolean(settings) && settings?.deleteGuard?.canDelete !== false
|
||||
|
||||
const handleDelete = () => {
|
||||
if (!appId)
|
||||
return
|
||||
|
||||
void (async () => {
|
||||
setIsDeleting(true)
|
||||
try {
|
||||
@ -114,8 +116,9 @@ function DeleteInstanceControl({
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsForm({ app, settings, onSave }: SettingsFormProps) {
|
||||
function SettingsForm({ app, settings }: SettingsFormProps) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const updateInstance = useMutation(consoleQuery.enterprise.appDeploy.updateAppInstance.mutationOptions())
|
||||
const appName = app.name ?? app.id ?? ''
|
||||
const [name, setName] = useState(settings?.name ?? appName)
|
||||
const [description, setDescription] = useState(settings?.description ?? app.description ?? '')
|
||||
@ -125,14 +128,20 @@ function SettingsForm({ app, settings, onSave }: SettingsFormProps) {
|
||||
const canSave = Boolean(name.trim() && (name !== initialName || description !== initialDescription) && !isSaving)
|
||||
|
||||
const handleSave = () => {
|
||||
if (!canSave)
|
||||
const appId = app.id
|
||||
if (!canSave || !appId)
|
||||
return
|
||||
void (async () => {
|
||||
setIsSaving(true)
|
||||
try {
|
||||
await onSave({
|
||||
name: name.trim(),
|
||||
description: description.trim() || undefined,
|
||||
await updateInstance.mutateAsync({
|
||||
params: {
|
||||
appInstanceId: appId,
|
||||
},
|
||||
body: {
|
||||
name: name.trim(),
|
||||
description: description.trim() || undefined,
|
||||
},
|
||||
})
|
||||
toast.success(t('settings.updated'))
|
||||
}
|
||||
@ -194,7 +203,6 @@ function SettingsForm({ app, settings, onSave }: SettingsFormProps) {
|
||||
export function SettingsTab({ instanceId }: {
|
||||
instanceId: string
|
||||
}) {
|
||||
const updateInstance = useMutation(consoleQuery.enterprise.appDeploy.updateAppInstance.mutationOptions())
|
||||
const appInput = { params: { appInstanceId: instanceId } }
|
||||
const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({
|
||||
input: appInput,
|
||||
@ -220,18 +228,9 @@ export function SettingsTab({ instanceId }: {
|
||||
key={formKey}
|
||||
app={app}
|
||||
settings={settingsQuery.data}
|
||||
onSave={async (patch) => {
|
||||
await updateInstance.mutateAsync({
|
||||
params: {
|
||||
appInstanceId: instanceId,
|
||||
},
|
||||
body: patch,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<DeleteInstanceControl
|
||||
appId={instanceId}
|
||||
appName={appName}
|
||||
app={app}
|
||||
settings={settingsQuery.data}
|
||||
hasDeployments={hasDeployments}
|
||||
/>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { AppInstanceCard } from '@dify/contracts/enterprise/types.gen'
|
||||
import type { AppModeEnum } from '@/types/app'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
DropdownMenu,
|
||||
@ -20,6 +19,7 @@ import AppIcon from '@/app/components/base/app-icon'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import Link from '@/next/link'
|
||||
import { openDeployDrawerAtom } from '../store'
|
||||
import { toAppMode } from '../utils'
|
||||
|
||||
export function InstanceCard({ app }: {
|
||||
app: AppInstanceCard
|
||||
@ -32,7 +32,7 @@ export function InstanceCard({ app }: {
|
||||
|
||||
const appId = app.id
|
||||
const appName = app.name ?? appId
|
||||
const appMode = app.mode ?? 'workflow'
|
||||
const appMode = toAppMode(app.mode)
|
||||
const detailHref = `/deployments/${appId}/overview`
|
||||
|
||||
const statusCount = (status: string) =>
|
||||
@ -128,7 +128,7 @@ export function InstanceCard({ app }: {
|
||||
background={app.iconBackground}
|
||||
/>
|
||||
<AppTypeIcon
|
||||
type={appMode as AppModeEnum}
|
||||
type={appMode}
|
||||
wrapperClassName="absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm"
|
||||
className="h-3 w-3"
|
||||
/>
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
import type { AppInstanceBasicInfo, AppInstanceCard } from '@dify/contracts/enterprise/types.gen'
|
||||
import type { NavItem } from '@/app/components/header/nav/nav-selector'
|
||||
import type { AppModeEnum } from '@/types/app'
|
||||
import { skipToken, useQuery } from '@tanstack/react-query'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -11,6 +10,7 @@ import { useParams, useRouter, useSelectedLayoutSegment } from '@/next/navigatio
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { SOURCE_APPS_PAGE_SIZE } from '../data'
|
||||
import { openCreateInstanceModalAtom } from '../store'
|
||||
import { toAppMode } from '../utils'
|
||||
|
||||
function navItemFromListApp(app: AppInstanceCard): NavItem[] {
|
||||
if (!app.id || !app.name)
|
||||
@ -24,7 +24,7 @@ function navItemFromListApp(app: AppInstanceCard): NavItem[] {
|
||||
icon: app.icon ?? '',
|
||||
icon_background: app.iconBackground ?? null,
|
||||
icon_url: null,
|
||||
mode: app.mode as AppModeEnum | undefined,
|
||||
mode: toAppMode(app.mode),
|
||||
}]
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ function navItemFromOverview(instance?: AppInstanceBasicInfo): NavItem | undefin
|
||||
icon: instance.icon ?? '',
|
||||
icon_background: instance.iconBackground ?? null,
|
||||
icon_url: null,
|
||||
mode: instance.mode as AppModeEnum | undefined,
|
||||
mode: toAppMode(instance.mode),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,11 @@ type OpenRollbackParams = {
|
||||
deploymentId?: string
|
||||
}
|
||||
|
||||
type CreatedDeveloperApiToken = {
|
||||
appId: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export const deployDrawerOpenAtom = atom(false)
|
||||
export const deployDrawerAppInstanceIdAtom = atom<string | undefined>(undefined)
|
||||
export const deployDrawerEnvironmentIdAtom = atom<string | undefined>(undefined)
|
||||
@ -25,6 +30,7 @@ export const rollbackModalDeploymentIdAtom = atom<string | undefined>(undefined)
|
||||
export const rollbackModalTargetReleaseIdAtom = atom<string | undefined>(undefined)
|
||||
|
||||
export const createInstanceModalOpenAtom = atom(false)
|
||||
export const createdDeveloperApiTokenAtom = atom<CreatedDeveloperApiToken | undefined>(undefined)
|
||||
|
||||
export const openDeployDrawerAtom = atom(null, (_get, set, params: OpenDeployDrawerParams) => {
|
||||
set(deployDrawerAppInstanceIdAtom, params.appInstanceId)
|
||||
|
||||
@ -11,9 +11,16 @@ import type {
|
||||
EnvironmentOption,
|
||||
} from './types'
|
||||
import { PUBLIC_API_PREFIX } from '@/config'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
export type DeploymentUiStatus = 'ready' | 'deploying' | 'deploy_failed'
|
||||
|
||||
const appModeValues = new Set<string>(Object.values(AppModeEnum))
|
||||
|
||||
export function toAppMode(mode?: string): AppModeEnum {
|
||||
return appModeValues.has(mode ?? '') ? (mode as AppModeEnum) : AppModeEnum.WORKFLOW
|
||||
}
|
||||
|
||||
export function formatDate(value?: string) {
|
||||
if (!value)
|
||||
return '—'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user