aigems commited on
Commit
246bcdf
·
1 Parent(s): fed74fd
Files changed (3) hide show
  1. output.txt +0 -463
  2. public/index.html +18 -16
  3. public/js/main.js +25 -14
output.txt DELETED
@@ -1,463 +0,0 @@
1
- 文件名:/workspaces/hf-exec/exec/Dockerfile
2
- 文件内容:
3
- FROM node:20
4
-
5
- ARG ADMIN_USERNAME=admin
6
- ARG ADMIN_PASSWORD=password
7
-
8
- WORKDIR /app
9
-
10
- COPY package*.json ./
11
-
12
- RUN npm install
13
-
14
- COPY . .
15
-
16
- RUN mkdir -p /app/data && chown -R node:node /app /app/data
17
-
18
- EXPOSE 7860
19
-
20
- ENV ADMIN_USERNAME=${ADMIN_USERNAME}
21
- ENV ADMIN_PASSWORD=${ADMIN_PASSWORD}
22
-
23
- CMD ["node", "app.js"]
24
-
25
- --------------------------------------------------
26
-
27
- 文件名:/workspaces/hf-exec/exec/package.json
28
- 文件内容:
29
- {
30
- "name": "web-command-executor",
31
- "version": "1.2.0",
32
- "main": "app.js",
33
- "scripts": {
34
- "start": "node app.js",
35
- "dev": "nodemon app.js",
36
- "lint": "eslint .",
37
- "test": "jest"
38
- },
39
- "dependencies": {
40
- "express": "^4.17.1",
41
- "winston": "^3.3.3",
42
- "helmet": "^4.6.0",
43
- "express-rate-limit": "^5.3.0",
44
- "jsonwebtoken": "^8.5.1"
45
- },
46
- "devDependencies": {
47
- "nodemon": "^2.0.7",
48
- "eslint": "^7.32.0",
49
- "jest": "^27.0.6"
50
- },
51
- "engines": {
52
- "node": ">=18.0.0"
53
- }
54
- }
55
-
56
- --------------------------------------------------
57
-
58
- 文件名:/workspaces/hf-exec/exec/app.js
59
- 文件内容:
60
- const express = require('express');
61
- const winston = require('winston');
62
- const fs = require('fs').promises;
63
- const path = require('path');
64
- const rateLimit = require('express-rate-limit');
65
- const { promises: fsPromises } = require('fs');
66
- const helmet = require('helmet');
67
- const jwt = require('jsonwebtoken');
68
-
69
- const app = express();
70
- const port = process.env.PORT || 7860;
71
-
72
- // 配置日志记录器
73
- const logger = winston.createLogger({
74
- level: process.env.LOG_LEVEL || 'info',
75
- format: winston.format.combine(
76
- winston.format.timestamp(),
77
- winston.format.json()
78
- ),
79
- transports: [
80
- new winston.transports.File({ filename: 'error.log', level: 'error' }),
81
- new winston.transports.File({ filename: 'combined.log' })
82
- ]
83
- });
84
-
85
- if (process.env.NODE_ENV !== 'production') {
86
- logger.add(new winston.transports.Console({
87
- format: winston.format.simple()
88
- }));
89
- }
90
-
91
- // 安全中间件
92
- app.use(helmet());
93
-
94
- // 速率限制
95
- const limiter = rateLimit({
96
- windowMs: 15 * 60 * 1000, // 15分钟
97
- max: 100 // 每个IP限制100个请求
98
- });
99
- app.use('/api', limiter);
100
-
101
- app.use(express.json());
102
- app.use(express.static('public'));
103
-
104
- // JWT 密钥
105
- const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
106
-
107
- // 中间件:验证 JWT
108
- const authenticateJWT = (req, res, next) => {
109
- const authHeader = req.headers.authorization;
110
-
111
- if (authHeader) {
112
- const token = authHeader.split(' ')[1];
113
-
114
- jwt.verify(token, JWT_SECRET, (err, user) => {
115
- if (err) {
116
- return res.sendStatus(403);
117
- }
118
-
119
- req.user = user;
120
- next();
121
- });
122
- } else {
123
- res.sendStatus(401);
124
- }
125
- };
126
-
127
- // 路由
128
- const authRoutes = require('./routes/auth');
129
- const commandRoutes = require('./routes/command');
130
-
131
- app.use('/api', authRoutes);
132
- app.use('/api', authenticateJWT, commandRoutes);
133
-
134
- // 错误处理中间件
135
- app.use((err, req, res, next) => {
136
- logger.error(`未捕获的错误: ${err.message}`);
137
- res.status(500).json({ error: '服务器内部错误' });
138
- });
139
-
140
- // 确保命令历史文件存在
141
- const historyFilePath = path.join(__dirname, 'data', 'command_history.json');
142
- fsPromises.access(historyFilePath)
143
- .catch(() => fsPromises.writeFile(historyFilePath, '[]'))
144
- .then(() => {
145
- app.listen(port, () => {
146
- logger.info(`Web 命令执行应用正在监听 http://localhost:${port}`);
147
- });
148
- })
149
- .catch(err => {
150
- logger.error('无法创建命令历史文件:', err);
151
- process.exit(1);
152
- });
153
-
154
- --------------------------------------------------
155
-
156
- 文件名:/workspaces/hf-exec/exec/utils/logger.js
157
- 文件内容:
158
- const winston = require('winston');
159
-
160
- const logger = winston.createLogger({
161
- level: process.env.LOG_LEVEL || 'info',
162
- format: winston.format.combine(
163
- winston.format.timestamp(),
164
- winston.format.json()
165
- ),
166
- transports: [
167
- new winston.transports.File({ filename: 'error.log', level: 'error' }),
168
- new winston.transports.File({ filename: 'combined.log' })
169
- ]
170
- });
171
-
172
- if (process.env.NODE_ENV !== 'production') {
173
- logger.add(new winston.transports.Console({
174
- format: winston.format.simple()
175
- }));
176
- }
177
-
178
- module.exports = logger;
179
-
180
- --------------------------------------------------
181
-
182
- 文件名:/workspaces/hf-exec/exec/routes/command.js
183
- 文件内容:
184
- const express = require('express');
185
- const { execFile } = require('child_process');
186
- const fs = require('fs').promises;
187
- const path = require('path');
188
- const router = express.Router();
189
-
190
- const logger = require('../utils/logger');
191
-
192
- // 命令白名单
193
- const allowedCommands = ['ls', 'pwd', 'whoami', 'date', 'echo', 'cat'];
194
-
195
- // 历史命令文件路径
196
- const historyFilePath = path.join(__dirname, '..', 'data', 'command_history.json');
197
-
198
- router.get('/command-history', async (req, res) => {
199
- try {
200
- let history = [];
201
- try {
202
- const historyData = await fs.readFile(historyFilePath, 'utf-8');
203
- history = JSON.parse(historyData);
204
- } catch (readError) {
205
- logger.warn('读取命令历史失败,使用空数组:', readError);
206
- }
207
- res.json(history);
208
- } catch (error) {
209
- logger.error('处理命令历史请求失败:', error);
210
- res.status(500).json({ error: '无法读取命令历史' });
211
- }
212
- });
213
-
214
- router.post('/execute', async (req, res) => {
215
- const { command } = req.body;
216
- const baseCommand = command.split(' ')[0];
217
-
218
- // 白名单检查的部分
219
- // if (!allowedCommands.includes(baseCommand)) {
220
- // logger.warn(`用户 ${req.user.username} 尝试执行未授权的命令: ${command}`);
221
- // return res.status(403).json({ error: '未授权的命令' });
222
- // }
223
-
224
- execFile(baseCommand, command.split(' ').slice(1), { timeout: 5000 }, async (error, stdout, stderr) => {
225
- if (error) {
226
- logger.error(`命令执行错误: ${error.message}`);
227
- return res.status(500).json({ error: error.message });
228
- }
229
-
230
- // 记录命令历史
231
- try {
232
- let history = [];
233
- try {
234
- const historyData = await fs.readFile(historyFilePath, 'utf-8');
235
- history = JSON.parse(historyData);
236
- } catch (readError) {
237
- // 如果文件不存在或为空,使用空数组
238
- }
239
-
240
- history.push({ command, timestamp: new Date().toISOString(), user: req.user.username });
241
- if (history.length > 100) history.shift(); // 保留最近100条命令
242
-
243
- await fs.writeFile(historyFilePath, JSON.stringify(history, null, 2));
244
- } catch (writeError) {
245
- logger.error('写入命令历史失败:', writeError);
246
- }
247
-
248
- logger.info(`用户 ${req.user.username} 成功执行命令: ${command}`);
249
- res.json({ output: stdout, error: stderr });
250
- });
251
- });
252
-
253
- module.exports = router;
254
-
255
- --------------------------------------------------
256
-
257
- 文件名:/workspaces/hf-exec/exec/routes/auth.js
258
- 文件内容:
259
- const express = require('express');
260
- const jwt = require('jsonwebtoken');
261
- const router = express.Router();
262
-
263
- const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
264
-
265
- const users = [
266
- {
267
- id: 1,
268
- username: process.env.ADMIN_USERNAME,
269
- password: process.env.ADMIN_PASSWORD
270
- }
271
- ];
272
-
273
- router.post('/login', (req, res) => {
274
- const { username, password } = req.body;
275
- const user = users.find(u => u.username === username && u.password === password);
276
-
277
- if (user) {
278
- const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: '1h' });
279
- res.json({ token });
280
- } else {
281
- res.status(401).json({ error: '无效的用户名或密码' });
282
- }
283
- });
284
-
285
- module.exports = router;
286
-
287
- --------------------------------------------------
288
-
289
- 文件名:/workspaces/hf-exec/exec/public/index.html
290
- 文件内容:
291
- <!DOCTYPE html>
292
- <html lang="zh-CN">
293
-
294
- <head>
295
- <meta charset="UTF-8">
296
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
297
- <title>Web 命令执行</title>
298
- <script src="https://cdn.tailwindcss.com"></script>
299
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/xss.min.js"></script>
300
- <style>
301
- @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&display=swap');
302
-
303
- body {
304
- font-family: 'Noto Sans SC', sans-serif;
305
- }
306
- </style>
307
- </head>
308
-
309
- <body class="bg-gradient-to-r from-blue-100 to-purple-100 min-h-screen flex items-center justify-center">
310
- <div class="max-w-2xl w-full mx-auto bg-white p-8 rounded-lg shadow-xl">
311
- <h1 class="text-4xl font-bold mb-6 text-center text-blue-600">Web 命令执行</h1>
312
-
313
- <div id="loginForm" class="space-y-4">
314
- <input type="text" id="username" placeholder="用户名"
315
- class="p-3 border rounded w-full focus:ring-2 focus:ring-blue-300 transition">
316
- <input type="password" id="password" placeholder="密码"
317
- class="p-3 border rounded w-full focus:ring-2 focus:ring-blue-300 transition">
318
- <button id="loginButton"
319
- class="w-full bg-blue-500 text-white px-4 py-3 rounded hover:bg-blue-600 transition">登录</button>
320
- </div>
321
-
322
- <div id="commandInterface" style="display: none;" class="space-y-6">
323
- <p class="text-gray-600">此应用允许您在服务器上执行命令。请在下方输入命令并点击"执行"或按回车键。</p>
324
- <div class="flex">
325
- <input type="text" id="command" placeholder="输入命令"
326
- class="flex-grow p-3 border rounded-l focus:outline-none focus:ring-2 focus:ring-blue-300 transition">
327
- <button id="executeButton"
328
- class="bg-green-500 text-white px-6 py-3 rounded-r hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-300 transition">执行</button>
329
- </div>
330
- <div id="loadingIndicator" class="text-center" style="display: none;">
331
- <div class="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
332
- </div>
333
- <div id="output" class="bg-gray-100 p-4 rounded h-64 overflow-y-auto font-mono text-sm"></div>
334
- <div>
335
- <h2 class="text-2xl font-bold mb-3 text-blue-600">命令历史</h2>
336
- <ul id="history" class="list-disc pl-5 space-y-2 text-gray-700"></ul>
337
- </div>
338
- </div>
339
- </div>
340
-
341
- <script src="/js/main.js"></script>
342
- </body>
343
-
344
- </html>
345
-
346
- --------------------------------------------------
347
-
348
- 文件名:/workspaces/hf-exec/exec/public/js/main.js
349
- 文件内容:
350
- const commandInput = document.getElementById('command');
351
- const output = document.getElementById('output');
352
- const history = document.getElementById('history');
353
- const executeButton = document.getElementById('executeButton');
354
- const loadingIndicator = document.getElementById('loadingIndicator');
355
-
356
- commandInput.addEventListener('keypress', function (event) {
357
- if (event.key === 'Enter') {
358
- executeCommand();
359
- }
360
- });
361
-
362
- async function executeCommand() {
363
- const command = commandInput.value;
364
- if (!command.trim()) return;
365
-
366
- showLoading(true);
367
- output.textContent = '正在执行命令...';
368
-
369
- try {
370
- const response = await fetch('/api/execute', {
371
- method: 'POST',
372
- headers: {
373
- 'Content-Type': 'application/json',
374
- 'Authorization': `Bearer ${localStorage.getItem('token')}`
375
- },
376
- body: JSON.stringify({ command })
377
- });
378
- const data = await response.json();
379
- if (response.ok) {
380
- output.textContent = data.output || data.error;
381
- commandInput.value = '';
382
- loadCommandHistory();
383
- } else {
384
- output.textContent = `错误: ${data.error}`;
385
- }
386
- } catch (error) {
387
- output.textContent = '错误: ' + error.message;
388
- } finally {
389
- showLoading(false);
390
- }
391
- }
392
-
393
- async function loadCommandHistory() {
394
- try {
395
- const response = await fetch('/api/command-history', {
396
- headers: {
397
- 'Authorization': `Bearer ${localStorage.getItem('token')}`
398
- }
399
- });
400
- const data = await response.json();
401
- history.innerHTML = '';
402
- data.reverse().forEach(item => {
403
- const li = document.createElement('li');
404
- li.textContent = `${filterXSS(item.command)} (${new Date(item.timestamp).toLocaleString()})`;
405
- li.className = 'cursor-pointer hover:text-blue-500';
406
- li.onclick = () => {
407
- commandInput.value = item.command;
408
- };
409
- history.appendChild(li);
410
- });
411
- } catch (error) {
412
- console.error('加载命令历史失败:', error);
413
- }
414
- }
415
-
416
- function showLoading(isLoading) {
417
- loadingIndicator.style.display = isLoading ? 'block' : 'none';
418
- executeButton.disabled = isLoading;
419
- }
420
-
421
- async function login() {
422
- const username = document.getElementById('username').value;
423
- const password = document.getElementById('password').value;
424
- try {
425
- const response = await fetch('/api/login', {
426
- method: 'POST',
427
- headers: { 'Content-Type': 'application/json' },
428
- body: JSON.stringify({ username, password })
429
- });
430
- const data = await response.json();
431
- if (response.ok) {
432
- localStorage.setItem('token', data.token);
433
- document.getElementById('loginForm').style.display = 'none';
434
- document.getElementById('commandInterface').style.display = 'block';
435
- loadCommandHistory();
436
- } else {
437
- alert('登录失败: ' + data.error);
438
- }
439
- } catch (error) {
440
- alert('登录错误: ' + error.message);
441
- }
442
- }
443
-
444
- document.getElementById('loginButton').addEventListener('click', login);
445
-
446
- loadCommandHistory();
447
-
448
- function checkLoginStatus() {
449
- const token = localStorage.getItem('token');
450
- if (token) {
451
- document.getElementById('loginForm').style.display = 'none';
452
- document.getElementById('commandInterface').style.display = 'block';
453
- loadCommandHistory();
454
- }
455
- }
456
-
457
- // 在页面加载时调用此函数
458
- window.addEventListener('load', checkLoginStatus);
459
-
460
- document.getElementById('executeButton').addEventListener('click', executeCommand);
461
-
462
- --------------------------------------------------
463
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public/index.html CHANGED
@@ -4,45 +4,47 @@
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
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
  <style>
