Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 67 additions & 10 deletions src/src/Static/WebStaticData.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,12 +341,12 @@ static const char jsClipboardCopyPart2[] PROGMEM = {
// "if(t==null){i= max_loop + 1;"
"if(t==null){i=101;"
"}else{"
"cb+=t.innerHTML.replace(/<[Bb][Rr]\\s*\\/?>/gim,'\\n')+'"
"const visibleLines=Array.from(t.children).filter((e=>null!==e.offsetParent)).map((e=>e.innerHTML));visibleLines.length>0&&(cb+=visibleLines.join('\\n').replace(/<[Bb][Rr]\\s*\\/?>/gim,'\\n')+'"
};

//Fix HTML
static const char jsClipboardCopyPart3[] PROGMEM = {
"';}}"
"');}}"
"cb=cb.replace(/<\\/[Dd][Ii][Vv]\\s*\\/?>/gim,'\\n');"
"cb=cb.replace(/<[^>]*>/gim,'');"
"var ti=document.createElement('textarea');"
Expand Down Expand Up @@ -405,14 +405,71 @@ static const char DATA_UPDATE_SENSOR_VALUES_DEVICE_PAGE_JS[] PROGMEM = {

#ifdef WEBSERVER_INCLUDE_JS
static const char DATA_FETCH_AND_PARSE_LOG_JS[] PROGMEM = {
"function elId(e){return document.getElementById(e)}"
"function getBrowser(){var e,o=navigator.userAgent,r=o.match(/(opera|chrome|safari|firefox|msie|trident(?=\\/))\\/?\\s*(\\d+)/i)||[];return/trident/i.test(r[1])?{name:'IE',version:(e=/\brv[ :]+(\\d+)/g.exec(o)||[])[1]||''}:'Chrome'===r[1]&&null!=(e=o.match(/\bOPR|Edge\\/(\\d+)/))?{name:'Opera',version:e[1]}:(r=r[2]?[r[1],r[2]]:[navigator.appName,navigator.appVersion,'-?'],null!=(e=o.match(/version\\/(\\d+)/i))&&r.splice(1,1,e[1]),{name:r[0],version:r[1]})}"
"var browser=getBrowser(),currentBrowser=browser.name+browser.version;textToDisplay=(browser.name=browser.version<12)?'Error: '+currentBrowser+' is not supported! Please try a modern web browser.':'Fetching log entries...',elId('copyText_1').innerHTML=textToDisplay,loopDeLoop(1e3,0);var logLevel=['Unused','Error','Info','Debug','Debug More','Undefined','Undefined','Undefined','Undefined','Debug Dev'];"
"function loopDeLoop(e,o){let r='copyText_1';isNaN(o)&&(o=1),null==e&&(e=1e3),scrolling_type=e<=500?'auto':'smooth';var n,t,i='',l=0,"
"s=setInterval(function(){if(l>0){clearInterval(s);return}++o>1?l=1:fetch('/logjson').then(function(o){if(200!==o.status){console.log('Looks like there was a problem. Status Code: '+o.status);return}o.json()"
".then(function(o){var l;for(null==t&&(t=''),n=0;n<o.Log.nrEntries;++n)try{l=o.Log.Entries[n].timestamp}catch(a){l=a.name}finally{'TypeError'!==l&&(i=o.Log.Entries[n].timestamp,t+='<div class=level_'+o.Log.Entries[n].level+' id='+i+'><font color=\"gray\">'+o.Log.Entries[n].timestamp+':</font> '+o.Log.Entries[n].text+'</div>')}e=o.Log.TTL,''!==t&&('Fetching log entries...'==elId(r).innerHTML&&(elId(r).innerHTML=''),elId(r).innerHTML+=t),t='',!0==(autoscroll_on=elId('autoscroll').checked)&&''!==i&&elId(i).scrollIntoView({behavior:scrolling_type}),elId('current_loglevel').innerHTML='Logging: '+logLevel[o.Log.SettingsWebLogLevel]+' ('+o.Log.SettingsWebLogLevel+')',clearInterval(s),"
"loopDeLoop(e,0)})}).catch(function(o){elId(r).innerHTML+='<div>>> '+o.message+' <<</div>',autoscroll_on=elId('autoscroll').checked,elId(r).scrollTop=elId(r).scrollHeight,e=5e3,clearInterval(s),"
"loopDeLoop(e,0)}),l=1},e)}"
"function elId(e){return document.getElementById(e)}"
"function getBrowser(){var e,t=navigator.userAgent,o=t.match(/(opera|chrome|safari|firefox|msie|trident(?=\\/))\\/?\\s*(\\d+)/i)||[];"
"return/trident/i.test(o[1])?{name:'IE',version:(e=/\\brv[ :]+(\\d+)/g.exec(t)||[])[1]||''}:"
"'Chrome'===o[1]&&null!=(e=t.match(/\\bOPR|Edge\\/(\\d+)/))?"
"{name:'Opera',version:e[1]}:"
"(o=o[2]?[o[1],o[2]]:[navigator.appName,navigator.appVersion,'-?'],"
"null!=(e=t.match(/version\\/(\\d+)/i))&&o.splice(1,1,e[1]),{name:o[0],version:o[1]})}"
"var browser=getBrowser(),currentBrowser=browser.name+browser.version;"
"textToDisplay=(browser.name=browser.version<12)?"
"'Error: '+currentBrowser+' is not supported! Please try a modern web browser.':"
"'Fetching log entries...';"
"elId('copyText_1').innerHTML=textToDisplay;"
"loopDeLoop(1000,0);"
"var logLevel=['Unused','Error','Info','Debug','Debug More','Undefined','Undefined','Undefined','Undefined','Debug Dev'];"
"function loopDeLoop(e,t){let o='copyText_1';"
"isNaN(t)&&(t=1),null==e&&(e=1000),scrolling_type=e<=500?'auto':'smooth';"
"var r,n,i='',l=0,s=setInterval(function(){"
"if(l>0){clearInterval(s);return}"
"++t>1?l=1:fetch('/logjson').then(function(t){"
"if(200!==t.status){console.log('Looks like there was a problem. Status Code: '+t.status);return}"
"t.json().then(function(t){var l;"
"for(null==n&&(n=''),r=0;r<t.Log.nrEntries;++r)"
"try{l=t.Log.Entries[r].timestamp}"
"catch(a){l=a.name}"
"finally{'TypeError'!==l&&(i=t.Log.Entries[r].timestamp,"
"n+='<div class=level_'+t.Log.Entries[r].level+' id='+i+'><font color=\\'gray\\'>'+t.Log.Entries[r].timestamp+':</font> '+"
"t.Log.Entries[r].text+'</div>')}"
"e=t.Log.TTL,"
"''!==n&&('Fetching log entries...'==elId(o).innerHTML&&(elId(o).innerHTML=''),"
"elId(o).innerHTML+=n),"
"n='';"
"!0==(autoscroll_on=elId('autoscroll').checked)&&''!==i&&elId(i).scrollIntoView({behavior:scrolling_type});"
"applyLogFilter();"
"elId('current_loglevel').innerHTML='Logging: '+logLevel[t.Log.SettingsWebLogLevel]+' ('+t.Log.SettingsWebLogLevel+')';"
"clearInterval(s);loopDeLoop(e,0)"
"})}).catch(function(t){"
"elId(o).innerHTML+='<div>>> '+t.message+' <<</div>';"
"autoscroll_on=elId('autoscroll').checked;"
"elId(o).scrollTop=elId(o).scrollHeight;"
"e=5000;clearInterval(s);loopDeLoop(e,0)"
"}),l=1},e)}"
"function applyLogFilter(){let e=elId('logfilter').value.split(';').map(e=>e.trim()).filter(Boolean),"
"t=document.querySelectorAll('#copyText_1 > div'),"
"o=e.some(e=>!e.startsWith('!'));"
"for(let r of t){r.dataset.originalHtml||(r.dataset.originalHtml=r.innerHTML);"
"let n=r.dataset.originalHtml,i=r.textContent.toLowerCase(),l=[],s=!1,a=!o;"
"for(let d of e){"
"if(d.startsWith('!')){let c=d.slice(1);"
"if(!c.includes('&')||c.startsWith('&')||c.endsWith('&')){"
"if(c&&i.includes(c.toLowerCase())){s=!0;break}}else{"
"let g=c.split('&').map(e=>e.trim()).filter(Boolean),f=-1,u=!0;"
"for(let p of g){let L=i.indexOf(p.toLowerCase(),f+1);if(-1===L){u=!1;break}f=L}if(u){s=!0;break}}continue}"
"if(d.includes('&')&&!d.startsWith('&')&&!d.endsWith('&')){"
"let h=d.split('&').map(e=>e.trim()).filter(Boolean);"
"if(h.length>=2){let m=-1,$=!0;for(let _ of h){let b=i.indexOf(_.toLowerCase(),m+1);if(-1===b){$=!1;break}m=b}if($){l.push(...h),a=!0;break}}continue}"
"if(i.includes(d.toLowerCase())){l.push(d),a=!0;break}}"
"if(s||!a){r.style.display='none';continue}r.style.display='';r.innerHTML=highlightOutsideTags(n,l)}}"
"function highlightOutsideTags(e,t){if(!t.length)return e;"
"let o='',r=!1,n=0;"
"for(;n<e.length;){let i=e[n];"
"if('<'===i&&(r=!0),'>'===i){r=!1;o+=i;n++;continue}"
"if(!r){let l=!1;for(let s of t){let a=e.slice(n,n+s.length);"
"if(a.toLowerCase()===s.toLowerCase()){o+=`<span style='background-color: #bb9300;color: black;'>${a}</span>`;n+=s.length;l=!0;break}}if(l)continue}o+=i;n++}return o}"
"const input=document.getElementById('logfilter');"
"input&&(input.placeholder='\"!\"=exclude \";\"=or \"&\"=and (e.g. !act)');"
};
#endif // WEBSERVER_INCLUDE_JS

Expand Down
2 changes: 2 additions & 0 deletions src/src/WebServer/Log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "../WebServer/JSON.h"
#include "../WebServer/Markup.h"
#include "../WebServer/Markup_Buttons.h"
#include "../WebServer/Markup_Forms.h"

#include "../DataStructs/LogBuffer.h"
#include "../DataStructs/TimingStats.h"
Expand Down Expand Up @@ -35,6 +36,7 @@ void handle_log() {
"</TR></table><div id='current_loglevel' style='font-weight: bold;'>Logging: </div><div class='logviewer' id='copyText_1'></div>"));
addHtml(F("Autoscroll: "));
addCheckBox(F("autoscroll"), true);
addFormTextBox(F("Filter"), F("logfilter"), "", 30);
addHtml(F("<BR></body>"));

serve_JS(JSfiles_e::FetchAndParseLog);
Expand Down
138 changes: 136 additions & 2 deletions static/fetch_and_parse_log.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
//---------------no need to be included into WebStaticData.h---------------------------------------------
// if no filter input exists, create it
document.getElementById("logfilter") ||
(document.querySelector("section").innerHTML +=
'<label>Filter: <input id="logfilter" size="35"></label>');
//------------------------------------------------------------
function elId(e) {
return document.getElementById(e);
return document.getElementById(e);
}
function getBrowser() {
var ua = navigator.userAgent,
Expand Down Expand Up @@ -104,6 +110,7 @@ function loopDeLoop(timeForNext, activeRequests) {
behavior: scrolling_type
});
}
applyLogFilter();
elId('current_loglevel').innerHTML = 'Logging: ' + logLevel[data.Log.SettingsWebLogLevel] + ' (' + data.Log.SettingsWebLogLevel + ')';
clearInterval(i);
loopDeLoop(timeForNext, 0);
Expand All @@ -121,4 +128,131 @@ function loopDeLoop(timeForNext, activeRequests) {
};
check = 1;
}, timeForNext);
}
}

