Add status progress for batch tool operations

This commit is contained in:
2026-05-29 13:40:17 +08:00
parent 5625d94106
commit 80f155dfe8
14 changed files with 307 additions and 65 deletions

View File

@@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"fmt"
"io/fs"
"net/http"
"os"
@@ -89,6 +90,24 @@ function addPaths(paths) {
selectedPage = Math.max(0, Math.ceil(pickedFiles.length / PAGE_SIZE) - 1);
renderSelection();
}
async function readJSONStream(response, handle) {
if (!response.ok || !response.body) throw new Error('Request failed.');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let newline;
while ((newline = buffer.indexOf('\n')) >= 0) {
const line = buffer.slice(0, newline);
buffer = buffer.slice(newline + 1);
if (line.trim()) handle(JSON.parse(line));
}
}
if (buffer.trim()) handle(JSON.parse(buffer));
}
renderSelection();
setupDropZone('dropZone', statusEl, (j) => {
addPaths(j.paths || []);
@@ -119,8 +138,25 @@ document.getElementById('scan').addEventListener('click', async () => {
resultsEl.innerHTML = '';
if (!pickedFiles.length) { statusEl.classList.remove('is-busy'); statusEl.textContent = 'Pick files first.'; return; }
const payload = { files: pickedFiles };
const r = await fetch('/api/base64/scan', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) });
const j = await r.json();
let j = null;
try {
const r = await fetch('/api/base64/scan', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) });
await readJSONStream(r, (msg) => {
if (msg.type === 'status') {
statusEl.textContent = msg.message;
statusEl.classList.add('is-busy');
} else if (msg.type === 'done') {
j = msg;
} else if (msg.type === 'error') {
j = { error: msg.message };
}
});
} catch (e) {
statusEl.classList.remove('is-busy');
statusEl.textContent = 'Error: ' + (e.message || e);
return;
}
if (!j) { statusEl.classList.remove('is-busy'); statusEl.textContent = 'Error: No response.'; return; }
if (j.error) { statusEl.classList.remove('is-busy'); statusEl.textContent = 'Error: ' + j.error; return; }
const total = j.results.length;
const flagged = j.results.filter(r => !r.ok).length;
@@ -168,12 +204,13 @@ type scanResult struct {
}
func Base64Scan(w http.ResponseWriter, r *http.Request) {
send := newJSONStream(w)
var req struct {
Folder string `json:"folder"`
Files []string `json:"files"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, map[string]any{"results": []scanResult{}, "error": err.Error()})
send(map[string]any{"type": "error", "message": err.Error()})
return
}
@@ -193,12 +230,13 @@ func Base64Scan(w http.ResponseWriter, r *http.Request) {
targets = collectHTMLFiles(req.Folder)
baseDir = req.Folder
} else {
writeJSON(w, map[string]any{"results": []scanResult{}, "error": "Pick a folder or files first"})
send(map[string]any{"type": "error", "message": "Pick a folder or files first"})
return
}
results := []scanResult{}
for _, file := range targets {
for i, file := range targets {
send(map[string]any{"type": "status", "message": fmt.Sprintf("Scanning %d/%d %s", i+1, len(targets), filepath.Base(file))})
data, err := os.ReadFile(file)
if err != nil {
continue
@@ -219,7 +257,7 @@ func Base64Scan(w http.ResponseWriter, r *http.Request) {
Assets: offenders,
})
}
writeJSON(w, map[string]any{"results": results})
send(map[string]any{"type": "done", "results": results})
}
func commonBaseDir(files []string) string {