@yuku123/z-agent-frontend-component 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/dist/z-agent-frontend-component.css +1 -0
- package/dist/z-agent-frontend-component.es.js +9956 -0
- package/dist/z-agent-frontend-component.umd.js +219 -0
- package/package.json +77 -0
- package/src/api/apiRouter.js +78 -0
- package/src/api/index.js +23 -0
- package/src/api/request.js +59 -0
- package/src/api/routes.js +140 -0
- package/src/dev.jsx +80 -0
- package/src/index.js +86 -0
- package/src/pages/agent/app/index.jsx +2 -0
- package/src/pages/agent/editor/AgentAppEditor.jsx +456 -0
- package/src/pages/agent/editor/WorkflowEditor.jsx +495 -0
- package/src/pages/agent/editor/nodes/index.ts +225 -0
- package/src/pages/agent/index.jsx +1379 -0
- package/src/pages/agent/share.jsx +512 -0
- package/src/pages/ak/AkUsageDrawer.jsx +208 -0
- package/src/pages/ak/index.jsx +496 -0
- package/src/pages/llm/index.jsx +736 -0
- package/src/pages/llm/model/index.jsx +220 -0
- package/src/pages/llm/provider/index.jsx +173 -0
- package/src/pages/mcp/index.jsx +359 -0
- package/src/pages/oss/BucketList.jsx +320 -0
- package/src/pages/oss/ObjectBrowser.jsx +409 -0
- package/src/pages/product/execute.jsx +608 -0
- package/src/pages/product/index.jsx +628 -0
- package/src/pages/product/scene.jsx +746 -0
- package/src/pages/script/ApiBridgeEditor.jsx +255 -0
- package/src/pages/script/CurlImportModal.jsx +263 -0
- package/src/pages/script/FieldMappingEditor.jsx +131 -0
- package/src/pages/script/OpenApiImportModal.jsx +212 -0
- package/src/pages/script/index.jsx +532 -0
- package/src/pages/skill/index.jsx +1595 -0
- package/src/pages/trace/DebugPlayground.jsx +357 -0
- package/src/pages/trace/components/MetricsDashboard.jsx +164 -0
- package/src/pages/trace/components/RagFragments.jsx +134 -0
- package/src/pages/trace/components/Timeline.jsx +142 -0
- package/src/pages/trace/components/ToolCallTree.jsx +116 -0
- package/src/pages/trace/index.jsx +13 -0
- package/src/pages/usage/index.jsx +352 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import {useEffect, useState} from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Alert,
|
|
4
|
+
Button,
|
|
5
|
+
Card,
|
|
6
|
+
Col,
|
|
7
|
+
Form,
|
|
8
|
+
Input,
|
|
9
|
+
message,
|
|
10
|
+
Modal,
|
|
11
|
+
Radio,
|
|
12
|
+
Row,
|
|
13
|
+
Select,
|
|
14
|
+
Space,
|
|
15
|
+
Spin,
|
|
16
|
+
Statistic,
|
|
17
|
+
Table,
|
|
18
|
+
Tabs,
|
|
19
|
+
Tag,
|
|
20
|
+
Tooltip
|
|
21
|
+
} from 'antd'
|
|
22
|
+
import {
|
|
23
|
+
ApiOutlined,
|
|
24
|
+
AppstoreOutlined,
|
|
25
|
+
CheckCircleOutlined,
|
|
26
|
+
CopyOutlined,
|
|
27
|
+
DeleteOutlined,
|
|
28
|
+
KeyOutlined,
|
|
29
|
+
PlusOutlined,
|
|
30
|
+
ReloadOutlined,
|
|
31
|
+
StopOutlined,
|
|
32
|
+
ThunderboltOutlined,
|
|
33
|
+
UserOutlined
|
|
34
|
+
} from '@ant-design/icons'
|
|
35
|
+
import request from '../../api'
|
|
36
|
+
import AkUsageDrawer from './AkUsageDrawer'
|
|
37
|
+
|
|
38
|
+
const {confirm} = Modal
|
|
39
|
+
|
|
40
|
+
const API_BASE_URL = 'http://localhost:8888'
|
|
41
|
+
|
|
42
|
+
const formatTokens = (n) => {
|
|
43
|
+
if (!n) return '0'
|
|
44
|
+
if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M'
|
|
45
|
+
if (n >= 1000) return (n / 1000).toFixed(1) + 'K'
|
|
46
|
+
return Number(n).toLocaleString()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const AkManage = () => {
|
|
50
|
+
const [activeTab, setActiveTab] = useState('PERSONAL') // PERSONAL / APP
|
|
51
|
+
const [keys, setKeys] = useState([])
|
|
52
|
+
const [loading, setLoading] = useState(false)
|
|
53
|
+
const [visible, setVisible] = useState(false)
|
|
54
|
+
const [editing, setEditing] = useState(null)
|
|
55
|
+
const [form] = Form.useForm()
|
|
56
|
+
const [showSkModal, setShowSkModal] = useState(null)
|
|
57
|
+
const [drawerAk, setDrawerAk] = useState(null)
|
|
58
|
+
const [appOptions, setAppOptions] = useState([])
|
|
59
|
+
|
|
60
|
+
const fetchList = async () => {
|
|
61
|
+
setLoading(true)
|
|
62
|
+
try {
|
|
63
|
+
const res = await request('/ak/list', {method: 'GET', params: {akType: activeTab}})
|
|
64
|
+
setKeys(Array.isArray(res) ? res : (res?.data || []))
|
|
65
|
+
} catch (e) {
|
|
66
|
+
message.error('加载失败')
|
|
67
|
+
} finally {
|
|
68
|
+
setLoading(false)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const fetchApps = async () => {
|
|
73
|
+
try {
|
|
74
|
+
const res = await request('/agent/app/page', {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
body: JSON.stringify({current: 1, size: 100})
|
|
77
|
+
})
|
|
78
|
+
const list = Array.isArray(res) ? res : (res?.records || res?.data || [])
|
|
79
|
+
setAppOptions(list.map(a => ({value: a.appCode, label: a.appName || a.appCode})))
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// 静默失败
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
fetchList()
|
|
87
|
+
}, [activeTab])
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
fetchApps()
|
|
90
|
+
}, [])
|
|
91
|
+
|
|
92
|
+
const handleAdd = () => {
|
|
93
|
+
setEditing(null)
|
|
94
|
+
form.resetFields()
|
|
95
|
+
form.setFieldsValue({akType: activeTab}) // 按当前 Tab 预填类型
|
|
96
|
+
setVisible(true)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const handleEdit = (record) => {
|
|
100
|
+
setEditing(record)
|
|
101
|
+
form.setFieldsValue({
|
|
102
|
+
akName: record.akName,
|
|
103
|
+
description: record.description,
|
|
104
|
+
akType: record.akType || 'PERSONAL',
|
|
105
|
+
appCode: record.appCode,
|
|
106
|
+
appName: record.appName,
|
|
107
|
+
})
|
|
108
|
+
setVisible(true)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const handleDelete = (record) => {
|
|
112
|
+
confirm({
|
|
113
|
+
title: '确认删除',
|
|
114
|
+
content: `删除 AK「${record.akName}」?此操作不可恢复。`,
|
|
115
|
+
okText: '删除',
|
|
116
|
+
okType: 'danger',
|
|
117
|
+
onOk: async () => {
|
|
118
|
+
await request('/ak/delete', {method: 'POST', params: {id: record.id}})
|
|
119
|
+
message.success('删除成功')
|
|
120
|
+
fetchList()
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const handleToggle = async (record) => {
|
|
126
|
+
const newStatus = record.status === 1 ? 0 : 1
|
|
127
|
+
await request('/ak/toggle', {method: 'POST', params: {id: record.id, status: newStatus}})
|
|
128
|
+
message.success(newStatus === 1 ? '已启用' : '已禁用')
|
|
129
|
+
fetchList()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const handleSubmit = async () => {
|
|
133
|
+
try {
|
|
134
|
+
const values = await form.validateFields()
|
|
135
|
+
if (editing) {
|
|
136
|
+
await request('/ak/update', {method: 'POST', data: {...values, id: editing.id}})
|
|
137
|
+
message.success('更新成功')
|
|
138
|
+
setVisible(false)
|
|
139
|
+
fetchList()
|
|
140
|
+
} else {
|
|
141
|
+
const res = await request('/ak/create', {method: 'POST', data: values})
|
|
142
|
+
const data = res?.accessKey ? res : (res?.data || {})
|
|
143
|
+
setVisible(false)
|
|
144
|
+
fetchList()
|
|
145
|
+
setShowSkModal({
|
|
146
|
+
akName: values.akName,
|
|
147
|
+
accessKey: data.accessKey,
|
|
148
|
+
secretKey: data.secretKey,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
} catch (e) {
|
|
152
|
+
// 校验失败不弹错误(form 自己展示)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const handleCopy = (text, label) => {
|
|
157
|
+
navigator.clipboard.writeText(text)
|
|
158
|
+
message.success(`${label} 已复制`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const columns = [
|
|
162
|
+
{
|
|
163
|
+
title: '名称',
|
|
164
|
+
dataIndex: 'akName',
|
|
165
|
+
key: 'akName',
|
|
166
|
+
width: 160,
|
|
167
|
+
render: (text, record) => (
|
|
168
|
+
<Space>
|
|
169
|
+
<KeyOutlined style={{color: '#1677ff'}}/>
|
|
170
|
+
<span style={{fontWeight: 500}}>{text}</span>
|
|
171
|
+
{record.akType === 'APP' && record.appName && (
|
|
172
|
+
<Tag color="purple" style={{borderRadius: 10, fontSize: 11}}>{record.appName}</Tag>
|
|
173
|
+
)}
|
|
174
|
+
</Space>
|
|
175
|
+
),
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
title: 'AccessKey',
|
|
179
|
+
dataIndex: 'accessKey',
|
|
180
|
+
key: 'accessKey',
|
|
181
|
+
width: 200,
|
|
182
|
+
render: (text) => (
|
|
183
|
+
<Space>
|
|
184
|
+
<code style={{
|
|
185
|
+
fontFamily: 'monospace', fontSize: 12, color: '#595959',
|
|
186
|
+
background: '#f5f5f5', padding: '3px 8px', borderRadius: 4,
|
|
187
|
+
}}>
|
|
188
|
+
{text}
|
|
189
|
+
</code>
|
|
190
|
+
<Tooltip title="复制 AccessKey">
|
|
191
|
+
<Button size="small" type="text" icon={<CopyOutlined/>} onClick={(e) => {
|
|
192
|
+
e.stopPropagation();
|
|
193
|
+
handleCopy(text, 'AccessKey')
|
|
194
|
+
}}/>
|
|
195
|
+
</Tooltip>
|
|
196
|
+
</Space>
|
|
197
|
+
),
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
title: 'Token 消耗',
|
|
201
|
+
dataIndex: 'totalTokens',
|
|
202
|
+
key: 'totalTokens',
|
|
203
|
+
width: 110,
|
|
204
|
+
sorter: (a, b) => (a.totalTokens || 0) - (b.totalTokens || 0),
|
|
205
|
+
render: (t) => (
|
|
206
|
+
<Space>
|
|
207
|
+
<ThunderboltOutlined style={{color: '#faad14', fontSize: 12}}/>
|
|
208
|
+
<span style={{fontWeight: 500}}>{formatTokens(t)}</span>
|
|
209
|
+
</Space>
|
|
210
|
+
),
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
title: '最后使用',
|
|
214
|
+
dataIndex: 'lastUsedTime',
|
|
215
|
+
key: 'lastUsedTime',
|
|
216
|
+
width: 160,
|
|
217
|
+
render: (t) => (
|
|
218
|
+
<Space size={4}>
|
|
219
|
+
<span style={{color: t ? '#595959' : '#bfbfbf', fontSize: 13}}>{t || '从未使用'}</span>
|
|
220
|
+
</Space>
|
|
221
|
+
),
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
title: '状态',
|
|
225
|
+
dataIndex: 'status',
|
|
226
|
+
key: 'status',
|
|
227
|
+
width: 70,
|
|
228
|
+
render: (s) => (
|
|
229
|
+
<Tag color={s === 1 ? 'green' : 'red'} style={{borderRadius: 20}}>
|
|
230
|
+
{s === 1 ? '正常' : '禁用'}
|
|
231
|
+
</Tag>
|
|
232
|
+
),
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
title: '描述',
|
|
236
|
+
dataIndex: 'description',
|
|
237
|
+
key: 'description',
|
|
238
|
+
ellipsis: true,
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
title: '创建时间',
|
|
242
|
+
dataIndex: 'createdTime',
|
|
243
|
+
key: 'createdTime',
|
|
244
|
+
width: 160,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
title: '操作',
|
|
248
|
+
key: 'action',
|
|
249
|
+
width: 130,
|
|
250
|
+
render: (_, record) => (
|
|
251
|
+
<Space size="small" onClick={(e) => e.stopPropagation()}>
|
|
252
|
+
<Tooltip title={record.status === 1 ? '禁用' : '启用'}>
|
|
253
|
+
<Button
|
|
254
|
+
size="small"
|
|
255
|
+
type="text"
|
|
256
|
+
icon={record.status === 1 ? <StopOutlined style={{color: '#faad14'}}/> :
|
|
257
|
+
<CheckCircleOutlined style={{color: '#52c41a'}}/>}
|
|
258
|
+
onClick={() => handleToggle(record)}
|
|
259
|
+
/>
|
|
260
|
+
</Tooltip>
|
|
261
|
+
<Tooltip title="编辑">
|
|
262
|
+
<Button size="small" type="text" icon={<KeyOutlined/>} onClick={() => handleEdit(record)}/>
|
|
263
|
+
</Tooltip>
|
|
264
|
+
<Tooltip title="删除">
|
|
265
|
+
<Button size="small" type="text" icon={<DeleteOutlined style={{color: '#ff4d4f'}}/>}
|
|
266
|
+
onClick={() => handleDelete(record)}/>
|
|
267
|
+
</Tooltip>
|
|
268
|
+
</Space>
|
|
269
|
+
),
|
|
270
|
+
},
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
const totalTokensAll = keys.reduce((s, k) => s + (k.totalTokens || 0), 0)
|
|
274
|
+
const activeCount = keys.filter(k => k.status === 1).length
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<div style={{padding: 16, display: 'flex', flexDirection: 'column', gap: 12}}>
|
|
278
|
+
<Alert
|
|
279
|
+
type="info"
|
|
280
|
+
showIcon
|
|
281
|
+
icon={<ApiOutlined/>}
|
|
282
|
+
message={
|
|
283
|
+
<Space>
|
|
284
|
+
<span style={{fontWeight: 500}}>API 接入地址:</span>
|
|
285
|
+
<code style={{
|
|
286
|
+
fontFamily: 'monospace', fontSize: 13, background: '#e6f4ff',
|
|
287
|
+
padding: '2px 8px', borderRadius: 4,
|
|
288
|
+
}}>{API_BASE_URL}</code>
|
|
289
|
+
<Button size="small" type="text" icon={<CopyOutlined/>}
|
|
290
|
+
onClick={() => handleCopy(API_BASE_URL, 'API 地址')}/>
|
|
291
|
+
<span style={{color: '#8c8c8c', fontSize: 12}}>使用此地址 + AccessKey 调用平台所有模型</span>
|
|
292
|
+
</Space>
|
|
293
|
+
}
|
|
294
|
+
style={{border: 'none', background: '#e6f4ff'}}
|
|
295
|
+
/>
|
|
296
|
+
|
|
297
|
+
<Row gutter={12}>
|
|
298
|
+
<Col span={6}>
|
|
299
|
+
<Card size="small" style={{background: '#f5f5f5'}} bodyStyle={{padding: '10px 14px'}}>
|
|
300
|
+
<Statistic title="AK 总数" value={keys.length} prefix={<KeyOutlined/>}
|
|
301
|
+
valueStyle={{fontSize: 18}}/>
|
|
302
|
+
</Card>
|
|
303
|
+
</Col>
|
|
304
|
+
<Col span={6}>
|
|
305
|
+
<Card size="small" style={{background: '#f5f5f5'}} bodyStyle={{padding: '10px 14px'}}>
|
|
306
|
+
<Statistic title="累计 Token" value={formatTokens(totalTokensAll)}
|
|
307
|
+
prefix={<ThunderboltOutlined/>} valueStyle={{fontSize: 18}}/>
|
|
308
|
+
</Card>
|
|
309
|
+
</Col>
|
|
310
|
+
<Col span={6}>
|
|
311
|
+
<Card size="small" style={{background: '#f5f5f5'}} bodyStyle={{padding: '10px 14px'}}>
|
|
312
|
+
<Statistic title="有效 AK" value={activeCount}
|
|
313
|
+
prefix={<CheckCircleOutlined style={{color: '#52c41a'}}/>}
|
|
314
|
+
valueStyle={{fontSize: 18}}/>
|
|
315
|
+
</Card>
|
|
316
|
+
</Col>
|
|
317
|
+
<Col span={6}>
|
|
318
|
+
<Card size="small" style={{background: '#f5f5f5'}} bodyStyle={{padding: '10px 14px'}}>
|
|
319
|
+
<Statistic title="所属应用" value={new Set(keys.map(k => k.appCode).filter(Boolean)).size}
|
|
320
|
+
prefix={<AppstoreOutlined/>} valueStyle={{fontSize: 18}}/>
|
|
321
|
+
</Card>
|
|
322
|
+
</Col>
|
|
323
|
+
</Row>
|
|
324
|
+
|
|
325
|
+
<Card size="small" styles={{body: {padding: 0}}}>
|
|
326
|
+
<Tabs
|
|
327
|
+
activeKey={activeTab}
|
|
328
|
+
onChange={setActiveTab}
|
|
329
|
+
size="small"
|
|
330
|
+
tabBarStyle={{margin: 0, padding: '0 16px'}}
|
|
331
|
+
tabBarExtraContent={
|
|
332
|
+
<Space style={{padding: '8px 0'}}>
|
|
333
|
+
<Button type="primary" icon={<PlusOutlined/>} onClick={handleAdd} size="small">
|
|
334
|
+
{activeTab === 'PERSONAL' ? '申请个人 AK' : '申请应用 AK'}
|
|
335
|
+
</Button>
|
|
336
|
+
<Button icon={<ReloadOutlined/>} onClick={fetchList} size="small"/>
|
|
337
|
+
</Space>
|
|
338
|
+
}
|
|
339
|
+
items={[
|
|
340
|
+
{
|
|
341
|
+
key: 'PERSONAL',
|
|
342
|
+
label: <Space><UserOutlined/>个人 AK</Space>,
|
|
343
|
+
children: (
|
|
344
|
+
<Spin spinning={loading}>
|
|
345
|
+
<Table
|
|
346
|
+
dataSource={keys}
|
|
347
|
+
columns={columns}
|
|
348
|
+
rowKey="id"
|
|
349
|
+
pagination={false}
|
|
350
|
+
size="small"
|
|
351
|
+
onRow={(record) => ({
|
|
352
|
+
onClick: () => setDrawerAk(record),
|
|
353
|
+
style: {cursor: 'pointer'},
|
|
354
|
+
})}
|
|
355
|
+
locale={{emptyText: '暂无个人 AK,点右上角"申请个人 AK"创建'}}
|
|
356
|
+
/>
|
|
357
|
+
</Spin>
|
|
358
|
+
),
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
key: 'APP',
|
|
362
|
+
label: <Space><AppstoreOutlined/>应用 AK</Space>,
|
|
363
|
+
children: (
|
|
364
|
+
<Spin spinning={loading}>
|
|
365
|
+
<Table
|
|
366
|
+
dataSource={keys}
|
|
367
|
+
columns={columns}
|
|
368
|
+
rowKey="id"
|
|
369
|
+
pagination={false}
|
|
370
|
+
size="small"
|
|
371
|
+
onRow={(record) => ({
|
|
372
|
+
onClick: () => setDrawerAk(record),
|
|
373
|
+
style: {cursor: 'pointer'},
|
|
374
|
+
})}
|
|
375
|
+
locale={{emptyText: '暂无应用 AK,点右上角"申请应用 AK"并绑定一个应用'}}
|
|
376
|
+
/>
|
|
377
|
+
</Spin>
|
|
378
|
+
),
|
|
379
|
+
},
|
|
380
|
+
]}
|
|
381
|
+
/>
|
|
382
|
+
</Card>
|
|
383
|
+
|
|
384
|
+
{/* 创建/编辑弹窗 */}
|
|
385
|
+
<Modal title={editing ? '编辑 AK' : (activeTab === 'APP' ? '申请应用 AK' : '申请个人 AK')}
|
|
386
|
+
open={visible}
|
|
387
|
+
onOk={handleSubmit}
|
|
388
|
+
onCancel={() => setVisible(false)}
|
|
389
|
+
width={520}
|
|
390
|
+
okText={editing ? '保存' : '申请'}
|
|
391
|
+
destroyOnClose>
|
|
392
|
+
<Form form={form} layout="vertical" preserve={false}>
|
|
393
|
+
<Form.Item name="akType" label="AK 类型" rules={[{required: true}]}>
|
|
394
|
+
<Radio.Group disabled={!!editing}>
|
|
395
|
+
<Radio.Button value="PERSONAL">个人 AK</Radio.Button>
|
|
396
|
+
<Radio.Button value="APP">应用 AK</Radio.Button>
|
|
397
|
+
</Radio.Group>
|
|
398
|
+
</Form.Item>
|
|
399
|
+
<Form.Item shouldUpdate={(prev, curr) => prev.akType !== curr.akType} noStyle>
|
|
400
|
+
{() => form.getFieldValue('akType') === 'APP' && (
|
|
401
|
+
<Form.Item name="appCode" label="所属应用"
|
|
402
|
+
rules={[{required: true, message: '应用 AK 必须绑定一个应用'}]}>
|
|
403
|
+
<Select
|
|
404
|
+
showSearch
|
|
405
|
+
optionFilterProp="label"
|
|
406
|
+
placeholder="选择应用"
|
|
407
|
+
options={appOptions}
|
|
408
|
+
/>
|
|
409
|
+
</Form.Item>
|
|
410
|
+
)}
|
|
411
|
+
</Form.Item>
|
|
412
|
+
<Form.Item name="akName" label="名称" rules={[{required: true, message: '请输入 AK 名称'}]}>
|
|
413
|
+
<Input placeholder={activeTab === 'APP' ? '如:生产环境-订单服务' : '如:本地调试'}/>
|
|
414
|
+
</Form.Item>
|
|
415
|
+
<Form.Item name="description" label="描述">
|
|
416
|
+
<Input.TextArea rows={2} placeholder="密钥用途..."/>
|
|
417
|
+
</Form.Item>
|
|
418
|
+
{!editing && (
|
|
419
|
+
<Alert
|
|
420
|
+
type="info"
|
|
421
|
+
showIcon
|
|
422
|
+
message="AccessKey 和 SecretKey 将由系统自动生成。SecretKey 仅在创建时展示一次,请妥善保管。"
|
|
423
|
+
style={{background: '#e6f4ff', border: 'none'}}
|
|
424
|
+
/>
|
|
425
|
+
)}
|
|
426
|
+
</Form>
|
|
427
|
+
</Modal>
|
|
428
|
+
|
|
429
|
+
{/* SecretKey 一次性展示弹窗 */}
|
|
430
|
+
<Modal
|
|
431
|
+
title="AK 创建成功"
|
|
432
|
+
open={!!showSkModal}
|
|
433
|
+
onCancel={() => setShowSkModal(null)}
|
|
434
|
+
footer={
|
|
435
|
+
<Button type="primary" onClick={() => setShowSkModal(null)}>
|
|
436
|
+
我已保存,关闭
|
|
437
|
+
</Button>
|
|
438
|
+
}
|
|
439
|
+
width={520}
|
|
440
|
+
>
|
|
441
|
+
{showSkModal && (
|
|
442
|
+
<div>
|
|
443
|
+
<Alert
|
|
444
|
+
type="warning"
|
|
445
|
+
showIcon
|
|
446
|
+
message="SecretKey 仅在此展示一次,关闭后将无法再次查看。请立即复制并妥善保管。"
|
|
447
|
+
style={{marginBottom: 16}}
|
|
448
|
+
/>
|
|
449
|
+
|
|
450
|
+
<div style={{marginBottom: 12}}>
|
|
451
|
+
<div style={{fontSize: 12, color: '#8c8c8c', marginBottom: 4}}>AccessKey</div>
|
|
452
|
+
<div style={{display: 'flex', gap: 8, alignItems: 'center'}}>
|
|
453
|
+
<code style={{
|
|
454
|
+
flex: 1, fontFamily: 'monospace', fontSize: 14, padding: '8px 12px',
|
|
455
|
+
background: '#f5f5f5', borderRadius: 6, border: '1px solid #d9d9d9',
|
|
456
|
+
}}>
|
|
457
|
+
{showSkModal.accessKey}
|
|
458
|
+
</code>
|
|
459
|
+
<Button icon={<CopyOutlined/>}
|
|
460
|
+
onClick={() => handleCopy(showSkModal.accessKey, 'AccessKey')}>复制</Button>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
<div style={{marginBottom: 12}}>
|
|
465
|
+
<div style={{fontSize: 12, color: '#8c8c8c', marginBottom: 4}}>SecretKey</div>
|
|
466
|
+
<div style={{display: 'flex', gap: 8, alignItems: 'center'}}>
|
|
467
|
+
<code style={{
|
|
468
|
+
flex: 1, fontFamily: 'monospace', fontSize: 14, padding: '8px 12px',
|
|
469
|
+
background: '#fff7e6', borderRadius: 6, border: '1px solid #ffd591',
|
|
470
|
+
color: '#d46b08',
|
|
471
|
+
}}>
|
|
472
|
+
{showSkModal.secretKey}
|
|
473
|
+
</code>
|
|
474
|
+
<Button type="primary" icon={<CopyOutlined/>}
|
|
475
|
+
onClick={() => handleCopy(showSkModal.secretKey, 'SecretKey')}>复制</Button>
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
|
|
479
|
+
<div style={{fontSize: 12, color: '#ff4d4f', marginTop: 8}}>
|
|
480
|
+
⚠ 请立即复制 SecretKey,此弹窗关闭后将无法再次获取。如遗失,请删除后重新申请。
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
)}
|
|
484
|
+
</Modal>
|
|
485
|
+
|
|
486
|
+
{/* 用量详情抽屉 */}
|
|
487
|
+
<AkUsageDrawer
|
|
488
|
+
open={!!drawerAk}
|
|
489
|
+
ak={drawerAk}
|
|
490
|
+
onClose={() => setDrawerAk(null)}
|
|
491
|
+
/>
|
|
492
|
+
</div>
|
|
493
|
+
)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export default AkManage
|