A debug tool for Node-RED using UIBUILDER
This is an example using UIBUILDER to create a debug output page for Node-RED. It was inspired, as so often, by a question in the Node-RED forum.
The issue with Node-RED's debug panel is that it limits the size of the information that can be output. If you have really large messages you need to debug, this can be a challenge. While you can increase the allowed max message size, it will eventually slow down the editor.
This flow also gives an example of how to use UIBUILDER's low-code capabilities in your front-end code. Each inbound msg is processed and turned into a new set of HTML elements with new msgs being shown at the top of the list for easy access.
Each output msg also shows some meta-data and has buttons that copy to clipboard and delete an entry.
There are also buttons to pause the display of messages and to clear all the messages.
To set things up, import the flow, deploy it, then transfer the contents of the 3 comment nodes to the matching front-end code files. Open the page and then send messages to the link-in node in the group.
The group definition has a description with more information. There are also a couple of environment variables defined to the group which can be overridden with msg properties of the same name.
[{"id":"38dea932ad8bf5b0","type":"group","z":"30fdd9a9702231b0","name":"UIBUILDER Node-RED debug page - with full output and copy (Read my description for details)","style":{"label":true,"fill":"#ffefbf","fill-opacity":"0.34","color":"#000000"},"nodes":["b2b9cd88db08056f","30f917ba22b7601a","465991cb13575bdc","2dcf170369271854","85e33c87a851fd7b","cd117dec98696547"],"env":[{"name":"debug_name","value":"Debug","type":"str"},{"name":"debug_property","value":"msg","type":"str"}],"x":514,"y":99,"w":578,"h":142,"info":"A simple debug output page using uibuilder.\r\n\r\nNote that this group has 2 defined environment\r\nvariables. \r\n\r\n`debug_name` and `debug_property`. These\r\ncan also be overridden using msg properties\r\nof the same name.\r\n\r\nSimple send any message to the `link-in` node\r\nin this group and view a formatted version of\r\nthe message on the debug web page.\r\n\r\nThe web page has pause/resume buttons and \r\nclear all entries buttons.\r\n\r\nEach entry on the page can be copied to the \r\nclipboard using the ❒ button. Or the entry \r\ncan be deleted using the ❌ button.\r\n\r\nThe formatted output for each entry appears in \r\na scrollable box. You can grab the bottom-right\r\ncorner and expand the box manually.\r\n\r\nUnlike Node-RED's debug panel, ALL of the msg\r\nobject is included in the output - up to the \r\nmax limit of the Socket.IO client. If you need\r\nto extend the limit, you can do so in the \r\nuibuilder settings in the `settings.js` file.\r\n\r\nHowever, as with any web app, be wary of How\r\nmuch data you keep in the browser window. If \r\nyou let it get too big, your browser will slow\r\ndown and eventually break."},{"id":"b2b9cd88db08056f","type":"function","z":"30fdd9a9702231b0","g":"38dea932ad8bf5b0","name":"","func":"// What msg property to debug (or the whole msg)\nconst debug_property = msg.debug_property || env.get('debug_property') || 'msg'\n// Each property part as an array. e.g `msg._ui` becomes ['msg','_ui']\nlet debug_property_array = []\n// Record any errors\nlet err = []\n\nlet dt = (new Date()).toLocaleString()\n\ntry {\n debug_property_array = RED.util.normalisePropertyExpression(debug_property)\n // node.send([null, {debug_property, debug_property_array}])\n} catch (e) {\n node.warn(e.message)\n err.push(e.message)\n}\n\nlet msg1 = {data: null}\ntry {\n // Try to get a specific msg property if requested\n let ans = RED.util.getMessageProperty(msg, debug_property)\n if (ans === undefined) {\n err.push(`Message property not found. '${debug_property}'`)\n } else {\n msg1.data = ans\n }\n} catch(e) {\n // Else get the whole msg\n msg1.data = RED.util.cloneMessage(msg)\n // but remove the debug meta data if it was passed in on the orig msg\n delete msg1.data.debug_name\n delete msg1.data.debug_property\n delete msg1.data.pass_debug_property\n delete msg1.data.debug_array\n err.push(e.message)\n}\n\nmsg1.topic = msg.topic\nmsg1.errors = err\nmsg1.debug_property = debug_property\nmsg1.debug_property_array = debug_property_array\n// Optional msg name - will default to original msg id\nmsg1.debug_name = msg.debug_name || env.get(\"debug_name\") || msg._msgid || \"name error\"\nmsg1.debug_dt = dt\n\nreturn [msg1,msg]","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":700,"y":140,"wires":[["30f917ba22b7601a"],[]],"inputLabels":["msg to debug"],"outputLabels":["debug msg","Original msg"]},{"id":"30f917ba22b7601a","type":"uibuilder","z":"30fdd9a9702231b0","g":"38dea932ad8bf5b0","name":"","topic":"","url":"debug","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"templateFolder":"blank","extTemplate":"","showfolder":false,"reload":false,"sourceFolder":"src","deployedVersion":"6.5.0","showMsgUib":false,"x":960,"y":140,"wires":[[],[]]},{"id":"465991cb13575bdc","type":"link in","z":"30fdd9a9702231b0","g":"38dea932ad8bf5b0","name":"link in 13","links":["0919ecfaa892d896","e81d0d1c57d90da7"],"x":555,"y":140,"wires":[["b2b9cd88db08056f"]]},{"id":"2dcf170369271854","type":"comment","z":"30fdd9a9702231b0","g":"38dea932ad8bf5b0","name":"index.html","info":"<!doctype html>\n<html lang=\"en\"><head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <link rel=\"icon\" href=\"../uibuilder/images/node-blue.ico\">\n\n <title>Debug - Node-RED uibuilder</title>\n <meta name=\"description\" content=\"Node-RED uibuilder - Debug\">\n\n <!-- Your own CSS (defaults to loading uibuilders css)-->\n <link type=\"text/css\" rel=\"stylesheet\" href=\"./index.css\" media=\"all\">\n\n <!-- #region Supporting Scripts. These MUST be in the right order. Note no leading / -->\n <script defer src=\"../uibuilder/uibuilder.iife.min.js\"></script>\n <script defer src=\"./index.js\">/* OPTIONAL: Put your custom code in that */</script>\n <!-- #endregion -->\n\n</head><body class=\"uib\">\n <h1 class=\"with-subtitle\">uibuilder - Debug</h1>\n <div role=\"doc-subtitle\">Using the IIFE library.</div>\n\n <div id=\"header\">\n <button id=\"pauser1\" value=\"pause\" onclick=\"doPause(event)\">Pause</button>\n <button onclick=\"doClear(event)\">Clear All</button>\n </div>\n\n <div id=\"more\"><!-- '#more' is used as a parent for dynamic HTML content in examples --></div>\n\n <div id=\"footer\">\n <button id=\"pauser2\" value=\"pause\" onclick=\"doPause(event)\">Pause</button>\n <button onclick=\"doClear(event)\">Clear All</button>\n </div>\n\n</body></html>\n","x":600,"y":200,"wires":[]},{"id":"85e33c87a851fd7b","type":"comment","z":"30fdd9a9702231b0","g":"38dea932ad8bf5b0","name":"index.js","info":"// uibuilder.logLevel = 2 // info\n\nconst elPaused1 = document.getElementById(\"pauser1\") || {innerText:''}\nconst elPaused2 = document.getElementById(\"pauser2\") || {innerText:''}\nlet paused = false\nlet msgCount = 0\n\n// Stop/resume inbound msgs\nfunction doPause(event) {\n if (paused === true) {\n elPaused1.innerText = \"Pause\"\n elPaused2.innerText = \"Pause\"\n paused = false\n uibuilder.log('info', 'uibuilder.debug', 'Visual debug messages flowing')\n } else {\n elPaused1.innerText = \"Resume\"\n elPaused2.innerText = \"Resume\"\n paused = true\n uibuilder.log('info', 'uibuilder.debug', 'Visual debug messages paused')\n }\n}\n\n// Remove the selected debug entry\nfunction cpy(event) {\n const elToCopy = event.target.parentElement.parentElement.querySelector('pre')\n window.prompt(\"Copy to clipboard: Ctrl+C, Enter\", elToCopy.innerText)\n}\n\nfunction del(event) {\n event.target.parentElement.parentElement.remove()\n}\n\n// Clears all debug entries\nfunction doClear(event) {\n document.querySelector('#more').textContent = ''\n}\n\nuibuilder.onChange('msg', (msg) => {\n console.log('DEBUG msg', msg)\n\n if (paused === true || msg._ui) return\n\n ++msgCount\n\n // use uibuilder's low-code capability to build the output structure for the new debug entry\n const toAdd = [{\n method: 'add',\n components: [\n {\n type: 'article',\n parent: '#more',\n position: 'first',\n id: `dbg_${msgCount}`,\n attributes: {\n class: 'dbg',\n },\n components: [\n {\n type: 'div',\n slot: `#${msgCount} | ${msg.debug_name} | ${msg.debug_dt} | <i title=\"msg property\">${msg.debug_property}</i> | <button title=\"copy\" onclick=\"cpy(event)\"> ❒ </button><button title=\"delete\" onclick=\"del(event)\"> ❌ </button> `,\n },\n ],\n }\n ],\n\n }]\n\n // If there are errors, output a list of them\n if (msg.errors.length > 0) {\n toAdd[0].components[0].components.push({\n type: 'div',\n slot: `<p class=\"error\">${msg.errors.join('</p><p class=\"error\">')}</p>`\n })\n } else {\n // otherwise output the msg.data highlighted\n toAdd[0].components[0].components.push({\n type: 'pre',\n attributes: {\n class: 'syntax-highlight',\n },\n // Formates the data using a simple JSON colour formatter\n slot: uibuilder.syntaxHighlight(msg.data)\n })\n }\n\n uibuilder.ui(toAdd)\n})\n","x":730,"y":200,"wires":[]},{"id":"cd117dec98696547","type":"comment","z":"30fdd9a9702231b0","g":"38dea932ad8bf5b0","name":"index.css","info":"/* Load defaults from `<userDir>/node_modules/node-red-contrib-uibuilder/front-end/uib-brand.css`\n * This version auto-adjusts for light/dark browser settings but might not be as complete.\n */\n@import url(\"../uibuilder/uib-brand.css\");\n\n#header {\n margin-bottom: 1em;\n padding-bottom: .5em;\n border-bottom: 1px solid silver;\n}\n#footer {\n margin-top: 1em;\n padding-top: .5em;\n border-top: 1px solid silver;\n}\n.dbg {\n border: 1px solid silver;\n padding: 1rem;\n}\n","x":860,"y":200,"wires":[]}]