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
 
root / src / builder / b-artifacts.c
Author[]Andy Green <andy@warmcat.com> 2026-04-15 04:14 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-04-15 18:23 UTC
Tree1223d778450ec555d44f5561949de34e40923585   Raw Patch
 
js: extend ANSI control handling
js: extend ANSI control handling
diff --git a/assets/sai.js b/assets/sai.js index dd78704..8a162ec 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -706,32 +706,40 @@ function ansiToHtml(text, state) { state = state || {}; let currentClasses = new Set(state.classes || []); - const parts = text.split(/(\u001b\[[\d;]*m)/); + const parts = text.split(/(\u001b\[[0-9:;<=>?]*[ -/]*[@-~])/); 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 (part.endsWith('m')) { + const codes = part.substring(2, part.length - 1).split(';'); + + for (let code of codes) { + if (code === '0' || code === '') { + currentClasses.clear(); + continue; + } + + /* handle leading zeros like 01 */ + if (!classMap[code] && code.startsWith('0')) + code = code.substring(1); - 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) { + const icode = parseInt(code, 10); + if (icode >= 30 && icode <= 37) { currentClasses.forEach(c => { if (c.startsWith('ansi-fg-')) currentClasses.delete(c); }); } - if (code >= 40 && code <= 47) { + if (icode >= 40 && icode <= 47) { currentClasses.forEach(c => { if (c.startsWith('ansi-bg-')) currentClasses.delete(c); }); } currentClasses.add(classMap[code]); } } } + // Non-m sequences are just stripped (handled by the split and ignored here) } else { // It's plain text const sanitizedPart = hsanitize(part); if (currentClasses.size > 0) { @@ -1443,7 +1451,7 @@ function createBuilderDiv(plat) { platDiv.className += " powering-down"; platDiv.id = "binfo-" + plat.name; - platDiv.title = plat.platform + "@" + plat.name.split('.')[0] + " / " + plat.peer_ip; + platDiv.title = plat.platform + "@" + plat.name.split('.')[0] + (authd && auth_is_admin && plat.peer_ip ? " / " + plat.peer_ip : ""); let plat_parts = plat.platform.split('/'); let plat_os = plat_parts[0] || 'generic'; @@ -2528,11 +2536,45 @@ function ws_open_sai() case "com-warmcat-sai-logs": var s1; try { - s1 = decodeURIComponent(escape(atob(jso.log))); + var binString = atob(jso.log); + if (window.TextDecoder) { + window._sai_text_decoder = window._sai_text_decoder || new TextDecoder("utf-8"); + var bytes = new Uint8Array(binString.length); + for (var i = 0; i < binString.length; i++) { + bytes[i] = binString.charCodeAt(i); + } + s1 = window._sai_text_decoder.decode(bytes, {stream: true}); + } else { + s1 = decodeURIComponent(escape(binString)); + } } catch (e) { + console.log("decode err", e); break; } + if (window._sai_ansi_buffer) { + s1 = window._sai_ansi_buffer + s1; + window._sai_ansi_buffer = ""; + } + + var last_esc = s1.lastIndexOf('\u001b'); + if (last_esc >= 0) { + var tail = s1.substring(last_esc); + var is_complete = true; + if (tail.startsWith('\u001b[')) { + is_complete = /(\u001b\[[0-9:;<=>?]*[ -/]*[@-~])/.test(tail); + } else if (tail.startsWith('\u001b]')) { + is_complete = tail.indexOf('\x07') !== -1 || tail.indexOf('\u001b\\', 1) !== -1; + } else if (tail === '\u001b') { + is_complete = false; + } + + if (!is_complete && tail.length < 512) { + window._sai_ansi_buffer = tail; + s1 = s1.substring(0, last_esc); + } + } + if (!tfirst) tfirst = jso.timestamp; last_log_timestamp = jso.timestamp;
Page fetched 0s ago, creation time: 10ms (vhost etag hits: 0%, cache hits: 0%)