Phase 6: API Platform - Add authentication to existing endpoints and frontend API Key management UI
This commit is contained in:
3033
frontend/app.js
3033
frontend/app.js
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user