Phase 6: API Platform - Add authentication to existing endpoints and frontend API Key management UI

This commit is contained in:
OpenClaw Bot
2026-02-21 18:10:34 +08:00
parent d040cb7657
commit f360e1eec5
13 changed files with 2319 additions and 2895 deletions

View File

@@ -1925,6 +1925,344 @@
border-radius: 50%;
background: currentColor;
}
/* Phase 6: API Key Management Panel */
.api-keys-panel {
display: none;
flex-direction: column;
width: 100%;
height: 100%;
background: #0a0a0a;
}
.api-keys-panel.active {
display: flex;
}
.api-keys-header {
padding: 16px 20px;
background: #141414;
border-bottom: 1px solid #222;
display: flex;
justify-content: space-between;
align-items: center;
}
.api-keys-header h2 {
font-size: 1.3rem;
margin-bottom: 4px;
}
.api-keys-content {
flex: 1;
padding: 24px;
overflow-y: auto;
}
.api-keys-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.api-key-stat-card {
background: #141414;
border: 1px solid #222;
border-radius: 8px;
padding: 16px;
text-align: center;
}
.api-key-stat-value {
font-size: 1.5rem;
font-weight: 600;
color: #00d4ff;
}
.api-key-stat-label {
font-size: 0.75rem;
color: #666;
margin-top: 4px;
}
.api-keys-list {
background: #141414;
border: 1px solid #222;
border-radius: 12px;
overflow: hidden;
}
.api-keys-list-header {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr 1fr 120px;
padding: 12px 16px;
background: #1a1a1a;
border-bottom: 1px solid #222;
font-size: 0.85rem;
color: #888;
font-weight: 500;
}
.api-key-item {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr 1fr 120px;
padding: 16px;
border-bottom: 1px solid #222;
align-items: center;
transition: background 0.2s;
}
.api-key-item:hover {
background: #1a1a1a;
}
.api-key-item:last-child {
border-bottom: none;
}
.api-key-name {
font-weight: 500;
color: #e0e0e0;
}
.api-key-preview {
font-family: monospace;
font-size: 0.85rem;
color: #00d4ff;
background: #00d4ff11;
padding: 4px 8px;
border-radius: 4px;
}
.api-key-permissions {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
.api-key-permission {
font-size: 0.7rem;
padding: 2px 6px;
border-radius: 4px;
background: #333;
color: #888;
}
.api-key-permission.read {
background: #00d4ff22;
color: #00d4ff;
}
.api-key-permission.write {
background: #7b2cbf22;
color: #7b2cbf;
}
.api-key-permission.delete {
background: #ff6b6b22;
color: #ff6b6b;
}
.api-key-status {
font-size: 0.8rem;
padding: 4px 10px;
border-radius: 20px;
display: inline-block;
}
.api-key-status.active {
background: #00d4ff22;
color: #00d4ff;
}
.api-key-status.revoked {
background: #ff6b6b22;
color: #ff6b6b;
}
.api-key-status.expired {
background: #66666622;
color: #666;
}
.api-key-actions {
display: flex;
gap: 8px;
}
.api-key-btn {
background: transparent;
border: 1px solid #333;
color: #888;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 0.8rem;
transition: all 0.2s;
}
.api-key-btn:hover {
border-color: #00d4ff;
color: #00d4ff;
}
.api-key-btn.danger:hover {
border-color: #ff6b6b;
color: #ff6b6b;
}
.api-key-empty {
text-align: center;
padding: 60px 20px;
color: #666;
}
.api-key-modal-form {
display: flex;
flex-direction: column;
gap: 16px;
}
.api-key-form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.api-key-form-group label {
font-size: 0.9rem;
color: #888;
}
.api-key-form-group input,
.api-key-form-group select {
background: #0a0a0a;
border: 1px solid #333;
border-radius: 6px;
padding: 10px 12px;
color: #e0e0e0;
font-size: 0.95rem;
}
.api-key-form-group input:focus,
.api-key-form-group select:focus {
outline: none;
border-color: #00d4ff;
}
.api-key-permissions-select {
display: flex;
gap: 12px;
}
.api-key-permission-checkbox {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
}
.api-key-permission-checkbox input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: #00d4ff;
}
.api-key-created-modal .api-key-value {
background: #0a0a0a;
border: 1px solid #00d4ff;
border-radius: 8px;
padding: 16px;
font-family: monospace;
font-size: 1rem;
color: #00d4ff;
margin: 16px 0;
word-break: break-all;
}
.api-key-created-modal .warning {
background: #ff6b6b22;
border: 1px solid #ff6b6b;
border-radius: 8px;
padding: 12px;
color: #ff6b6b;
font-size: 0.85rem;
margin-bottom: 16px;
}
.api-key-stats-modal .stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.api-key-stats-modal .stat-item {
background: #0a0a0a;
border-radius: 8px;
padding: 16px;
text-align: center;
}
.api-key-stats-modal .stat-value {
font-size: 1.5rem;
font-weight: 600;
color: #00d4ff;
}
.api-key-stats-modal .stat-label {
font-size: 0.8rem;
color: #666;
margin-top: 4px;
}
.api-key-logs {
max-height: 300px;
overflow-y: auto;
}
.api-key-log-item {
display: grid;
grid-template-columns: 1fr 80px 60px 80px;
gap: 12px;
padding: 12px;
border-bottom: 1px solid #222;
font-size: 0.85rem;
}
.api-key-log-item:last-child {
border-bottom: none;
}
.api-key-log-endpoint {
color: #e0e0e0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.api-key-log-method {
color: #00d4ff;
font-family: monospace;
}
.api-key-log-status {
text-align: center;
}
.api-key-log-status.success {
color: #4ecdc4;
}
.api-key-log-status.error {
color: #ff6b6b;
}
.api-key-log-time {
color: #666;
text-align: right;
}
</style>
</head>
<body>
@@ -1946,6 +2284,7 @@
<button class="sidebar-btn" onclick="switchView('timeline')" title="时间线">📅</button>
<button class="sidebar-btn" onclick="switchView('reasoning')" title="智能推理">🧠</button>
<button class="sidebar-btn" onclick="switchView('graph-analysis')" title="图分析">🕸️</button>
<button class="sidebar-btn" onclick="switchView('api-keys')" title="API Keys">🔑</button>
</div>
<!-- Content Area -->
@@ -2324,6 +2663,54 @@
</div>
</div>
<!-- API Keys Management View -->
<div id="apiKeysView" class="api-keys-panel">
<div class="api-keys-header">
<div>
<h2>🔑 API Key 管理</h2>
<p style="color:#888;">管理 API 访问密钥和调用统计</p>
</div>
<button class="btn" onclick="showCreateApiKeyModal()">+ 创建 API Key</button>
</div>
<div class="api-keys-content">
<div class="api-keys-stats">
<div class="api-key-stat-card">
<div class="api-key-stat-value" id="apiKeyTotalCount">-</div>
<div class="api-key-stat-label">总 API Keys</div>
</div>
<div class="api-key-stat-card">
<div class="api-key-stat-value" id="apiKeyActiveCount">-</div>
<div class="api-key-stat-label">活跃</div>
</div>
<div class="api-key-stat-card">
<div class="api-key-stat-value" id="apiKeyRevokedCount">-</div>
<div class="api-key-stat-label">已撤销</div>
</div>
<div class="api-key-stat-card">
<div class="api-key-stat-value" id="apiKeyTotalCalls">-</div>
<div class="api-key-stat-label">总调用次数</div>
</div>
</div>
<div class="api-keys-list" id="apiKeysList">
<div class="api-keys-list-header">
<span>名称 / Key</span>
<span>权限</span>
<span>限流</span>
<span>状态</span>
<span>调用次数</span>
<span>操作</span>
</div>
<div id="apiKeysListContent">
<div class="api-key-empty">
<p>加载中...</p>
</div>
</div>
</div>
</div>
</div>
<!-- Entity Hover Card -->
<div class="entity-card" id="entityCard">
<div class="entity-card-header">
@@ -2663,6 +3050,122 @@
</div>
</div>
<!-- API Key Create Modal -->
<div class="modal-overlay" id="apiKeyCreateModal">
<div class="modal" style="max-width: 500px;">
<h3 class="modal-header">创建 API Key</h3>
<div class="api-key-modal-form">
<div class="api-key-form-group">
<label>名称 / 描述</label>
<input type="text" id="apiKeyName" placeholder="例如:移动应用开发">
</div>
<div class="api-key-form-group">
<label>权限</label>
<div class="api-key-permissions-select">
<label class="api-key-permission-checkbox">
<input type="checkbox" id="permRead" checked>
<span>读取 (read)</span>
</label>
<label class="api-key-permission-checkbox">
<input type="checkbox" id="permWrite">
<span>写入 (write)</span>
</label>
<label class="api-key-permission-checkbox">
<input type="checkbox" id="permDelete">
<span>删除 (delete)</span>
</label>
</div>
</div>
<div class="api-key-form-group">
<label>限流 (请求/分钟)</label>
<select id="apiKeyRateLimit">
<option value="60">60 (默认)</option>
<option value="120">120</option>
<option value="300">300</option>
<option value="600">600</option>
<option value="1000">1000</option>
</select>
</div>
<div class="api-key-form-group">
<label>过期时间 (可选)</label>
<select id="apiKeyExpires">
<option value="">永不过期</option>
<option value="7">7 天</option>
<option value="30">30 天</option>
<option value="90">90 天</option>
<option value="365">1 年</option>
</select>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideCreateApiKeyModal()">取消</button>
<button class="btn" onclick="createApiKey()">创建</button>
</div>
</div>
</div>
<!-- API Key Created Success Modal -->
<div class="modal-overlay" id="apiKeyCreatedModal">
<div class="modal api-key-created-modal" style="max-width: 600px;">
<h3 class="modal-header">✅ API Key 创建成功</h3>
<div class="warning">
⚠️ 请立即复制并保存此 API Key它只会显示一次之后无法再次查看。
</div>
<label style="color:#888;font-size:0.9rem;">你的 API Key</label>
<div class="api-key-value" id="createdApiKeyValue">
ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
</div>
<div style="display:flex;gap:12px;">
<button class="btn" onclick="copyApiKey()" style="flex:1;">📋 复制到剪贴板</button>
<button class="btn btn-secondary" onclick="hideApiKeyCreatedModal()" style="flex:1;">我已保存</button>
</div>
</div>
</div>
<!-- API Key Stats Modal -->
<div class="modal-overlay" id="apiKeyStatsModal">
<div class="modal api-key-stats-modal" style="max-width: 700px;">
<h3 class="modal-header" id="apiKeyStatsTitle">API Key 统计</h3>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value" id="statsTotalCalls">-</div>
<div class="stat-label">总调用次数</div>
</div>
<div class="stat-item">
<div class="stat-value" id="statsSuccessCalls">-</div>
<div class="stat-label">成功</div>
</div>
<div class="stat-item">
<div class="stat-value" id="statsErrorCalls">-</div>
<div class="stat-label">错误</div>
</div>
<div class="stat-item">
<div class="stat-value" id="statsAvgTime">-</div>
<div class="stat-label">平均响应时间 (ms)</div>
</div>
</div>
<h4 style="color:#888;font-size:0.9rem;margin:20px 0 12px;">最近调用日志</h4>
<div class="api-key-logs" id="apiKeyLogs">
<div class="api-key-empty">
<p>暂无调用记录</p>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideApiKeyStatsModal()">关闭</button>
</div>
</div>
</div>
<!-- Context Menu -->
<div class="context-menu" id="contextMenu">
<div class="context-menu-item" onclick="editEntity()">✏️ 编辑实体</div>