function applyLogFilter() {
const filterGroups = elId('logfilter').value
.split(';').map(s => s.trim()).filter(Boolean);

const entries = document.querySelectorAll('#copyText_1 > div');
const hasPositiveFilter = filterGroups.some(g => !g.startsWith('!'));

for (const entry of entries) {
if (!entry.dataset.originalHtml) entry.dataset.originalHtml = entry.innerHTML;
let html = entry.dataset.originalHtml;

const textLower = entry.textContent.toLowerCase();
let matched = [];
let hiddenByExclusion = false;

let isMatch = !hasPositiveFilter;

for (const group of filterGroups) {

// exclusion rule with ordered AND
if (group.startsWith('!')) {
const g = group.slice(1);

if (g.includes('&') && !g.startsWith('&') && !g.endsWith('&')) {
const parts = g.split('&').map(s => s.trim()).filter(Boolean);
let lastIndex = -1;
let ok = true;

for (const p of parts) {
const idx = textLower.indexOf(p.toLowerCase(), lastIndex + 1);
if (idx === -1) {
ok = false;
break;
}
lastIndex = idx;
}

if (ok) {
hiddenByExclusion = true;
break;
}
} else {
if (g && textLower.includes(g.toLowerCase())) {
hiddenByExclusion = true;
break;
}
}
continue;
}

// ordered AND: a&b&c
if (group.includes('&') && !group.startsWith('&') && !group.endsWith('&')) {
const parts = group.split('&').map(s => s.trim()).filter(Boolean);
if (parts.length >= 2) {
let lastIndex = -1;
let ok = true;

for (const p of parts) {
const idx = textLower.indexOf(p.toLowerCase(), lastIndex + 1);
if (idx === -1) {
ok = false;
break;
}
lastIndex = idx;
}
if (ok) {
matched.push(...parts);
isMatch = true;
break;
}
}
continue;
}
// normal include
if (textLower.includes(group.toLowerCase())) {
matched.push(group);
isMatch = true;
break;
}
}
if (hiddenByExclusion || !isMatch) {
entry.style.display = 'none';
continue;
}
entry.style.display = '';
entry.innerHTML = highlightOutsideTags(html, matched);
}
}

