@gregoriusrippenstein/node-red-contrib-introspection 0.6.16 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -99,6 +99,12 @@ Inspired by the [dsm](https://flows.nodered.org/node/node-red-contrib-dsm) packa
99
99
 
100
100
  Send a flow to another Node-RED instance. This will replace **any existing** flows on the other server, use with care. Under the hood this uses the [Node-RED API](https://nodered.org/docs/api/admin/methods/post/flows/) to post a new flow to the server. This node also supports authentication if the other server happens to have some.
101
101
 
102
+ ### InstallPackage
103
+
104
+ Node used to install packages on other Node-RED installations. Can be used to install existing modules using or .tgz files that can installed on the instance. To install an existing package, use `msg.payload = { module: '@fubar/example' }` with an optional `version`, this defaults to 'latest'.
105
+
106
+ To install a package on the fly, generate a .tgz as a Buffer and set the the payload to: `msg.payload = { data: Buffer[...]}` where buffer contains the .tgz contents.
107
+
102
108
  ### ClientCode
103
109
 
104
110
  ClientCode is a node for executing client side, i.e., in the editor, Javascript code triggered by a server side event. Any code that can be executed in the browseer console can be executed in the ClientCode node. A ClientCode node can also send a message back to the server using `node.send(...)` which becomes the server side output of the node.
@@ -165,6 +171,7 @@ There are [example flows](/examples) contained in the package, examples can also
165
171
  - [Orphans](https://flowhub.org/f/2401c255b056e0e1)
166
172
  - [Sink and Seeker](https://flowhub.org/f/139a816449acd89f)
167
173
  - [Obfuscation](https://flowhub.org/f/825ddf24d98eb011)
174
+ - [InstallPackage](https://flowhub.org/f/6dcbd2643ea80615)
168
175
 
169
176
  ## License
170
177
 
@@ -0,0 +1,111 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('InstallPackage',{
3
+ color: '#e5e4ef',
4
+ icon: "introspesubflow.svg",
5
+ category: 'introspection',
6
+ defaults: {
7
+ name: {
8
+ value:"",
9
+ },
10
+ hostUrl: {
11
+ value: ""
12
+ },
13
+ useAuthentication: {
14
+ value:false
15
+ },
16
+ apiUsername: {
17
+ value: "",
18
+ },
19
+ apiUsernameType: {
20
+ value: "env",
21
+ },
22
+ apiPassword: {
23
+ value: "",
24
+ },
25
+ apiPasswordType: {
26
+ value: "env",
27
+ },
28
+ },
29
+ inputs:1,
30
+ outputs:1,
31
+
32
+ label: function() {
33
+ return (this.name || this._def.paletteLabel);
34
+ },
35
+
36
+ labelStyle: function() {
37
+ return this.name?"node_label_italic":"";
38
+ },
39
+ oneditprepare: function() {
40
+ $("#node-input-apiUsername").typedInput({
41
+ types:["env", "msg", "flow","global", "cred"],
42
+ typeField: "#node-input-apiUsernameType"
43
+ });
44
+
45
+ $("#node-input-apiPassword").typedInput({
46
+ types:["env", "msg", "flow","global", "env", "cred"],
47
+ typeField: "#node-input-apiPasswordType"
48
+ });
49
+
50
+ if ( $('#node-input-useAuthentication').is(":checked") ) {
51
+ $('#useAuthentication-input-fields').show();
52
+ } else {
53
+ $('#useAuthentication-input-fields').hide()
54
+ }
55
+
56
+ $('#node-input-useAuthentication').on( 'change', function() {
57
+ if ( $('#node-input-useAuthentication').is(":checked") ) {
58
+ $('#useAuthentication-input-fields').show();
59
+ } else {
60
+ $('#useAuthentication-input-fields').hide()
61
+ }
62
+ });
63
+ },
64
+ });
65
+ </script>
66
+
67
+ <script type="text/html" data-template-name="InstallPackage">
68
+ <div class="form-row">
69
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
70
+ <input type="text" id="node-input-name" placeholder="Name">
71
+ </div>
72
+
73
+ <div class="form-row">
74
+ <label for="node-input-hostUrl"><i class="fa fa-tag"></i> Host</label>
75
+ <input type="text" id="node-input-hostUrl" placeholder="Host URL">
76
+ </div>
77
+
78
+ <div class="form-row">
79
+ <label for="node-input-useAuthentication">
80
+ <i class="fa "></i>
81
+ <span>Use Authentication?</span>
82
+ </label>
83
+
84
+ <input type="checkbox" id="node-input-useAuthentication"
85
+ style="display:inline-block; width:15px; vertical-align:baseline;">
86
+ </div>
87
+
88
+ <div id="useAuthentication-input-fields" class="hidden">
89
+ <div class="form-row">
90
+ <label for="node-input-apiUsername">
91
+ <i class="fa fa-tag"></i>
92
+ Username
93
+ </label>
94
+ <input type="text" id="node-input-apiUsername">
95
+ <input type="hidden" id="node-input-apiUsernameType">
96
+ </div>
97
+
98
+ <div class="form-row">
99
+ <label for="node-input-apiPassword">
100
+ <i class="fa fa-tag"></i>
101
+ Password
102
+ </label>
103
+ <input type="text" id="node-input-apiPassword">
104
+ <input type="hidden" id="node-input-apiPasswordType">
105
+ </div>
106
+ </div>
107
+ </script>
108
+
109
+ <script type="text/html" data-help-name="InstallPackage">
110
+ <p>Send flow to another Node-RED instance.</p>
111
+ </script>
@@ -0,0 +1,137 @@
1
+ module.exports = function(RED) {
2
+ function InstallPackageFunctionality(config) {
3
+ RED.nodes.createNode(this,config);
4
+
5
+ var node = this;
6
+ var cfg = config;
7
+
8
+ node.on('close', function() {
9
+ node.status({});
10
+ });
11
+
12
+ node.on("input", function(msg, send, done) {
13
+
14
+ if (!msg.payload || Object.prototype.toString.call(msg.payload) !== '[object Object]') {
15
+ return node.error("msg.payload missing or payload not hash")
16
+ }
17
+
18
+ var installPackage = (hdrs, got) => {
19
+ let body = undefined
20
+ let headers = {}
21
+
22
+ if ( msg.payload.module ) {
23
+ body = JSON.stringify({
24
+ module: msg.payload.module,
25
+ version: msg.payload.version || "latest"
26
+ })
27
+
28
+ headers = {
29
+ "Content-Type": "application/json"
30
+ }
31
+ } else if ( msg.payload.data && Buffer.isBuffer(msg.payload.data) ) {
32
+
33
+ let formData = require('form-data')
34
+ body = new formData();
35
+ body.append("tarball", msg.payload.data, "tarball.tgz");
36
+ headers = body.getHeaders()
37
+ body = body.getBuffer()
38
+
39
+ } else {
40
+ return node.error("msg.payload not well defined")
41
+ }
42
+
43
+ got.post( cfg.hostUrl + "/nodes", {
44
+ headers: {
45
+ ...headers,
46
+ ...hdrs
47
+ },
48
+ body: body
49
+ }).then( res => {
50
+ send({
51
+ ...msg,
52
+ payload: JSON.parse(res.body)
53
+ });
54
+
55
+ node.status({fill:"green",shape:"dot",text:"Good"});
56
+ setTimeout( function() {
57
+ node.status({})
58
+ }, 450);
59
+
60
+ }).catch( err => {
61
+ node.status({fill:"red",shape:"dot",text:"Failed"});
62
+ node.error(err)
63
+ });
64
+ };
65
+
66
+ /**
67
+ * Authentication
68
+ **/
69
+ if ( cfg.useAuthentication ) {
70
+ var username = undefined;
71
+ var password = undefined;
72
+
73
+ node.status({fill:"blue",shape:"dot",text:"Requesting token"});
74
+
75
+ RED.util.evaluateNodeProperty(cfg.apiUsername, cfg.apiUsernameType,
76
+ node, msg, (err, result) => {
77
+ if (err) {
78
+ node.status({fill:"red",shape:"dot",text:"Failed"});
79
+ node.error(err)
80
+ } else {
81
+ username = result;
82
+
83
+ RED.util.evaluateNodeProperty(cfg.apiPassword, cfg.apiPasswordType,
84
+ node, msg, (err, result) => {
85
+ if (err) {
86
+ node.status({fill:"red",shape:"dot",text:"Failed"});
87
+ node.error(err)
88
+ } else {
89
+ password = result;
90
+
91
+ var data = {
92
+ "client_id": "node-red-admin",
93
+ "grant_type": "password",
94
+ "scope": "*",
95
+ "username": username,
96
+ "password": password
97
+ }
98
+
99
+ import('got').then( (module) => {
100
+ module.got.post( cfg.hostUrl + "/auth/token", {
101
+ json: data
102
+ }).then( res => {
103
+ node.status({
104
+ fill:"blue",
105
+ shape:"dot",
106
+ text:"Sending flow"
107
+ });
108
+
109
+ var access_token = JSON.parse(res.body).access_token;
110
+
111
+ installPackage({
112
+ "Authorization": "Bearer " + access_token
113
+ }, module.got);
114
+
115
+ }).catch((err) => {
116
+ node.status({fill:"red",shape:"dot",text:"Failed"});
117
+ node.error( err );
118
+ });
119
+ });
120
+ }
121
+ })
122
+ }
123
+ })
124
+ } else {
125
+ /*
126
+ * Authentication free zone...
127
+ */
128
+ node.status({fill:"blue",shape:"dot",text:"Sending flow"});
129
+ import('got').then( (module) => {
130
+ installPackage({}, module.got);
131
+ });
132
+ }
133
+ });
134
+ }
135
+
136
+ RED.nodes.registerType("InstallPackage", InstallPackageFunctionality);
137
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gregoriusrippenstein/node-red-contrib-introspection",
3
- "version": "0.6.16",
3
+ "version": "0.7.0",
4
4
  "dependencies": {
5
5
  "got": "^13",
6
6
  "uglify-js": "^3.17.4",
@@ -28,11 +28,12 @@
28
28
  "screenshot": "plugins/sidebar.html"
29
29
  },
30
30
  "nodes": {
31
- "seeker": "nodes/05-seeker.js",
32
- "sink": "nodes/10-sink.js",
33
- "getflows": "nodes/45-get-flows.js",
34
- "sendflow": "nodes/50-send-flow.js",
35
- "clientcode": "nodes/60-client-code.js"
31
+ "seeker": "nodes/05-seeker.js",
32
+ "sink": "nodes/10-sink.js",
33
+ "getflows": "nodes/45-get-flows.js",
34
+ "sendflow": "nodes/50-send-flow.js",
35
+ "installpackage": "nodes/51-install-package.js",
36
+ "clientcode": "nodes/60-client-code.js"
36
37
  }
37
38
  },
38
39
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  let obfuscateHelpers={getFlowDataFromCurrentWorkspace:e=>{var e=e||RED.workspaces.active(),t=RED.nodes.groups(e),e=(t=(t=t.concat(RED.nodes.junctions(e))).concat(RED.nodes.filterNodes({z:e})),RED.nodes.eachConfig(function(e){e.z===RED.workspaces.active()&&!1===e._def.hasUsers&&t.push(e)}),RED.nodes.workspace(e)||RED.nodes.subflow(e));return t.unshift(e),RED.nodes.createExportableNodeSet(t)},openImportDialog:e=>{RED.clipboard.import();let t=e;setTimeout(()=>{$("#red-ui-clipboard-dialog-import-text").val(JSON.stringify(t)).trigger("paste")},300)}};function obfuscatieCurrentFlow(l){let p=obfuscateHelpers.getFlowDataFromCurrentWorkspace(),f=(l.remove_groups&&(p=p.filter(e=>"group"!=e.type)).forEach(e=>{delete e.g}),{}),t={},d={},c={},s=[],h={},r={};if(p.forEach(e=>{"subflow"==(f[e.id]=e).type&&(r[e.id]=e),"junction"==e.type&&(t[e.id]=e),"link out"==e.type&&(c[e.id]=e),"link in"==e.type&&(d[e.id]=e),"link call"==e.type&&s.push(e.id),"catch"==e.type&&(h[e.id]=(e.wires||[])[0]||[])}),l.remove_junctions&&(Object.keys(t).forEach(n=>{let s=t[n];r[s.z]||p.forEach(t=>{for(let e=0;e<(t.wires||[]).length;e++){var r=t.wires[e].indexOf(n);-1<r&&(t.wires[e].splice(r,1),t.wires[e].push(...s.wires[0]))}})}),p=p.filter(e=>!("junction"==e.type&&!r[e.z]))),l.remove_linkout_nodes||l.remove_linkcall_nodes){let r=[],n=[],i=[],a=(l.remove_linkout_nodes&&!l.copy_linkin_nodes&&Object.keys(c).forEach(n=>{var e=c[n];let s=e.links||[];s.reduce((e,t)=>e&&(!!d[t]||l.ignore_off_flow_links),!0)&&"return"!=e.mode&&(r.push(n),p.forEach(r=>{for(let t=0;t<(r.wires||[]).length;t++){var e=r.wires[t].indexOf(n);-1<e&&(r.wires[t].splice(e,1),s.forEach(e=>{d[e]&&r.wires[t].push(...d[e].wires[0])}))}}))}),(s,e)=>{if(!e)return!1;let t=[],o=[],r=[];function u(e){if(r.indexOf(e.id)<0)switch(r.push(e.id),t.push(e),e.type){case"link out":if("return"!=e.mode)throw"not handable";break;case"link call":throw"unhandled";default:RED.nodes.getNodeLinks(e).forEach(function(e){u(e.target)})}}try{RED.nodes.getNodeLinks(e).forEach(function(e){u(e.target),o.push(e.target.id)});if(Object.keys(h).forEach(e=>{((t,e)=>{try{return e.map(e=>e.id).forEach(e=>{if(t.includes(e))throw"yes"}),!1}catch(e){return!0}})(h[e],t)&&t.push(f[e])}),0==t.length)return!1;let r={};var i=t[0];let n={x:s.x-i.x,y:s.y-i.y};var a=t.map(e=>{var t=structuredClone(f[e.id]);return t||console.log("WARNING: nodeid not found",[e.id,f[e.id]]),t.id=RED.nodes.id(),t.x+=n.x,t.y+=n.y,r[e.id]=t});return a.forEach(e=>{"catch"==e.type&&Array.isArray(e.scope)&&(e.scope=e.scope.map(e=>{var t=r[e];return t&&t.id||e})),e.wires&&(e.wires=e.wires.map(e=>e.map(e=>{var t=r[e];return t&&"link out"==t.type?[...s.wires[0],(t||{}).id||e]:(t||{}).id||e}).flat()))}),{nodes:a.filter(e=>"link out"!=e.type),entryNodes:o.map(e=>r[e])}}catch(e){return!1}});l.remove_linkout_nodes&&l.copy_linkin_nodes&&Object.keys(c).forEach(s=>{let o=c[s],u=o.links||[];if("return"!=o.mode){let t=u.map(e=>{var t=d[e],t=a(o,t);return t?[e,t]:[void 0,void 0]});t.reduce((e,t)=>e&&(!!t[0]||l.ignore_off_flow_links),!0)&&(r.push(s),p.forEach(n=>{for(let r=0;r<(n.wires||[]).length;r++){var e=n.wires[r].indexOf(s);-1<e&&(n.wires[r].splice(e,1),(t=u.map(e=>{var t=d[e],t=a(o,t);return t?[e,t]:[void 0,void 0]})).forEach(([,e])=>{var t;e&&(t=e.entryNodes.map(e=>e.id),n.wires[r].push(...t),i.push(...e.nodes))}))}}))}}),l.remove_linkcall_nodes&&s.forEach(s=>{var e=f[s],t=d[e.links[0]];let o=a(e,t);t&&o&&(n.push(e.id),p.forEach(t=>{for(let e=0;e<(t.wires||[]).length;e++){var r,n=t.wires[e].indexOf(s);-1<n&&(r=o.entryNodes.map(e=>e.id),t.wires[e].splice(n,1),t.wires[e].push(...r),i.push(...o.nodes))}}))}),(p=p.filter(e=>r.indexOf(e.id)<0&&n.indexOf(e.id)<0)).push(...i)}if((p=l.remove_debugs?p.filter(e=>"debug"!=e.type&&"comment"!=e.type):p).forEach(e=>{l.name&&(e.name=RED.nodes.id()),l.shrink_node&&(e.l=!1),l.info&&(e.info="",delete e.outputLabels,delete e.inputLabels),l.add_license&&(l.append_license?e.info=(e.info||"")+"\n---\n\n"+l.license_text:e.info=l.license_text),l.position&&(e.x=150,e.y=150),l.remove_icons&&delete e.icon}),l.replace_switch){let n=(t,r)=>{for(let e=0;e<r.rules.length;e++){var n=r.rules[e];if("nnull"==n.t)t.push("if ( _propValue != null ) { _returnValue.push(msg) ; return node.send(_returnValue) } else { _returnValue.push(undefined) }");else if("null"==n.t)t.push("if ( _propValue == null ) { _returnValue.push(msg) ; return node.send(_returnValue) } else { _returnValue.push(undefined) }");else if("eq"==n.t&&"str"==n.vt){s=n.v;s=btoa(encodeURIComponent(s).replace(/%([0-9A-F]{2})/g,function(e,t){return String.fromCharCode("0x"+t)}));t.push("if ( _propValue === Buffer.from('"+s+"', 'base64').toString('utf-8') ) { _returnValue.push(msg) ; return node.send(_returnValue) } else { _returnValue.push(undefined) }")}else if("gt"==n.t&&"num"==n.vt)t.push("if ( _propValue > "+n.v+" ) { _returnValue.push(msg) ; return node.send(_returnValue) } else { _returnValue.push(undefined) }");else if("eq"==n.t&&"num"==n.vt)t.push("if ( _propValue == "+n.v+" ) { _returnValue.push(msg) ; return node.send(_returnValue) } else { _returnValue.push(undefined) }");else if("empty"==n.t)t.push('let p = _propValue ; if ( (Array.isArray(p) && p.length == 0) || ((typeof p === "object") && p != null && Object.keys(p).length == 0) || ((typeof p === "string") && p.length == 0) ) { _returnValue.push(msg) ; return node.send(_returnValue) } else { _returnValue.push(undefined) }');else if("nempty"==n.t)t.push('let p = _propValue ; if ( (Array.isArray(p) && p.length > 0) || ((typeof p === "object") && p != null && Object.keys(p).length > 0) || ((typeof p === "string") && p.length > 0) ) { _returnValue.push(msg) ; return node.send(_returnValue) } else { _returnValue.push(undefined) }');else{if("else"!=n.t)throw"unhandled";t.push("_returnValue.push(msg)","return node.send(_returnValue)")}}var s};p=p.map(t=>{if("switch"!=t.type)return t;var e={id:t.id,name:t.name,type:"function",x:t.x,y:t.y,func:"",outputs:t.outputs,timeout:"",noerr:0,initialize:"",finalize:"",libs:[{var:"process",module:"process"},{var:"jsonata",module:"jsonata"}],wires:t.wires};t.hasOwnProperty("z")&&(e.z=t.z),t.hasOwnProperty("l")&&(e.l=t.l);try{var r=[];if(t.checkAll)throw"unhandled";if("msg"==t.propertyType)r.push("((msg,node) => {","let _propValue = undefined","try { _propValue = RED.util.getMessageProperty(msg, '"+t.property+"') } catch (ex) {}","let _returnValue = []"),n(r,t),r.push("})(msg,node);");else{if("jsonata"!=t.propertyType)throw"unhandled";r.push("jsonata('"+t.property+"').evaluate(msg).then(_propValue => {","let _returnValue = []"),n(r,t),r.push("})")}return e.func=r.join("\n"),e}catch(e){return t}})}if(l.replace_change){let s=e=>btoa(encodeURIComponent(e).replace(/%([0-9A-F]{2})/g,function(e,t){return String.fromCharCode("0x"+t)}));p=p.map(t=>{if("change"!=t.type)return t;var e={id:t.id,name:t.name,type:"function",x:t.x,y:t.y,func:"",outputs:1,timeout:"",noerr:0,initialize:"",finalize:"",libs:[{var:"process",module:"process"},{var:"jsonata",module:"jsonata"}],wires:t.wires};t.hasOwnProperty("z")&&(e.z=t.z),t.hasOwnProperty("l")&&(e.l=t.l);try{var r=["(async (msg,node) => {","let __s = RED.util.setMessageProperty","let __g = RED.util.getMessageProperty"];for(let e=0;e<t.rules.length;e++){var n=t.rules[e];if("set"==n.t){if(n.dc)throw"unhandled";if("msg"==n.pt&&"str"==n.tot)r.push("__s(msg,'"+n.p+"', Buffer.from('"+s(n.to)+"', 'base64').toString('utf-8') )");else if("msg"==n.pt&&"bool"==n.tot)r.push("__s(msg,'"+n.p+"', "+n.to+")");else if("msg"==n.pt&&"num"==n.tot)r.push("__s(msg,'"+n.p+"', Number(Buffer.from('"+s(n.to)+"', 'base64').toString('utf-8')))");else if("msg"==n.pt&&"json"==n.tot)r.push("__s(msg,'"+n.p+"', JSON.parse(Buffer.from('"+s(n.to)+"', 'base64').toString('utf-8')))");else if("msg"==n.pt&&"msg"==n.tot)r.push("try { __s(msg,'"+n.p+"', __g(msg, '"+n.to+"')) } catch (ex) {}");else if("msg"==n.pt&&"env"==n.tot)r.push("__s(msg,'"+n.p+"', process.env['"+n.to+"'])");else{if("msg"!=n.pt||"jsonata"!=n.tot||!n.to.startsWith("/* @obfuscate-safe */"))throw"unhandlable";r.push("__s(msg,'"+n.p+"', await jsonata(Buffer.from('"+s(n.to)+"', 'base64').toString('utf-8')).evaluate(msg) )")}}else{if("delete"!=n.t||"msg"!=n.pt)throw"unhandlable";r.push("delete msg."+n.p)}}return r.push("node.send(msg) })(msg,node);"),e.func=r.join("\n"),e}catch(e){return console.log(e),t}})}if(l.javascript){let r={},t=[];p.forEach(e=>{"javascript"!=(r[e.id]=e).format&&"function"!=e.type&&"css"!=e.format&&"json"!=e.format||t.push(e)});var e={parse:{bare_returns:!0,expression:!1},compress:{},mangle:{reserved:["$","export","require"]},output:null,sourceMap:null,nameCache:null,toplevel:!1,warnings:!1},o={compatibility:"*",level:2};$.ajax({url:"ClientCode/"+RED.nodes.id()+"/ugify",type:"POST",contentType:"application/json; charset=utf-8",data:JSON.stringify({nodes:t,jscfg:e,csscfg:o}),success:function(e){e.forEach(e=>{var t=r[e.id];e._error&&console.log("ERROR NODE",e),"function"==t.type?t.func=e.func:"javascript"!=t.format&&"css"!=t.format&&"json"!=t.format||(t.template=e.template)}),obfuscateHelpers.openImportDialog(p)},error:function(e,t,r){RED.notify("ClientCode Communcation Failure: "+n.id+": "+t,{type:"error",timeout:3e3})}})}else obfuscateHelpers.openImportDialog(p)}
6
6
 
7
- function setupTreelist(){var e=collectOrphans();if(0==e.length){RED.notify("No Orphans Found",{type:"warning",timeout:2e3});try{$("#node-input-orphan-target-container-div").treeList("empty")}catch(e){}}else{try{$("#node-input-orphan-target-container-div").treeList("empty")}catch(e){$("#node-input-orphan-target-container-div").css({width:"100%",height:"calc(100%)"}).treeList({multi:!1}).on("treelistitemmouseover",function(e,t){}).on("treelistitemmouseout",function(e,t){}).on("treelistselect",function(e,t){t.node&&(RED.workspaces.show(t.node.z,!1,!1,!0),RED.view.reveal(t.node.id,!0),RED.view.redraw())}).on("treelistconfirm",function(e,t){var n;t.node&&(n=t.node.id,setTimeout(()=>{var e=RED.nodes.node(n);e&&(RED.view.reveal(e.id),RED.view.select(e.id),RED.editor.edit(e)),n==RED.workspaces.active()&&RED.workspaces.edit()},50))}),$("#node-input-orphan-target-filter").show();var n=$("#node-input-orphan-target-filter").searchBox({style:"compact",delay:300,change:function(){var e,t=$(this).val().trim().toLowerCase();""===t?($("#node-input-orphan-target-container-div").treeList("filter",null),n.searchBox("count","")):(e=$("#node-input-orphan-target-container-div").treeList("filter",function(e){return-1<e.label.toLowerCase().indexOf(t)||-1<e.node.type.toLowerCase().indexOf(t)}),n.searchBox("count",e+" / "+$("#node-input-orphan-target-container-div").treeList("data").length))}})}$("#node-input-orphan-target-container-div").treeList("data",e.sort((e,t)=>e.node.z>t.node.z))}}function collectOrphans(){const t=new Set;var n=[],i=(RED.nodes.eachLink(e=>{t.add(e.source),t.add(e.target)}),RED.nodes.eachNode(e=>{t.has(e)||n.push(e)}),[]),r={};return n.forEach(function(e){var t=RED.nodes.getType(e.type);if(t){var n=t.label,n=("function"==typeof n?n.call(e):n)||"",o=e.type;if(0===o.indexOf("subflow:"))return}t&&n||(n=e.type),r[e.id]={node:e,label:n,sublabel:o,selected:!1,checkbox:!1},i.push(r[e.id])}),i}
7
+ function setupTreelist(){var e=collectOrphans();if(0==e.length){RED.notify("No Orphans Found",{type:"success",timeout:2e3});try{$("#node-input-orphan-target-container-div").treeList("empty")}catch(e){}}else{try{$("#node-input-orphan-target-container-div").treeList("empty")}catch(e){$("#node-input-orphan-target-container-div").css({width:"100%",height:"calc(100%)"}).treeList({multi:!1}).on("treelistitemmouseover",function(e,t){}).on("treelistitemmouseout",function(e,t){}).on("treelistselect",function(e,t){t.node&&(RED.workspaces.show(t.node.z,!1,!1,!0),RED.view.reveal(t.node.id,!0),RED.view.redraw())}).on("treelistconfirm",function(e,t){var n;t.node&&(n=t.node.id,setTimeout(()=>{var e=RED.nodes.node(n);e&&(RED.view.reveal(e.id),RED.view.select(e.id),RED.editor.edit(e)),n==RED.workspaces.active()&&RED.workspaces.edit()},50))}),$("#node-input-orphan-target-filter").show();var n=$("#node-input-orphan-target-filter").searchBox({style:"compact",delay:300,change:function(){var e,t=$(this).val().trim().toLowerCase();""===t?($("#node-input-orphan-target-container-div").treeList("filter",null),n.searchBox("count","")):(e=$("#node-input-orphan-target-container-div").treeList("filter",function(e){return-1<e.label.toLowerCase().indexOf(t)||-1<e.node.type.toLowerCase().indexOf(t)}),n.searchBox("count",e+" / "+$("#node-input-orphan-target-container-div").treeList("data").length))}})}$("#node-input-orphan-target-container-div").treeList("data",e.sort((e,t)=>e.node.z>t.node.z))}}function collectOrphans(){const t=new Set;var n=[],i=(RED.nodes.eachLink(e=>{t.add(e.source),t.add(e.target)}),RED.nodes.eachNode(e=>{(!t.has(e)||"link out"==e.type&&"link"==e.mode&&0==e.links.reduce((e,t)=>e||!!RED.nodes.node(t),!1))&&n.push(e)}),[]),r={};return n.forEach(function(e){var t=RED.nodes.getType(e.type);if(t){var n=t.label,n=("function"==typeof n?n.call(e):n)||"",o=e.type;if(0===o.indexOf("subflow:"))return}t&&n||(n=e.type),r[e.id]={node:e,label:n,sublabel:o,selected:!1,checkbox:!1},i.push(r[e.id])}),i}
8
8
 
9
9
  function nr_intro_generate_svg_3_1(r,t){return e=>{try{handleSvgObject($($("svg[width=8000]")[0]),e,t)}catch(t){var n="Error Generating SVG: "+JSON.stringify(t);r.notify(n,{type:"error"}),e('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+n+"</text></svg>")}}}function nr_intro_generate_svg_3_0(r,t){return e=>{try{handleSvgObject($($("svg")[0]),e,t)}catch(t){var n="Error Generating SVG: "+JSON.stringify(t);r.notify(n,{type:"error"}),e('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+n+"</text></svg>")}}}function handleSvgObject(a,e,t){var n=a.clone(),r=(n.find("foreignObject").remove(),n.find("svg.__screenshot").remove(),'width="'+a.attr("width")+'" height="'+a.attr("height")+'"'),s=RED.settings.version.split("."),i=parseInt(s[0]),s=parseInt(s[1]);if(3<=i&&1<=s){var o=$($($(a).children("g")[0]).children("g")[0]).children("g"),l={x:8e3,y:8e3,w:-1,h:-1};for(let t=1;t<o.length;t++){var g=o[t].getBBox();0==g.width&&0==g.height||(l.x=Math.min(g.x,l.x),l.y=Math.min(g.y,l.y),l.w=Math.max(g.width,l.w),l.h=Math.max(g.height,l.h))}r+=` viewBox='${l.x} ${l.y} ${l.w} ${l.h}'`}function c(t,e){for(var n=0;n<t.length;n++){var s=t.item(n),i=e[n];["stroke-width","fill-opacity","stroke-opacity","opacity","stroke-dasharray"].forEach(function(t){s.setAttribute(t,$(i).attr(t)||$(i).css(t))}),["fill","stroke"].forEach(function(t){var e,n,r=(e=$(i).attr(t)||$(i).css(t))&&null!==e&&"none"!=e?(n=e.match(/^#(.)(.)(.)$/))?"#"+n[1]+n[1]+n[2]+n[2]+n[3]+n[3]:(n=e.match(/^#......$/))?e:null===(n=e.match(/^rgb\(([0-9]+),\s+([0-9]+),\s+([0-9]+)/))?(r=e.match(/^rgba\(([0-9]+),\s+([0-9]+),\s+([0-9]+),\s+([0-9]+)/))?{clr:"#"+("0"+parseInt(r[1],10).toString(16)).slice(-2)+("0"+parseInt(r[2],10).toString(16)).slice(-2)+("0"+parseInt(r[3],10).toString(16)).slice(-2),opa:r[4]}:(console.log("Screenshot node: returned unknown color: "+e),e):"#"+("0"+parseInt(n[1],10).toString(16)).slice(-2)+("0"+parseInt(n[2],10).toString(16)).slice(-2)+("0"+parseInt(n[3],10).toString(16)).slice(-2):"none";"object"==typeof r?(s.setAttribute(t+"-opacity",r.opa),s.setAttribute(t,r.clr)):s.setAttribute(t,r)}),$(i).hasClass("hide")&&("g"==s.tagName&&s.setAttribute("opacity","0"),s.setAttribute("visibility","hidden"))}}var i='<?xml version="1.0" standalone="no"?>\r\n<svg '+r+' pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" class="__screenshot" xmlns:xlink="http://www.w3.org/1999/xlink">\r\n',s=n.html(),h=(new DOMParser).parseFromString(i+s+"\r\n</svg>","image/svg+xml"),v=t=>t,f=(t.rmidsandclasses&&(v=e=>(["g","rect","line","path","circle","image","text"].forEach(t=>{$(e.getElementsByTagName(t)).each((t,e)=>{e.setAttribute("class",""),e.setAttribute("id","")})}),e)),["g","rect","line","path","circle","image"].forEach(function(t){c(h.getElementsByTagName(t),a.find(t))}),["text"].forEach(function(t){c(h.getElementsByTagName(t),a.find(t));for(var e=h.getElementsByTagName(t),n=a.find(t),r=0;r<e.length;r++){var s=e.item(r),i=n[r];["font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","text-anchor","dominant-baseline"].forEach(function(t){s.setAttribute(t,$(i).attr(t)||$(i).css(t))})}}),h.getElementsByTagName("image")),m={},w=(n,r,s)=>{var i=n.getAttribute("xlink:href"),a=i.substr(-4,4).toLowerCase(),o={".jpg":"jpeg",jpeg:"jpeg",".png":"png",".svg":"svg+xml"};if(m[i])return n.setAttribute("xlink:href","data:image/"+o[a]+";base64,"+m[i]),s(r-1);switch(a){case".jpg":case"jpeg":case".png":var l=new XMLHttpRequest;l.open("GET",i,!0),l.responseType="arraybuffer";l.onload=function(t){var e=l.response;e&&(e=(t=>{for(var e="",n=new Uint8Array(t),r=n.byteLength,s=0;s<r;s++)e+=String.fromCharCode(n[s]);return window.btoa(e)})(e),m[i]=e,n.setAttribute("xlink:href","data:image/"+o[a]+";base64,"+e)),s(r-1)},l.send(null);break;case".svg":$.get(i,function(t){var e=new XMLSerializer,e=btoa(e.serializeToString(t));m[i]=e,n.setAttribute("xlink:href","data:image/svg+xml;base64,"+e),s(r-1)})}},d=t=>{t<0?e((new XMLSerializer).serializeToString(v(h))):w(f.item(t),t,d)};0<f.length?w(f.item(f.length-1),f.length-1,d):e((new XMLSerializer).serializeToString(v(h)))}function generatorFunctionForVersion(t,e){var n,r=t.settings.version.split("."),s=r[0],r=r[1];if("3"==s){if("0"==r)return nr_intro_generate_svg_3_0(t,e);if("1"==r)return nr_intro_generate_svg_3_1(t,e)}return n=t,t=>{var e="Node-RED version ("+n.settings.version+") not supported";n.notify(e,{type:"error"}),t&&t('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+e+"</text></svg>")}}function addPanZoom(){var t=d3.select("#node-input-screenshot-svgcontainer svg"),e=(t.html("<g>"+t.html()+"</g>"),setTimeout(()=>{var t=$("#node-input-screenshot-svgcontainer svg"),e=$(t).attr("viewBox").split(" ");t.animate({height:parseInt(e[3]),width:parseInt(e[2])},800,"swing")},100),t.select("g")),n=d3.behavior.zoom().scaleExtent([.1,200]).on("zoom",function(t){e.attr({transform:"translate("+n.translate()+") scale("+n.scale()+")"})});t.call(n)}
10
10
 
@@ -12,6 +12,8 @@
12
12
 
13
13
  function highlightAllLinkCalls(){let e=[];RED.nodes.eachNode(t=>{t.z==RED.workspaces.active()&&"link call"==t.type&&e.push(t)}),e.forEach(t=>{$("#grplnkcallclk-"+t.id).remove();var e=document.createElementNS("http://www.w3.org/2000/svg","g"),l=(e.setAttribute("class","red-ui-linkcall-link-indicator"),e.setAttribute("transform","translate(12,0)"),e.setAttribute("id","grplnkcallclk-"+t.id),document.createElementNS("http://www.w3.org/2000/svg","circle")),i=(l.setAttribute("cx","5"),l.setAttribute("cy","5"),l.setAttribute("r","5"),l.setAttribute("id","lnkcallclk-"+t.id),l.setAttribute("style","cursor: pointer;"),t.links[0]);i?RED.nodes.node(i)?l.setAttribute("fill","green"):l.setAttribute("fill","red"):l.setAttribute("fill","orange"),e.append(l),$(e).insertBefore($("#"+t.id).find(".red-ui-flow-node-changed"));let r=t.id;$("#lnkcallclk-"+t.id).on("click",t=>{t&&t.preventDefault();t=RED.nodes.node(r);if(t){let l=t.links[0];1<t.links.length&&RED.notify("Multiple links, showing first","warning"),RED.nodes.node(l)?(RED.view.reveal(l),RED.view.select(l),setTimeout(()=>{$("#lnkcallclk-"+l).remove();var t=document.createElementNS("http://www.w3.org/2000/svg","g"),e=(t.setAttribute("class","red-ui-linkcall-link-indicator"),t.setAttribute("transform","translate(12,0)"),t.setAttribute("id","lnkcallclk-"+l),document.createElementNS("http://www.w3.org/2000/svg","circle"));e.setAttribute("cx","5"),e.setAttribute("cy","5"),e.setAttribute("r","5"),e.setAttribute("id","lnkcallclk-"+l),e.setAttribute("style","cursor: pointer;"),e.setAttribute("fill","green"),t.append(e),$(t).insertBefore($("#"+l).find(".red-ui-flow-node-changed")),$("#lnkcallclk-"+l).on("click",t=>{t&&t.preventDefault(),RED.view.reveal(r),RED.view.select(r),setTimeout(highlightAllLinkCalls,500)})},500)):RED.notify("Link in node not found","error")}else RED.notify("Node not found","error")})})}
14
14
 
15
+ function setupTreelistAllLinksForLinkIn(e){e=collectAllLinksNodes(e);if(0==e.length){RED.notify("No links found",{type:"error",timeout:2e3});try{$("#node-input-orphan-target-container-div").treeList("empty")}catch(e){}}else{try{$("#node-input-orphan-target-container-div").treeList("empty")}catch(e){$("#node-input-orphan-target-container-div").css({width:"100%",height:"calc(100%)"}).treeList({multi:!1}).on("treelistitemmouseover",function(e,t){}).on("treelistitemmouseout",function(e,t){}).on("treelistselect",function(e,t){t.node&&(RED.workspaces.show(t.node.z,!1,!1,!0),RED.view.reveal(t.node.id,!0),RED.view.redraw())}).on("treelistconfirm",function(e,t){var n;t.node&&(n=t.node.id,setTimeout(()=>{var e=RED.nodes.node(n);e&&(RED.view.reveal(e.id),RED.view.select(e.id),RED.editor.edit(e,"editor-tab-description")),n==RED.workspaces.active()&&RED.workspaces.edit()},50))}),$("#node-input-orphan-target-filter").show();var n=$("#node-input-orphan-target-filter").searchBox({style:"compact",delay:300,change:function(){var e,t=$(this).val().trim().toLowerCase();""===t?($("#node-input-orphan-target-container-div").treeList("filter",null),n.searchBox("count","")):(e=$("#node-input-orphan-target-container-div").treeList("filter",function(e){return-1<e.label.toLowerCase().indexOf(t)||-1<e.node.type.toLowerCase().indexOf(t)}),n.searchBox("count",e+" / "+$("#node-input-orphan-target-container-div").treeList("data").length))}})}$("#node-input-orphan-target-container-div").treeList("data",e.sort((e,t)=>e.node.z>t.node.z))}}function collectAllLinksNodes(t){let n=[];RED.nodes.eachNode(e=>{"link call"!=e.type&&"link out"!=e.type||-1<e.links.indexOf(t)&&n.push(e)});var i=[],r={};return n.forEach(function(e){var t=RED.nodes.getType(e.type);if(t){var n,o=t.label,o=("function"==typeof o?o.call(e):o)||"";if(0===(n=e.type).indexOf("subflow:"))return}t&&o||(o=e.type),r[e.id]={node:e,label:o,sublabel:n,selected:!1,checkbox:!1},i.push(r[e.id])}),i}
16
+
15
17
  // Add your plugin as a new tabsheet in the right sidebar AFTER the flow editor is completely started
16
18
  var initialiseConfigNodeOnce = () => {
17
19
  RED.events.off('runtime-state', initialiseConfigNodeOnce);
@@ -48,8 +50,8 @@
48
50
  // Add tab, orphans
49
51
  tabs.addTab({
50
52
  id: 'func-introspection-tab-orphans',
51
- iconClass: 'fa fa-life-ring',
52
- label: 'Orphans'
53
+ iconClass: 'fa fa-eyedropper',
54
+ label: 'Lint'
53
55
  });
54
56
 
55
57
  // Add tab, obfuscation
@@ -213,11 +215,38 @@
213
215
  setupTreelistInfoness();
214
216
  })
215
217
 
218
+ $('#node-input-linkin-nodes-find-btn').on('click', (e) => {
219
+ if ( e ) { e.preventDefault() }
220
+
221
+ let linkNodes = (RED.view.selection().nodes || []).filter( (e) => {
222
+ return e.type == "link in"
223
+ }).map( e => e.id )
224
+
225
+ if ( linkNodes.length == 0 ) {
226
+ RED.notify(
227
+ "No link in node selected, selected exactly one.", {
228
+ type: "error"
229
+ }
230
+ );
231
+ } else {
232
+ setupTreelistAllLinksForLinkIn(linkNodes[0])
233
+ }
234
+ })
235
+
236
+ $('#node-input-orphan-clear-workspace-btn').on('click', (e) => {
237
+ if ( e ) { e.preventDefault() }
238
+ $('.red-ui-linkcall-link-indicator').remove()
239
+ })
240
+
216
241
  RED.actions.add( "introspection:show-link-call-links", highlightAllLinkCalls)
217
242
 
218
243
  $('#node-input-linkcalls-find-btn').on('click', (e) => {
219
244
  if ( e ) { e.preventDefault() }
220
245
  highlightAllLinkCalls()
246
+
247
+ try {
248
+ $("#node-input-orphan-target-container-div").treeList('empty')
249
+ } catch (ex) { }
221
250
  })
222
251
  };
223
252
  RED.events.on('runtime-state', initialiseConfigNodeOnce);
@@ -301,6 +330,13 @@
301
330
  class="red-ui-button"><i class="fa fa-cc"></i> Undocumented</button>
302
331
  <button id="node-input-linkcalls-find-btn"
303
332
  class="red-ui-button"><i class="fa fa-link"></i> Link Calls</button>
333
+ <button id="node-input-linkin-nodes-find-btn"
334
+ class="red-ui-button"><i class="fa fa-link"></i> Link Ins</button>
335
+ </div>
336
+
337
+ <div class="form-row" style="margin-left: 10px;">
338
+ <button id="node-input-orphan-clear-workspace-btn"
339
+ class="red-ui-button">Clear dots</button>
304
340
  </div>
305
341
 
306
342
  <div class="form-row node-input-target-row node-input-target-list-row"
@@ -310,6 +346,7 @@
310
346
  </div>
311
347
  <div id="node-input-orphan-target-container-div"></div>
312
348
  </div>
349
+
313
350
  </div>
314
351
  </div>
315
352
  </div>