diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts
index 097df9c..6b027ed 100644
--- a/BACKEND/app/services/line_connection.ts
+++ b/BACKEND/app/services/line_connection.ts
@@ -2186,28 +2186,46 @@ Ports Missing/Down: ${missing.length}\n\n`
// Helper function to highlight SNs from listInventory in outputTestLog
const highlightSnInConsoleOutput = (text: string, listInventory: any[] | undefined) => {
if (!text || !listInventory || listInventory.length === 0) {
- return escapeHtml(text || 'No test log available')
+ return escapeHtml(text || 'No test log available');
}
- let result = escapeHtml(text)
- const snList = listInventory.map((item) => item.sn).filter((sn) => sn)
+ // 1. Extract, Deduplicate (Set), filter, and sort SNs
+ const uniqueSns = [...new Set(listInventory.map((item) => item.sn).filter(Boolean))];
+ const snList = uniqueSns.sort((a, b) => b.length - a.length);
- // Sort by length descending to match longest SNs first (avoid partial matches)
- snList.sort((a, b) => b.length - a.length)
+ if (snList.length === 0) {
+ return escapeHtml(text);
+ }
- snList.forEach((sn) => {
- if (sn) {
- // Create a regex that matches the SN as a whole word/token
- const regex = new RegExp(`\\b${sn.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}\\b`, 'g')
- result = result.replace(
- regex,
- `${escapeHtml(sn)}`
- )
- }
- })
+ // 2. Escape regex special chars and combine into a single Regex OR statement
+ const escapedForRegex = snList.map((sn) => sn.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
+ const combinedRegex = new RegExp(`\\b(${escapedForRegex.join('|')})\\b`, 'g');
- return result
- }
+ let result = '';
+ let lastIndex = 0;
+ let match;
+
+ // 3. Single-pass execution over the raw text
+ while ((match = combinedRegex.exec(text)) !== null) {
+ const matchedSn = match[0];
+ const startIndex = match.index;
+
+ // Escape and append the text BEFORE the match
+ result += escapeHtml(text.substring(lastIndex, startIndex));
+
+ // Escape the SN and wrap it in the highlight span
+ const safeSn = escapeHtml(matchedSn);
+ result += `${safeSn}`;
+
+ // Update the index to move forward
+ lastIndex = combinedRegex.lastIndex;
+ }
+
+ // Append any remaining text after the final match
+ result += escapeHtml(text.substring(lastIndex));
+
+ return result;
+ };
// ---- Body: full template mirroring index.html, table-based + inline styles ----
const body = `