This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Banner, Button, Input, Table, Toast } from "@douyinfe/semi-ui-19";
|
||||
import { api, formatTime, post } from "../api";
|
||||
import { api, formatTime, patch, post } from "../api";
|
||||
import "./mcp-keys.css";
|
||||
|
||||
type Token = {
|
||||
@@ -22,8 +22,16 @@ type TokenPage = {
|
||||
};
|
||||
const scopes = [
|
||||
["research:read", "读取研究数据", "查询目录、历史、运行与结果"],
|
||||
["research:refresh", "刷新研究数据", "更新缓存、检查自相关及恢复 WorldQuant 认证"],
|
||||
["research:write", "保存研究模板", "保存大模型总结的模板及来源,供后续批量回测"],
|
||||
[
|
||||
"research:refresh",
|
||||
"刷新研究数据",
|
||||
"更新缓存、检查自相关及恢复 WorldQuant 认证",
|
||||
],
|
||||
[
|
||||
"research:write",
|
||||
"保存研究模板",
|
||||
"保存大模型总结的模板及来源,供后续批量回测",
|
||||
],
|
||||
["backtests:execute", "执行回测", "提交新的回测批次"],
|
||||
["backtests:control", "控制回测", "暂停、继续、停止与恢复采集"],
|
||||
] as const;
|
||||
@@ -47,6 +55,9 @@ export function MCPKeysPage() {
|
||||
const [created, setCreated] = useState<(Token & { token: string }) | null>(
|
||||
null,
|
||||
);
|
||||
const [editing, setEditing] = useState<Token | null>(null);
|
||||
const [editPermissions, setEditPermissions] = useState<string[]>([]);
|
||||
const [editError, setEditError] = useState("");
|
||||
const [revoking, setRevoking] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
@@ -89,6 +100,24 @@ export function MCPKeysPage() {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
async function savePermissions(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
if (!editing || busy) return;
|
||||
setBusy(true);
|
||||
setEditError("");
|
||||
try {
|
||||
await patch<Token>(`/mcp-tokens/${editing.id}`, {
|
||||
scopes: editPermissions,
|
||||
});
|
||||
setEditing(null);
|
||||
setVersion((v) => v + 1);
|
||||
Toast.success("MCP Key 权限已更新");
|
||||
} catch (e) {
|
||||
setEditError((e as Error).message);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
async function revoke(id: string) {
|
||||
setBusy(true);
|
||||
try {
|
||||
@@ -238,6 +267,53 @@ export function MCPKeysPage() {
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
{editing && (
|
||||
<form
|
||||
className="mcp-key-card"
|
||||
aria-label="编辑 Key 权限"
|
||||
onSubmit={(event) => void savePermissions(event)}
|
||||
>
|
||||
<h2>编辑权限:{editing.name}</h2>
|
||||
<p>保存后新请求使用更新后的权限,无需更换客户端 Key。</p>
|
||||
{editError && <Banner type="danger" description={editError} />}
|
||||
<fieldset>
|
||||
<legend>访问权限</legend>
|
||||
<div className="mcp-key-permissions">
|
||||
{scopes.map(([value, label, description]) => (
|
||||
<label key={value}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editPermissions.includes(value)}
|
||||
disabled={value === "research:read" || busy}
|
||||
onChange={(e) =>
|
||||
setEditPermissions((old) =>
|
||||
e.target.checked
|
||||
? [...old, value]
|
||||
: old.filter((item) => item !== value),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span>
|
||||
<strong>{label}</strong>
|
||||
<small>
|
||||
{description}
|
||||
{value === "research:read" ? "(必选)" : ""}
|
||||
</small>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className="mcp-key-actions">
|
||||
<Button htmlType="submit" theme="solid" loading={busy}>
|
||||
保存权限
|
||||
</Button>
|
||||
<Button disabled={busy} onClick={() => setEditing(null)}>
|
||||
取消编辑
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
<section
|
||||
className="mcp-key-card"
|
||||
aria-label="Key 列表"
|
||||
@@ -317,13 +393,29 @@ export function MCPKeysPage() {
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
type="danger"
|
||||
disabled={busy}
|
||||
onClick={() => setRevoking(item.id)}
|
||||
>
|
||||
撤销
|
||||
</Button>
|
||||
<div>
|
||||
<Button
|
||||
disabled={busy || item.status !== "active"}
|
||||
onClick={() => {
|
||||
setEditing(item);
|
||||
setEditPermissions([...item.scopes]);
|
||||
setEditError("");
|
||||
setRevoking(null);
|
||||
}}
|
||||
>
|
||||
编辑权限
|
||||
</Button>
|
||||
<Button
|
||||
type="danger"
|
||||
disabled={busy}
|
||||
onClick={() => {
|
||||
setEditing(null);
|
||||
setRevoking(item.id);
|
||||
}}
|
||||
>
|
||||
撤销
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
|
||||
Reference in New Issue
Block a user