Refine reclass explorer detail
Browse files- backend/app.py +15 -2
- frontend/app.js +10 -7
- frontend/style.css +0 -16
backend/app.py
CHANGED
|
@@ -1400,6 +1400,12 @@ async def start_reclass(req: ReclassRequest, bg: BackgroundTasks):
|
|
| 1400 |
if not targets:
|
| 1401 |
await finish_reclass_job(job_id, 0, [], 0, allowed)
|
| 1402 |
return {"id": job_id, "status": "empty"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1403 |
bg.add_task(bg_reclass_task, job_id, targets, allowed, PHPSESSID)
|
| 1404 |
return {"id": job_id, "status": "started", "total": len(targets)}
|
| 1405 |
|
|
@@ -1420,17 +1426,24 @@ async def get_reclass(job_id: str):
|
|
| 1420 |
return {"error": "not found"}
|
| 1421 |
row = rows[0]
|
| 1422 |
summary = parse_json_maybe(cell_value(row, 6, "{}")) or {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1423 |
return {
|
| 1424 |
"id": cell_value(row, 0, ""),
|
| 1425 |
"kind": "reclass",
|
| 1426 |
"post_ids": json.loads(cell_value(row, 1, "[]")),
|
| 1427 |
"filters": json.loads(cell_value(row, 2, "[]")),
|
| 1428 |
-
"total":
|
| 1429 |
-
"scanned":
|
| 1430 |
"changed": int(cell_value(row, 5, "0")),
|
| 1431 |
"summary": summary,
|
| 1432 |
"log": decrypt_log(cell_value(row, 7, "")),
|
| 1433 |
"created_at": int(cell_value(row, 8, "0")),
|
|
|
|
| 1434 |
}
|
| 1435 |
|
| 1436 |
|
|
|
|
| 1400 |
if not targets:
|
| 1401 |
await finish_reclass_job(job_id, 0, [], 0, allowed)
|
| 1402 |
return {"id": job_id, "status": "empty"}
|
| 1403 |
+
ACTIVE_TASKS[job_id] = {
|
| 1404 |
+
"type": "reclass",
|
| 1405 |
+
"phase": "queued",
|
| 1406 |
+
"total": len(targets),
|
| 1407 |
+
"done": 0,
|
| 1408 |
+
}
|
| 1409 |
bg.add_task(bg_reclass_task, job_id, targets, allowed, PHPSESSID)
|
| 1410 |
return {"id": job_id, "status": "started", "total": len(targets)}
|
| 1411 |
|
|
|
|
| 1426 |
return {"error": "not found"}
|
| 1427 |
row = rows[0]
|
| 1428 |
summary = parse_json_maybe(cell_value(row, 6, "{}")) or {}
|
| 1429 |
+
active = ACTIVE_TASKS.get(job_id)
|
| 1430 |
+
total = int(cell_value(row, 3, "0"))
|
| 1431 |
+
scanned = int(cell_value(row, 4, "0"))
|
| 1432 |
+
if active:
|
| 1433 |
+
total = int(active.get("total") or total)
|
| 1434 |
+
scanned = int(active.get("done") or scanned)
|
| 1435 |
return {
|
| 1436 |
"id": cell_value(row, 0, ""),
|
| 1437 |
"kind": "reclass",
|
| 1438 |
"post_ids": json.loads(cell_value(row, 1, "[]")),
|
| 1439 |
"filters": json.loads(cell_value(row, 2, "[]")),
|
| 1440 |
+
"total": total,
|
| 1441 |
+
"scanned": scanned,
|
| 1442 |
"changed": int(cell_value(row, 5, "0")),
|
| 1443 |
"summary": summary,
|
| 1444 |
"log": decrypt_log(cell_value(row, 7, "")),
|
| 1445 |
"created_at": int(cell_value(row, 8, "0")),
|
| 1446 |
+
"active": active,
|
| 1447 |
}
|
| 1448 |
|
| 1449 |
|
frontend/app.js
CHANGED
|
@@ -16,6 +16,7 @@ let explorerDebounce = null
|
|
| 16 |
let submittedSearchId = null
|
| 17 |
let submittedFilterKey = null
|
| 18 |
let resultCardCache = new Map()
|
|
|
|
| 19 |
|
| 20 |
|
| 21 |
$$(".tab").forEach(tab => {
|
|
@@ -116,11 +117,13 @@ function route() {
|
|
| 116 |
const id = decodeURIComponent(parts[1])
|
| 117 |
openSearch(id, parseInt(params.get("page")) || 1, submittedSearchId === id)
|
| 118 |
} else {
|
|
|
|
| 119 |
resetSubmittedSearch()
|
| 120 |
loadSearches(parseInt(params.get("page")) || 1)
|
| 121 |
openExplorerEvents()
|
| 122 |
}
|
| 123 |
} else {
|
|
|
|
| 124 |
closeExplorerEvents()
|
| 125 |
}
|
| 126 |
}
|
|
@@ -325,6 +328,7 @@ async function openSearch(id, page = 1, show = false) {
|
|
| 325 |
$("#detail-stats").textContent = ""
|
| 326 |
$("#pager").innerHTML = ""
|
| 327 |
$("#results-grid").classList.remove("log-grid")
|
|
|
|
| 328 |
if (!show) $("#results-grid").replaceChildren()
|
| 329 |
$("#btn-back").onclick = () => { location.hash = "#/explorer" }
|
| 330 |
const scanBtn = $("#btn-scan")
|
|
@@ -418,8 +422,8 @@ function renderReclassDetail(data) {
|
|
| 418 |
$("#pager").innerHTML = ""
|
| 419 |
$("#btn-scan").style.display = "none"
|
| 420 |
const filters = (data.summary.filters || data.filters.map(code => CATEGORY_NAMES[code] || code)).join(", ")
|
| 421 |
-
|
| 422 |
-
|
| 423 |
const log = data.log.length
|
| 424 |
? data.log.map(item => `<div class="log-row">
|
| 425 |
<span>${esc(item.post_id)}</span>
|
|
@@ -438,12 +442,11 @@ function renderReclassDetail(data) {
|
|
| 438 |
<h2>Detailed log</h2>
|
| 439 |
<div class="log-head"><span>Post ID</span><span>From</span><span>To</span></div>
|
| 440 |
<div class="log-list">${log}</div>
|
| 441 |
-
<h2>Post IDs</h2>
|
| 442 |
-
<div class="post-id-list">${postIds}</div>
|
| 443 |
</div>`
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
|
|
|
| 447 |
}
|
| 448 |
|
| 449 |
function pageSuffix(url) {
|
|
|
|
| 16 |
let submittedSearchId = null
|
| 17 |
let submittedFilterKey = null
|
| 18 |
let resultCardCache = new Map()
|
| 19 |
+
let reclassDetailTimer = null
|
| 20 |
|
| 21 |
|
| 22 |
$$(".tab").forEach(tab => {
|
|
|
|
| 117 |
const id = decodeURIComponent(parts[1])
|
| 118 |
openSearch(id, parseInt(params.get("page")) || 1, submittedSearchId === id)
|
| 119 |
} else {
|
| 120 |
+
clearTimeout(reclassDetailTimer)
|
| 121 |
resetSubmittedSearch()
|
| 122 |
loadSearches(parseInt(params.get("page")) || 1)
|
| 123 |
openExplorerEvents()
|
| 124 |
}
|
| 125 |
} else {
|
| 126 |
+
clearTimeout(reclassDetailTimer)
|
| 127 |
closeExplorerEvents()
|
| 128 |
}
|
| 129 |
}
|
|
|
|
| 328 |
$("#detail-stats").textContent = ""
|
| 329 |
$("#pager").innerHTML = ""
|
| 330 |
$("#results-grid").classList.remove("log-grid")
|
| 331 |
+
clearTimeout(reclassDetailTimer)
|
| 332 |
if (!show) $("#results-grid").replaceChildren()
|
| 333 |
$("#btn-back").onclick = () => { location.hash = "#/explorer" }
|
| 334 |
const scanBtn = $("#btn-scan")
|
|
|
|
| 422 |
$("#pager").innerHTML = ""
|
| 423 |
$("#btn-scan").style.display = "none"
|
| 424 |
const filters = (data.summary.filters || data.filters.map(code => CATEGORY_NAMES[code] || code)).join(", ")
|
| 425 |
+
const phase = data.active ? ` | ${data.active.phase}` : ""
|
| 426 |
+
$("#detail-stats").textContent = `${data.changed}/${data.total} changed | ${data.scanned}/${data.total} scanned | ${filters}${phase}`
|
| 427 |
const log = data.log.length
|
| 428 |
? data.log.map(item => `<div class="log-row">
|
| 429 |
<span>${esc(item.post_id)}</span>
|
|
|
|
| 442 |
<h2>Detailed log</h2>
|
| 443 |
<div class="log-head"><span>Post ID</span><span>From</span><span>To</span></div>
|
| 444 |
<div class="log-list">${log}</div>
|
|
|
|
|
|
|
| 445 |
</div>`
|
| 446 |
+
if (data.active) {
|
| 447 |
+
clearTimeout(reclassDetailTimer)
|
| 448 |
+
reclassDetailTimer = setTimeout(() => openSearch(data.id, 1, true), 2500)
|
| 449 |
+
}
|
| 450 |
}
|
| 451 |
|
| 452 |
function pageSuffix(url) {
|
frontend/style.css
CHANGED
|
@@ -344,22 +344,6 @@ input:focus, select:focus { outline: none; border-color: var(--accent); backgrou
|
|
| 344 |
border: 1px solid var(--line);
|
| 345 |
border-radius: 6px;
|
| 346 |
}
|
| 347 |
-
.post-id-list {
|
| 348 |
-
display: flex;
|
| 349 |
-
flex-wrap: wrap;
|
| 350 |
-
gap: .4rem;
|
| 351 |
-
}
|
| 352 |
-
.post-id {
|
| 353 |
-
height: 30px;
|
| 354 |
-
padding: 0 .55rem;
|
| 355 |
-
background: #0b0d0f;
|
| 356 |
-
border: 1px solid var(--line);
|
| 357 |
-
border-radius: 6px;
|
| 358 |
-
color: var(--accent);
|
| 359 |
-
cursor: pointer;
|
| 360 |
-
font-family: Consolas, "SFMono-Regular", monospace;
|
| 361 |
-
}
|
| 362 |
-
.post-id:hover { border-color: var(--accent); }
|
| 363 |
.result-card {
|
| 364 |
position: relative;
|
| 365 |
min-width: 0;
|
|
|
|
| 344 |
border: 1px solid var(--line);
|
| 345 |
border-radius: 6px;
|
| 346 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
.result-card {
|
| 348 |
position: relative;
|
| 349 |
min-width: 0;
|