11
- @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&display=swap');
12
-
13
  body {
14
- font-family: 'Noto Sans SC', sans-serif;
15
  }
16
  </style>
17
  </head>
18
 
19
- <body class="bg-gradient-to-r from-blue-100 to-purple-100 min-h-screen flex items-center justify-center">
20
- <div class="max-w-2xl w-full mx-auto bg-white p-8 rounded-lg shadow-xl">
21
- <h1 class="text-4xl font-bold mb-6 text-center text-blue-600">Web 命令执行</h1>
 
 
22
 
23
  <div id="loginForm" class="space-y-4">
24
  <input type="text" id="username" placeholder="用户名"
25
- class="p-3 border rounded w-full focus:ring-2 focus:ring-blue-300 transition">
26
  <input type="password" id="password" placeholder="密码"
27
- class="p-3 border rounded w-full focus:ring-2 focus:ring-blue-300 transition">
28
  <button id="loginButton"
29
- class="w-full bg-blue-500 text-white px-4 py-3 rounded hover:bg-blue-600 transition">登录</button>
30
  </div>
31
 
32
  <div id="commandInterface" style="display: none;" class="space-y-6">
33
- <p class="text-gray-600">此应用允许您在服务器上执行命令。请在下方输入命令并点击"执行"或按回车键。</p>
34
  <div class="flex">
