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/[email protected]/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/[email protected]/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 |
+
});
|