admin-projects/src/pages/Secrets.tsx
WangDL 11297127d6
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 6s
feat: M0-12 secrets admin page
2026-05-23 20:37:26 +08:00

66 lines
4.0 KiB
TypeScript

import { useState } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Table, Button, Typography, App, Modal, Input, Select, Tag, Tabs, DatePicker } from 'antd'
import { ReloadOutlined, PlusOutlined, DeleteOutlined, KeyOutlined } from '@ant-design/icons'
import { api } from '@/services/http-client'
import dayjs from 'dayjs'
const { Title, Text } = Typography
function SecretsPage() {
const { modal, message } = App.useApp()
const qc = useQueryClient()
const [addOpen, setAddOpen] = useState(false)
const [form, setForm] = useState({ name: '', provider: 'deepseek', value: '', expiresAt: '' })
const { data: secrets } = useQuery({ queryKey: ['secrets'], queryFn: (): Promise<any> => api.get('/admin-api/secrets') })
const { data: logs } = useQuery({ queryKey: ['secrets', 'logs'], queryFn: (): Promise<any> => api.get('/admin-api/secrets/logs') })
const addSecret = async () => {
await api.post('/admin-api/secrets', form)
message.success('已添加')
setAddOpen(false); qc.invalidateQueries({ queryKey: ['secrets'] })
}
const deleteSecret = (id: string) => modal.confirm({
title: '删除密钥', okType: 'danger',
onOk: async () => { await api.delete(`/admin-api/secrets/${id}`); qc.invalidateQueries({ queryKey: ['secrets'] }) },
})
const secCols = [
{ title: '名称', dataIndex: 'name', width: 150 },
{ title: '服务商', dataIndex: 'provider', width: 100, render: (v: string) => <Tag color={v==='deepseek'?'blue':v==='siliconflow'?'green':v==='minimax'?'purple':'default'}>{v}</Tag> },
{ title: '末四位', dataIndex: 'maskLast4', width: 80, render: (v: string) => <Text code>****{v}</Text> },
{ title: '状态', dataIndex: 'status', width: 70, render: (v: string) => <Tag color={v==='active'?'green':'red'}>{v}</Tag> },
{ title: '到期', dataIndex: 'expiresAt', width: 100, render: (d: string) => d ? dayjs(d).format('MM-DD') : <Text type="secondary"></Text> },
{ title: '操作', width: 80, render: (_:any, r:any) => <Button type="link" size="small" danger icon={<DeleteOutlined />} onClick={() => deleteSecret(r.id)} /> },
]
const logCols = [
{ title: '时间', dataIndex: 'createdAt', width: 150, render: (d: string) => dayjs(d).format('MM-DD HH:mm:ss') },
{ title: '密钥', dataIndex: 'secretName', width: 120 },
{ title: '访问者', dataIndex: 'accessedBy', width: 120 },
]
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<Title level={5} style={{ margin: 0 }}><KeyOutlined /> </Title>
<Button icon={<PlusOutlined />} type="primary" onClick={() => setAddOpen(true)}></Button>
</div>
<Tabs items={[
{ key: 'keys', label: '密钥列表', children: <Table dataSource={secrets || []} columns={secCols} rowKey="id" pagination={false} size="small" /> },
{ key: 'logs', label: '访问日志', children: <Table dataSource={logs || []} columns={logCols} rowKey="id" pagination={{ pageSize: 20 }} size="small" /> },
]} />
<Modal title="新增密钥" open={addOpen} onOk={addSecret} onCancel={() => setAddOpen(false)} okText="添加">
<Input placeholder="名称" value={form.name} onChange={e => setForm({...form, name: e.target.value})} style={{ marginBottom: 12 }} />
<Select value={form.provider} onChange={v => setForm({...form, provider: v})} style={{ width: '100%', marginBottom: 12 }} options={[{label:'DeepSeek',value:'deepseek'},{label:'硅基流动',value:'siliconflow'},{label:'MiniMax',value:'minimax'},{label:'百度OCR',value:'baidu'},{label:'腾讯COS',value:'cos'}]} />
<Input.Password placeholder="Key 值" value={form.value} onChange={e => setForm({...form, value: e.target.value})} style={{ marginBottom: 12 }} />
<DatePicker placeholder="到期日期" onChange={d => setForm({...form, expiresAt: d?.toISOString() || ''})} style={{ width: '100%' }} />
</Modal>
</div>
)
}
export default SecretsPage