Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
    npro  
 Modern all-safe Rust Network Protocol library supporting h1, h2, h3, ws, wt sans-IO and with socket IO + tls
git clone https://npro.rs/repo/npro
Author[]google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.c...> 2025-08-23 19:20 UTC
Committer[]Andy Green <andy@warmcat.com> 2025-08-23 19:20 UTC
Tree57edfc0a356e6df6645aaedbea91f2bfc911f3b8   Raw Patch
 
feat: Add ANSI color support to log viewer
feat: Add ANSI color support to log viewer
diff --git a/assets/sai.css b/assets/sai.css index e985090..c2f5b1d 100644 --- a/assets/sai.css +++ b/assets/sai.css @@ -919,3 +919,35 @@ img.branch { background-color: #f44336; /* red */ height: 10px; } + +/* ANSI Color Support */ +.ansi-bold { font-weight: bold; } +.ansi-underline { text-decoration: underline; } + +.ansi-fg-black { color: #000; } +.ansi-fg-red { color: #A00; } +.ansi-fg-green { color: #0A0; } +.ansi-fg-yellow { color: #A50; } +.ansi-fg-blue { color: #00A; } +.ansi-fg-magenta { color: #A0A; } +.ansi-fg-cyan { color: #0AA; } +.ansi-fg-white { color: #AAA; } + +/* Bright colors for bold text */ +.ansi-bold .ansi-fg-black { color: #555; } +.ansi-bold .ansi-fg-red { color: #F55; } +.ansi-bold .ansi-fg-green { color: #5F5; } +.ansi-bold .ansi-fg-yellow { color: #FF5; } +.ansi-bold .ansi-fg-blue { color: #55F; } +.ansi-bold .ansi-fg-magenta { color: #F5F; } +.ansi-bold .ansi-fg-cyan { color: #5FF; } +.ansi-bold .ansi-fg-white { color: #FFF; } + +.ansi-bg-black { background-color: #000; } +.ansi-bg-red { background-color: #A00; } +.ansi-bg-green { background-color: #0A0; } +.ansi-bg-yellow { background-color: #A50; } +.ansi-bg-blue { background-color: #00A; } +.ansi-bg-magenta { background-color: #A0A; } +.ansi-bg-cyan { background-color: #0AA; } +.ansi-bg-white { background-color: #AAA; } diff --git a/assets/sai.js b/assets/sai.js index c6681f4..8bc7959 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -395,6 +395,7 @@ var lang_zhs = "{" + "}}"; var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, exptimer, auth_user = "", + logAnsiState = {}, ongoing_task_activities = {}, last_log_timestamp = 0; function update_task_activities() { @@ -439,6 +440,59 @@ function humanize(s) return s; } +function ansiToHtml(text, state) { + const classMap = { + '1': 'ansi-bold', '4': 'ansi-underline', + '30': 'ansi-fg-black', '31': 'ansi-fg-red', '32': 'ansi-fg-green', '33': 'ansi-fg-yellow', '34': 'ansi-fg-blue', '35': 'ansi-fg-magenta', '36': 'ansi-fg-cyan', '37': 'ansi-fg-white', + '40': 'ansi-bg-black', '41': 'ansi-bg-red', '42': 'ansi-bg-green', '43': 'ansi-bg-yellow', '44': 'ansi-bg-blue', '45': 'ansi-bg-magenta', '46': 'ansi-bg-cyan', '47': 'ansi-bg-white', + }; + + // Ensure state is a valid object + state = state || {}; + let currentClasses = new Set(state.classes || []); + + const parts = text.split(/(\u001b\[[\d;]*m)/); + let html = ''; + + for (const part of parts) { + if (!part) continue; + + if (part.startsWith('\u001b[')) { // It's an ANSI code + const codes = part.substring(2, part.length - 1).split(';'); + + if (codes.length === 1 && (codes[0] === '0' || codes[0] === '')) { + // Reset + currentClasses.clear(); + } else { + for (const code of codes) { + if (classMap[code]) { + // Handle foreground/background colors: remove old before adding new + if (code >= 30 && code <= 37) { + currentClasses.forEach(c => { if (c.startsWith('ansi-fg-')) currentClasses.delete(c); }); + } + if (code >= 40 && code <= 47) { + currentClasses.forEach(c => { if (c.startsWith('ansi-bg-')) currentClasses.delete(c); }); + } + currentClasses.add(classMap[code]); + } + } + } + } else { // It's plain text + const sanitizedPart = hsanitize(part); + if (currentClasses.size > 0) { + html += `<span class="${Array.from(currentClasses).join(' ')}">${sanitizedPart}</span>`; + } else { + html += sanitizedPart; + } + } + } + + return { + html: html, + newState: { classes: Array.from(currentClasses) } + }; +} + function hsanitize(s) { var table = { @@ -1523,6 +1577,7 @@ function ws_open_sai() document.getElementById("dlogst").innerHTML = ""; document.getElementById("logs").innerHTML = ""; lines = times = logs = ""; + logAnsiState = {}; tfirst = 0; lli = 1; last_log_timestamp = 0; @@ -1648,8 +1703,11 @@ function ws_open_sai() case "com-warmcat-sai-logs": try { - var s1 = decodeURIComponent(escape(atob(jso.log))), s = hsanitize(s1), li, - en = "", yo, dh, ce, tn = ""; + var s1 = decodeURIComponent(escape(atob(jso.log))), + ansiResult = ansiToHtml(s1, logAnsiState), + s = ansiResult.html, li, + en = "", yo, dh, ce, tn = ""; + logAnsiState = ansiResult.newState; } catch (e) { break; }
Page fetched 0s ago, creation time: 4ms (vhost etag hits: 0%, cache hits: 0%)