35
  <input type="text" id="command" placeholder="输入命令"
36
- class="flex-grow p-3 border rounded-l focus:outline-none focus:ring-2 focus:ring-blue-300 transition">
37
  <button id="executeButton"
38
- class="bg-green-500 text-white px-6 py-3 rounded-r hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-300 transition">执行</button>
39
  </div>
40
  <div id="loadingIndicator" class="text-center" style="display: none;">
41
- <div class="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
 
42
  </div>
43
- <div id="output" class="bg-gray-100 p-4 rounded h-64 overflow-y-auto font-mono text-sm"></div>
44
  <div>
45
- <h2 class="text-2xl font-bold mb-3 text-blue-600">命令历史</h2>
46
  <ul id="history" class="list-disc pl-5 space-y-2 text-gray-700"></ul>
47
  </div>
48
  </div>
 
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
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" rel="stylesheet">
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-indigo-100 via-purple-100 to-pink-100 min-h-screen flex items-center justify-center p-4">
20
+ <div
21
+ class="max-w-2xl w-full mx-auto bg-white p-8 rounded-2xl shadow-2xl transition-all duration-300 ease-in-out hover:shadow-3xl">
22
+ <h1 class="text-4xl font-bold mb-6 text-center text-indigo-600 tracking-tight">Web 命令执行器</h1>
23
 
