@@ -0,0 +1,150 @@
< div class = "plugin-page-root" >
< h2 > ⚙ 框架设置< / h2 >
<!-- ── API Key 管理 ── -->
< div class = "card" style = "margin-bottom:24px" >
< div style = "display:flex;justify-content:space-between;align-items:center;margin-bottom:16px" >
< h3 style = "margin:0" > 🔑 API Key 管理< / h3 >
< button class = "btn btn-filled" onclick = "showCreateKey()" > + 创建 Key< / button >
< / div >
< p style = "color:var(--text-dim);font-size:.85rem;margin-bottom:12px" >
API Key 用于服务器间安全通信。支持权限范围控制和过期时间。
< / p >
< div id = "apikey-list" style = "overflow-x:auto" >
< table style = "width:100%;border-collapse:collapse" >
< thead > < tr style = "text-align:left;border-bottom:1px solid var(--border)" >
< th style = "padding:8px" > 名称< / th > < th style = "padding:8px" > Key< / th > < th style = "padding:8px" > 权限< / th > < th style = "padding:8px" > 用量< / th > < th style = "padding:8px" > 过期< / th > < th style = "padding:8px" > 操作< / th >
< / tr > < / thead >
< tbody id = "apikey-tbody" > < tr > < td colspan = "6" style = "padding:16px;text-align:center;color:var(--text-dim)" > 加载中...< / td > < / tr > < / tbody >
< / table >
< / div >
< / div >
<!-- ── 创建对话框 (hidden) ── -->
< div id = "create-dialog" style = "display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5);z-index:1000;display:none;align-items:center;justify-content:center" >
< div class = "card" style = "min-width:420px;max-width:500px" >
< h3 style = "margin:0 0 16px 0" > 创建 API Key< / h3 >
< div class = "form-group" style = "margin-bottom:12px" >
< label style = "display:block;margin-bottom:4px;font-size:.85rem" > 名称< / label >
< input id = "new-key-name" class = "input" placeholder = "例如: 监控插件" style = "width:100%;padding:8px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:var(--shape-xs)" >
< / div >
< div class = "form-group" style = "margin-bottom:12px" >
< label style = "display:block;margin-bottom:4px;font-size:.85rem" > 权限范围< / label >
< select id = "new-key-preset" class = "input" style = "width:100%;padding:8px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:var(--shape-xs)" >
< option value = "readonly" > 只读 — 仅查询系统状态< / option >
< option value = "monitor" selected > 监控 — 查询 + 事件订阅< / option >
< option value = "full" > 管理 — 全部权限< / option >
< / select >
< / div >
< div class = "form-group" style = "margin-bottom:16px" >
< label style = "display:block;margin-bottom:4px;font-size:.85rem" > 过期时间 (秒, 0=永不过期)< / label >
< input id = "new-key-ttl" class = "input" type = "number" value = "0" min = "0" style = "width:100%;padding:8px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:var(--shape-xs)" >
< span style = "font-size:.75rem;color:var(--text-dim)" > 常用: 86400(1天) 604800(7天) 2592000(30天)< / span >
< / div >
< div style = "display:flex;gap:8px;justify-content:flex-end" >
< button class = "btn btn-outlined" onclick = "hideCreateKey()" > 取消< / button >
< button class = "btn btn-filled" onclick = "doCreateKey()" > 创建< / button >
< / div >
< div id = "create-result" style = "margin-top:12px;display:none" >
< div style = "background:var(--md-sys-color-surface-container-lowest);padding:12px;border-radius:var(--shape-xs);word-break:break-all" >
< strong style = "color:var(--success)" > ✅ Key 已创建 (仅显示一次):< / strong >
< code id = "new-key-display" style = "display:block;margin-top:4px;font-size:.9rem;color:var(--primary)" > < / code >
< / div >
< / div >
< / div >
< / div >
< style >
. input : focus { outline : 1 px solid var ( - - primary ) }
# apikey-tbody td { padding : 8 px ; border-bottom : 1 px solid var ( - - md - sys - color - surface - container - lowest ) }
# apikey-tbody tr : hover { background : var ( - - md - sys - color - surface - container - lowest ) }
< / style >
< script >
// ── 加载 API Keys ──
async function loadKeys ( ) {
var tbody = document . getElementById ( "apikey-tbody" ) ;
try {
var resp = await fetch ( "./api/apikeys" ) ;
var data = await resp . json ( ) ;
if ( ! data . keys || ! data . keys . length ) {
tbody . innerHTML = '<tr><td colspan="6" style="padding:16px;text-align:center;color:var(--text-dim)">暂无 API Key</td></tr>' ;
return ;
}
var rows = [ ] ;
data . keys . forEach ( function ( k ) {
var expStr = k . expires _at > 0 ? new Date ( k . expires _at * 1000 ) . toLocaleDateString ( ) : "永不过期" ;
if ( k . expired ) expStr = '<span style="color:var(--error)">已过期</span>' ;
var permLabel = k . permissions && k . permissions [ 0 ] === "admin" ? "管理" : ( k . permissions && k . permissions . length <= 2 ? "只读" : "监控" ) ;
rows . push (
'<tr>' +
'<td>' + esc ( k . name ) + '</td>' +
'<td><code>' + esc ( k . key ) + '</code></td>' +
'<td><span class="badge">' + permLabel + '</span></td>' +
'<td>' + ( k . request _count || 0 ) + '</td>' +
'<td>' + expStr + '</td>' +
'<td><button class="btn btn-sm btn-outlined" style="color:var(--error);border-color:var(--error)" onclick="deleteKey(\'' + esc ( k . full _key ) + '\',\'' + esc ( k . name ) + '\')">删除</button></td>' +
'</tr>'
) ;
} ) ;
tbody . innerHTML = rows . join ( "" ) ;
} catch ( e ) {
tbody . innerHTML = '<tr><td colspan="6" style="padding:16px;text-align:center;color:var(--error)">加载失败: ' + esc ( String ( e ) ) + '</td></tr>' ;
}
}
function esc ( s ) { return String ( s ) . replace ( /&/g , '&' ) . replace ( /</g , '<' ) . replace ( />/g , '>' ) . replace ( /"/g , '"' ) ; }
// ── 创建对话框 ──
function showCreateKey ( ) {
document . getElementById ( "create-dialog" ) . style . display = "flex" ;
document . getElementById ( "create-result" ) . style . display = "none" ;
document . getElementById ( "new-key-name" ) . value = "" ;
document . getElementById ( "new-key-ttl" ) . value = "0" ;
}
function hideCreateKey ( ) {
document . getElementById ( "create-dialog" ) . style . display = "none" ;
}
async function doCreateKey ( ) {
var name = document . getElementById ( "new-key-name" ) . value . trim ( ) ;
if ( ! name ) { alert ( "请输入名称" ) ; return ; }
var preset = document . getElementById ( "new-key-preset" ) . value ;
var ttl = parseInt ( document . getElementById ( "new-key-ttl" ) . value ) || 0 ;
try {
var resp = await fetch ( "./api/apikeys" , {
method : "POST" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON . stringify ( { name : name , preset : preset , ttl : ttl } )
} ) ;
var data = await resp . json ( ) ;
if ( data . ok ) {
document . getElementById ( "create-result" ) . style . display = "block" ;
document . getElementById ( "new-key-display" ) . textContent = data . key ;
loadKeys ( ) ;
} else {
alert ( "创建失败: " + ( data . error || "未知错误" ) ) ;
}
} catch ( e ) { alert ( "请求失败: " + e ) ; }
}
// ── 删除 ──
async function deleteKey ( fullKey , name ) {
if ( ! confirm ( "确认删除 Key '" + name + "'? 此操作不可撤销。" ) ) return ;
try {
var resp = await fetch ( "./api/apikeys" , {
method : "DELETE" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON . stringify ( { key : fullKey } )
} ) ;
var data = await resp . json ( ) ;
if ( data . ok ) loadKeys ( ) ;
else alert ( "删除失败: " + ( data . error || "未知错误" ) ) ;
} catch ( e ) { alert ( "请求失败: " + e ) ; }
}
// ── 初始加载 ──
loadKeys ( ) ;
< / script >
< / div >