diff options
Diffstat (limited to 'pw_console/py/pw_console/html/main.js')
-rw-r--r-- | pw_console/py/pw_console/html/main.js | 316 |
1 files changed, 143 insertions, 173 deletions
diff --git a/pw_console/py/pw_console/html/main.js b/pw_console/py/pw_console/html/main.js index d08d019ed..79e239eca 100644 --- a/pw_console/py/pw_console/html/main.js +++ b/pw_console/py/pw_console/html/main.js @@ -12,136 +12,146 @@ // License for the specific language governing permissions and limitations under // the License. -var VirtualizedList = window.VirtualizedList.default; -const rowHeight = 30; +// eslint-disable-next-line no-undef +const { createLogViewer, LogSource, LogEntry, Severity } = PigweedLogging; -function formatDate(dt) { - function pad2(n) { - return (n < 10 ? '0' : '') + n; - } +let currentTheme = {}; +let defaultLogStyleRule = 'color: #ffffff;'; +let columnStyleRules = {}; +let defaultColumnStyles = []; +let logLevelStyles = {}; - return dt.getFullYear() + pad2(dt.getMonth() + 1) + pad2(dt.getDate()) + ' ' + - pad2(dt.getHours()) + ':' + pad2(dt.getMinutes()) + ':' + - pad2(dt.getSeconds()); -} +const logLevelToString = { + 10: 'DBG', + 20: 'INF', + 21: 'OUT', + 30: 'WRN', + 40: 'ERR', + 50: 'CRT', + 70: 'FTL', +}; -let data = []; -function clearLogs() { - data = [{ - 'message': 'Logs started', - 'levelno': 20, - time: formatDate(new Date()), - 'levelname': '\u001b[35m\u001b[1mINF\u001b[0m', - 'args': [], - 'fields': {'module': '', 'file': '', 'timestamp': '', 'keys': ''} - }]; -} -clearLogs(); +const logLevelToSeverity = { + 10: Severity.DEBUG, + 20: Severity.INFO, + 21: Severity.INFO, + 30: Severity.WARNING, + 40: Severity.ERROR, + 50: Severity.CRITICAL, + 70: Severity.CRITICAL, +}; -let nonAdditionalDataFields = - ['_hosttime', 'levelname', 'levelno', 'args', 'fields', 'message', 'time']; +let nonAdditionalDataFields = [ + '_hosttime', + 'levelname', + 'levelno', + 'args', + 'fields', + 'message', + 'time', +]; let additionalHeaders = []; -function updateHeadersFromData(data) { - let dirty = false; - Object.keys(data).forEach((columnName) => { - if (nonAdditionalDataFields.indexOf(columnName) === -1 && - additionalHeaders.indexOf(columnName) === -1) { - additionalHeaders.push(columnName); - dirty = true; - } - }); - Object.keys(data.fields || {}).forEach((columnName) => { - if (nonAdditionalDataFields.indexOf(columnName) === -1 && - additionalHeaders.indexOf(columnName) === -1) { - additionalHeaders.push(columnName); - dirty = true; - } - }); - const headerDOM = document.querySelector('.log-header'); - if (dirty) { - headerDOM.innerHTML = ` - <span class="_hosttime">Time</span> - <span class="level">Level</span> - ${ - additionalHeaders - .map((key) => ` - <span class="${key}">${key}</span> - `).join('\n')} - <span class="msg">Message</span>` +// New LogSource to consume pw-console log json messages +class PwConsoleLogSource extends LogSource { + constructor() { + super(); + } + append_log(data) { + var fields = [ + { key: 'severity', value: logLevelToSeverity[data.levelno] }, + { key: 'time', value: data.time }, + ]; + Object.keys(data.fields).forEach((columnName) => { + if ( + nonAdditionalDataFields.indexOf(columnName) === -1 && + additionalHeaders.indexOf(columnName) === -1 + ) { + fields.push({ key: columnName, value: data.fields[columnName] }); + } + }); + fields.push({ key: 'message', value: data.message }); + fields.push({ key: 'py_file', value: data.py_file }); + fields.push({ key: 'py_logger', value: data.py_logger }); + this.emitEvent('logEntry', { + severity: logLevelToSeverity[data.levelno], + timestamp: new Date(), + fields: fields, + }); } +} - // Also update column widths to match actual row. - const headerChildren = Array.from(headerDOM.children); +// Setup the pigweedjs log-viewer +const logSource = new PwConsoleLogSource(); +const containerEl = document.querySelector('#log-viewer-container'); +let unsubscribe = createLogViewer(logSource, containerEl); - const firstRow = document.querySelector('.log-container .log-entry'); - const firstRowChildren = Array.from(firstRow.children); - headerChildren.forEach((col, index) => { - if (firstRowChildren[index]) { - col.setAttribute( - 'style', - `width:${firstRowChildren[index].getBoundingClientRect().width}`); - col.setAttribute('title', col.innerText); - } - }) +// Format a date in the standard pw_cli style YYYY-mm-dd HH:MM:SS +function formatDate(dt) { + function pad2(n) { + return (n < 10 ? '0' : '') + n; + } + + return ( + dt.getFullYear() + + pad2(dt.getMonth() + 1) + + pad2(dt.getDate()) + + ' ' + + pad2(dt.getHours()) + + ':' + + pad2(dt.getMinutes()) + + ':' + + pad2(dt.getSeconds()) + ); } +// Return the value for the given # parameter name. function getUrlHashParameter(param) { var params = getUrlHashParameters(); return params[param]; } +// Capture all # parameters from the current URL. function getUrlHashParameters() { var sPageURL = window.location.hash; - if (sPageURL) - sPageURL = sPageURL.split('#')[1]; + if (sPageURL) sPageURL = sPageURL.split('#')[1]; var pairs = sPageURL.split('&'); var object = {}; - pairs.forEach(function(pair, i) { + pairs.forEach(function (pair, i) { pair = pair.split('='); - if (pair[0] !== '') - object[pair[0]] = pair[1]; + if (pair[0] !== '') object[pair[0]] = pair[1]; }); return object; } -let currentTheme = {}; -let defaultLogStyleRule = 'color: #ffffff;'; -let columnStyleRules = {}; -let defaultColumnStyles = []; -let logLevelStyles = {}; -const logLevelToString = { - 10: 'DBG', - 20: 'INF', - 21: 'OUT', - 30: 'WRN', - 40: 'ERR', - 50: 'CRT', - 70: 'FTL' -}; +// Update web page CSS styles based on a pw-console color json log message. function setCurrentTheme(newTheme) { currentTheme = newTheme; - defaultLogStyleRule = parseStyle(newTheme.default); - document.querySelector('body').setAttribute('style', defaultLogStyleRule); + defaultLogStyleRule = parsePromptToolkitStyle(newTheme.default); + // Set body background color + // document.querySelector('body').setAttribute('style', defaultLogStyleRule); + // Apply default font styles to columns let styles = []; - Object.keys(newTheme).forEach(key => { + Object.keys(newTheme).forEach((key) => { if (key.startsWith('log-table-column-')) { styles.push(newTheme[key]); } if (key.startsWith('log-level-')) { logLevelStyles[parseInt(key.replace('log-level-', ''))] = - parseStyle(newTheme[key]); + parsePromptToolkitStyle(newTheme[key]); } }); defaultColumnStyles = styles; } -function parseStyle(rule) { +// Convert prompt_toolkit color format strings to CSS. +// 'bg:#BG-HEX #FG-HEX STYLE' where STYLE is either 'bold' or 'underline' +function parsePromptToolkitStyle(rule) { const ruleList = rule.split(' '); - let outputStyle = ruleList.map(fragment => { + let outputStyle = ruleList.map((fragment) => { if (fragment.startsWith('bg:')) { - return `background-color: ${fragment.replace('bg:', '')}` + return `background-color: ${fragment.replace('bg:', '')}`; } else if (fragment === 'bold') { return `font-weight: bold`; } else if (fragment === 'underline') { @@ -150,32 +160,35 @@ function parseStyle(rule) { return `color: ${fragment}`; } }); - return outputStyle.join(';') + return outputStyle.join(';'); } +// Inject styled spans into the log message column values. function applyStyling(data, applyColors = false) { let colIndex = 0; - Object.keys(data).forEach(key => { + Object.keys(data).forEach((key) => { if (columnStyleRules[key] && typeof data[key] === 'string') { - Object.keys(columnStyleRules[key]).forEach(token => { + Object.keys(columnStyleRules[key]).forEach((token) => { data[key] = data[key].replaceAll( - token, - `<span + token, + `<span style="${defaultLogStyleRule};${ - applyColors ? (defaultColumnStyles - [colIndex % defaultColumnStyles.length]) : - ''};${parseStyle(columnStyleRules[key][token])};"> + applyColors + ? defaultColumnStyles[colIndex % defaultColumnStyles.length] + : '' + };${parsePromptToolkitStyle(columnStyleRules[key][token])};"> ${token} - </span>`); + </span>`, + ); }); } else if (key === 'fields') { data[key] = applyStyling(data.fields, true); } if (applyColors) { data[key] = `<span - style="${ - parseStyle( - defaultColumnStyles[colIndex % defaultColumnStyles.length])}"> + style="${parsePromptToolkitStyle( + defaultColumnStyles[colIndex % defaultColumnStyles.length], + )}"> ${data[key]} </span>`; } @@ -184,78 +197,35 @@ function applyStyling(data, applyColors = false) { return data; } -(function() { -const container = document.querySelector('.log-container'); -const height = window.innerHeight - 50 -let follow = true; -// Initialize our VirtualizedList -var virtualizedList = new VirtualizedList(container, { - height, - rowCount: data.length, - rowHeight: rowHeight, - estimatedRowHeight: rowHeight, - renderRow: (index) => { - const element = document.createElement('div'); - element.classList.add('log-entry'); - element.setAttribute('style', `height: ${rowHeight}px;`); - const logData = data[index]; - element.innerHTML = ` - <span class="time">${logData.time}</span> - <span class="level" style="${logLevelStyles[logData.levelno] || ''}">${ - logLevelToString[logData.levelno]}</span> - ${ - additionalHeaders - .map( - (key) => ` - <span class="${key}">${ - logData[key] || logData.fields[key] || ''}</span> - `).join('\n')} - <span class="msg">${logData.message}</span> - `; - return element; - }, - initialIndex: 0, - onScroll: (scrollTop, event) => { - const offset = - virtualizedList._sizeAndPositionManager.getUpdatedOffsetForIndex({ - containerSize: height, - targetIndex: data.length - 1, - }); - - if (scrollTop < offset) { - follow = false; - } else { - follow = true; - } - } -}); - -const port = getUrlHashParameter('ws') -const hostname = location.hostname || '127.0.0.1'; -var ws = new WebSocket(`ws://${hostname}:${port}/`); -ws.onmessage = function(event) { - let dataObj; - try { - dataObj = JSON.parse(event.data); - } catch (e) { - } - if (!dataObj) - return; - - if (dataObj.__pw_console_colors) { - const colors = dataObj.__pw_console_colors; - setCurrentTheme(colors.classes); - if (colors.column_values) { - columnStyleRules = {...colors.column_values}; +// Connect to the pw-console websocket and start emitting logs. +(function () { + const container = document.querySelector('.log-container'); + const height = window.innerHeight - 50; + let follow = true; + + const port = getUrlHashParameter('ws'); + const hostname = location.hostname || '127.0.0.1'; + var ws = new WebSocket(`ws://${hostname}:${port}/`); + ws.onmessage = function (event) { + let dataObj; + try { + dataObj = JSON.parse(event.data); + } catch (e) { + // empty } - } else { - const currentData = {...dataObj, time: formatDate(new Date())}; - updateHeadersFromData(currentData); - data.push(applyStyling(currentData)); - virtualizedList.setRowCount(data.length); - if (follow) { - virtualizedList.scrollToIndex(data.length - 1); + if (!dataObj) return; + + if (dataObj.__pw_console_colors) { + // If this is a color theme message, update themes. + const colors = dataObj.__pw_console_colors; + setCurrentTheme(colors.classes); + if (colors.column_values) { + columnStyleRules = { ...colors.column_values }; + } + } else { + // Normal log message. + const currentData = { ...dataObj, time: formatDate(new Date()) }; + logSource.append_log(currentData); } - } -}; + }; })(); |