const commandInput = document.getElementById('command'); const output = document.getElementById('output'); const history = document.getElementById('history'); const executeButton = document.getElementById('executeButton'); const loadingIndicator = document.getElementById('loadingIndicator'); commandInput.addEventListener('keypress', function (event) { if (event.key === 'Enter') { executeCommand(); } }); async function executeCommand() { const command = commandInput.value; if (!command.trim()) return; showLoading(true); clearOutput(); appendToOutput('$ ' + command, 'text-blue-400'); appendToOutput('正在执行命令...', 'text-yellow-400'); try { let response = await fetch('/api/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, body: JSON.stringify({ command }) }); if (response.status === 403) { const refreshed = await refreshToken(); if (refreshed) { response = await fetch('/api/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, body: JSON.stringify({ command }) }); } else { throw new Error('Token 刷新失败'); } } if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); clearOutput(); appendToOutput('$ ' + command, 'text-blue-400'); appendToOutput(data.output || data.error || '命令执行成功,但没有输出。', 'text-green-400'); commandInput.value = ''; loadCommandHistory(); } catch (error) { console.error('执行命令时出错:', error); clearOutput(); appendToOutput('$ ' + command, 'text-blue-400'); appendToOutput('错误: ' + (error.message || '未知错误'), 'text-red-500'); if (error.message.includes('Token 刷新失败')) { showNotification('访问被拒绝。请重新登录。', 'error'); localStorage.removeItem('token'); localStorage.removeItem('username'); localStorage.removeItem('password'); checkLoginStatus(); } } finally { showLoading(false); } } function clearOutput() { output.innerHTML = ''; } function appendToOutput(text, className = 'text-green-400') { const lines = text.split('\n'); lines.forEach((line, index) => { const p = document.createElement('p'); p.className = className; p.textContent = line; output.appendChild(p); }); output.scrollTop = output.scrollHeight; } async function loadCommandHistory() { try { const response = await fetch('/api/command-history', { headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` } }); const data = await response.json(); history.innerHTML = ''; data.reverse().forEach(item => { const li = document.createElement('li'); li.innerHTML = `
${filterXSS(item.command)} ${new Date(item.timestamp).toLocaleString()}
`; li.className = 'cursor-pointer'; li.onclick = () => { commandInput.value = item.command; commandInput.focus(); }; history.appendChild(li); }); } catch (error) { console.error('加载命令历史失败:', error); showNotification('加载命令历史失败', 'error'); } } function showLoading(isLoading) { loadingIndicator.style.display = isLoading ? 'block' : 'none'; executeButton.disabled = isLoading; executeButton.classList.toggle('opacity-50', isLoading); } async function login() { const username = document.getElementById('username').value; const password = document.getElementById('password').value; try { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await response.json(); if (response.ok) { localStorage.setItem('token', data.token); localStorage.setItem('username', username); localStorage.setItem('password', password); document.getElementById('loginForm').style.display = 'none'; document.getElementById('commandInterface').style.display = 'block'; loadCommandHistory(); showNotification('登录成功', 'success'); } else { showNotification('登录失败: ' + data.error, 'error'); } } catch (error) { showNotification('登录错误: ' + error.message, 'error'); } } async function refreshToken() { const username = localStorage.getItem('username'); const password = localStorage.getItem('password'); if (!username || !password) { document.getElementById('loginForm').style.display = 'block'; document.getElementById('commandInterface').style.display = 'none'; return; } try { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await response.json(); if (response.ok) { localStorage.setItem('token', data.token); return true; } else { throw new Error(data.error); } } catch (error) { console.error('刷新 token 失败:', error); return false; } } function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.textContent = message; notification.className = `fixed top-4 right-4 p-4 rounded-lg text-white ${type === 'error' ? 'bg-red-500' : 'bg-green-500'} shadow-lg transition-opacity duration-300`; document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = '0'; setTimeout(() => notification.remove(), 300); }, 3000); } document.getElementById('loginButton').addEventListener('click', login); function checkLoginStatus() { const token = localStorage.getItem('token'); if (token) { document.getElementById('loginForm').style.display = 'none'; document.getElementById('commandInterface').style.display = 'block'; loadCommandHistory(); } } window.addEventListener('load', checkLoginStatus); document.getElementById('executeButton').addEventListener('click', executeCommand); // 添加打字机效果 function typeWriter(element, text, speed = 50) { let i = 0; function type() { if (i < text.length) { element.textContent += text.charAt(i); i++; setTimeout(type, speed); } } type(); } // 在页面加载时添加一些酷炫的效果 window.addEventListener('load', () => { const title = document.querySelector('h1'); title.textContent = ''; typeWriter(title, 'Web 命令执行器', 100); });