/** * 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 filterVisible = false; var debugNodeList; var debugNodeListExpandedFlows = {}; function init(_config) { config = _config; var content = $("
").css({"position":"relative","height":"100%"}); var toolbar = $('
'+ ' '+ '
').appendTo(content); var footerToolbar = $('
'+ // ''+ // 'list'+ // 'table '+ // ''+ ' ' + '
'); messageList = $('
').appendTo(content); sbc = messageList[0]; messageTable = $('
').appendTo(content); var filterDialog = $('
'+ '
'+ ''+ ''+ ''+ ' '+ ''+ '
'+ '
').appendTo(toolbar);//content); // var filterTypeRow = $('
').appendTo(filterDialog); // $('').appendTo(filterTypeRow); var debugNodeListRow = $('
').appendTo(filterDialog); var flowCheckboxes = {}; var debugNodeListHeader = $('
'); var headerCheckbox = $('').appendTo(debugNodeListHeader.find("span")[1]).checkboxSet(); debugNodeList = $('
    ',{style:"text-align: left; min-height: 250px; max-height: 250px"}).appendTo(debugNodeListRow).editableList({ header: debugNodeListHeader, class: 'red-ui-nodeList', addItem: function(container,i,node) { var row = $("
    ").appendTo(container); row.attr('id','debug-filter-node-list-node-'+node.id.replace(/\./g,"_")); if (node.type === 'tab') { container.parent().addClass('red-ui-editableList-section-header'); if (!debugNodeListExpandedFlows.hasOwnProperty(node.id)) { debugNodeListExpandedFlows[node.id] = true; } var chevron = $('').appendTo(row); $('').text(RED.utils.getNodeLabel(node,node.id)).appendTo(row); var muteControl = $('').appendTo($('').appendTo(row)); muteControl.checkboxSet({ parent: headerCheckbox }); flowCheckboxes[node.id] = muteControl; row.on("click", function(e) { e.stopPropagation(); debugNodeListExpandedFlows[node.id] = !debugNodeListExpandedFlows[node.id]; row.toggleClass('expanded',debugNodeListExpandedFlows[node.id]); debugNodeList.editableList('filter'); }) row.addClass("expandable"); if (node.disabled) { container.addClass('disabled'); muteControl.checkboxSet('disable'); debugNodeListExpandedFlows[node.id] = false; } row.toggleClass('expanded',debugNodeListExpandedFlows[node.id]); } else { $('',{style: "margin-left: 20px"}).text(RED.utils.getNodeLabel(node,node.id)).appendTo(row); row.on("mouseenter",function() { config.messageMouseEnter(node.id); }); row.on("mouseleave",function() { config.messageMouseLeave(node.id); }); var muteControl = $('').prop('checked',!filteredNodes[node.id]).appendTo($('').appendTo(row)); muteControl.checkboxSet({ parent: flowCheckboxes[node.z] }).on("change", function(e) { filteredNodes[node.id] = !$(this).prop('checked'); $(".red-ui-debug-msg-node-"+node.id.replace(/\./g,"_")).toggleClass('hide',filteredNodes[node.id]); }); if ((node.hasOwnProperty("active") && !node.active) || RED.nodes.workspace(node.z).disabled) { container.addClass('disabled'); muteControl.checkboxSet('disable'); } } }, addButton: false, scrollOnAdd: false, filter: function(node) { return (node.type === 'tab' || debugNodeListExpandedFlows[node.z] ) }, sort: function(A,B) { } }); 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')); var filterButtonHandler = function(type) { return function(e) { e.preventDefault(); if (filterType !== type) { $('.red-ui-sidebar-debug-filter-option').removeClass('selected'); $(this).addClass('selected'); if (filterType === 'filterSelected') { debugNodeListRow.slideUp(); } filterType = type; if (filterType === 'filterSelected') { debugNodeListRow.slideDown(); } $('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.'+filterType)); refreshMessageList(); } } } filterDialog.find('#red-ui-sidebar-debug-filterAll').on("click",filterButtonHandler('filterAll')); filterDialog.find('#red-ui-sidebar-debug-filterSelected').on("click",filterButtonHandler('filterSelected')); filterDialog.find('#red-ui-sidebar-debug-filterCurrent').on("click",filterButtonHandler('filterCurrent')); // $('#red-ui-sidebar-debug-view-list').on("click",function(e) { // e.preventDefault(); // if (!$(this).hasClass('selected')) { // $(this).addClass('selected'); // $('#red-ui-sidebar-debug-view-table').removeClass('selected'); // showMessageList(); // } // }); // $('#red-ui-sidebar-debug-view-table').on("click",function(e) { // e.preventDefault(); // if (!$(this).hasClass('selected')) { // $(this).addClass('selected'); // $('#red-ui-sidebar-debug-view-list').removeClass('selected'); // showMessageTable(); // } // }); var hideFilterTimeout; toolbar.on('mouseleave',function() { if ($('#red-ui-sidebar-debug-filter').hasClass('selected')) { clearTimeout(hideFilterTimeout); hideFilterTimeout = setTimeout(function() { filterVisible = false; $('#red-ui-sidebar-debug-filter').removeClass('selected'); filterDialog.slideUp(200); },300); } }); toolbar.on('mouseenter',function() { if ($('#red-ui-sidebar-debug-filter').hasClass('selected')) { clearTimeout(hideFilterTimeout); } }) toolbar.find('#red-ui-sidebar-debug-filter').on("click",function(e) { e.preventDefault(); if ($(this).hasClass('selected')) { filterVisible = false; $(this).removeClass('selected'); clearTimeout(hideFilterTimeout); filterDialog.slideUp(200); } else { $(this).addClass('selected'); filterVisible = true; refreshDebugNodeList(); filterDialog.slideDown(200); } }); 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(); clearMessageList(false); }); RED.popover.tooltip(toolbar.find("#red-ui-sidebar-debug-clear"),RED._('node-red:debug.sidebar.clearLog'),"core:clear-debug-messages"); 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() { debugNodeList.editableList('empty'); 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 nodeList = []; candidateNodes.forEach(function(node) { if (currentWs !== node.z) { currentWs = node.z; nodeList.push(RED.nodes.workspace(node.z)); } nodeList.push(node); }); debugNodeList.editableList('addItems',nodeList); } function getTimestamp() { var d = new Date(); return d.toLocaleString(); } function sanitize(m) { return m.replace(/&/g,"&").replace(//g,">"); } 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) { menuOptionMenu = RED.menu.init({id:"red-ui-debug-msg-option-menu", options: [ {id:"red-ui-debug-msg-menu-item-collapse",label:RED._("node-red:debug.messageMenu.collapseAll"),onselect:function(){ activeMenuMessage.collapse(); }}, {id:"red-ui-debug-msg-menu-item-clear-pins",label:RED._("node-red:debug.messageMenu.clearPinned"),onselect:function(){ activeMenuMessage.clearPinned(); }}, 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"); 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.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) { $('',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+(o.name||sourceNode.name||sourceNode.id)) .appendTo(metaRow) .on("click", function(evt) { evt.preventDefault(); config.messageSourceClick(sourceNode.id, sourceNode._alias, sourceNode.path); }); } 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) { $(".red-ui-debug-msg").remove(); config.clear(); if (!!clearFilter) { clearFilterSettings(); } refreshDebugNodeList(); } function clearFilterSettings() { filteredNodes = {}; filterType = 'filterAll'; $('.red-ui-sidebar-debug-filter-option').removeClass('selected'); $('#red-ui-sidebar-debug-filterAll').addClass('selected'); $('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.filterAll')); $('#red-ui-sidebar-debug-filter-node-list-row').slideUp(); } return { init: init, refreshMessageList:refreshMessageList, handleDebugMessage: handleDebugMessage, clearMessageList: clearMessageList } })();