/* Read-only presentation of the existing local RX/TX logs. */ ((root)=>{ const quiet=/^(HEARTBEAT|TIMESYNC|LINK_PROBE|HEAVY_PROBE|GCS)/; function parse(line){ const match=String(line).match(/^(\S+) \[([^\]]+)\] ([\s\S]*)$/); if(!match)return {time:'',type:'Message',payload:String(line),raw:String(line),tone:'normal',quiet:false}; const [,timestamp,category,message]=match; let payload=message,type=category.replace(/^(RX|TX)_?/,'')||category; try{payload=JSON.parse(message);type=payload.mavpackettype||type}catch(_){} const tone=/BAD_DATA|ERROR|TIMEOUT|FAIL/.test(category)||type==='STATUSTEXT'&&Number(payload.severity)<=4?'warning':/COMMAND|ACK|PARAM_SET|OPERATOR/.test(category+' '+type)?'command':'normal'; const time=timestamp.match(/T(\d{2}:\d{2}:\d{2}(?:\.\d+)?)/)?.[1]||timestamp; const pairs=typeof payload==='string'?[...payload.matchAll(/(?:^|\s)([a-zA-Z_]\w*)=([^\s]+)/g)]:[]; const fields=pairs.length?Object.fromEntries(pairs.map(([,key,value])=>[key,value])):payload; return {time,type,payload,fields,raw:String(line),tone,quiet:tone!=='warning'&&(quiet.test(type)||/LINK_PROBE|HEAVY_PROBE|HEARTBEAT/.test(category))}; } function valueText(value){ if(typeof value==='number')return Number.isInteger(value)?String(value):Number(value.toFixed(3)).toString(); if(Array.isArray(value))return value.slice(0,6).map(valueText).join(', ')+(value.length>6?'…':''); return typeof value==='object'?JSON.stringify(value):String(value); } function makeRow(row,previous){ if(previous&&previous.dataset.key===row.raw&&previous.dataset.tone===row.tone)return previous; const details=previous||document.createElement('details'); details.className='console-row';details.dataset.key=row.raw;details.dataset.tone=row.tone; let summary=details.querySelector('summary'),time,body,type,fields,raw; if(!summary){ details.replaceChildren();summary=document.createElement('summary');time=document.createElement('time'); body=document.createElement('span');body.className='console-message';type=document.createElement('strong'); fields=document.createElement('span');fields.className='console-fields';body.append(type,fields);summary.append(time,body); raw=document.createElement('pre');details.append(summary,raw); }else{ time=summary.querySelector('time');body=summary.querySelector('.console-message'); type=body.querySelector('strong');fields=body.querySelector('.console-fields');raw=details.querySelector('pre'); } time.textContent=row.time;type.textContent=row.type;fields.replaceChildren(); const values=row.fields??row.payload; if(typeof values==='object'&&values!==null){ for(const [key,value] of Object.entries(values).filter(([key])=>key!=='mavpackettype').slice(0,6)){ const item=document.createElement('span'),label=document.createElement('i'),text=document.createElement('b'); label.textContent=key+' ';text.textContent=valueText(value);item.append(label,text);fields.append(item); } }else fields.textContent=row.payload; raw.textContent=typeof row.payload==='object'?JSON.stringify(row.payload,null,2):row.raw; return details; } function render(host,lines,filter){ const atBottom=host.scrollHeight-host.scrollTop-host.clientHeight<36,scroll=host.scrollTop; const rows=lines.map(parse).filter(row=>filter==='all'||!row.quiet); const existing=new Map(); for(const node of host.querySelectorAll('details.console-row')){ const key=node.dataset.key,list=existing.get(key)||[];list.push(node);existing.set(key,list); } if(!rows.length){ if(!host.querySelector('.console-empty')){ const empty=document.createElement('p');empty.className='console-empty'; empty.textContent='No messages'; empty.title=filter==='all'?'Waiting for session messages.':'Routine heartbeat and probe traffic is hidden. Choose All traffic to see it.'; host.replaceChildren(empty); } return; } const wanted=rows.map(row=>makeRow(row,existing.get(row.raw)?.shift()||null)); let cursor=host.firstElementChild; for(const node of wanted){if(node!==cursor)host.insertBefore(node,cursor);cursor=node.nextElementSibling} while(cursor){const next=cursor.nextElementSibling;cursor.remove();cursor=next} if(atBottom&&!host.querySelector('details[open]'))host.scrollTop=host.scrollHeight;else host.scrollTop=scroll; } root.ConsoleView={parse,render}; if(typeof module!=='undefined')module.exports=root.ConsoleView; })(typeof window!=='undefined'?window:globalThis);