ok
Browse files- public/index.html +41 -16
- public/js/main.js +44 -8
public/index.html
CHANGED
|
@@ -7,45 +7,70 @@
|
|
| 7 |
<title>Web 命令执行器</title>
|
| 8 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
<script src="https://cdn.jsdelivr.net/npm/xss@1.0.14/dist/xss.min.js"></script>
|
| 10 |
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
|
|
|
|
| 11 |
<style>
|
| 12 |
body {
|
| 13 |
font-family: 'Inter', sans-serif;
|
| 14 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
</style>
|
| 16 |
</head>
|
| 17 |
|
| 18 |
<body
|
| 19 |
-
class="bg-gradient-to-br from-
|
| 20 |
<div
|
| 21 |
-
class="max-w-
|
| 22 |
-
<h1 class="text-
|
| 23 |
|
| 24 |
-
<div id="loginForm" class="space-y-
|
| 25 |
<input type="text" id="username" placeholder="用户名"
|
| 26 |
-
class="p-
|
| 27 |
<input type="password" id="password" placeholder="密码"
|
| 28 |
-
class="p-
|
| 29 |
<button id="loginButton"
|
| 30 |
-
class="w-full bg-
|
| 31 |
</div>
|
| 32 |
|
| 33 |
-
<div id="commandInterface" style="display: none;" class="space-y-
|
| 34 |
-
<p class="text-
|
| 35 |
<div class="flex">
|
| 36 |
<input type="text" id="command" placeholder="输入命令"
|
| 37 |
-
class="flex-grow p-
|
| 38 |
<button id="executeButton"
|
| 39 |
-
class="bg-green-500 text-white px-
|
| 40 |
</div>
|
| 41 |
<div id="loadingIndicator" class="text-center" style="display: none;">
|
| 42 |
-
<div class="inline-block animate-spin rounded-full h-
|
| 43 |
</div>
|
| 44 |
</div>
|
| 45 |
-
<div id="output"
|
|
|
|
|
|
|
| 46 |
<div>
|
| 47 |
-
<h2 class="text-
|
| 48 |
-
<ul id="history" class="
|
| 49 |
</div>
|
| 50 |
</div>
|
| 51 |
</div>
|
|
|
|
| 7 |
<title>Web 命令执行器</title>
|
| 8 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
<script src="https://cdn.jsdelivr.net/npm/xss@1.0.14/dist/xss.min.js"></script>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Fira+Code&display=swap"
|
| 11 |
+
rel="stylesheet">
|
| 12 |
<style>
|
| 13 |
body {
|
| 14 |
font-family: 'Inter', sans-serif;
|
| 15 |
}
|
| 16 |
+
|
| 17 |
+
.terminal {
|
| 18 |
+
font-family: 'Fira Code', monospace;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
@keyframes glow {
|
| 22 |
+
0% {
|
| 23 |
+
box-shadow: 0 0 5px rgba(66, 153, 225, 0.5);
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
50% {
|
| 27 |
+
box-shadow: 0 0 20px rgba(66, 153, 225, 0.8);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
100% {
|
| 31 |
+
box-shadow: 0 0 5px rgba(66, 153, 225, 0.5);
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.glow-effect {
|
| 36 |
+
animation: glow 2s infinite;
|
| 37 |
+
}
|
| 38 |
</style>
|
| 39 |
</head>
|
| 40 |
|
| 41 |
<body
|
| 42 |
+
class="bg-gradient-to-br from-gray-900 via-blue-900 to-blue-700 min-h-screen flex items-center justify-center p-4">
|
| 43 |
<div
|
| 44 |
+
class="max-w-4xl w-full mx-auto bg-gray-800 p-8 rounded-3xl shadow-2xl transition-all duration-300 ease-in-out hover:shadow-3xl border border-blue-500">
|
| 45 |
+
<h1 class="text-5xl font-bold mb-8 text-center text-blue-400 tracking-tight glow-effect">Web 命令执行器</h1>
|
| 46 |
|
| 47 |
+
<div id="loginForm" class="space-y-6">
|
| 48 |
<input type="text" id="username" placeholder="用户名"
|
| 49 |
+
class="p-4 border-2 border-blue-500 rounded-lg w-full bg-gray-700 text-white focus:ring-2 focus:ring-blue-300 transition-all duration-200 ease-in-out placeholder-gray-400">
|
| 50 |
<input type="password" id="password" placeholder="密码"
|
| 51 |
+
class="p-4 border-2 border-blue-500 rounded-lg w-full bg-gray-700 text-white focus:ring-2 focus:ring-blue-300 transition-all duration-200 ease-in-out placeholder-gray-400">
|
| 52 |
<button id="loginButton"
|
| 53 |
+
class="w-full bg-blue-600 text-white px-6 py-4 rounded-lg hover:bg-blue-700 transition-all duration-200 ease-in-out transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-50">登录</button>
|
| 54 |
</div>
|
| 55 |
|
| 56 |
+
<div id="commandInterface" style="display: none;" class="space-y-8">
|
| 57 |
+
<p class="text-blue-300 text-center text-lg">在下方输入命令并点击"执行"或按回车键来在服务器上执行命令。</p>
|
| 58 |
<div class="flex">
|
| 59 |
<input type="text" id="command" placeholder="输入命令"
|
| 60 |
+
class="flex-grow p-4 border-2 border-blue-500 rounded-l-lg bg-gray-700 text-white focus:outline-none focus:ring-2 focus:ring-blue-300 transition-all duration-200 ease-in-out placeholder-gray-400 terminal">
|
| 61 |
<button id="executeButton"
|
| 62 |
+
class="bg-green-500 text-white px-8 py-4 rounded-r-lg hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-300 transition-all duration-200 ease-in-out transform hover:scale-105">执行</button>
|
| 63 |
</div>
|
| 64 |
<div id="loadingIndicator" class="text-center" style="display: none;">
|
| 65 |
+
<div class="inline-block animate-spin rounded-full h-12 w-12 border-t-4 border-b-4 border-blue-500">
|
| 66 |
</div>
|
| 67 |
</div>
|
| 68 |
+
<div id="output"
|
| 69 |
+
class="bg-gray-900 p-6 rounded-lg h-80 overflow-y-auto terminal text-green-400 text-sm leading-relaxed">
|
| 70 |
+
</div>
|
| 71 |
<div>
|
| 72 |
+
<h2 class="text-3xl font-bold mb-4 text-blue-400">命令历史</h2>
|
| 73 |
+
<ul id="history" class="space-y-2 text-gray-300"></ul>
|
| 74 |
</div>
|
| 75 |
</div>
|
| 76 |
</div>
|
public/js/main.js
CHANGED
|
@@ -15,7 +15,8 @@ async function executeCommand() {
|
|
| 15 |
if (!command.trim()) return;
|
| 16 |
|
| 17 |
showLoading(true);
|
| 18 |
-
|
|
|
|
| 19 |
|
| 20 |
try {
|
| 21 |
let response = await fetch('/api/execute', {
|
|
@@ -48,12 +49,12 @@ async function executeCommand() {
|
|
| 48 |
}
|
| 49 |
|
| 50 |
const data = await response.json();
|
| 51 |
-
|
| 52 |
commandInput.value = '';
|
| 53 |
loadCommandHistory();
|
| 54 |
} catch (error) {
|
| 55 |
console.error('执行命令时出错:', error);
|
| 56 |
-
|
| 57 |
if (error.message.includes('Token 刷新失败')) {
|
| 58 |
showNotification('访问被拒绝。请重新登录。', 'error');
|
| 59 |
localStorage.removeItem('token');
|
|
@@ -66,8 +67,15 @@ async function executeCommand() {
|
|
| 66 |
}
|
| 67 |
}
|
| 68 |
|
| 69 |
-
function
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
| 72 |
|
| 73 |
async function loadCommandHistory() {
|
|
@@ -78,13 +86,21 @@ async function loadCommandHistory() {
|
|
| 78 |
}
|
| 79 |
});
|
| 80 |
const data = await response.json();
|
|
|
|
| 81 |
history.innerHTML = '';
|
| 82 |
data.reverse().forEach(item => {
|
| 83 |
const li = document.createElement('li');
|
| 84 |
-
li.innerHTML =
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
li.onclick = () => {
|
|
|
|
| 87 |
commandInput.value = item.command;
|
|
|
|
| 88 |
};
|
| 89 |
history.appendChild(li);
|
| 90 |
});
|
|
@@ -176,4 +192,24 @@ function checkLoginStatus() {
|
|
| 176 |
}
|
| 177 |
|
| 178 |
window.addEventListener('load', checkLoginStatus);
|
| 179 |
-
document.getElementById('executeButton').addEventListener('click', executeCommand);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
if (!command.trim()) return;
|
| 16 |
|
| 17 |
showLoading(true);
|
| 18 |
+
appendToOutput('$ ' + command, 'text-blue-400');
|
| 19 |
+
appendToOutput('正在执行命令...', 'text-yellow-400');
|
| 20 |
|
| 21 |
try {
|
| 22 |
let response = await fetch('/api/execute', {
|
|
|
|
| 49 |
}
|
| 50 |
|
| 51 |
const data = await response.json();
|
| 52 |
+
appendToOutput(data.output || data.error || '命令执行成功,但没有输出。', 'text-green-400');
|
| 53 |
commandInput.value = '';
|
| 54 |
loadCommandHistory();
|
| 55 |
} catch (error) {
|
| 56 |
console.error('执行命令时出错:', error);
|
| 57 |
+
appendToOutput('错误: ' + (error.message || '未知错误'), 'text-red-500');
|
| 58 |
if (error.message.includes('Token 刷新失败')) {
|
| 59 |
showNotification('访问被拒绝。请重新登录。', 'error');
|
| 60 |
localStorage.removeItem('token');
|
|
|
|
| 67 |
}
|
| 68 |
}
|
| 69 |
|
| 70 |
+
function appendToOutput(text, className = 'text-green-400') {
|
| 71 |
+
const lines = text.split('\n');
|
| 72 |
+
lines.forEach((line, index) => {
|
| 73 |
+
const p = document.createElement('p');
|
| 74 |
+
p.className = className;
|
| 75 |
+
p.textContent = line;
|
| 76 |
+
output.appendChild(p);
|
| 77 |
+
});
|
| 78 |
+
output.scrollTop = output.scrollHeight;
|
| 79 |
}
|
| 80 |
|
| 81 |
async function loadCommandHistory() {
|
|
|
|
| 86 |
}
|
| 87 |
});
|
| 88 |
const data = await response.json();
|
| 89 |
+
console.log('命令历史数据:', data); // 添加调试信息
|
| 90 |
history.innerHTML = '';
|
| 91 |
data.reverse().forEach(item => {
|
| 92 |
const li = document.createElement('li');
|
| 93 |
+
li.innerHTML = `
|
| 94 |
+
<div class="flex justify-between items-center p-2 hover:bg-gray-700 rounded transition-colors duration-200">
|
| 95 |
+
<span class="font-semibold text-blue-400">${filterXSS(item.command)}</span>
|
| 96 |
+
<span class="text-sm text-gray-500">${new Date(item.timestamp).toLocaleString()}</span>
|
| 97 |
+
</div>
|
| 98 |
+
`;
|
| 99 |
+
li.className = 'cursor-pointer';
|
| 100 |
li.onclick = () => {
|
| 101 |
+
console.log(`Clicked on command: ${item.command}`); // 添加调试信息
|
| 102 |
commandInput.value = item.command;
|
| 103 |
+
commandInput.focus();
|
| 104 |
};
|
| 105 |
history.appendChild(li);
|
| 106 |
});
|
|
|
|
| 192 |
}
|
| 193 |
|
| 194 |
window.addEventListener('load', checkLoginStatus);
|
| 195 |
+
document.getElementById('executeButton').addEventListener('click', executeCommand);
|
| 196 |
+
|
| 197 |
+
// 添加打字机效果
|
| 198 |
+
function typeWriter(element, text, speed = 50) {
|
| 199 |
+
let i = 0;
|
| 200 |
+
function type() {
|
| 201 |
+
if (i < text.length) {
|
| 202 |
+
element.textContent += text.charAt(i);
|
| 203 |
+
i++;
|
| 204 |
+
setTimeout(type, speed);
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
type();
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
// 在页面加载时添加一些酷炫的效果
|
| 211 |
+
window.addEventListener('load', () => {
|
| 212 |
+
const title = document.querySelector('h1');
|
| 213 |
+
title.textContent = '';
|
| 214 |
+
typeWriter(title, 'Web 命令执行器', 100);
|
| 215 |
+
});
|