@gregoriusrippenstein/node-red-contrib-nodedev 0.1.6 → 0.1.8

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
@@ -1,5 +1,8 @@
1
1
  ## Node-RED nodes for the development of Node-RED nodes in Node-RED using Node-RED nodes.
2
2
 
3
+
4
+ *** Work In Progress, not stable. ***
5
+
3
6
  *What?*
4
7
 
5
8
  The idea behind this collection of nodes to democratise the development of Node-RED nodes. Normally the development of your [own nodes](https://nodered.org/docs/creating-nodes/) will require the use of a third-party editor. Something like VSCode or Vim or Atom or god forbid, [Emacs](https://discourse.nodered.org/t/node-red-node-development-in-node-red/81525/2). This requirement makes creating nodes that much harder. So why not create nodes for Node-RED in Node-RED? After all, if you are using Node-RED, you probably understand how to use Node-RED.
@@ -59,10 +62,14 @@ Since all this happens in the browser, there is no leaving Node-RED, making it m
59
62
  ### Artifacts
60
63
 
61
64
  - [Flow that maintains](https://flowhub.org/f/b92be5062203ff69) this codebase
62
- - [GitHub repo](https://flows.nodered.org/node/@gregoriusrippenstein/node-red-contrib-nodedev)
65
+ - [GitHub repo](https://github.com/gorenje/node-red-contrib-nodedev)
63
66
  - [NPMjs node package](https://www.npmjs.com/package/@gregoriusrippenstein/node-red-contrib-nodedev)
64
67
  - [Node-RED node package](https://flows.nodered.org/node/@gregoriusrippenstein/node-red-contrib-nodedev)
65
68
 
69
+ ### Similar Projects
70
+
71
+ - [node-maker](https://github.com/steveorevo/node-maker) which helps to make UI components for nodes in Node-RED.
72
+
66
73
  ### Outlook
67
74
 
68
75
  Wherever the road shall lead, there it will go.
@@ -97,21 +97,34 @@
97
97
 
98
98
  </script>
99
99
 
100
+ <script type="text/html" data-help-name="NodeFactory">
101
+ <p>Generate node templates providing starting points for node development</p>
102
+ The Node-Factory can be used to create example code for various types of Node-RED nodes.
103
+ </script>
104
+
100
105
  <script type="text/javascript">
101
106
  (function () {
102
107
 
103
- RED.comms.subscribe("nodedev:perform-autoimport-nodes", (event,data) => {
104
- if ( data.msg == "autoimport" ) {
105
- setTimeout(() => {
106
- RED.clipboard.import();
107
-
108
+ RED.comms.subscribe("nodedev:perform-autoimport-nodes", (event,data) => {
109
+ if ( data.msg == "notify") {
110
+ RED.notify(data.text, {
111
+ type: data.type,
112
+ id: data.id || "NodeFactory",
113
+ timeout: data.timeout || 2000
114
+ });
115
+ }
116
+
117
+ if ( data.msg == "autoimport" ) {
108
118
  setTimeout(() => {
109
- $('#red-ui-clipboard-dialog-import-text').val(
110
- data.payload
111
- ).trigger("paste");
112
- }, 300);
113
- },400)
114
- }
119
+ RED.clipboard.import();
120
+
121
+ setTimeout(() => {
122
+ $('#red-ui-clipboard-dialog-import-text').val(
123
+ data.payload
124
+ ).trigger("paste");
125
+ }, 300);
126
+ },400)
127
+ }
115
128
  });
116
129
 
117
130
  function doSubmission(node) {
@@ -5,9 +5,6 @@ module.exports = function (RED) {
5
5
  var path = require('path');
6
6
 
7
7
  var mustache = require("mustache");
8
- var tarStream = require('tar-stream');
9
- var streamx = require('streamx');
10
- var pakoGzip = require('pako');
11
8
 
12
9
  var spcRepDict = {
13
10
  "ocb2": "{{",
@@ -229,16 +226,22 @@ module.exports = function (RED) {
229
226
  function createSimpleNodeDefintions(msg,node) {
230
227
  var htmlPath = path.join(__dirname, 'templates', 'tmpl.html');
231
228
  var jsPath = path.join(__dirname, 'templates', 'tmpl.js');
229
+ var localeJsonPath = path.join(__dirname, 'templates', 'locale', 'en-US', 'tmpl.json')
230
+ var localeHtmlPath = path.join(__dirname, 'templates', 'locale', 'en-US', 'tmpl.html')
232
231
 
233
232
  var content = {};
234
233
 
235
234
  var promises = [
236
235
  handleTemplate(msg, node, fs.readFileSync(htmlPath, 'utf8')).then((c) => { content["html"] = c }),
237
236
  handleTemplate(msg, node, fs.readFileSync(jsPath, 'utf8')).then((c) => { content["jasc"] = c }),
237
+ handleTemplate(msg, node, fs.readFileSync(localeJsonPath, 'utf8')).then((c) => { content["ljsn"] = c }),
238
+ handleTemplate(msg, node, fs.readFileSync(localeHtmlPath, 'utf8')).then((c) => { content["lhtm"] = c }),
238
239
  ];
239
240
 
240
241
  return Promise.all(promises).then(() => {
241
242
  var secondId = RED.util.generateId();
243
+ var thirdId = RED.util.generateId();
244
+ var fourthId = RED.util.generateId();
242
245
 
243
246
  return [{
244
247
  id: RED.util.generateId(),
@@ -253,7 +256,6 @@ module.exports = function (RED) {
253
256
  y: 50,
254
257
  wires: [[secondId]]
255
258
  },
256
-
257
259
  {
258
260
  id: secondId,
259
261
  type: "PkgFile",
@@ -265,8 +267,35 @@ module.exports = function (RED) {
265
267
  output: "str",
266
268
  x: 100,
267
269
  y: 100,
270
+ wires: [[thirdId]]
271
+ },
272
+ {
273
+ id: thirdId,
274
+ type: "PkgFile",
275
+ name: "Locale: " + msg.node.name + ".json",
276
+ filename: "nodes/locales/en-US/" + msg.node.namelwr + ".json",
277
+ template: content["ljsn"],
278
+ syntax: "mustache",
279
+ format: "json",
280
+ output: "str",
281
+ x: 120,
282
+ y: 150,
283
+ wires: [[fourthId]]
284
+ },
285
+ {
286
+ id: fourthId,
287
+ type: "PkgFile",
288
+ name: "Locale: " + msg.node.name + ".html",
289
+ filename: "nodes/locales/en-US/" + msg.node.namelwr + ".html",
290
+ template: content["lhtm"],
291
+ syntax: "mustache",
292
+ format: "html",
293
+ output: "str",
294
+ x: 120,
295
+ y: 200,
268
296
  wires: [[]]
269
- }]
297
+ }
298
+ ]
270
299
  })
271
300
  }
272
301
 
@@ -430,76 +459,12 @@ module.exports = function (RED) {
430
459
  } else {
431
460
  /* assume that payload is a buffer with a .tgz if not, error out */
432
461
  try {
433
- const extract = tarStream.extract()
434
-
435
- var allFiles = [];
436
-
437
- /*
438
- * there is no indication in a tar file of whether a file is binary or textual.
439
- * we can only make a guess by the extension of the filename.
440
- ***/
441
- var computeFormat = (filename) => {
442
- var ext = filename.split(".").at(-1);
443
-
444
- return {
445
- "html": "html",
446
- "js": "javascript",
447
- "md": "markdown",
448
- "json": "json",
449
- /* binary formats are encoded in base64 */
450
- "png": "base64",
451
- "tiff": "base64",
452
- "tif": "base64",
453
- "jpg": "base64",
454
- "jpeg": "base64",
455
- "bin": "base64",
456
- "bmp": "base64",
457
- "gif": "base64",
458
- }[ext.toLowerCase()] || "text";
459
- };
460
-
461
- extract.on('entry', function (header, stream, next) {
462
- // header is the tar header
463
- // stream is the content body (might be an empty stream)
464
- // call next when you are done with this entry
465
-
466
- var buffer = [];
467
-
468
- stream.on('data', function (data) {
469
- buffer.push(data)
470
- });
471
-
472
- stream.on('end', function () {
473
- var frmt = computeFormat(header.name.split("/").at(-1));
474
-
475
- allFiles.push({
476
- id: RED.util.generateId(),
477
- type: "PkgFile",
478
- name: header.name.split("/").at(-1),
479
- filename: header.name.replace(/^package\//, ''),
480
- template: Buffer.concat(buffer).toString(frmt == "base64" ? 'base64' : 'utf8'),
481
- syntax: "mustache",
482
- format: frmt,
483
- output: "str",
484
- x: 100,
485
- y: 50 * (allFiles.length + 1),
486
- wires: [
487
- []
488
- ]
489
- })
490
-
491
- next() // ready for next entry
492
- })
493
-
494
- stream.resume() // just auto drain the stream
495
- })
496
-
497
- extract.on('finish', function () {
498
- // all entries read, wire them together
499
- for (var idx = 0; idx < allFiles.length - 1; idx++) {
500
- allFiles[idx].wires = [[allFiles[idx + 1].id]];
501
- }
462
+ const onError = (err) => {
463
+ msg.error = err;
464
+ done("extraction error", msg)
465
+ }
502
466
 
467
+ const onFinish = (allFiles) => {
503
468
  msg.payload = JSON.stringify(allFiles);
504
469
  send(msg)
505
470
 
@@ -515,15 +480,9 @@ module.exports = function (RED) {
515
480
  );
516
481
 
517
482
  done()
518
- })
519
-
520
- extract.on('error', function (err) {
521
- msg.error = err;
522
- done("extraction error", msg)
523
- });
524
-
525
- var stream = streamx.Readable.from(Buffer.from(pakoGzip.inflate(new Uint8Array(msg.payload))))
526
- stream.pipe(extract);
483
+ }
484
+
485
+ require('./lib/tarhelpers.js').convertTarFile(RED, msg.payload, onFinish, onError)
527
486
  } catch (err) {
528
487
  msg.error = err
529
488
  done(err.message, msg)
@@ -0,0 +1,106 @@
1
+ module.exports = (function () {
2
+
3
+ var tarStream = require('tar-stream');
4
+ var streamx = require('streamx');
5
+ var pakoGzip = require('pako');
6
+
7
+ /*
8
+ * there is no indication in a tar file of whether a file is binary or textual.
9
+ * we can only make a guess by the extension of the filename.
10
+ ***/
11
+ var computeFormat = (filename) => {
12
+ var ext = filename.split(".").at(-1);
13
+
14
+ return {
15
+ "html": "html",
16
+ "js": "javascript",
17
+ "md": "markdown",
18
+ "json": "json",
19
+ "txt": "text",
20
+ "css": "css",
21
+ "yaml": "yaml",
22
+ "yml": "yaml",
23
+
24
+ /* binary formats are encoded in base64 */
25
+ "png": "base64",
26
+ "tiff": "base64",
27
+ "tif": "base64",
28
+ "jpg": "base64",
29
+ "jpeg": "base64",
30
+ "bin": "base64",
31
+ "bmp": "base64",
32
+ "gif": "base64",
33
+ "woff2": "base64",
34
+ "woff": "base64",
35
+ "ttf": "base64",
36
+ "mov": "base64",
37
+ "ico": "base64",
38
+ "eot": "base64",
39
+ }[ext.toLowerCase()] || "text";
40
+ }
41
+
42
+ var convertTarFile = (RED, tgzData, onFinish, onError) => {
43
+ const extract = tarStream.extract()
44
+
45
+ var allFiles = [];
46
+
47
+ extract.on('entry', function (header, stream, next) {
48
+ // header is the tar header
49
+ // stream is the content body (might be an empty stream)
50
+ // call next when you are done with this entry
51
+
52
+ var buffer = [];
53
+
54
+ stream.on('data', function (data) {
55
+ buffer.push(data)
56
+ });
57
+
58
+ stream.on('end', function () {
59
+ var frmt = computeFormat(header.name.split("/").at(-1));
60
+
61
+ allFiles.push({
62
+ id: RED.util.generateId(),
63
+ type: "PkgFile",
64
+ name: header.name.split("/").at(-1),
65
+ filename: header.name.replace(/^package\//, ''),
66
+ // @ts-ignore
67
+ template: Buffer.concat(buffer).toString(frmt == "base64" ? 'base64' : 'utf8'),
68
+ syntax: "plain", // not mustache templates, these are files.
69
+ format: frmt,
70
+ output: "str",
71
+ x: 250 * Math.floor(allFiles.length / 40),
72
+ y: 50 * (allFiles.length % 40),
73
+ wires: [
74
+ []
75
+ ]
76
+ })
77
+
78
+ next() // ready for next entry
79
+ })
80
+
81
+ stream.resume() // just auto drain the stream
82
+ })
83
+
84
+ extract.on('finish', function () {
85
+ // all entries read, wire them together
86
+ for (var idx = 0; idx < allFiles.length - 1; idx++) {
87
+ allFiles[idx].wires = [[allFiles[idx + 1].id]];
88
+ }
89
+
90
+ onFinish(allFiles)
91
+ })
92
+
93
+ extract.on('error', onError );
94
+
95
+ var stream = streamx.Readable.from(Buffer.from(pakoGzip.inflate(new Uint8Array(tgzData))))
96
+ stream.pipe(extract);
97
+ }
98
+
99
+
100
+ let exports = {
101
+ computeFormat: computeFormat,
102
+ convertTarFile: convertTarFile
103
+ }
104
+
105
+ return exports;
106
+ })();
@@ -0,0 +1,22 @@
1
+ <script type="text/javascript">
2
+ (function ($) {
3
+ RED.nodes.registerType('NodeFactorySidebarCfg', {
4
+ category: 'config',
5
+ hasUsers: false,
6
+ defaults: {
7
+ pkgname: { value: "node-red-dashboard" },
8
+ pkgversion: { value: "latest" }
9
+ },
10
+ paletteLabel: 'NodeFactorySidebarCfg',
11
+ label: function () {
12
+ return "NodeFactorySidebarCfg"
13
+ }
14
+ });
15
+ })(jQuery);
16
+ </script>
17
+
18
+ <!-- The html for the config node info panel (in right sidebar) -->
19
+ <script type="text/x-red" data-template-name="NodeFactorySidebarCfg">
20
+ <p>Generate nodes for an existing node package.</p>
21
+ Generate nodes for an existing node package.
22
+ </script>
@@ -0,0 +1,193 @@
1
+ module.exports = function (RED) {
2
+ let pacote = require('pacote');
3
+ let tarHelpers = require('./lib/tarhelpers.js');
4
+
5
+ function ConfigNodeFactorySidebarFunctionality(config) {
6
+ RED.nodes.createNode(this, config)
7
+ }
8
+
9
+ RED.nodes.registerType('NodeFactorySidebarCfg', ConfigNodeFactorySidebarFunctionality);
10
+
11
+ function createDevOpsNode(pkgname, pkgversion, x, y, nodeid) {
12
+ return {
13
+ "id": RED.util.generateId(),
14
+ "type": "NodeDevOps",
15
+ "name": "",
16
+ "pname": pkgname,
17
+ "pversion": pkgversion,
18
+ "pauthorname": "",
19
+ "pauthoremail": "",
20
+ "pdescription": "",
21
+ "noderedinstall": true,
22
+ "randompackagename": false,
23
+ "ignore_package_check": false,
24
+ "gitcommit": false,
25
+ "gitcheckforchange": false,
26
+ "githubowner": "",
27
+ "githubrepo": "",
28
+ "githubbranch": "main",
29
+ "githubauthor": "",
30
+ "githubauthoremail": "",
31
+ "githubmessage": "",
32
+ "npmpublish": false,
33
+ "npmunpublish": false,
34
+ "npmotp": "",
35
+ "x": x - 120,
36
+ "y": y - 50,
37
+ "wires": [
38
+ [nodeid]
39
+ ]
40
+ }
41
+ }
42
+
43
+ function createTarballNode(x,y,nodeid) {
44
+ return {
45
+ "id": RED.util.generateId(),
46
+ "type": "NpmTarBall",
47
+ "name": "",
48
+ "x": x+150,
49
+ "y": y+50,
50
+ "wires": [
51
+ [nodeid]
52
+ ]
53
+ };
54
+ }
55
+
56
+ function createNodeRedInstallNode(x,y) {
57
+ return {
58
+ "id": RED.util.generateId(),
59
+ "type": "NodeRedInstall",
60
+ "name": "",
61
+ "x": x+300,
62
+ "y": y+100,
63
+ "wires": [
64
+ []
65
+ ]
66
+ }
67
+ }
68
+
69
+ RED.httpAdmin.post("/NodeFactorySidebarCfg",
70
+ RED.auth.needsPermission("nodedev.write"),
71
+ (req, res) => {
72
+ try {
73
+ if (req.body) {
74
+ var msg = req.body;
75
+ if ( msg.pkgname && msg.pkgversion ) {
76
+ const onFinish = (allFiles) => {
77
+
78
+ /* these are no templates, these are files - no mustache intrepretation */
79
+ allFiles.forEach(function(element) {
80
+ element.syntax = "plain"
81
+ });
82
+
83
+ var lastNode = allFiles[allFiles.length - 1];
84
+
85
+ var nodeDevOp = createDevOpsNode(msg.pkgname, msg.pkgversion, allFiles[0].x, allFiles[0].y, allFiles[0].id)
86
+ var nrInstallNode = createNodeRedInstallNode(lastNode.x, lastNode.y)
87
+ var tarballNode = createTarballNode(lastNode.x, lastNode.y, nrInstallNode.id)
88
+
89
+ let pkgjson = allFiles.filter( a => {
90
+ return a.filename == "package.json"
91
+ })[0];
92
+
93
+ if ( pkgjson) {
94
+ let ctnt = JSON.parse(pkgjson.template);
95
+ nodeDevOp.pversion = ctnt.version
96
+ nodeDevOp.pdescription = ctnt.description || "Empty"
97
+ nodeDevOp.pauthorname = ctnt.author || "Empty"
98
+ nodeDevOp.pauthoremail = ctnt.author || "Empty"
99
+ }
100
+
101
+ lastNode.wires[0].push(tarballNode.id)
102
+
103
+ allFiles.push(nodeDevOp);
104
+ allFiles.push(tarballNode)
105
+ allFiles.push(nrInstallNode)
106
+
107
+ RED.comms.publish(
108
+ "nodedev:perform-autoimport-nodes",
109
+ RED.util.encodeObject({
110
+ msg: "autoimport",
111
+ payload: JSON.stringify(allFiles),
112
+ })
113
+ );
114
+ }
115
+
116
+ const onError = (err) => {
117
+ RED.comms.publish(
118
+ "nodedev:perform-autoimport-nodes",
119
+ RED.util.encodeObject({
120
+ msg: "notify",
121
+ text: "Failed to create nodes for " + msg.pkgname + "@" + msg.pkgversion + ": " + err,
122
+ type: "error",
123
+ })
124
+ );
125
+ }
126
+
127
+ pacote.manifest(
128
+ msg.pkgname + "@" + msg.pkgversion
129
+ ).then(manifest => {
130
+ RED.comms.publish(
131
+ "nodedev:perform-autoimport-nodes",
132
+ RED.util.encodeObject({
133
+ msg: "notify",
134
+ text: "Found url for " + msg.pkgname + "@" + msg.pkgversion,
135
+ type: "warning",
136
+ })
137
+ );
138
+
139
+ import('got').then((module) => {
140
+ module.got.get(manifest._resolved,
141
+ {
142
+ timeout: {
143
+ request: 25000,
144
+ response: 25000
145
+ },
146
+ responseType: 'buffer'
147
+ }).then(resp => {
148
+ try {
149
+ tarHelpers.convertTarFile(RED, resp.body, onFinish, onError);
150
+ } catch (err) {
151
+ onError(err)
152
+ }
153
+ }).catch(err => {
154
+ onError(err)
155
+ });
156
+ }).catch(err => {
157
+ onError(err)
158
+ });
159
+
160
+ }).catch(err => {
161
+ RED.comms.publish(
162
+ "nodedev:perform-autoimport-nodes",
163
+ RED.util.encodeObject({
164
+ msg: "notify",
165
+ text: "Failed to retrieve tgz file for " + msg.pkgname + "@" + msg.pkgversion + ": " + err,
166
+ type: "error",
167
+ })
168
+ );
169
+ })
170
+
171
+ res.sendStatus(200);
172
+
173
+ RED.comms.publish(
174
+ "nodedev:perform-autoimport-nodes",
175
+ RED.util.encodeObject({
176
+ msg: "notify",
177
+ text: "Retrieving " + msg.pkgname + "@" + msg.pkgversion,
178
+ type: "warning",
179
+ })
180
+ );
181
+ } else {
182
+ res.sendStatus(404);
183
+ }
184
+ } else {
185
+ res.sendStatus(405);
186
+ }
187
+ } catch (err) {
188
+ console.error("ERROR", err)
189
+ res.sendStatus(500);
190
+ }
191
+ }
192
+ );
193
+ }
@@ -0,0 +1,4 @@
1
+ <script type="text/html" data-help-name="{{ node.name }}">
2
+ <p>{{{ node.summary }}}</p>
3
+ {{{ node.description }}}
4
+ </script>
@@ -0,0 +1,24 @@
1
+ {
2
+ "{{ node.name }}": {
3
+ {{#node.bak2frtcomm}}
4
+ "notify": {
5
+ "commfrombackend": "Backend sent something to the frontend - check browser console.",
6
+ "msgfrombackend": "message from backend"
7
+ {{ #node.frt2bakcomm }}
8
+ },
9
+ {{ /node.frt2bakcomm }}
10
+ {{ ^node.frt2bakcomm }}
11
+ }
12
+ {{ /node.frt2bakcomm }}
13
+ {{/node.bak2frtcomm}}
14
+
15
+ {{ #node.frt2bakcomm }}
16
+ "notifyf2b": {
17
+ "submissionfailed": "submission failed",
18
+ "buttonpressed": "button pressed on frontend",
19
+ "success": "Successfully sent something to the backend"
20
+ }
21
+ {{ /node.frt2bakcomm }}
22
+
23
+ }
24
+ }
@@ -15,7 +15,7 @@
15
15
  }),
16
16
 
17
17
  success: function (resp) {
18
- RED.notify("Successfully sent something to the backend", {
18
+ RED.notify( RED._("{{ node.name}}.notifyf2b.success"), {
19
19
  type: "warning",
20
20
  id: "{{node.name}}",
21
21
  timeout: 2000
@@ -84,7 +84,7 @@
84
84
  this.messageFromBackendHandler = (topic,dataobj) => {
85
85
  console.log( "here goes the code for handling a message from the backend", topic, dataobj);
86
86
 
87
- RED.notify("Backend sent something to the frontend - check browser console.", {
87
+ RED.notify(RED._("{{ node.name}}.notify.commfrombackend"), {
88
88
  type: "success",
89
89
  id: "{{node.name}}",
90
90
  timeout: 2000
@@ -125,7 +125,7 @@
125
125
 
126
126
  {{ #node.frt2bakcomm }}
127
127
  var that = this;
128
- sendToBackend(that, {"payload": "button pressed on frontend"})
128
+ sendToBackend(that, {"payload": RED._("{{ node.name}}.notifyf2b.buttonpressed")})
129
129
  {{ /node.frt2bakcomm }}
130
130
 
131
131
  /* here goes the button code to be executed on click */
@@ -138,13 +138,8 @@
138
138
  </script>
139
139
 
140
140
  <script type="text/html" data-template-name="{{ node.name }}">
141
- <div class="form-row">
142
- <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
143
- <input type="text" id="node-input-name" placeholder="Name"/>
144
- </div>
145
- </script>
146
-
147
- <script type="text/html" data-help-name="{{ node.name }}">
148
- <p>{{{ node.summary }}}</p>
149
- {{{ node.description }}}
141
+ <div class="form-row">
142
+ <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
143
+ <input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
144
+ </div>
150
145
  </script>
@@ -14,8 +14,8 @@ module.exports = function(RED) {
14
14
  {{#node.bak2frtcomm}}
15
15
  RED.comms.publish("{{node.name}}:message-from-backend",
16
16
  RED.util.encodeObject({
17
- ...msg,
18
- "data": "message from backend",
17
+ ...msg,
18
+ "data": RED._("{{ node.name}}.notify.msgfrombackend"),
19
19
  })
20
20
  );
21
21
  {{/node.bak2frtcomm}}
@@ -49,7 +49,7 @@ module.exports = function(RED) {
49
49
  } catch (err) {
50
50
  console.error(err);
51
51
  res.status(500).send(err.toString());
52
- node.error("{{ node.name }}: Submission failed: " + err.toString())
52
+ node.error("{{ node.name }}: " + RED._("{{ node.name }}.notifyf2b.submissionfailed") + ": " + err.toString())
53
53
  }
54
54
  } else {
55
55
  res.sendStatus(404);
package/package.json CHANGED
@@ -1,34 +1,44 @@
1
1
  {
2
2
  "name" : "@gregoriusrippenstein/node-red-contrib-nodedev",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "dependencies": {
5
5
  "pako": "latest",
6
6
  "tar-stream": "latest",
7
7
  "mustache": "latest",
8
8
  "libnpmpublish": "latest",
9
- "streamx": "latest"
9
+ "streamx": "latest",
10
+ "got": "latest",
11
+ "pacote": "latest"
10
12
  },
11
13
 
12
14
  "keywords": [
13
- "node-red", "node development", "introspection", "bootstrapping", "recursion"
15
+ "node-red",
16
+ "node development",
17
+ "introspection",
18
+ "bootstrapping",
19
+ "recursion"
14
20
  ],
15
21
 
16
22
  "homepage": "https://github.com/gorenje/node-red-contrib-nodedev#readme",
17
23
  "license": "Don't do evil.",
18
- "author": "Gerrit Riessen <does.anyone@spread-the.love> (https://spread-the.love)",
24
+ "author": "Gerrit Riessen <gerrit@openmindmap.org> (https://spread-the.love)",
19
25
  "engines": {
20
26
  "node": ">=8"
21
27
  },
22
28
 
23
29
  "node-red" : {
24
30
  "version": ">=3.0.0",
31
+ "plugins": {
32
+ "nodefactory_sidebar": "plugins/node-factory-sidebar.html"
33
+ },
25
34
  "nodes": {
26
35
  "nodedevops": "nodes/05-node-dev-ops.js",
27
36
  "nodefactory": "nodes/10-node-factory.js",
28
37
  "pkgfile": "nodes/20-pkg-file.js",
29
38
  "npmtarball": "nodes/30-npm-tarball.js",
30
39
  "noderedinstall": "nodes/40-node-red-install.js",
31
- "npmpublish": "nodes/50-npm-publish.js"
40
+ "npmpublish": "nodes/50-npm-publish.js",
41
+ "nodefactorycfg": "nodes/node-factory-sidebar-cfg.js"
32
42
  }
33
43
  },
34
44
 
@@ -0,0 +1,188 @@
1
+ <script type="text/javascript">
2
+ (function() {
3
+ var globalYourConfigNode = null;
4
+
5
+ function doSubmission(pkgname,pkgversion) {
6
+ ensureYourConfigNodeExists();
7
+
8
+ $.ajax({
9
+ url: "NodeFactorySidebarCfg",
10
+ type: "POST",
11
+ contentType: "application/json; charset=utf-8",
12
+
13
+ data: JSON.stringify({
14
+ pkgname: $("#node-input-nodefactorysidebar-package-name").val(),
15
+ pkgversion: $("#node-input-nodefactorysidebar-package-version").val()
16
+ }),
17
+
18
+ success: function (resp) {
19
+ RED.notify("Submission triggered", {
20
+ type: "warning",
21
+ id: "NodeFactorySidebar",
22
+ timeout: 2000
23
+ });
24
+ },
25
+
26
+ error: function (jqXHR, textStatus, errorThrown) {
27
+ RED.notify("Submission failed: " + textStatus, {
28
+ type: "error",
29
+ id: "NodeFactorySidebar",
30
+ timeout: 2000
31
+ });
32
+ console.log(textStatus,errorThrown )
33
+ }
34
+ });
35
+ }
36
+
37
+ function ensureYourConfigNodeExists() {
38
+ // This function makes sure there is 1 instance of your config node is available, and that the globalYourConfigNode variable refers to it.
39
+ // Explained in the next step of this tutorial... --> https://discourse.nodered.org/t/tutorial-create-a-sidebar-plugin-and-persist-the-data-in-a-config-node/82020
40
+
41
+ // If we had found it previously, check if it has been deleted by the user behind our back
42
+ if (globalYourConfigNode !== null) {
43
+ var configNode = RED.nodes.node(globalYourConfigNode.id);
44
+ if (configNode === null) { globalYourConfigNode = null; }
45
+ }
46
+
47
+ // If not found previously, let's go find it
48
+ if (globalYourConfigNode === null) {
49
+ var configNodes = [];
50
+ RED.nodes.eachConfig(function(configNode) {
51
+ if (configNode.type === 'NodeFactorySidebarCfg') {
52
+ configNodes.push(configNode);
53
+ }
54
+ });
55
+
56
+ // Make sure we only have 1 config node
57
+ while (configNodes.length > 1) {
58
+ var configNode = configNodes.pop();
59
+ RED.nodes.remove(configNode.id);
60
+ RED.nodes.dirty(true);
61
+ }
62
+
63
+ // When we found a config node, let's use that one
64
+ if (configNodes.length === 1) { globalYourConfigNode = configNodes[0]; }
65
+ }
66
+
67
+ // When it doesn't exist yet, create it if required
68
+ if (globalYourConfigNode === null) {
69
+ // Remark: since this config node is dynamically created (and only used in this sidebar which isn't another node), the config
70
+ // node is in fact "unused". But since we don't want it to appear "unused" in the "config nodes" panel, we need to set hasUsers
71
+ // to false (see https://github.com/node-red/node-red/blob/master/CHANGELOG.md#0161-maintenance-release).
72
+ // The hasUsers needs also to be specified in the RED.nodes.registerType statement!
73
+ let typ = RED.nodes.getType("NodeFactorySidebarCfg");
74
+
75
+ globalYourConfigNode = {
76
+ id: RED.nodes.id(), // on the server side, this is called RED.util.generateId()
77
+ _def: typ,
78
+ type: "NodeFactorySidebarCfg",
79
+ hasUsers: false,
80
+ users: [],
81
+ name: "NodeFactorySidebar",
82
+ label: function() { return this.name || "NodeFactorySidebar"},
83
+ /* values and data defined by this config node */
84
+ pkgname: typ.defaults.pkgname.value,
85
+ pkgversion: typ.defaults.pkgversion.value
86
+ }
87
+
88
+ // Add the new config node to the collection of Node-RED nodes
89
+ RED.nodes.add(globalYourConfigNode);
90
+
91
+ // Make sure the "Deploy" button becomes active
92
+ RED.nodes.dirty(true);
93
+ }
94
+ }
95
+
96
+ // Add your plugin as a new tabsheet in the right sidebar AFTER the flow editor is completely started
97
+ var initialiseConfigNodeOnce = () => {
98
+ RED.events.off('runtime-state', initialiseConfigNodeOnce);
99
+
100
+ // The html content of the sidebar has been specified below as a data-template, from where it can be loaded:
101
+ var content = $($('script[type="text/x-red"][data-template-name="NodeFactorySidebar"]').i18n().html());
102
+
103
+ // Add a "Your sidebar" tabsheet to the right sidebar panel, in which this sidebar panel can be displayed
104
+ // --> more details: https://nodered.org/docs/api/ui/sidebar/
105
+ RED.sidebar.addTab({
106
+ id: "NodeFactorySidebar",
107
+ label: "Node Factory", // short name for the tab
108
+ name: "Node Factory", // long name for the menu
109
+ content: content,
110
+ closeable: true,
111
+ // disableOnEdit: true,
112
+ enableOnEdit: true,
113
+ iconClass: "fa fa-industry" // your fontawesome icon
114
+ });
115
+
116
+ ensureYourConfigNodeExists();
117
+
118
+ var doSomething = (e) => {
119
+ if (e) { e.preventDefault(); }
120
+ doSubmission()
121
+ }
122
+
123
+ $('#node-input-nodefactorysidebar-generate-btn').on('click', doSomething );
124
+
125
+ $("#node-input-nodefactorysidebar-package-version").val(globalYourConfigNode.pkgversion);
126
+
127
+ $("#node-input-nodefactorysidebar-package-version").on("change", function() {
128
+ ensureYourConfigNodeExists();
129
+
130
+ let data = $(this).val();
131
+
132
+ if (globalYourConfigNode.pkgversion != data) {
133
+ globalYourConfigNode.pkgversion = data;
134
+ RED.nodes.dirty(true);
135
+ }
136
+ })
137
+ $("#node-input-nodefactorysidebar-package-name").val(globalYourConfigNode.pkgname);
138
+
139
+ $("#node-input-nodefactorysidebar-package-name").on("change", function() {
140
+ ensureYourConfigNodeExists();
141
+
142
+ let data = $(this).val();
143
+
144
+ if (globalYourConfigNode.pkgname != data) {
145
+ globalYourConfigNode.pkgname = data;
146
+ RED.nodes.dirty(true);
147
+ }
148
+ })
149
+
150
+ };
151
+
152
+ RED.events.on('runtime-state', initialiseConfigNodeOnce);
153
+ })();
154
+ </script>
155
+
156
+ <!-- The html for the right sidebar plugin screen -->
157
+ <script type="text/x-red" data-template-name="NodeFactorySidebar">
158
+ <div class="form-row" style="margin-left: 10px; margin-top: 30px;">
159
+ Enter package details here:
160
+ </div>
161
+
162
+ <div class="form-row" style="margin-left: 10px;">
163
+ <label for="node-input-nodefactorysidebar-package-name">
164
+ <i class="fa fa-tag"></i>
165
+ Name
166
+ </label>
167
+ <input type="text" id="node-input-nodefactorysidebar-package-name"
168
+ placeholder="node-red-dashboard">
169
+ </div>
170
+
171
+ <div class="form-row" style="margin-left: 10px;">
172
+ <label for="node-input-nodefactorysidebar-package-version">
173
+ <i class="fa fa-tag"></i>
174
+ Version
175
+ </label>
176
+ <input type="text" id="node-input-nodefactorysidebar-package-version"
177
+ placeholder="3.5.0">
178
+ </div>
179
+
180
+ <div class="form-row">
181
+ <div style="position: relative; height: 100%; margin: 15px;">
182
+ <button id="node-input-nodefactorysidebar-generate-btn"
183
+ class="red-ui-button"
184
+ style="width: 100%; margin-top: 30px">Generate Nodes</button>
185
+ </div>
186
+ </div>
187
+
188
+ </script>