diff --git a/dashboard/public/app.js b/dashboard/public/app.js index 7deb119f5cb32b9b66fa0b3a8e26db7225937ab4..4948be20bae648f050142e5d62f18c1cd66e8a37 100644 --- a/dashboard/public/app.js +++ b/dashboard/public/app.js @@ -26,6 +26,16 @@ const commandForm = document.getElementById('command-form'); const commandInput = document.getElementById('command-input'); const commandHistory = document.getElementById('command-history'); const userEventsList = document.getElementById('user-events-list'); +const aiConfigForm = document.getElementById('ai-config-form'); +const aiApiUrlInput = document.getElementById('ai-api-url'); +const aiModelIdInput = document.getElementById('ai-model-id'); +const aiApiKeyInput = document.getElementById('ai-api-key'); +const aiApiKeyHint = document.getElementById('ai-api-key-hint'); +const aiSystemPromptInput = document.getElementById('ai-system-prompt'); +const aiEnabledCheckbox = document.getElementById('ai-enabled'); +const aiTestBtn = document.getElementById('ai-test-btn'); +const aiDeleteBtn = document.getElementById('ai-delete-btn'); +const aiStatus = document.getElementById('ai-status'); function authHeaders(key) { return { Authorization: `Bearer ${key}` }; @@ -92,6 +102,7 @@ function showAdminPanel() { } loadAdminKeys(); + loadAIConfig(); } function showUserPanel() { @@ -605,3 +616,170 @@ if (commandForm) { window.activateKey = activateKey; window.deactivateKey = deactivateKey; window.deleteKey = deleteKey; + +async function loadAIConfig() { + if (!apiKey) { + return; + } + try { + const response = await fetch(`${API_URL}/api/ai/config`, { + headers: authHeaders(apiKey) + }); + + if (response.ok) { + const config = await response.json(); + if (aiApiUrlInput) aiApiUrlInput.value = config.apiUrl || ''; + if (aiModelIdInput) aiModelIdInput.value = config.modelId || ''; + if (aiApiKeyInput) aiApiKeyInput.value = ''; + if (aiApiKeyHint) aiApiKeyHint.textContent = config.apiKey ? `Current: ${config.apiKey}` : ''; + if (aiSystemPromptInput) aiSystemPromptInput.value = config.systemPrompt || ''; + if (aiEnabledCheckbox) aiEnabledCheckbox.checked = config.enabled; + showAIStatus('Configuration loaded', 'success'); + } else if (response.status === 404) { + if (aiApiUrlInput) aiApiUrlInput.value = ''; + if (aiModelIdInput) aiModelIdInput.value = ''; + if (aiApiKeyInput) aiApiKeyInput.value = ''; + if (aiApiKeyHint) aiApiKeyHint.textContent = ''; + if (aiSystemPromptInput) aiSystemPromptInput.value = ''; + if (aiEnabledCheckbox) aiEnabledCheckbox.checked = true; + showAIStatus('No configuration found. Please set up AI configuration.', 'info'); + } + } catch (error) { + showAIStatus(`Failed to load AI config: ${error.message}`, 'error'); + } +} + +async function saveAIConfig(event) { + event.preventDefault(); + if (!apiKey) { + return; + } + + const apiUrl = aiApiUrlInput?.value?.trim(); + const modelId = aiModelIdInput?.value?.trim(); + const apiKeyValue = aiApiKeyInput?.value?.trim(); + const systemPrompt = aiSystemPromptInput?.value?.trim() || null; + const enabled = aiEnabledCheckbox?.checked ?? true; + + if (!apiUrl || !modelId) { + showAIStatus('API URL and Model ID are required', 'error'); + return; + } + + try { + const existingResponse = await fetch(`${API_URL}/api/ai/config`, { + headers: authHeaders(apiKey) + }); + const isUpdate = existingResponse.ok; + + const payload = { + api_url: apiUrl, + model_id: modelId, + enabled: enabled, + system_prompt: systemPrompt + }; + + if (apiKeyValue) { + payload.api_key = apiKeyValue; + } else if (!isUpdate) { + showAIStatus('API Key is required for new configuration', 'error'); + return; + } + + const method = isUpdate ? 'PATCH' : 'POST'; + const response = await fetch(`${API_URL}/api/ai/config`, { + method: method, + headers: { + ...authHeaders(apiKey), + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (response.ok) { + const config = await response.json(); + if (aiApiKeyInput) aiApiKeyInput.value = ''; + if (aiApiKeyHint) aiApiKeyHint.textContent = config.apiKey ? `Current: ${config.apiKey}` : ''; + showAIStatus('Configuration saved successfully', 'success'); + } else { + const error = await response.json(); + showAIStatus(`Failed to save: ${error.detail}`, 'error'); + } + } catch (error) { + showAIStatus(`Failed to save AI config: ${error.message}`, 'error'); + } +} + +async function testAIConnection() { + if (!apiKey) { + return; + } + showAIStatus('Testing connection...', 'info'); + + try { + const response = await fetch(`${API_URL}/api/ai/config/test`, { + method: 'POST', + headers: authHeaders(apiKey) + }); + + const result = await response.json(); + if (result.success) { + showAIStatus(`Connection successful! Model: ${result.model}, Response: ${result.response}`, 'success'); + } else { + showAIStatus(`Connection failed: ${result.error}`, 'error'); + } + } catch (error) { + showAIStatus(`Test failed: ${error.message}`, 'error'); + } +} + +async function deleteAIConfig() { + if (!apiKey) { + return; + } + if (!confirm('Are you sure you want to delete the AI configuration?')) { + return; + } + + try { + const response = await fetch(`${API_URL}/api/ai/config`, { + method: 'DELETE', + headers: authHeaders(apiKey) + }); + + if (response.ok || response.status === 204) { + if (aiApiUrlInput) aiApiUrlInput.value = ''; + if (aiModelIdInput) aiModelIdInput.value = ''; + if (aiApiKeyInput) aiApiKeyInput.value = ''; + if (aiApiKeyHint) aiApiKeyHint.textContent = ''; + if (aiSystemPromptInput) aiSystemPromptInput.value = ''; + if (aiEnabledCheckbox) aiEnabledCheckbox.checked = true; + showAIStatus('Configuration deleted', 'success'); + } else { + const error = await response.json(); + showAIStatus(`Failed to delete: ${error.detail}`, 'error'); + } + } catch (error) { + showAIStatus(`Failed to delete AI config: ${error.message}`, 'error'); + } +} + +function showAIStatus(message, type) { + if (!aiStatus) { + return; + } + aiStatus.textContent = message; + aiStatus.className = `ai-status ${type}`; +} + +if (aiConfigForm) { + aiConfigForm.addEventListener('submit', saveAIConfig); +} + +if (aiTestBtn) { + aiTestBtn.addEventListener('click', testAIConnection); +} + +if (aiDeleteBtn) { + aiDeleteBtn.addEventListener('click', deleteAIConfig); +} diff --git a/dashboard/public/index.html b/dashboard/public/index.html index 0eb58abe126ca99298672aaa6b5b170ee582a56d..8ccaa5d0657a4dcdddfbe0935349f1534d22256a 100644 --- a/dashboard/public/index.html +++ b/dashboard/public/index.html @@ -34,6 +34,44 @@
密钥创建成功!
-名称: ${keyData.name}
-类型: ${keyData.isSuperKey ? 'SuperKey' : '普通密钥'}
-Failed to load keys.
'; } } catch (error) { - console.error('Failed to load keys:', error); + adminKeysList.innerHTML = `Failed to load keys: ${error.message}
`; } } -async function loadRegularServerKeys() { +async function loadUserServerKeys() { + if (!userKeysList || !apiKey) { + return; + } try { const response = await fetch(`${API_URL}/manage/keys/server-keys`, { - headers: { 'Authorization': `Bearer ${superKey}` } + headers: authHeaders(apiKey) }); - + if (response.ok) { - const serverKeys = await response.json(); - renderServerKeys(serverKeys); + const keys = await response.json(); + renderUserServerKeys(keys); } else { - document.getElementById('keys-list').innerHTML = '无法加载Server Key列表
'; + userKeysList.innerHTML = 'Failed to load server keys.
'; } } catch (error) { - console.error('Failed to load server keys:', error); - document.getElementById('keys-list').innerHTML = '无法加载Server Key列表
'; + userKeysList.innerHTML = `Failed to load server keys: ${error.message}
`; } } -function renderServerKeys(keys) { - const keysList = document.getElementById('keys-list'); - - if (keys.length === 0) { - keysList.innerHTML = '暂无关联的Server Key
'; +function renderAdminKeys(keys) { + if (!adminKeysList) { return; } - - keysList.innerHTML = keys.map(key => ` -No API keys found.
'; + return; + } + + adminKeysList.innerHTML = keys.map((key) => ` +ID: ${key.id}
-前缀: ${key.keyPrefix}
- ${key.serverId ? `服务器ID: ${key.serverId}
` : ''} -创建时间: ${new Date(key.createdAt).toLocaleString('zh-CN')}
-最后使用: ${key.lastUsed ? new Date(key.lastUsed).toLocaleString('zh-CN') : '从未使用'}
+Prefix: ${key.keyPrefix}
+ ${key.serverId ? `Server ID: ${key.serverId}
` : ''} +Created: ${new Date(key.createdAt).toLocaleString()}
+Last Used: ${key.lastUsed ? new Date(key.lastUsed).toLocaleString() : 'Never'}
暂无API密钥
'; +function renderUserServerKeys(keys) { + if (!userKeysList) { return; } - - keysList.innerHTML = keys.map(key => ` -No server keys available.
'; + return; + } + + userKeysList.innerHTML = keys.map((key) => ` +ID: ${key.id}
-前缀: ${key.keyPrefix}
- ${key.serverId ? `服务器ID: ${key.serverId}
` : ''} -创建时间: ${new Date(key.createdAt).toLocaleString('zh-CN')}
-最后使用: ${key.lastUsed ? new Date(key.lastUsed).toLocaleString('zh-CN') : '从未使用'}
+Prefix: ${key.keyPrefix}
+ ${key.serverId ? `Server ID: ${key.serverId}
` : ''} +Created: ${new Date(key.createdAt).toLocaleString()}
+Last Used: ${key.lastUsed ? new Date(key.lastUsed).toLocaleString() : 'Never'}
Regular Key
+Name: ${payload.regularKey.name}
+Type: ${payload.regularKey.keyType}
+ID: ${payload.regularKey.id}
+Key: ${payload.regularKey.key}
+Server Key
+Name: ${payload.serverKey.name}
+Type: ${payload.serverKey.keyType}
+ID: ${payload.serverKey.id}
+Key: ${payload.serverKey.key}
+ `; + } else { + content = ` +Key Created
+Name: ${payload.name}
+Type: ${payload.keyType}
+ID: ${payload.id}
+Key: ${payload.key}
+ `; + } + + const detailsContent = document.getElementById('key-details-content'); + if (detailsContent) { + detailsContent.innerHTML = content; + } + + keyDetailsModal.classList.remove('hidden'); +} + +async function loadStats() { + try { + const response = await fetch(`${API_URL}/health`); + if (!response.ok) { + return; + } + const data = await response.json(); + + const connections = document.getElementById('user-stat-connections'); + if (connections) { + connections.textContent = data.active_ws || 0; + } + + const totalKeys = document.getElementById('user-stat-total-keys'); + if (totalKeys) { + totalKeys.textContent = data.keys_total || 0; + } + + const adminKeys = document.getElementById('user-stat-admin-keys'); + if (adminKeys) { + adminKeys.textContent = data.admin_active || 0; + } + + const regularKeys = document.getElementById('user-stat-regular-keys'); + if (regularKeys) { + regularKeys.textContent = data.regular_active || 0; + } + + const serverKeys = document.getElementById('user-stat-server-keys'); + if (serverKeys) { + serverKeys.textContent = data.server_active || 0; + } + } catch (error) { + console.error('Failed to load stats:', error); } } function connectWebSocket() { - if (!superKey) return; - - const wsUrl = `ws://localhost:8000/ws?api_key=${superKey}`; + if (!apiKey) { + return; + } + + const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; + const wsUrl = `${protocol}://${window.location.host}/ws?api_key=${apiKey}`; ws = new WebSocket(wsUrl); - + ws.onopen = () => { console.log('WebSocket connected'); }; - + ws.onmessage = (event) => { try { const message = JSON.parse(event.data); - if (message.type === 'minecraft_event') { addEventToList(message.event); } @@ -404,21 +401,23 @@ function connectWebSocket() { console.error('Failed to parse WebSocket message:', error); } }; - + ws.onerror = (error) => { console.error('WebSocket error:', error); }; - + ws.onclose = () => { console.log('WebSocket disconnected'); - setTimeout(() => { - if (superKey) { - connectWebSocket(); - } - }, 5000); + if (wsPingIntervalId) { + clearInterval(wsPingIntervalId); + wsPingIntervalId = null; + } + if (apiKey && currentRole === 'regular') { + setTimeout(() => connectWebSocket(), 5000); + } }; - - setInterval(() => { + + wsPingIntervalId = setInterval(() => { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'ping' })); } @@ -426,19 +425,183 @@ function connectWebSocket() { } function addEventToList(event) { - const eventsList = document.getElementById('events-list'); - + if (!userEventsList) { + return; + } + const eventItem = document.createElement('div'); eventItem.className = 'event-item'; eventItem.innerHTML = ` ${event.event_type} - ${event.server_name}${JSON.stringify(event.data, null, 2)}
`;
-
- eventsList.insertBefore(eventItem, eventsList.firstChild);
-
- while (eventsList.children.length > 50) {
- eventsList.removeChild(eventsList.lastChild);
+
+ userEventsList.insertBefore(eventItem, userEventsList.firstChild);
+
+ while (userEventsList.children.length > 50) {
+ userEventsList.removeChild(userEventsList.lastChild);
+ }
+}
+
+function appendCommandHistory(command, status, detail) {
+ if (!commandHistory) {
+ return;
}
+
+ const entry = document.createElement('div');
+ entry.className = 'command-item';
+ entry.innerHTML = `
+ 密钥类型说明:
-👑 Admin Key - 完全管理权限
-🖥️ Server Key - 服务器管理权限
+Key Types
+Admin Key - full management permissions
+Regular Key - server monitoring and command access