Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 6s
66 lines
4.0 KiB
TypeScript
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
|