mirror of
https://github.com/langgenius/dify.git
synced 2026-08-27 03:01:54 -04:00
242 lines
8.4 KiB
TypeScript
242 lines
8.4 KiB
TypeScript
'use client'
|
|
import type { ApiKeyItem } from '@dify/contracts/api/console/apps/types.gen'
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogActions,
|
|
AlertDialogCancelButton,
|
|
AlertDialogConfirmButton,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogTitle,
|
|
} from '@langgenius/dify-ui/alert-dialog'
|
|
import { Button } from '@langgenius/dify-ui/button'
|
|
import {
|
|
Dialog,
|
|
DialogClose,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogTitle,
|
|
} from '@langgenius/dify-ui/dialog'
|
|
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
|
import { skipToken, useMutation, useQuery } from '@tanstack/react-query'
|
|
import { useAtomValue } from 'jotai'
|
|
import { useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import Loading from '@/app/components/base/loading'
|
|
import { currentWorkspaceAtom } from '@/context/workspace-state'
|
|
import { consoleQuery } from '@/service/client'
|
|
import { ApiKeyTable } from './api-key-table'
|
|
import { CreatedApiKeyDialog } from './created-api-key-dialog'
|
|
|
|
type CreatedApiKey = Pick<ApiKeyItem, 'token'>
|
|
type ApiKeyScope =
|
|
| { type: 'app'; appId: string }
|
|
| { type: 'dataset' }
|
|
| { type: 'environment'; appId: string; environmentId: string }
|
|
|
|
type ApiKeyModalProps = {
|
|
open: boolean
|
|
canManage: boolean
|
|
scope: ApiKeyScope
|
|
onOpenChange: (open: boolean) => void
|
|
}
|
|
|
|
export function ApiKeyModal({ open, canManage, scope, onOpenChange }: ApiKeyModalProps) {
|
|
const { t } = useTranslation()
|
|
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
|
const [deleteKeyId, setDeleteKeyId] = useState<string>()
|
|
const [createdApiKey, setCreatedApiKey] = useState<CreatedApiKey>()
|
|
|
|
const appApiKeysQuery = useQuery(
|
|
consoleQuery.apps.byResourceId.apiKeys.get.queryOptions({
|
|
input: open && scope.type === 'app' ? { params: { resource_id: scope.appId } } : skipToken,
|
|
}),
|
|
)
|
|
const datasetApiKeysQuery = useQuery({
|
|
...consoleQuery.datasets.apiKeys.get.queryOptions(),
|
|
enabled: open && scope.type === 'dataset',
|
|
})
|
|
const environmentApiKeysQuery = useQuery(
|
|
consoleQuery.enterprise.appDeploy.accessService.listEnvironmentApiKeys.queryOptions({
|
|
input:
|
|
open && scope.type === 'environment'
|
|
? { params: { app_id: scope.appId, environment_id: scope.environmentId } }
|
|
: skipToken,
|
|
}),
|
|
)
|
|
|
|
const createAppApiKey = useMutation(consoleQuery.apps.byResourceId.apiKeys.post.mutationOptions())
|
|
const deleteAppApiKey = useMutation(
|
|
consoleQuery.apps.byResourceId.apiKeys.byApiKeyId.delete.mutationOptions(),
|
|
)
|
|
const createDatasetApiKey = useMutation(consoleQuery.datasets.apiKeys.post.mutationOptions())
|
|
const deleteDatasetApiKey = useMutation(
|
|
consoleQuery.datasets.apiKeys.byApiKeyId.delete.mutationOptions(),
|
|
)
|
|
const createEnvironmentApiKey = useMutation(
|
|
consoleQuery.enterprise.appDeploy.accessService.createEnvironmentApiKey.mutationOptions(),
|
|
)
|
|
const deleteEnvironmentApiKey = useMutation(
|
|
consoleQuery.enterprise.appDeploy.accessService.deleteEnvironmentApiKey.mutationOptions(),
|
|
)
|
|
|
|
const apiKeys =
|
|
scope.type === 'app'
|
|
? appApiKeysQuery.data?.data
|
|
: scope.type === 'dataset'
|
|
? datasetApiKeysQuery.data?.data
|
|
: environmentApiKeysQuery.data?.data
|
|
const isLoading =
|
|
scope.type === 'app'
|
|
? appApiKeysQuery.isLoading
|
|
: scope.type === 'dataset'
|
|
? datasetApiKeysQuery.isLoading
|
|
: environmentApiKeysQuery.isLoading
|
|
const isCreating =
|
|
scope.type === 'app'
|
|
? createAppApiKey.isPending
|
|
: scope.type === 'dataset'
|
|
? createDatasetApiKey.isPending
|
|
: createEnvironmentApiKey.isPending
|
|
const isDeleting =
|
|
scope.type === 'app'
|
|
? deleteAppApiKey.isPending
|
|
: scope.type === 'dataset'
|
|
? deleteDatasetApiKey.isPending
|
|
: deleteEnvironmentApiKey.isPending
|
|
const createDisabled = !currentWorkspace.id || !canManage
|
|
|
|
const handleOpenChange = (nextOpen: boolean) => {
|
|
if (!nextOpen) {
|
|
setDeleteKeyId(undefined)
|
|
setCreatedApiKey(undefined)
|
|
}
|
|
onOpenChange(nextOpen)
|
|
}
|
|
|
|
const handleCreate = () => {
|
|
if (createDisabled || isCreating) return
|
|
|
|
switch (scope.type) {
|
|
case 'app':
|
|
createAppApiKey.mutate(
|
|
{ params: { resource_id: scope.appId } },
|
|
{ onSuccess: setCreatedApiKey },
|
|
)
|
|
break
|
|
case 'dataset':
|
|
createDatasetApiKey.mutate(undefined, { onSuccess: setCreatedApiKey })
|
|
break
|
|
case 'environment':
|
|
createEnvironmentApiKey.mutate(
|
|
{ params: { app_id: scope.appId, environment_id: scope.environmentId } },
|
|
{ onSuccess: setCreatedApiKey },
|
|
)
|
|
break
|
|
}
|
|
}
|
|
|
|
const handleDelete = () => {
|
|
if (!deleteKeyId || isDeleting) return
|
|
|
|
const onSuccess = () => setDeleteKeyId(undefined)
|
|
switch (scope.type) {
|
|
case 'app':
|
|
deleteAppApiKey.mutate(
|
|
{ params: { resource_id: scope.appId, api_key_id: deleteKeyId } },
|
|
{ onSuccess },
|
|
)
|
|
break
|
|
case 'dataset':
|
|
deleteDatasetApiKey.mutate({ params: { api_key_id: deleteKeyId } }, { onSuccess })
|
|
break
|
|
case 'environment':
|
|
deleteEnvironmentApiKey.mutate(
|
|
{
|
|
params: {
|
|
app_id: scope.appId,
|
|
environment_id: scope.environmentId,
|
|
api_key_id: deleteKeyId,
|
|
},
|
|
},
|
|
{ onSuccess },
|
|
)
|
|
break
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
<DialogContent className="flex w-200 flex-col overflow-hidden p-0">
|
|
<div className="flex shrink-0 flex-col gap-1 px-6 pt-6 pr-14 pb-4">
|
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
|
{t(($) => $['apiKeyModal.apiSecretKey'], { ns: 'appApi' })}
|
|
</DialogTitle>
|
|
<DialogDescription className="system-sm-regular text-text-tertiary">
|
|
{t(($) => $['apiKeyModal.apiSecretKeyTips'], { ns: 'appApi' })}
|
|
</DialogDescription>
|
|
</div>
|
|
<DialogClose
|
|
render={
|
|
<IconButton
|
|
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
|
size="lg"
|
|
className="absolute inset-e-6 top-6"
|
|
>
|
|
<span aria-hidden className="i-ri-close-line size-4" />
|
|
</IconButton>
|
|
}
|
|
/>
|
|
{isLoading && (
|
|
<div className="flex min-h-24 items-center border-y border-divider-subtle px-6">
|
|
<Loading />
|
|
</div>
|
|
)}
|
|
{!!apiKeys?.length && (
|
|
<ApiKeyTable apiKeys={apiKeys} canManage={canManage} onDeleteRequest={setDeleteKeyId} />
|
|
)}
|
|
<div className="flex shrink-0 px-6 py-4">
|
|
<Button disabled={createDisabled} loading={isCreating} onClick={handleCreate}>
|
|
<span aria-hidden className="i-ri-add-line size-4 shrink-0" />
|
|
{t(($) => $['apiKeyModal.createNewSecretKey'], { ns: 'appApi' })}
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
<AlertDialog
|
|
open={deleteKeyId !== undefined}
|
|
onOpenChange={(open) => {
|
|
if (!open && !isDeleting) setDeleteKeyId(undefined)
|
|
}}
|
|
>
|
|
<AlertDialogContent>
|
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
|
{t(($) => $['actionMsg.deleteConfirmTitle'], { ns: 'appApi' })}
|
|
</AlertDialogTitle>
|
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
|
{t(($) => $['actionMsg.deleteConfirmTips'], { ns: 'appApi' })}
|
|
</AlertDialogDescription>
|
|
</div>
|
|
<AlertDialogActions>
|
|
<AlertDialogCancelButton disabled={isDeleting}>
|
|
{t(($) => $['operation.cancel'], { ns: 'common' })}
|
|
</AlertDialogCancelButton>
|
|
<AlertDialogConfirmButton loading={isDeleting} onClick={handleDelete}>
|
|
{t(($) => $['operation.confirm'], { ns: 'common' })}
|
|
</AlertDialogConfirmButton>
|
|
</AlertDialogActions>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
<CreatedApiKeyDialog
|
|
open={open && createdApiKey !== undefined}
|
|
onOpenChange={(nextOpen) => {
|
|
if (!nextOpen) setCreatedApiKey(undefined)
|
|
}}
|
|
value={createdApiKey?.token ?? ''}
|
|
/>
|
|
</>
|
|
)
|
|
}
|