function highlightOutsideTags(html, terms) {
if (!terms.length) return html;
let result = '';
let insideTag = false;
let i = 0;

while (i < html.length) {
const char = html[i];
if (char === '<') insideTag = true;
if (char === '>') {
insideTag = false;
result += char;
i++;
continue;
}

if (!insideTag) {
let matched = false;
for (const term of terms) {
const slice = html.slice(i, i + term.length);
if (slice.toLowerCase() === term.toLowerCase()) {
result += `<span style="background-color: #bb9300;color: black;">${slice}</span>`;
i += term.length;
matched = true;
break;
}
}
if (matched) continue;
}
result += char;
i++;
}
return result;
}

const input = document.getElementById("logfilter");
input && (input.placeholder = '"!"=exclude ";"=or "&"=and (e.g. !act)');
11 changes: 10 additions & 1 deletion static/github_clipboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ function setGithubClipboard() {
if (i % 2 == 0) {
separatorSymbol += '\n'
}
clipboard += test.innerHTML.replace(/<[Bb][Rr]\s*\/?>/gim, '\n') + separatorSymbol;
// collect only visible log entry divs
const visibleLines = Array.from(test.children)
.filter(div => div.offsetParent !== null) // visible only
.map(div => div.innerHTML);

if (visibleLines.length > 0) {
clipboard +=
visibleLines.join('\n').replace(/<[Bb][Rr]\s*\/?>/gim, '\n') +
separatorSymbol;
}
}
}
clipboard = clipboard.replace(/<\/[Dd][Ii][Vv]\s*\/?>/gim, '\n');
Expand Down