| <!DOCTYPE html>
|
| <html lang="zh-CN">
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <title>API密钥测试更新</title>
|
| <link rel="stylesheet" href="/static/css/base.css">
|
| <link rel="stylesheet" href="/static/css/style.css">
|
| <style>
|
| .container {
|
| max-width: 800px;
|
| margin: 0 auto;
|
| padding: 20px;
|
| }
|
| .key-list {
|
| margin-top: 20px;
|
| border: 1px solid #e5e7eb;
|
| border-radius: 8px;
|
| overflow: hidden;
|
| }
|
| .key-item {
|
| padding: 15px;
|
| border-bottom: 1px solid #e5e7eb;
|
| display: flex;
|
| justify-content: space-between;
|
| align-items: center;
|
| }
|
| .key-item:last-child {
|
| border-bottom: none;
|
| }
|
| .key-item:hover {
|
| background-color: #f9fafb;
|
| }
|
| .key-info {
|
| flex-grow: 1;
|
| }
|
| .key-name {
|
| font-weight: 500;
|
| margin-bottom: 5px;
|
| }
|
| .key-value {
|
| font-family: monospace;
|
| color: #6b7280;
|
| word-break: break-all;
|
| }
|
| .key-platform {
|
| color: #4f46e5;
|
| font-size: 0.875rem;
|
| margin-bottom: 8px;
|
| }
|
| .key-status {
|
| display: flex;
|
| flex-direction: column;
|
| align-items: flex-end;
|
| min-width: 120px;
|
| }
|
| .update-btn {
|
| padding: 6px 12px;
|
| border-radius: 6px;
|
| background-color: #4f46e5;
|
| color: white;
|
| font-size: 0.875rem;
|
| border: none;
|
| cursor: pointer;
|
| transition: background-color 0.2s;
|
| }
|
| .update-btn:hover {
|
| background-color: #4338ca;
|
| }
|
| .success-badge {
|
| margin-top: 8px;
|
| padding: 4px 8px;
|
| border-radius: 9999px;
|
| font-size: 0.75rem;
|
| background-color: #dcfce7;
|
| color: #16a34a;
|
| }
|
| .error-badge {
|
| margin-top: 8px;
|
| padding: 4px 8px;
|
| border-radius: 9999px;
|
| font-size: 0.75rem;
|
| background-color: #fee2e2;
|
| color: #dc2626;
|
| }
|
| .pending-badge {
|
| margin-top: 8px;
|
| padding: 4px 8px;
|
| border-radius: 9999px;
|
| font-size: 0.75rem;
|
| background-color: #e0f2fe;
|
| color: #0284c7;
|
| }
|
| #loading {
|
| display: none;
|
| margin: 20px auto;
|
| text-align: center;
|
| font-style: italic;
|
| color: #6b7280;
|
| }
|
| .response-container {
|
| margin-top: 15px;
|
| background-color: #f9fafb;
|
| border-radius: 6px;
|
| padding: 10px;
|
| font-family: monospace;
|
| font-size: 0.875rem;
|
| white-space: pre-wrap;
|
| word-break: break-all;
|
| display: none;
|
| max-height: 200px;
|
| overflow-y: auto;
|
| }
|
| </style>
|
| </head>
|
| <body>
|
| <div class="container">
|
| <h1>API密钥测试与更新</h1>
|
| <p>在此页面可以测试update.py脚本的功能,验证API密钥并更新其状态。</p>
|
|
|
| <div id="loading">正在加载API密钥列表...</div>
|
|
|
| <div id="keyList" class="key-list"></div>
|
| </div>
|
|
|
| <script>
|
| document.addEventListener('DOMContentLoaded', function() {
|
| loadKeys();
|
|
|
|
|
| const container = document.querySelector('.container');
|
| const backButton = document.createElement('a');
|
| backButton.href = '/';
|
| backButton.textContent = '返回主页';
|
| backButton.style.display = 'inline-block';
|
| backButton.style.marginTop = '20px';
|
| backButton.style.color = '#4f46e5';
|
| backButton.style.textDecoration = 'none';
|
| container.appendChild(backButton);
|
| });
|
|
|
| async function loadKeys() {
|
| const loadingElement = document.getElementById('loading');
|
| const keyListElement = document.getElementById('keyList');
|
|
|
| loadingElement.style.display = 'block';
|
| keyListElement.innerHTML = '';
|
|
|
| try {
|
| const response = await fetch('/api/keys');
|
| const data = await response.json();
|
|
|
| if (data && data.api_keys && data.api_keys.length > 0) {
|
| data.api_keys.forEach(key => {
|
| const keyElement = createKeyElement(key);
|
| keyListElement.appendChild(keyElement);
|
| });
|
| } else {
|
| keyListElement.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">没有找到API密钥</div>';
|
| }
|
| } catch (error) {
|
| console.error('加载API密钥失败:', error);
|
| keyListElement.innerHTML = `<div style="padding: 20px; text-align: center; color: #dc2626;">加载API密钥失败: ${error.message}</div>`;
|
| } finally {
|
| loadingElement.style.display = 'none';
|
| }
|
| }
|
|
|
| function createKeyElement(key) {
|
| const keyItem = document.createElement('div');
|
| keyItem.className = 'key-item';
|
| keyItem.id = `key-${key.id}`;
|
|
|
|
|
| const keyInfo = document.createElement('div');
|
| keyInfo.className = 'key-info';
|
|
|
|
|
| const platformElement = document.createElement('div');
|
| platformElement.className = 'key-platform';
|
| platformElement.textContent = getPlatformName(key.platform);
|
| keyInfo.appendChild(platformElement);
|
|
|
|
|
| const nameElement = document.createElement('div');
|
| nameElement.className = 'key-name';
|
| nameElement.textContent = key.name;
|
| keyInfo.appendChild(nameElement);
|
|
|
|
|
| const valueElement = document.createElement('div');
|
| valueElement.className = 'key-value';
|
| valueElement.textContent = key.key;
|
| keyInfo.appendChild(valueElement);
|
|
|
|
|
| if (key.updated_at) {
|
| const updatedElement = document.createElement('div');
|
| updatedElement.style.fontSize = '0.75rem';
|
| updatedElement.style.color = '#6b7280';
|
| updatedElement.style.marginTop = '5px';
|
|
|
| const date = new Date(key.updated_at);
|
| updatedElement.textContent = `上次更新: ${date.toLocaleString()}`;
|
| keyInfo.appendChild(updatedElement);
|
| }
|
|
|
|
|
| const responseContainer = document.createElement('div');
|
| responseContainer.className = 'response-container';
|
| responseContainer.id = `response-${key.id}`;
|
| keyInfo.appendChild(responseContainer);
|
|
|
| keyItem.appendChild(keyInfo);
|
|
|
|
|
| const keyStatus = document.createElement('div');
|
| keyStatus.className = 'key-status';
|
|
|
|
|
| const updateButton = document.createElement('button');
|
| updateButton.className = 'update-btn';
|
| updateButton.textContent = '更新状态';
|
| updateButton.onclick = function() {
|
| updateKeyStatus(key.id);
|
| };
|
| keyStatus.appendChild(updateButton);
|
|
|
|
|
| const statusBadge = document.createElement('div');
|
| if (key.success === true) {
|
| statusBadge.className = 'success-badge';
|
| statusBadge.textContent = '验证成功';
|
| } else if (key.success === false) {
|
| statusBadge.className = 'error-badge';
|
| statusBadge.textContent = key.return_message || '验证失败';
|
| } else {
|
| statusBadge.className = 'pending-badge';
|
| statusBadge.textContent = '等待验证';
|
| }
|
| keyStatus.appendChild(statusBadge);
|
|
|
| keyItem.appendChild(keyStatus);
|
|
|
| return keyItem;
|
| }
|
|
|
| async function updateKeyStatus(keyId) {
|
| const keyElement = document.getElementById(`key-${keyId}`);
|
| const updateButton = keyElement.querySelector('.update-btn');
|
| const responseContainer = document.getElementById(`response-${keyId}`);
|
|
|
|
|
| updateButton.disabled = true;
|
| updateButton.textContent = '更新中...';
|
|
|
| try {
|
| const response = await fetch(`/api/keys/update/${keyId}`, {
|
| method: 'POST'
|
| });
|
|
|
| const result = await response.json();
|
|
|
|
|
| responseContainer.textContent = JSON.stringify(result, null, 2);
|
| responseContainer.style.display = 'block';
|
|
|
|
|
| loadKeys();
|
|
|
| } catch (error) {
|
| console.error(`更新密钥 ${keyId} 失败:`, error);
|
| responseContainer.textContent = `更新失败: ${error.message}`;
|
| responseContainer.style.display = 'block';
|
| } finally {
|
|
|
| updateButton.disabled = false;
|
| updateButton.textContent = '更新状态';
|
| }
|
| }
|
|
|
| function getPlatformName(platformId) {
|
| const platforms = {
|
| 'openai': 'OpenAI',
|
| 'anthropic': 'Anthropic',
|
| 'google': 'Google',
|
| 'deepseek': 'DeepSeek'
|
| };
|
|
|
| return platforms[platformId] || platformId;
|
| }
|
| </script>
|
| </body>
|
| </html>
|
|
|