24
  <div id="loginForm" class="space-y-4">
25
  <input type="text" id="username" placeholder="用户名"
26
+ class="p-3 border rounded-lg w-full focus:ring-2 focus:ring-indigo-300 transition-all duration-200 ease-in-out">
27
  <input type="password" id="password" placeholder="密码"
28
+ class="p-3 border rounded-lg w-full focus:ring-2 focus:ring-indigo-300 transition-all duration-200 ease-in-out">
29
  <button id="loginButton"
30
+ class="w-full bg-indigo-500 text-white px-4 py-3 rounded-lg hover:bg-indigo-600 transition-all duration-200 ease-in-out transform hover:scale-105">登录</button>
31
  </div>
32
 
33
  <div id="commandInterface" style="display: none;" class="space-y-6">
34
+ <p class="text-gray-600 text-center">在下方输入命令并点击"执行"或按回车键来在服务器上执行命令。</p>
35
  <div class="flex">
36
  <input type="text" id="command" placeholder="输入命令"
37
+ class="flex-grow p-3 border rounded-l-lg focus:outline-none focus:ring-2 focus:ring-indigo-300 transition-all duration-200 ease-in-out">
38
  <button id="executeButton"
39
+ class="bg-green-500 text-white px-6 py-3 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>
40
  </div>
