/** * Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ if (!RED) { var RED = {} } RED.debug = (function() { var config; var messageList; var messageTable; var filterType = "filterAll"; var filteredNodes = {}; // id->true means hide, so default to all visible var view = 'list'; var messages = []; var messagesByNode = {}; var sbc; var activeWorkspace; var numMessages = 100; // Hardcoded number of message to show in debug window scrollback var debugNodeTreeList; function init(_config) { config = _config; var content = $("
").css({"position":"relative","height":"100%"}); var toolbar = $('
'+ ''+ ' '+ ''+ ''+ ' all' + ''+ '
').appendTo(content); var footerToolbar = $('
'+ ' ' + '
'); messageList = $('
').appendTo(content); sbc = messageList[0]; messageTable = $('
').appendTo(content); var filterDialogCloseTimeout; var filterDialogShown = false; var filterDialog = $('
').appendTo(toolbar);//content); filterDialog.on('mouseleave' ,function(evt) { if (filterDialogShown) { filterDialogCloseTimeout = setTimeout(function() { filterDialog.slideUp(200); filterDialogShown = false; },500) } }) filterDialog.on('mouseenter' ,function(evt) { clearTimeout(filterDialogCloseTimeout) }) var filterToolbar = $('
'+ ''+ '' + '' + ''+ ''+ '
').appendTo(filterDialog); filterToolbar.find("#red-ui-sidebar-filter-select-close").on('click', function(evt) { clearTimeout(filterDialogCloseTimeout) filterDialogShown = false; filterDialog.slideUp(200); }) filterToolbar.find("#red-ui-sidebar-filter-select-all").on('click', function(evt) { evt.preventDefault(); var data = debugNodeTreeList.treeList('data'); data.forEach(function(flow) { if (!flow.selected) { if (flow.treeList.checkbox) { flow.treeList.checkbox.trigger('click') } } else { flow.children.forEach(function(item) { if (!item.selected) { item.treeList.select(); } }) } }); refreshMessageList(); }) filterToolbar.find("#red-ui-sidebar-filter-select-none").on('click', function(evt) { evt.preventDefault(); debugNodeTreeList.treeList('clearSelection'); var data = debugNodeTreeList.treeList('data'); data.forEach(function(flow) { if (flow.children) { flow.children.forEach(function(item) { filteredNodes[item.node.id] = true; }) } }); RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes)) refreshMessageList(); }) var debugNodeListRow = $('
').appendTo(filterDialog); debugNodeTreeList = $("
").appendTo(debugNodeListRow).css({width: "100%", height: "300px"}) .treeList({autoSelect: false}).on("treelistitemmouseover", function(e, item) { if (item.node) { item.node.highlighted = true; item.node.dirty = true; RED.view.redraw(); } }).on("treelistitemmouseout", function(e, item) { if (item.node) { item.node.highlighted = false; item.node.dirty = true; RED.view.redraw(); } }).on("treelistselect", function(e, item) { if (item.children) { item.children.forEach(function(child) { if (child.checkbox) { child.treeList.select(item.selected) } }) } else { if (item.node) { if (item.selected) { delete filteredNodes[item.node.id] } else { filteredNodes[item.node.id] = true; } RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes)) refreshMessageList(); } } }) try { content.i18n(); } catch(err) { console.log("TODO: i18n library support"); } toolbar.find('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.filterAll')); toolbar.find('#red-ui-sidebar-debug-filter').on("click",function(e) { e.preventDefault(); var options = [ { label: $(' ').i18n() , value: "filterAll" }, { label: $(' ...').i18n(), value: "filterSelected" }, { label: $(' ').i18n(), value: "filterCurrent" } ] var menu = RED.popover.menu({ options: options, onselect: function(item) { if (item.value !== filterType) { filterType = item.value; $('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.'+filterType)); refreshMessageList(); RED.settings.set("debug.filter",filterType) } if (filterType === 'filterSelected') { refreshDebugNodeList(); filterDialog.slideDown(200); filterDialogShown = true; debugNodeTreeList.focus(); } } }); menu.show({ target: $("#red-ui-sidebar-debug-filter"), align: "left", offset: [$("#red-ui-sidebar-debug-filter").outerWidth()-2, -1] }) $('input[name="filter-type"][value="'+RED.settings.get("debug.filter","filterAll")+'"]').prop("checked", true) }); RED.popover.tooltip(toolbar.find('#red-ui-sidebar-debug-filter'),RED._('node-red:debug.sidebar.filterLog')); toolbar.find("#red-ui-sidebar-debug-clear").on("click", function(e) { e.preventDefault(); var action = RED.settings.get("debug.clearType","all") clearMessageList(false, action === 'filtered'); }); var clearTooltip = RED.popover.tooltip(toolbar.find("#red-ui-sidebar-debug-clear"),RED._('node-red:debug.sidebar.clearLog'),"core:clear-debug-messages"); toolbar.find("#red-ui-sidebar-debug-clear-opts").on("click", function(e) { e.preventDefault(); var options = [ { label: $(' ').i18n() , value: "all" }, { label: $(' ').i18n(), value: "filtered" } ] var menu = RED.popover.menu({ options: options, onselect: function(item) { if (item.value === "all") { $("#red-ui-sidebar-debug-clear > span").text(RED._('node-red:debug.sidebar.all')); clearTooltip.setAction("core:clear-debug-messages"); clearTooltip.setContent(RED._('node-red:debug.sidebar.clearLog')) RED.settings.set("debug.clearType","all") } else { $("#red-ui-sidebar-debug-clear > span").text(RED._('node-red:debug.sidebar.filtered')); clearTooltip.setAction("core:clear-filtered-debug-messages"); clearTooltip.setContent(RED._('node-red:debug.sidebar.clearFilteredLog')) RED.settings.set("debug.clearType","filtered") } } }); menu.show({ target: $("#red-ui-sidebar-debug-clear-opts"), align: "left", offset: [$("#red-ui-sidebar-debug-clear-opts").outerWidth()-2, -1] }) $('input[name="clear-type"][value="'+RED.settings.get("debug.clearType","all")+'"]').prop("checked", true) }) var clearType = RED.settings.get("debug.clearType","all"); if (clearType === "all") { toolbar.find("#red-ui-sidebar-debug-clear > span").text(RED._('node-red:debug.sidebar.all')); clearTooltip.setAction("core:clear-debug-messages"); clearTooltip.setContent(RED._('node-red:debug.sidebar.clearLog')) } else { toolbar.find("#red-ui-sidebar-debug-clear > span").text(RED._('node-red:debug.sidebar.filtered')); clearTooltip.setAction("core:clear-filtered-debug-messages"); clearTooltip.setContent(RED._('node-red:debug.sidebar.clearFilteredLog')) } filterType = RED.settings.get("debug.filter","filterAll") var filteredNodeList = RED.settings.get("debug.filteredNodes",[]); filteredNodes = {} filteredNodeList.forEach(function(id) { filteredNodes[id] = true }) toolbar.find('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.'+filterType)); refreshMessageList(); return { content: content, footer: footerToolbar }; } function containsDebug(sid, map) { var item = map[sid]; if (item) { if (item.debug === undefined) { var sfs = Object.keys(item.subflows); var contain = false; for (var i = 0; i < sfs.length; i++) { var sf = sfs[i]; if (containsDebug(sf, map)) { contain = true; break; } } item.debug = contain; } return item.debug; } return false; } function refreshDebugNodeList() { var workspaceOrder = RED.nodes.getWorkspaceOrder(); var workspaceOrderMap = {}; workspaceOrder.forEach(function(ws,i) { workspaceOrderMap[ws] = i; }); var candidateNodes = []; var candidateSFs = []; var subflows = {}; RED.nodes.eachNode(function (n) { var nt = n.type; if (nt === "debug") { if (n.z in workspaceOrderMap) { candidateNodes.push(n); } else { var sf = RED.nodes.subflow(n.z); if (sf) { subflows[sf.id] = { debug: true, subflows: {} }; } } } else if(nt.substring(0, 8) === "subflow:") { if (n.z in workspaceOrderMap) { candidateSFs.push(n); } else { var psf = RED.nodes.subflow(n.z); if (psf) { var sid = nt.substring(8); var item = subflows[psf.id]; if (!item) { item = { debug: undefined, subflows: {} }; subflows[psf.id] = item; } item.subflows[sid] = true; } } } }); candidateSFs.forEach(function (sf) { var sid = sf.type.substring(8); if (containsDebug(sid, subflows)) { candidateNodes.push(sf); } }); candidateNodes.sort(function(A,B) { var wsA = workspaceOrderMap[A.z]; var wsB = workspaceOrderMap[B.z]; if (wsA !== wsB) { return wsA-wsB; } var labelA = RED.utils.getNodeLabel(A,A.id); var labelB = RED.utils.getNodeLabel(B,B.id); return labelA.localeCompare(labelB); }); var currentWs = null; var data = []; var currentFlow; var currentSelectedCount = 0; candidateNodes.forEach(function(node) { if (currentWs !== node.z) { if (currentFlow && currentFlow.checkbox) { currentFlow.selected = currentSelectedCount === currentFlow.children.length } currentSelectedCount = 0; currentWs = node.z; var parent = RED.nodes.workspace(currentWs) || RED.nodes.subflow(currentWs); currentFlow = { label: RED.utils.getNodeLabel(parent, currentWs), } if (!parent.disabled) { currentFlow.children = []; currentFlow.checkbox = true; } else { currentFlow.class = "disabled" } data.push(currentFlow); } if (currentFlow.children) { if (!filteredNodes[node.id]) { currentSelectedCount++; } currentFlow.children.push({ label: RED.utils.getNodeLabel(node,node.id), node: node, checkbox: true, selected: !filteredNodes[node.id] }); } }); if (currentFlow && currentFlow.checkbox) { currentFlow.selected = currentSelectedCount === currentFlow.children.length } debugNodeTreeList.treeList("data", data); } function getTimestamp() { var d = new Date(); return d.toLocaleString(); } function sanitize(m) { return m.replace(/&/g,"&").replace(//g,">"); } var refreshTimeout; function refreshMessageList(_activeWorkspace) { if (refreshTimeout) { clearTimeout(refreshTimeout); } refreshTimeout = setTimeout(function() { _refreshMessageList(_activeWorkspace); },200); } function _refreshMessageList(_activeWorkspace) { if (_activeWorkspace) { activeWorkspace = _activeWorkspace.replace(/\./g,"_"); } if (filterType === "filterAll") { $(".red-ui-debug-msg").removeClass("hide"); } else { $(".red-ui-debug-msg").each(function() { if (filterType === 'filterCurrent') { $(this).toggleClass('hide',!$(this).hasClass('red-ui-debug-msg-flow-'+activeWorkspace)); } else if (filterType === 'filterSelected') { var id = $(this).data('source'); if (id) { $(this).toggleClass('hide',!!filteredNodes[id]); } } }); } } function refreshMessageTable() { } function showMessageList() { view = 'list'; messageTable.hide(); messageTable.empty(); messages.forEach(function(m) { messageList.append(m.el); }) messageList.show(); } function showMessageTable() { view = 'table'; messageList.hide(); messageList.empty(); Object.keys(messagesByNode).forEach(function(id) { var m = messagesByNode[id]; var msg = m.el; var sourceNode = m.source; if (sourceNode) { var wrapper = $("
",{id:"red-ui-debug-msg-source-"+sourceNode.id.replace(/\./g,"_")}).appendTo(messageTable); wrapper.append(msg); } }); messageTable.show(); } function formatString(str) { return str.replace(/\n/g,"↵").replace(/\t/g,"→"); } var menuOptionMenu; var activeMenuMessage; function showMessageMenu(button,dbgMessage,sourceId) { activeMenuMessage = dbgMessage; if (!menuOptionMenu) { var opts = [ {id:"red-ui-debug-msg-menu-item-collapse",label:RED._("node-red:debug.messageMenu.collapseAll"),onselect:function(){ activeMenuMessage.collapse(); }}, ]; if (activeMenuMessage.clearPinned) { opts.push( {id:"red-ui-debug-msg-menu-item-clear-pins",label:RED._("node-red:debug.messageMenu.clearPinned"),onselect:function(){ activeMenuMessage.clearPinned(); }}, ); } opts.push( null, {id:"red-ui-debug-msg-menu-item-filter", label:RED._("node-red:debug.messageMenu.filterNode"),onselect:function(){ var candidateNodes = RED.nodes.filterNodes({type:'debug'}); candidateNodes.forEach(function(n) { filteredNodes[n.id] = true; }); delete filteredNodes[sourceId]; $("#red-ui-sidebar-debug-filterSelected").trigger("click"); RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes)) refreshMessageList(); }}, {id:"red-ui-debug-msg-menu-item-clear-filter",label:RED._("node-red:debug.messageMenu.clearFilter"),onselect:function(){ $("#red-ui-sidebar-debug-filterAll").trigger("click"); refreshMessageList(); }} ); menuOptionMenu = RED.menu.init({id:"red-ui-debug-msg-option-menu", options: opts }); menuOptionMenu.css({ position: "absolute" }) menuOptionMenu.on('mouseleave', function(){ $(this).hide() }); menuOptionMenu.on('mouseup', function() { $(this).hide() }); menuOptionMenu.appendTo("body"); } var filterOptionDisabled = false; var sourceNode = RED.nodes.node(sourceId); if (sourceNode && sourceNode.type !== 'debug') { filterOptionDisabled = true; } RED.menu.setDisabled('red-ui-debug-msg-menu-item-filter',filterOptionDisabled); RED.menu.setDisabled('red-ui-debug-msg-menu-item-clear-filter',filterOptionDisabled); var elementPos = button.offset(); menuOptionMenu.css({ top: elementPos.top+"px", left: (elementPos.left - menuOptionMenu.width() + 20)+"px" }) menuOptionMenu.show(); } var stack = []; var busy = false; function handleDebugMessage(o) { if (o) { stack.push(o); } if (!busy && (stack.length > 0)) { busy = true; processDebugMessage(stack.shift()); setTimeout(function() { busy = false; handleDebugMessage(); }, 15); // every 15mS = 66 times a second if (stack.length > numMessages) { stack = stack.splice(-numMessages); } } } function processDebugMessage(o) { var msg = $("
"); var sourceNode = o._source; msg.on("mouseenter", function() { msg.addClass('red-ui-debug-msg-hover'); if (o._source) { // highlight the top-level node (could be subflow instance) config.messageMouseEnter(o._source.id); if (o._source._alias) { // this is inside a subflow - highlight the node itself config.messageMouseEnter(o._source._alias); } // if path.length > 2, we are nested - highlight subflow instances for (var i=2;i
').appendTo(msg); $(''+ getTimestamp()+'').appendTo(metaRow); if (sourceNode) { var nodeLink = $('',{href:"#",class:"red-ui-debug-msg-name"}).text(RED._("node-red:debug.node")+": "+(o.name||sourceNode.name||sourceNode.id)) .appendTo(metaRow) .on("click", function(evt) { evt.preventDefault(); config.messageSourceClick(sourceNode.id, sourceNode._alias, sourceNode.path); }); if (sourceNode.pathHierarchy) { RED.popover.create({ tooltip: true, target:nodeLink, trigger: "hover", size: "small", direction: "bottom", interactive: true, content: function() { const content = $("
") sourceNode.pathHierarchy.forEach((pathPart,idx) => { const link = $("", {href:"#" ,style:'display: block'}) .css({ paddingLeft:((idx*10)+((idx === sourceNode.pathHierarchy.length - 1)?10:0))+"px", paddingRight:'2px' }) .text(pathPart.label) .appendTo(content) .on("click", function(evt) { evt.preventDefault(); config.messageSourceClick(pathPart.id); }) if (idx < sourceNode.pathHierarchy.length - 1) { $('').prependTo(link) } }) return content }, delay: { show: 50, hide: 150 } }); } } else if (name) { $(''+name+'').appendTo(metaRow); } payload = RED.utils.decodeObject(payload,format); var el = $('').appendTo(msg); var path = o.property||''; var debugMessage = RED.utils.createObjectElement(payload, { key: /*true*/null, typeHint: format, hideKey: false, path: path, sourceId: sourceNode&&sourceNode.id, rootPath: path }); // Do this in a separate step so the element functions aren't stripped debugMessage.appendTo(el); // NOTE: relying on function error to have a "type" that all other msgs don't if (o.hasOwnProperty("type") && (o.type === "function")) { var errorLvlType = 'error'; var errorLvl = 20; if (o.hasOwnProperty("level") && o.level === 30) { errorLvl = 30; errorLvlType = 'warn'; } msg.addClass('red-ui-debug-msg-level-' + errorLvl); $('function : (' + errorLvlType + ')').appendTo(metaRow); } else { var tools = $('').appendTo(metaRow); var filterMessage = $('').appendTo(tools); filterMessage.on("click", function(e) { e.preventDefault(); e.stopPropagation(); showMessageMenu(filterMessage,debugMessage,sourceNode&&sourceNode.id); }); $(''+ (o.topic?topic+' : ':'')+ (o.property?'msg.'+property:'msg')+" : "+format+ '').appendTo(metaRow); } var atBottom = (sbc.scrollHeight-messageList.height()-sbc.scrollTop) < 5; var m = { el: msg }; messages.push(m); if (sourceNode) { m.source = sourceNode; messagesByNode[sourceNode.id] = m; } if (view == "list") { messageList.append(msg); } else { if (sourceNode) { var wrapper = $("#red-ui-debug-msg-source-"+sourceNode.id.replace(/\./g,"_")); if (wrapper.length === 0 ) { wrapper = $("
",{id:"red-ui-debug-msg-source-"+sourceNode.id.replace(/\./g,"_")}).appendTo(messageTable); } wrapper.empty(); wrapper.append(msg); } } if (messages.length === numMessages) { m = messages.shift(); if (view === "list") { m.el.remove(); } } if (atBottom) { messageList.scrollTop(sbc.scrollHeight); } } function clearMessageList(clearFilter, filteredOnly) { if (!filteredOnly) { $(".red-ui-debug-msg").remove(); } else { $(".red-ui-debug-msg:not(.hide)").remove(); } config.clear(); if (!!clearFilter) { clearFilterSettings(); } refreshDebugNodeList(); } function clearFilterSettings() { filteredNodes = {}; filterType = 'filterAll'; RED.settings.set("debug.filter",filterType); RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes)) $('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.filterAll')); } return { init: init, refreshMessageList:refreshMessageList, handleDebugMessage: handleDebugMessage, clearMessageList: clearMessageList } })();