<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MCP Maximo Server - 連接測試</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Microsoft JhengHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.card {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.test-section {
margin-bottom: 30px;
}
.test-section:last-child {
margin-bottom: 0;
}
.test-section h2 {
color: #333;
margin-bottom: 15px;
font-size: 1.5rem;
display: flex;
align-items: center;
gap: 10px;
}
.test-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 30px;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
margin-right: 10px;
margin-bottom: 10px;
}
.test-button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
}
.test-button:active {
transform: translateY(0);
}
.test-button:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.result-box {
margin-top: 15px;
padding: 15px;
border-radius: 8px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 400px;
overflow-y: auto;
}
.result-box.success {
background: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.result-box.error {
background: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
.result-box.info {
background: #d1ecf1;
border: 1px solid #bee5eb;
color: #0c5460;
}
.status-badge {
display: inline-block;
padding: 5px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: bold;
margin-left: 10px;
}
.status-badge.healthy {
background: #d4edda;
color: #155724;
}
.status-badge.unhealthy {
background: #f8d7da;
color: #721c24;
}
.status-badge.unknown {
background: #e2e3e5;
color: #383d41;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.input-group {
margin-bottom: 15px;
}
.input-group label {
display: block;
margin-bottom: 5px;
color: #555;
font-weight: 500;
}
.input-group input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 1rem;
}
.input-group input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.tool-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
margin-top: 15px;
}
.config-info {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
}
.config-info p {
margin-bottom: 8px;
color: #555;
}
.config-info strong {
color: #333;
}
/* 日誌區域樣式 */
.log-container {
background: #1e1e1e;
border-radius: 8px;
padding: 15px;
max-height: 400px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
margin-top: 15px;
}
.log-entry {
margin-bottom: 10px;
padding: 8px;
border-left: 3px solid #666;
background: #2d2d2d;
}
.log-entry.log-request {
border-left-color: #4a9eff;
}
.log-entry.log-response {
border-left-color: #4caf50;
}
.log-entry.log-error {
border-left-color: #f44336;
}
.log-timestamp {
color: #888;
font-size: 0.75rem;
}
.log-method {
color: #4a9eff;
font-weight: bold;
}
.log-url {
color: #fff;
}
.log-status {
color: #4caf50;
}
.log-status.error {
color: #f44336;
}
.log-data {
color: #ddd;
margin-top: 5px;
white-space: pre-wrap;
word-wrap: break-word;
}
.log-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.log-header h3 {
color: #fff;
font-size: 1rem;
margin: 0;
}
.clear-log-btn {
background: #444;
color: white;
border: none;
padding: 5px 15px;
border-radius: 4px;
font-size: 0.85rem;
cursor: pointer;
transition: background 0.2s;
}
.clear-log-btn:hover {
background: #555;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔧 MCP Maximo Server</h1>
<p>連接測試與驗證工具</p>
</div>
<!-- 系統健康檢查 -->
<div class="card">
<div class="test-section">
<h2>
🏥 系統健康檢查
<span id="health-badge" class="status-badge unknown">未檢測</span>
</h2>
<div class="config-info">
<p><strong>Server URL:</strong> <span id="server-url">http://localhost:8000</span></p>
</div>
<button class="test-button" onclick="testHealth()">檢查系統健康</button>
<div id="health-result"></div>
<!-- 此區塊的日誌 -->
<div class="log-container" style="display: none;">
<div class="log-header">
<h3>📋 請求/回應日誌</h3>
<button class="clear-log-btn" onclick="clearSectionLogs('health-logs')">清除</button>
</div>
<div id="health-logs"></div>
</div>
</div>
</div>
<!-- Maximo 連接測試 -->
<div class="card">
<div class="test-section">
<h2>🔌 Maximo 連接測試</h2>
<button class="test-button" onclick="testMaximoConnection()">測試 Maximo 連接</button>
<div id="maximo-result"></div>
<!-- 此區塊的日誌 -->
<div class="log-container" style="display: none;">
<div class="log-header">
<h3>📋 請求/回應日誌</h3>
<button class="clear-log-btn" onclick="clearSectionLogs('maximo-logs')">清除</button>
</div>
<div id="maximo-logs"></div>
</div>
</div>
</div>
<!-- MCP Tools 測試 -->
<div class="card">
<div class="test-section">
<h2>🛠️ MCP Tools 功能測試</h2>
<p style="color: #666; margin-bottom: 15px;">測試各項 Maximo API 工具是否正常運作(需要提供測試資料)</p>
<h3 style="color: #555; margin-top: 20px; margin-bottom: 10px;">資產管理測試</h3>
<div class="input-group">
<label>Asset Number (測試用資產編號):</label>
<input type="text" id="test-assetnum" placeholder="例如: PUMP001">
</div>
<div class="input-group">
<label>Site ID:</label>
<input type="text" id="test-siteid" placeholder="例如: BEDFORD">
</div>
<div class="tool-grid">
<button class="test-button" onclick="testGetAsset()">測試查詢資產</button>
<button class="test-button" onclick="testSearchAssets()">測試搜尋資產</button>
</div>
<h3 style="color: #555; margin-top: 20px; margin-bottom: 10px;">工單管理測試</h3>
<div class="input-group">
<label>Work Order Number (測試用工單編號):</label>
<input type="text" id="test-wonum" placeholder="例如: WO1001">
</div>
<div class="tool-grid">
<button class="test-button" onclick="testGetWorkOrder()">測試查詢工單</button>
<button class="test-button" onclick="testSearchWorkOrders()">測試搜尋工單</button>
</div>
<h3 style="color: #555; margin-top: 20px; margin-bottom: 10px;">庫存管理測試</h3>
<div class="input-group">
<label>Item Number (測試用物料編號):</label>
<input type="text" id="test-itemnum" placeholder="例如: FILTER-001">
</div>
<div class="tool-grid">
<button class="test-button" onclick="testGetInventory()">測試查詢庫存</button>
<button class="test-button" onclick="testSearchInventory()">測試搜尋庫存</button>
</div>
<h3 style="color: #555; margin-top: 20px; margin-bottom: 10px;">用戶管理測試</h3>
<div class="input-group">
<label>User ID (測試用用戶 ID):</label>
<input type="text" id="test-userid" placeholder="例如: testuser">
</div>
<div class="tool-grid">
<button class="test-button" onclick="testGetUserStatus()">測試查詢用戶狀態</button>
<button class="test-button" onclick="testSearchUsers()">測試搜尋用戶</button>
<button class="test-button" onclick="testUnlockUser()">測試解鎖帳號</button>
</div>
<div id="tools-result"></div>
<!-- 此區塊的日誌 -->
<div class="log-container" style="display: none;">
<div class="log-header">
<h3>📋 請求/回應日誌</h3>
<button class="clear-log-btn" onclick="clearSectionLogs('tools-logs')">清除</button>
</div>
<div id="tools-logs"></div>
</div>
</div>
</div>
<!-- Maximo 認證設定 -->
<div class="card">
<div class="test-section">
<h2>🔑 Maximo 認證設定</h2>
<p style="color: #666; margin-bottom: 15px;">若需要測試需要認證的端點,請輸入 Maximo 用戶名和密碼</p>
<div class="input-group">
<label>Maximo Username (選填):</label>
<input type="text" id="maxauth-username" placeholder="輸入 Maximo 用戶名">
</div>
<div class="input-group">
<label>Maximo Password (選填):</label>
<input type="password" id="maxauth-password" placeholder="輸入 Maximo 密碼">
</div>
</div>
</div>
</div>
<script>
// 從 URL 參數或 localStorage 獲取 server URL,預設使用 port 8001
const urlParams = new URLSearchParams(window.location.search);
const defaultServer = window.location.port ?
`http://${window.location.hostname}:${window.location.port}` :
'http://localhost:8001';
const serverUrl = urlParams.get('server') || localStorage.getItem('serverUrl') || defaultServer;
document.getElementById('server-url').textContent = serverUrl;
// Log management - track current log target
let currentLogTarget = null;
const logs = {};
function setLogTarget(targetId) {
currentLogTarget = targetId;
// Show the log container for this section
const container = document.getElementById(targetId);
if (container) {
container.parentElement.style.display = 'block';
}
}
function addLog(type, method, url, status, data) {
const timestamp = new Date().toLocaleString('zh-TW', {
hour12: false,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const log = { type, method, url, status, data, timestamp };
// Store log in target-specific array
if (currentLogTarget) {
if (!logs[currentLogTarget]) {
logs[currentLogTarget] = [];
}
logs[currentLogTarget].push(log);
displayLog(log, currentLogTarget);
}
}
function displayLog(log, targetId) {
const logDiv = document.createElement('div');
logDiv.className = `log-entry log-${log.type}`;
let statusHtml = '';
if (log.status !== null && log.status !== undefined) {
const statusClass = log.status >= 400 ? 'error' : '';
statusHtml = `<div><span class="log-status ${statusClass}">Status: ${log.status}</span></div>`;
}
let dataPreview = '';
try {
const dataStr = typeof log.data === 'string' ? log.data : JSON.stringify(log.data, null, 2);
dataPreview = dataStr.length > 500 ? dataStr.substring(0, 500) + '...' : dataStr;
} catch (e) {
dataPreview = String(log.data);
}
logDiv.innerHTML = `
<div class="log-timestamp">${log.timestamp}</div>
<div><span class="log-method">${log.method}</span> <span class="log-url">${log.url}</span></div>
${statusHtml}
<div class="log-data">${dataPreview}</div>
`;
const container = document.getElementById(targetId);
if (container) {
container.insertBefore(logDiv, container.firstChild);
// Keep only last 20 logs in each section
while (container.children.length > 20) {
container.removeChild(container.lastChild);
}
}
}
function clearSectionLogs(targetId) {
if (logs[targetId]) {
logs[targetId].length = 0;
}
const container = document.getElementById(targetId);
if (container) {
container.innerHTML = '<div style="color: #888; text-align: center; padding: 20px;">日誌已清除</div>';
}
}
// Wrap fetch to log all requests
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const [url, options = {}] = args;
const method = options.method || 'GET';
// Log request
let requestData = {};
if (options.body) {
try {
requestData = JSON.parse(options.body);
} catch (e) {
requestData = { body: options.body };
}
}
addLog('request', method, url, null, requestData);
try {
const response = await originalFetch(...args);
const clone = response.clone();
// Try to parse response
let responseData;
try {
responseData = await clone.json();
} catch (e) {
responseData = { message: 'Non-JSON response' };
}
addLog('response', method, url, response.status, responseData);
return response;
} catch (error) {
addLog('error', method, url, null, { error: error.message, stack: error.stack });
throw error;
}
};
function getHeaders() {
const username = document.getElementById('maxauth-username').value;
const password = document.getElementById('maxauth-password').value;
const headers = {
'Content-Type': 'application/json'
};
if (username && password) {
// Encode username:password to Base64
const credentials = `${username}:${password}`;
const base64Credentials = btoa(credentials);
headers['maxauth'] = base64Credentials;
}
return headers;
}
function showResult(elementId, message, type = 'info') {
const element = document.getElementById(elementId);
element.innerHTML = `<div class="result-box ${type}">${message}</div>`;
}
async function testHealth() {
const button = event.target;
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 檢測中...';
// Set log target for this test
setLogTarget('health-logs');
try {
const response = await fetch(`${serverUrl}/health`);
const data = await response.json();
const badge = document.getElementById('health-badge');
if (data.status === 'healthy') {
badge.className = 'status-badge healthy';
badge.textContent = '✓ 健康';
} else {
badge.className = 'status-badge unhealthy';
badge.textContent = '✗ 異常';
}
const resultText = `狀態: ${data.status}
版本: ${data.version}
環境: ${data.environment}
快取狀態: ${data.cache}
Maximo 連接: ${data.maximo}
完整回應:
${JSON.stringify(data, null, 2)}`;
showResult('health-result', resultText, data.status === 'healthy' ? 'success' : 'error');
} catch (error) {
const badge = document.getElementById('health-badge');
badge.className = 'status-badge unhealthy';
badge.textContent = '✗ 錯誤';
showResult('health-result', `錯誤: ${error.message}\n\n請確認:\n1. MCP Server 是否正在運行\n2. Server URL 是否正確: ${serverUrl}\n3. 網路連接是否正常`, 'error');
} finally {
button.disabled = false;
button.textContent = '檢查系統健康';
}
}
async function testMaximoConnection() {
const button = event.target;
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 測試中...';
// Set log target for this test
setLogTarget('maximo-logs');
try {
const response = await fetch(`${serverUrl}/api/test-maximo`, {
method: 'POST',
headers: getHeaders()
});
const data = await response.json();
if (data.success) {
showResult('maximo-result', `✓ Maximo 連接成功!\n\n${JSON.stringify(data, null, 2)}`, 'success');
} else {
showResult('maximo-result', `✗ Maximo 連接失敗\n\n${JSON.stringify(data, null, 2)}`, 'error');
}
} catch (error) {
showResult('maximo-result', `錯誤: ${error.message}\n\n請檢查:\n1. Maximo API URL 是否正確\n2. Maximo 認證資訊(用戶名和密碼)是否正確\n3. 網路是否可以連接到 Maximo`, 'error');
} finally {
button.disabled = false;
button.textContent = '測試 Maximo 連接';
}
}
async function testGetAsset() {
const assetnum = document.getElementById('test-assetnum').value;
const siteid = document.getElementById('test-siteid').value;
if (!assetnum) {
showResult('tools-result', '請輸入 Asset Number', 'error');
return;
}
const button = event.target;
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 測試中...';
// Set log target for this test
setLogTarget('tools-logs');
try {
const response = await fetch(`${serverUrl}/api/test-tool`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({
tool: 'get_asset',
params: { assetnum, siteid }
})
});
const data = await response.json();
if (response.ok) {
showResult('tools-result', `✓ 查詢資產成功!\n\n${JSON.stringify(data, null, 2)}`, 'success');
} else {
showResult('tools-result', `✗ 查詢失敗\n\n${JSON.stringify(data, null, 2)}`, 'error');
}
} catch (error) {
showResult('tools-result', `錯誤: ${error.message}`, 'error');
} finally {
button.disabled = false;
button.textContent = '測試查詢資產';
}
}
async function testSearchAssets() {
const button = event.target;
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 測試中...';
// Set log target for this test
setLogTarget('tools-logs');
try {
const response = await fetch(`${serverUrl}/api/test-tool`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({
tool: 'search_assets',
params: { page_size: 5 }
})
});
const data = await response.json();
if (response.ok) {
showResult('tools-result', `✓ 搜尋資產成功!\n找到 ${data.result?.length || 0} 筆資料\n\n${JSON.stringify(data, null, 2)}`, 'success');
} else {
showResult('tools-result', `✗ 搜尋失敗\n\n${JSON.stringify(data, null, 2)}`, 'error');
}
} catch (error) {
showResult('tools-result', `錯誤: ${error.message}`, 'error');
} finally {
button.disabled = false;
button.textContent = '測試搜尋資產';
}
}
async function testGetWorkOrder() {
const wonum = document.getElementById('test-wonum').value;
const siteid = document.getElementById('test-siteid').value;
if (!wonum) {
showResult('tools-result', '請輸入 Work Order Number', 'error');
return;
}
const button = event.target;
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 測試中...';
// Set log target for this test
setLogTarget('tools-logs');
try {
const response = await fetch(`${serverUrl}/api/test-tool`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({
tool: 'get_work_order',
params: { wonum, siteid }
})
});
const data = await response.json();
if (response.ok) {
showResult('tools-result', `✓ 查詢工單成功!\n\n${JSON.stringify(data, null, 2)}`, 'success');
} else {
showResult('tools-result', `✗ 查詢失敗\n\n${JSON.stringify(data, null, 2)}`, 'error');
}
} catch (error) {
showResult('tools-result', `錯誤: ${error.message}`, 'error');
} finally {
button.disabled = false;
button.textContent = '測試查詢工單';
}
}
async function testSearchWorkOrders() {
const button = event.target;
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 測試中...';
// Set log target for this test
setLogTarget('tools-logs');
try {
const response = await fetch(`${serverUrl}/api/test-tool`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({
tool: 'search_work_orders',
params: { page_size: 5 }
})
});
const data = await response.json();
if (response.ok) {
showResult('tools-result', `✓ 搜尋工單成功!\n找到 ${data.result?.length || 0} 筆資料\n\n${JSON.stringify(data, null, 2)}`, 'success');
} else {
showResult('tools-result', `✗ 搜尋失敗\n\n${JSON.stringify(data, null, 2)}`, 'error');
}
} catch (error) {
showResult('tools-result', `錯誤: ${error.message}`, 'error');
} finally {
button.disabled = false;
button.textContent = '測試搜尋工單';
}
}
async function testGetInventory() {
const itemnum = document.getElementById('test-itemnum').value;
const siteid = document.getElementById('test-siteid').value;
if (!itemnum) {
showResult('tools-result', '請輸入 Item Number', 'error');
return;
}
const button = event.target;
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 測試中...';
// Set log target for this test
setLogTarget('tools-logs');
try {
const response = await fetch(`${serverUrl}/api/test-tool`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({
tool: 'get_inventory',
params: { itemnum, siteid }
})
});
const data = await response.json();
if (response.ok) {
showResult('tools-result', `✓ 查詢庫存成功!\n\n${JSON.stringify(data, null, 2)}`, 'success');
} else {
showResult('tools-result', `✗ 查詢失敗\n\n${JSON.stringify(data, null, 2)}`, 'error');
}
} catch (error) {
showResult('tools-result', `錯誤: ${error.message}`, 'error');
} finally {
button.disabled = false;
button.textContent = '測試查詢庫存';
}
}
async function testSearchInventory() {
const button = event.target;
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 測試中...';
// Set log target for this test
setLogTarget('tools-logs');
try {
const response = await fetch(`${serverUrl}/api/test-tool`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({
tool: 'search_inventory',
params: { page_size: 5 }
})
});
const data = await response.json();
if (response.ok) {
showResult('tools-result', `✓ 搜尋庫存成功!\n找到 ${data.result?.length || 0} 筆資料\n\n${JSON.stringify(data, null, 2)}`, 'success');
} else {
showResult('tools-result', `✗ 搜尋失敗\n\n${JSON.stringify(data, null, 2)}`, 'error');
}
} catch (error) {
showResult('tools-result', `錯誤: ${error.message}`, 'error');
} finally {
button.disabled = false;
button.textContent = '測試搜尋庫存';
}
}
async function testGetUserStatus() {
const userid = document.getElementById('test-userid').value;
if (!userid) {
showResult('tools-result', '請輸入 User ID', 'error');
return;
}
const button = event.target;
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 測試中...';
// Set log target for this test
setLogTarget('tools-logs');
try {
const response = await fetch(`${serverUrl}/api/test-tool`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({
tool: 'get_user_status',
params: { userid }
})
});
const data = await response.json();
if (response.ok) {
const user = data.result;
const statusInfo = `
用戶狀態資訊:
- User ID: ${user.userid}
- 顯示名稱: ${user.displayname || 'N/A'}
- 狀態: ${user.status}
- 是否啟用: ${user.is_active ? '是' : '否'}
- 是否鎖定: ${user.is_locked ? '是' : '否'}
- 登入失敗次數: ${user.failed_login_count}
- Email: ${user.emailaddress || 'N/A'}
- 電話: ${user.primaryphone || 'N/A'}
完整回應:
${JSON.stringify(data, null, 2)}`;
showResult('tools-result', `✓ 查詢用戶狀態成功!\n${statusInfo}`, 'success');
} else {
showResult('tools-result', `✗ 查詢失敗\n\n${JSON.stringify(data, null, 2)}`, 'error');
}
} catch (error) {
showResult('tools-result', `錯誤: ${error.message}`, 'error');
} finally {
button.disabled = false;
button.textContent = '測試查詢用戶狀態';
}
}
async function testSearchUsers() {
const button = event.target;
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 測試中...';
// Set log target for this test
setLogTarget('tools-logs');
try {
const response = await fetch(`${serverUrl}/api/test-tool`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({
tool: 'search_users',
params: { page_size: 5 }
})
});
const data = await response.json();
if (response.ok) {
const users = data.result || [];
let summary = `找到 ${users.length} 個用戶\n\n`;
if (users.length > 0) {
summary += '用戶列表:\n';
users.forEach((user, idx) => {
summary += `${idx + 1}. ${user.userid} (${user.displayname || 'N/A'}) - 狀態: ${user.status}${user.is_locked ? ' [鎖定]' : ''}\n`;
});
}
showResult('tools-result', `✓ 搜尋用戶成功!\n${summary}\n完整回應:\n${JSON.stringify(data, null, 2)}`, 'success');
} else {
showResult('tools-result', `✗ 搜尋失敗\n\n${JSON.stringify(data, null, 2)}`, 'error');
}
} catch (error) {
showResult('tools-result', `錯誤: ${error.message}`, 'error');
} finally {
button.disabled = false;
button.textContent = '測試搜尋用戶';
}
}
async function testUnlockUser() {
const userid = document.getElementById('test-userid').value;
if (!userid) {
showResult('tools-result', '請輸入 User ID', 'error');
return;
}
if (!confirm(`確定要解鎖用戶 "${userid}" 嗎?\n\n此操作將:\n1. 設定帳號狀態為 ACTIVE\n2. 清除鎖定狀態\n3. 重設登入失敗次數為 0`)) {
return;
}
const button = event.target;
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 解鎖中...';
// Set log target for this test
setLogTarget('tools-logs');
try {
const response = await fetch(`${serverUrl}/api/test-tool`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({
tool: 'unlock_user_account',
params: {
userid,
memo: '透過測試頁面解鎖'
}
})
});
const data = await response.json();
if (response.ok) {
showResult('tools-result', `✓ 解鎖用戶成功!\n\n用戶 "${userid}" 已成功解鎖\n\n完整回應:\n${JSON.stringify(data, null, 2)}`, 'success');
} else {
showResult('tools-result', `✗ 解鎖失敗\n\n${JSON.stringify(data, null, 2)}`, 'error');
}
} catch (error) {
showResult('tools-result', `錯誤: ${error.message}`, 'error');
} finally {
button.disabled = false;
button.textContent = '測試解鎖帳號';
}
}
// 頁面載入時執行健康檢查
window.addEventListener('load', () => {
setTimeout(testHealth, 500);
});
</script>
</body>
</html>