41
  <div id="loadingIndicator" class="text-center" style="display: none;">
42
+ <div class="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-indigo-500">
43
+ </div>
44
  </div>
45
+ <div id="output" class="bg-gray-100 p-4 rounded-lg h-64 overflow-y-auto font-mono text-sm"></div>
46
  <div>
47
+ <h2 class="text-2xl font-bold mb-3 text-indigo-600">命令历史</h2>
48
  <ul id="history" class="list-disc pl-5 space-y-2 text-gray-700"></ul>
49
  </div>
50
  </div>
public/js/main.js CHANGED
@@ -28,10 +28,8 @@ async function executeCommand() {
28
  });
29
 
30
  if (response.status === 403) {
31
- // Token 可能已过期,尝试刷新
32
  const refreshed = await refreshToken();
33
  if (refreshed) {
34
- // 重试请求
35
  response = await fetch('/api/execute', {
36
  method: 'POST',
37
  headers: {
@@ -50,14 +48,14 @@ async function executeCommand() {
50
  }
51
 
52
  const data = await response.json();
53
- output.textContent = data.output || data.error || '命令执行成功,但没有输出。';
54
  commandInput.value = '';
55
  loadCommandHistory();
56
  } catch (error) {
57
  console.error('执行命令时出错:', error);
58
- output.textContent = '错误: ' + (error.message || '未知错误');
59
  if (error.message.includes('Token 刷新失败')) {
60
- alert('访问被拒绝。请重新登录。');
61
  localStorage.removeItem('token');
62
  localStorage.removeItem('username');
63
  localStorage.removeItem('password');
@@ -68,6 +66,10 @@ async function executeCommand() {
68
  }
69
  }
70
 
 
 
 
 
71
  async function loadCommandHistory() {
72
  try {
73
  const response = await fetch('/api/command-history', {
@@ -79,22 +81,23 @@ async function loadCommandHistory() {
79
  history.innerHTML = '';
80
  data.reverse().forEach(item => {
81
  const li = document.createElement('li');
82
- li.textContent = `${filterXSS(item.command)} (${new Date(item.timestamp).toLocaleString()})`;
83
- li.className = 'cursor-pointer hover:text-blue-500';
84
  li.onclick = () => {
85
- console.log(`Clicked on command: ${item.command}`); // 添加调试信息
86
  commandInput.value = item.command;
87
  };
88
  history.appendChild(li);
89
  });
90
  } catch (error) {
91
  console.error('加载命令历史失败:', error);
 
92
  }
93
  }
94
 
95
  function showLoading(isLoading) {
96
  loadingIndicator.style.display = isLoading ? 'block' : 'none';
97
  executeButton.disabled = isLoading;
 
98
  }
99
 
100
  async function login() {
@@ -114,11 +117,12 @@ async function login() {
114
  document.getElementById('loginForm').style.display = 'none';
115
  document.getElementById('commandInterface').style.display = 'block';
116
  loadCommandHistory();
 
117
  } else {
118
- alert('登录失败: ' + data.error);
119
  }
120
  } catch (error) {
121
- alert('登录错误: ' + error.message);
122
  }
123
  }
124
 
@@ -126,7 +130,6 @@ async function refreshToken() {
126
  const username = localStorage.getItem('username');
127
  const password = localStorage.getItem('password');
128
  if (!username || !password) {
129
- // 如果没有保存的凭据,重定向到登录页面
130
  document.getElementById('loginForm').style.display = 'block';
131
  document.getElementById('commandInterface').style.display = 'none';
132
  return;
@@ -150,9 +153,18 @@ async function refreshToken() {
150
  }
151
  }
152
 
153
- document.getElementById('loginButton').addEventListener('click', login);
 
 
 
 
 
 
 
 
 
154
 
155
- loadCommandHistory();
156
 
157
  function checkLoginStatus() {
158
  const token = localStorage.getItem('token');
@@ -163,6 +175,5 @@ function checkLoginStatus() {
163
  }
164
  }
165
 
166
- // 在页面加载时调用此函数
167
  window.addEventListener('load', checkLoginStatus);
168
  document.getElementById('executeButton').addEventListener('click', executeCommand);
 
28
  });
29
 
30
  if (response.status === 403) {
 
31
  const refreshed = await refreshToken();
32
  if (refreshed) {
 
33
  response = await fetch('/api/execute', {
34
  method: 'POST',
35
  headers: {
 
48
  }
49
 
50
  const data = await response.json();
51
+ output.innerHTML = formatOutput(data.output || data.error || '命令执行成功,但没有输出。');
52
  commandInput.value = '';
53
  loadCommandHistory();
54
  } catch (error) {
55
  console.error('执行命令时出错:', error);
56
+ output.innerHTML = formatOutput('错误: ' + (error.message || '未知错误'));
57
  if (error.message.includes('Token 刷新失败')) {
58
+ showNotification('访问被拒绝。请重新登录。', 'error');
59
  localStorage.removeItem('token');
60
  localStorage.removeItem('username');
61
  localStorage.removeItem('password');
 
66
  }
67
  }
68
 
69
+ function formatOutput(text) {
70
+ return text.replace(/\n/g, '<br>').replace(/ /g, '&nbsp;');
71
+ }
72
+
73
  async function loadCommandHistory() {
74
  try {
75
  const response = await fetch('/api/command-history', {
 
81
  history.innerHTML = '';
82
  data.reverse().forEach(item => {
83
  const li = document.createElement('li');
84
+ li.innerHTML = `<span class="font-semibold">${filterXSS(item.command)}</span> <span class="text-sm text-gray-500">(${new Date(item.timestamp).toLocaleString()})</span>`;
85
+ li.className = 'cursor-pointer hover:text-indigo-500 transition-colors duration-200';
86
  li.onclick = () => {
 
87
  commandInput.value = item.command;
88
  };
89
  history.appendChild(li);
90
  });
91
  } catch (error) {
92
  console.error('加载命令历史失败:', error);
93
+ showNotification('加载命令历史失败', 'error');
94
  }
95
  }
96
 
97
  function showLoading(isLoading) {
98
  loadingIndicator.style.display = isLoading ? 'block' : 'none';
99
  executeButton.disabled = isLoading;
100
+ executeButton.classList.toggle('opacity-50', isLoading);
101
  }
102
 
103
  async function login() {
 
117
  document.getElementById('loginForm').style.display = 'none';
118
  document.getElementById('commandInterface').style.display = 'block';
119
  loadCommandHistory();
120
+ showNotification('登录成功', 'success');
121
  } else {
122
+ showNotification('登录失败: ' + data.error, 'error');
123
  }
124
  } catch (error) {
125
+ showNotification('登录错误: ' + error.message, 'error');
126
  }
127
  }
128
 
 
130
  const username = localStorage.getItem('username');
131
  const password = localStorage.getItem('password');
132
  if (!username || !password) {
 
133
  document.getElementById('loginForm').style.display = 'block';
134
  document.getElementById('commandInterface').style.display = 'none';
135
  return;
 
153
  }
154
  }
155
 
156
+ function showNotification(message, type = 'info') {
157
+ const notification = document.createElement('div');
158
+ notification.textContent = message;
159
+ 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`;
160
+ document.body.appendChild(notification);
161
+ setTimeout(() => {
162
+ notification.style.opacity = '0';
163
+ setTimeout(() => notification.remove(), 300);
164
+ }, 3000);
165
+ }
166
 
167
+ document.getElementById('loginButton').addEventListener('click', login);
168
 
169
  function checkLoginStatus() {
170
  const token = localStorage.getItem('token');
 
175
  }
176
  }
177
 
 
178
  window.addEventListener('load', checkLoginStatus);
179
  document.getElementById('executeButton').addEventListener('click', executeCommand);