local-cmd-runner 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/app.js +52 -15
- package/public/index.html +7 -1
- package/public/style.css +46 -4
- package/server.js +23 -10
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -146,6 +146,9 @@ const fmCurrentPath = document.getElementById('fmCurrentPath');
|
|
|
146
146
|
const fmUploadBtn = document.getElementById('fmUploadBtn');
|
|
147
147
|
const fmUploadInput = document.getElementById('fmUploadInput');
|
|
148
148
|
const fmRefreshBtn = document.getElementById('fmRefreshBtn');
|
|
149
|
+
const fmUploadProgress = document.getElementById('fmUploadProgress');
|
|
150
|
+
const fmProgressBar = document.getElementById('fmProgressBar');
|
|
151
|
+
const fmProgressText = document.getElementById('fmProgressText');
|
|
149
152
|
|
|
150
153
|
const previewModal = document.getElementById('previewModal');
|
|
151
154
|
const previewTitle = document.getElementById('previewTitle');
|
|
@@ -243,27 +246,61 @@ function triggerDirectoryUpload(targetPath) {
|
|
|
243
246
|
if (fmRefreshBtn) {
|
|
244
247
|
fmRefreshBtn.onclick = () => loadTree(currentRelPath);
|
|
245
248
|
fmUploadBtn.onclick = () => triggerDirectoryUpload(currentRelPath);
|
|
246
|
-
fmUploadInput.onchange =
|
|
249
|
+
fmUploadInput.onchange = (e) => {
|
|
247
250
|
const file = e.target.files[0];
|
|
248
251
|
if (!file) return;
|
|
252
|
+
|
|
253
|
+
if (file.size > 5 * 1024 * 1024) {
|
|
254
|
+
alert('上传失败:文件大小不能超过 5MB!');
|
|
255
|
+
fmUploadInput.value = '';
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
249
259
|
const formData = new FormData();
|
|
250
260
|
formData.append('file', file);
|
|
251
261
|
formData.append('path', pendingUploadPath);
|
|
252
262
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
263
|
+
fmUploadBtn.textContent = '...';
|
|
264
|
+
if (fmUploadProgress) {
|
|
265
|
+
fmUploadProgress.classList.remove('hidden');
|
|
266
|
+
fmProgressBar.style.width = '0%';
|
|
267
|
+
fmProgressText.textContent = '0%';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const xhr = new XMLHttpRequest();
|
|
271
|
+
xhr.open('POST', '/cmd/api/upload');
|
|
272
|
+
|
|
273
|
+
xhr.upload.onprogress = (event) => {
|
|
274
|
+
if (event.lengthComputable && fmUploadProgress) {
|
|
275
|
+
const percentComplete = Math.floor((event.loaded / event.total) * 100);
|
|
276
|
+
fmProgressBar.style.width = percentComplete + '%';
|
|
277
|
+
fmProgressText.textContent = percentComplete + '%';
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
xhr.onload = () => {
|
|
260
282
|
fmUploadInput.value = '';
|
|
261
|
-
loadTree(currentRelPath);
|
|
262
|
-
} catch (err) {
|
|
263
|
-
alert('上传失败: ' + err.message);
|
|
264
|
-
} finally {
|
|
265
283
|
fmUploadBtn.textContent = '↑';
|
|
266
|
-
|
|
284
|
+
if (fmUploadProgress) fmUploadProgress.classList.add('hidden');
|
|
285
|
+
|
|
286
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
287
|
+
// Success
|
|
288
|
+
loadTree(currentRelPath);
|
|
289
|
+
} else {
|
|
290
|
+
let errMsg = '上传时服务器报错';
|
|
291
|
+
try { errMsg = JSON.parse(xhr.responseText).error || errMsg; } catch (e) {}
|
|
292
|
+
alert('上传失败: ' + errMsg);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
xhr.onerror = () => {
|
|
297
|
+
fmUploadInput.value = '';
|
|
298
|
+
fmUploadBtn.textContent = '↑';
|
|
299
|
+
if (fmUploadProgress) fmUploadProgress.classList.add('hidden');
|
|
300
|
+
alert('上传失败: 网络错误');
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
xhr.send(formData);
|
|
267
304
|
};
|
|
268
305
|
}
|
|
269
306
|
|
|
@@ -282,9 +319,9 @@ async function previewFile(path) {
|
|
|
282
319
|
const fileUrl = `/cmd/api/preview?path=${encodeURIComponent(path)}`;
|
|
283
320
|
|
|
284
321
|
if (isImage) {
|
|
285
|
-
previewBody.innerHTML = `<img src="${fileUrl}" style="max-width: 100%;
|
|
322
|
+
previewBody.innerHTML = `<img src="${fileUrl}" style="max-width: 100%; object-fit: contain; border-radius: 4px; display: block; margin: 0 auto;" />`;
|
|
286
323
|
} else if (isVideo) {
|
|
287
|
-
previewBody.innerHTML = `<video src="${fileUrl}" controls style="max-width: 100%;
|
|
324
|
+
previewBody.innerHTML = `<video src="${fileUrl}" controls style="max-width: 100%; border-radius: 4px; display: block; margin: 0 auto;"></video>`;
|
|
288
325
|
} else if (isAudio) {
|
|
289
326
|
previewBody.innerHTML = `<audio src="${fileUrl}" controls style="width: 100%; margin-top: 20px;"></audio>`;
|
|
290
327
|
} else if (isHtml) {
|
package/public/index.html
CHANGED
|
@@ -58,6 +58,12 @@
|
|
|
58
58
|
</div>
|
|
59
59
|
</div>
|
|
60
60
|
<div class="fm-path" id="fmCurrentPath">/root/.openclaw</div>
|
|
61
|
+
<div id="fmUploadProgress" class="fm-upload-progress hidden">
|
|
62
|
+
<div class="fm-progress-bar-wrap">
|
|
63
|
+
<div class="fm-progress-bar" id="fmProgressBar"></div>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="fm-progress-text" id="fmProgressText">0%</div>
|
|
66
|
+
</div>
|
|
61
67
|
<div class="fm-body" id="fmTree">
|
|
62
68
|
<div class="fm-loading">加载中...</div>
|
|
63
69
|
</div>
|
|
@@ -68,7 +74,7 @@
|
|
|
68
74
|
<div id="previewModal" class="modal-overlay hidden">
|
|
69
75
|
<div class="modal-content preview-modal-content">
|
|
70
76
|
<h3 id="previewTitle" style="word-break: break-all;">文件预览</h3>
|
|
71
|
-
<div class="preview-body" id="previewBody"
|
|
77
|
+
<div class="preview-body" id="previewBody"></div>
|
|
72
78
|
<div class="modal-actions">
|
|
73
79
|
<button id="previewClose" class="btn-primary">关闭</button>
|
|
74
80
|
</div>
|
package/public/style.css
CHANGED
|
@@ -20,7 +20,9 @@ body {
|
|
|
20
20
|
font-family: 'Inter', sans-serif;
|
|
21
21
|
background: linear-gradient(135deg, var(--bg-color), #000000);
|
|
22
22
|
color: var(--text-primary);
|
|
23
|
-
|
|
23
|
+
height: 100vh;
|
|
24
|
+
margin: 0;
|
|
25
|
+
overflow: hidden;
|
|
24
26
|
display: flex;
|
|
25
27
|
flex-direction: column;
|
|
26
28
|
align-items: center;
|
|
@@ -33,6 +35,8 @@ body {
|
|
|
33
35
|
display: grid;
|
|
34
36
|
grid-template-columns: 1fr 350px;
|
|
35
37
|
gap: 24px;
|
|
38
|
+
flex: 1;
|
|
39
|
+
overflow: hidden;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
.container {
|
|
@@ -40,6 +44,8 @@ body {
|
|
|
40
44
|
flex-direction: column;
|
|
41
45
|
gap: 24px;
|
|
42
46
|
min-width: 0;
|
|
47
|
+
height: 100%;
|
|
48
|
+
overflow: hidden;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
header {
|
|
@@ -163,8 +169,8 @@ input[type="text"]:focus {
|
|
|
163
169
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
|
|
164
170
|
display: flex;
|
|
165
171
|
flex-direction: column;
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
flex: 1;
|
|
173
|
+
min-height: 200px;
|
|
168
174
|
}
|
|
169
175
|
|
|
170
176
|
.terminal-header {
|
|
@@ -347,7 +353,6 @@ input[type="text"]:focus {
|
|
|
347
353
|
flex-direction: column;
|
|
348
354
|
overflow: hidden;
|
|
349
355
|
height: 100%;
|
|
350
|
-
max-height: calc(100vh - 80px); /* accounting for body padding */
|
|
351
356
|
}
|
|
352
357
|
|
|
353
358
|
.fm-header {
|
|
@@ -470,3 +475,40 @@ input[type="text"]:focus {
|
|
|
470
475
|
white-space: pre-wrap;
|
|
471
476
|
word-break: break-all;
|
|
472
477
|
}
|
|
478
|
+
|
|
479
|
+
/* File Manager Progress */
|
|
480
|
+
.fm-upload-progress {
|
|
481
|
+
display: flex;
|
|
482
|
+
align-items: center;
|
|
483
|
+
padding: 8px 16px;
|
|
484
|
+
background: rgba(59, 130, 246, 0.1);
|
|
485
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
486
|
+
gap: 12px;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.fm-upload-progress.hidden {
|
|
490
|
+
display: none;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.fm-progress-bar-wrap {
|
|
494
|
+
flex: 1;
|
|
495
|
+
background: rgba(0, 0, 0, 0.3);
|
|
496
|
+
border-radius: 4px;
|
|
497
|
+
height: 8px;
|
|
498
|
+
overflow: hidden;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.fm-progress-bar {
|
|
502
|
+
background: var(--accent);
|
|
503
|
+
width: 0%;
|
|
504
|
+
height: 100%;
|
|
505
|
+
transition: width 0.1s linear;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.fm-progress-text {
|
|
509
|
+
font-size: 0.8rem;
|
|
510
|
+
color: var(--text-primary);
|
|
511
|
+
min-width: 36px;
|
|
512
|
+
text-align: right;
|
|
513
|
+
font-variant-numeric: tabular-nums;
|
|
514
|
+
}
|
package/server.js
CHANGED
|
@@ -79,16 +79,29 @@ app.get('/cmd/api/download', (req, res) => {
|
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
const upload = multer({
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
const upload = multer({
|
|
83
|
+
dest: os.tmpdir(),
|
|
84
|
+
limits: { fileSize: 5 * 1024 * 1024 }
|
|
85
|
+
}).single('file');
|
|
86
|
+
|
|
87
|
+
app.post('/cmd/api/upload', (req, res) => {
|
|
88
|
+
upload(req, res, async (err) => {
|
|
89
|
+
if (err instanceof multer.MulterError && err.code === 'LIMIT_FILE_SIZE') {
|
|
90
|
+
return res.status(400).json({ error: '文件大小超出 5MB 限制!' });
|
|
91
|
+
} else if (err) {
|
|
92
|
+
return res.status(500).json({ error: err.message });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
if (!req.file) throw new Error('未提供文件');
|
|
97
|
+
const targetDir = getSecurePath(req.body.path || '');
|
|
98
|
+
const targetPath = path.join(targetDir, req.file.originalname);
|
|
99
|
+
await fs.promises.rename(req.file.path, targetPath);
|
|
100
|
+
res.json({ success: true });
|
|
101
|
+
} catch (e) {
|
|
102
|
+
res.status(500).json({ error: e.message });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
92
105
|
});
|
|
93
106
|
|
|
94
107
|
io.on('connection', (socket) => {
|