chore: 提交所有工作进度 — SaaS 后端增强、Admin UI、桌面端集成
包含大量 SaaS 平台改进、Admin 管理后台更新、桌面端集成完善、 文档同步、测试文件重构等内容。为 QA 测试准备干净工作树。
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import {
|
||||
Plus,
|
||||
Loader2,
|
||||
@@ -32,8 +33,10 @@ import {
|
||||
DialogDescription,
|
||||
} from '@/components/ui/dialog'
|
||||
import { api } from '@/lib/api-client'
|
||||
import { ErrorBanner, EmptyState } from '@/components/ui/state'
|
||||
import { ApiRequestError } from '@/lib/api-client'
|
||||
import { formatDate } from '@/lib/utils'
|
||||
import { TableSkeleton } from '@/components/ui/skeleton'
|
||||
import type { TokenInfo } from '@/lib/types'
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
@@ -45,11 +48,17 @@ const allPermissions = [
|
||||
]
|
||||
|
||||
export default function ApiKeysPage() {
|
||||
const [tokens, setTokens] = useState<TokenInfo[]>([])
|
||||
const [total, setTotal] = useState(0)
|
||||
const [page, setPage] = useState(1)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
const [mutationError, setMutationError] = useState('')
|
||||
|
||||
const { data, error: swrError, isLoading, mutate } = useSWR(
|
||||
['tokens', page],
|
||||
() => api.tokens.list({ page, page_size: PAGE_SIZE }),
|
||||
)
|
||||
|
||||
const tokens = data?.items ?? []
|
||||
const total = data?.total ?? 0
|
||||
const error = swrError?.message || mutationError
|
||||
|
||||
// 创建 Dialog
|
||||
const [createOpen, setCreateOpen] = useState(false)
|
||||
@@ -64,25 +73,6 @@ export default function ApiKeysPage() {
|
||||
const [revokeTarget, setRevokeTarget] = useState<TokenInfo | null>(null)
|
||||
const [revoking, setRevoking] = useState(false)
|
||||
|
||||
const fetchTokens = useCallback(async () => {
|
||||
setLoading(true)
|
||||
setError('')
|
||||
try {
|
||||
const res = await api.tokens.list({ page, page_size: PAGE_SIZE })
|
||||
setTokens(res.items)
|
||||
setTotal(res.total)
|
||||
} catch (err) {
|
||||
if (err instanceof ApiRequestError) setError(err.body.message)
|
||||
else setError('加载失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [page])
|
||||
|
||||
useEffect(() => {
|
||||
fetchTokens()
|
||||
}, [fetchTokens])
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))
|
||||
|
||||
function togglePermission(perm: string) {
|
||||
@@ -107,9 +97,9 @@ export default function ApiKeysPage() {
|
||||
setCreateOpen(false)
|
||||
setCreatedToken(res)
|
||||
setCreateForm({ name: '', expires_days: '', permissions: ['chat'] })
|
||||
fetchTokens()
|
||||
mutate()
|
||||
} catch (err) {
|
||||
if (err instanceof ApiRequestError) setError(err.body.message)
|
||||
if (err instanceof ApiRequestError) setMutationError(err.body.message)
|
||||
} finally {
|
||||
setCreating(false)
|
||||
}
|
||||
@@ -121,9 +111,9 @@ export default function ApiKeysPage() {
|
||||
try {
|
||||
await api.tokens.revoke(revokeTarget.id)
|
||||
setRevokeTarget(null)
|
||||
fetchTokens()
|
||||
mutate()
|
||||
} catch (err) {
|
||||
if (err instanceof ApiRequestError) setError(err.body.message)
|
||||
if (err instanceof ApiRequestError) setMutationError(err.body.message)
|
||||
} finally {
|
||||
setRevoking(false)
|
||||
}
|
||||
@@ -158,21 +148,12 @@ export default function ApiKeysPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md bg-destructive/10 border border-destructive/20 px-4 py-3 text-sm text-destructive">
|
||||
{error}
|
||||
<button onClick={() => setError('')} className="ml-2 underline cursor-pointer">关闭</button>
|
||||
</div>
|
||||
)}
|
||||
{error && <ErrorBanner message={error} onDismiss={() => setMutationError('')} />}
|
||||
|
||||
{loading ? (
|
||||
<div className="flex h-64 items-center justify-center">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : tokens.length === 0 ? (
|
||||
<div className="flex h-64 items-center justify-center text-muted-foreground text-sm">
|
||||
暂无数据
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<TableSkeleton rows={6} cols={7} />
|
||||
) : error ? null : tokens.length === 0 ? (
|
||||
<EmptyState />
|
||||
) : (
|
||||
<>
|
||||
<Table>
|
||||
|
||||
Reference in New Issue
Block a user