From 8c3df90a877e9af6798bf4697b4e9765c3f43b5e Mon Sep 17 00:00:00 2001 From: Epifnne Date: Mon, 3 Aug 2026 17:54:36 +0800 Subject: [PATCH] fix runid --- ControlPanel/electron-main.js | 4 ++- ControlPanel/electron-ui/index.html | 2 +- ControlPanel/electron-ui/renderer.js | 19 ++++++++++-- ControlPanel/electron-ui/styles.css | 8 +++++ server/src/app.js | 45 ++++++++++++++++++++++++---- server/test/server.test.js | 3 ++ 6 files changed, 71 insertions(+), 10 deletions(-) diff --git a/ControlPanel/electron-main.js b/ControlPanel/electron-main.js index f05884e..d3d9524 100644 --- a/ControlPanel/electron-main.js +++ b/ControlPanel/electron-main.js @@ -86,6 +86,7 @@ function startPanelInboxPoller(window) { filePath: imagePath, dataUrl: toDataUrl(imagePath), fileName: pending.fileName, + runId: pending.runId || pending.fileName, mediaType: pending.mediaType, uploadTime: pending.uploadTime, deviceId: requestContext.deviceId, @@ -95,7 +96,7 @@ function startPanelInboxPoller(window) { pendingReview = { deviceId: requestContext.deviceId, fileID: pending.fileID, - runId: pending.fileName, + runId: pending.runId || pending.fileName, credentials: requestContext }; } else { @@ -378,6 +379,7 @@ function registerHandlers() { filePath: imagePath, dataUrl: toDataUrl(imagePath), fileName: download.fileName || request.fileName, + runId: download.runId || request.runId || request.fileName, mediaType, uploadTime: download.uploadTime || request.uploadTime, deviceId: request.deviceId diff --git a/ControlPanel/electron-ui/index.html b/ControlPanel/electron-ui/index.html index ce61a74..682ef1c 100644 --- a/ControlPanel/electron-ui/index.html +++ b/ControlPanel/electron-ui/index.html @@ -111,7 +111,7 @@
-
上传时间文件名类型大小状态操作
+
上传时间文件名runId类型大小状态操作

选择公司和产线后刷新辨识数据

diff --git a/ControlPanel/electron-ui/renderer.js b/ControlPanel/electron-ui/renderer.js index 4245554..5481690 100644 --- a/ControlPanel/electron-ui/renderer.js +++ b/ControlPanel/electron-ui/renderer.js @@ -305,6 +305,7 @@ async function refreshIdentificationFiles() { state.identificationFiles = result.files || result.fileList || []; elements.identificationFileList.innerHTML = state.identificationFiles.map((file) => ` ${escapeHtml(file.uploadTime || "-")}${escapeHtml(file.fileName)} + ${escapeHtml(file.runId || "-")} ${file.mediaType === "json" ? "行程 JSON" : "辨识 CSV"}${formatSize(file.size)} ${file.status === "processed" ? "已处理" : "待处理"} @@ -457,8 +458,9 @@ function showImage(result) { plot.showImage.disabled = false; plot.openImage.disabled = false; if (mediaType === "csv" && result.reviewable) { - state.review = { runId: result.fileName, deviceId: result.deviceId }; - elements.reviewTarget.textContent = result.fileName; + const runId = result.runId || result.fileName; + state.review = { runId, deviceId: result.deviceId }; + elements.reviewTarget.textContent = `${result.fileName} (runId: ${runId})`; elements.approveReview.disabled = false; elements.rejectReview.disabled = false; } @@ -954,6 +956,12 @@ elements.identificationFileList.addEventListener("click", async (event) => { if (result) { showImage(result); activateTab("plot-panel"); + if ((file.mediaType !== "json") && file.runId) { + state.review = { runId: file.runId, deviceId: elements.deviceId.value }; + elements.reviewTarget.textContent = `${result.fileName} (runId: ${file.runId})`; + elements.approveReview.disabled = false; + elements.rejectReview.disabled = false; + } } } else if (event.target.dataset.identificationDownload) { const result = await runBusy("正在保存辨识原始数据", () => window.reinloop.downloadIdentificationFile({ @@ -1146,7 +1154,12 @@ function renderNotifications() { ? "查看结果" : "查看绘图"; const secondaryAction = notification.type === "identification_result_ready" ? "查看辨识数据" : ""; - return `
${escapeHtml(notification.title)}${escapeHtml(notification.message)}
${secondaryAction ? `` : ""}
`; + const traces = [ + notification.runId ? `runId: ${notification.runId}` : "", + notification.fileID ? `fileID: ${notification.fileID}` : "", + notification.requestId ? `requestId: ${notification.requestId}` : "" + ].filter(Boolean).join(" | "); + return `
${escapeHtml(notification.title)}${escapeHtml(notification.message)}${traces ? `${escapeHtml(traces)}` : ""}
${secondaryAction ? `` : ""}
`; }).join(""); } diff --git a/ControlPanel/electron-ui/styles.css b/ControlPanel/electron-ui/styles.css index dfcc0d5..764d78f 100644 --- a/ControlPanel/electron-ui/styles.css +++ b/ControlPanel/electron-ui/styles.css @@ -197,6 +197,14 @@ input:focus, select:focus, textarea:focus { border-color: var(--green); box-shad .notification-copy { display: grid; gap: 3px; min-width: 0; } .notification-copy strong { font-size: 13px; } .notification-copy span { color: var(--muted); font-size: 12px; overflow-wrap: anywhere; } +.notification-copy .notification-trace { + display: block; + margin-top: 4px; + color: var(--text-muted); + font-size: 12px; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; + word-break: break-all; +} .notification-actions { display: flex; gap: 8px; } .editor-layout { display: grid; grid-template-columns: minmax(0, 1fr) 270px; gap: 18px; } .editor-column, .publish-aside { background: var(--surface); border: 1px solid var(--line); } diff --git a/server/src/app.js b/server/src/app.js index 290bfee..c58b88b 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -496,6 +496,39 @@ function createApp({ return notification; } + function findIdentificationFeedbackRecord(database, deviceId, fileName) { + if (!deviceId || !fileName) return null; + const normalizedFileName = String(fileName); + return database.identificationFeedback.find((item) => + item.deviceId === deviceId + && (item.fileName === normalizedFileName || item.runId === normalizedFileName) + ) || null; + } + + function resolveIdentificationRunId(database, record) { + if (!record) return null; + if (record.runId) return String(record.runId); + const feedback = findIdentificationFeedbackRecord(database, record.deviceId, record.fileName); + if (feedback) return String(feedback.runId); + if (record.fileName) return String(record.fileName); + return null; + } + + function enrichIdentificationFile(database, record) { + const runId = resolveIdentificationRunId(database, record); + return runId ? { ...record, runId } : { ...record }; + } + + function enrichNotification(database, notification) { + if (!notification || notification.type !== "identification_result_ready" || !notification.fileID) { + return notification; + } + const related = database.identificationFiles.find((item) => item.fileID === notification.fileID) + || database.panelInbox.find((item) => item.fileID === notification.fileID); + const runId = resolveIdentificationRunId(database, related); + return runId ? { ...notification, runId } : notification; + } + async function purgeExpiredIdentificationFiles() { return store.update(async (database) => { backfillIdentificationFiles(database); @@ -901,16 +934,16 @@ function createApp({ const message = database.panelInbox.find((item) => { if (item.deviceId !== deviceId) return false; if (item.mediaType !== "csv") return true; - return database.identificationFeedback.some((feedback) => - feedback.deviceId === deviceId && feedback.runId === item.fileName - ); + return Boolean(findIdentificationFeedbackRecord(database, deviceId, item.fileName)); }); if (!message) return { success: true, pending: false }; + const runId = resolveIdentificationRunId(database, message); return { success: true, pending: true, fileID: message.fileID, fileName: message.fileName, + runId, mediaType: message.mediaType, uploadTime: message.uploadTime, url: issueDownloadUrl(req, message.fileID) @@ -923,7 +956,7 @@ function createApp({ const database = await store.read(); const notification = database.panelNotifications.find((item) => item.deviceId === deviceId); return notification - ? { success: true, pending: true, notification } + ? { success: true, pending: true, notification: enrichNotification(database, notification) } : { success: true, pending: false }; } case "ackPanelNotification": { @@ -988,7 +1021,7 @@ function createApp({ const offset = (page - 1) * pageSize; return { success: true, - files: files.slice(offset, offset + pageSize), + files: files.slice(offset, offset + pageSize).map((record) => enrichIdentificationFile(database, record)), total: files.length, page, pageSize @@ -1065,10 +1098,12 @@ function createApp({ if (!historyRecord || !fileRecord || !store.resolveStoredFile(fileRecord.fileID)) { return { success: false, errMsg: "辨识文件不存在" }; } + const runId = resolveIdentificationRunId(database, historyRecord); return { success: true, fileID: fileRecord.fileID, fileName: fileRecord.fileName, + runId, mediaType: historyRecord.mediaType, url: issueDownloadUrl(req, fileRecord.fileID) }; diff --git a/server/test/server.test.js b/server/test/server.test.js index 6d1fb74..c7eaa8f 100644 --- a/server/test/server.test.js +++ b/server/test/server.test.js @@ -294,6 +294,7 @@ test("panel consumes uploaded device files from an inbox without scanning folder }); assert.equal(pending.pending, true); assert.equal(pending.fileName, "result_20260724_120000.csv"); + assert.equal(pending.runId, "result_20260724_120000.csv"); assert.equal(await (await fetch(pending.url)).text(), "t,u,p\n0,10,20\n"); assert.equal((await post({ @@ -328,6 +329,7 @@ test("panel consumes uploaded device files from an inbox without scanning folder }); assert.equal(history.total, 2); const csvHistory = history.files.find((file) => file.fileID === pending.fileID); + assert.equal(csvHistory.runId, "result_20260724_120000.csv"); assert.equal(csvHistory.status, "processed"); assert.ok(csvHistory.processedAt); assert.ok(csvHistory.expiresAt); @@ -575,6 +577,7 @@ test("panel notifications and volume configuration stay isolated by device", asy type: "getPendingPanelNotification", deviceId, adminToken: "test-token" }); assert.equal(identificationResult.notification.type, "identification_result_ready"); + assert.equal(identificationResult.notification.runId, "identification_result.json"); }); test("admin manages companies and production lines with a stable device id", async () => {