@gregoriusrippenstein/node-red-contrib-nodedev 0.0.6

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.
@@ -0,0 +1,135 @@
1
+ <script type="text/html" data-template-name="PkgFile">
2
+ <div class="form-row">
3
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
4
+ <div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" placeholder="Name"></div>
5
+ </div>
6
+
7
+ <div class="form-row">
8
+ <label for="node-input-filename"><i class="fa fa-tag"></i> Filename</label>
9
+ <div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-filename" placeholder="dir1/dir2/file.txt"></div>
10
+ </div>
11
+
12
+ <div class="form-row" style="position: relative; margin-bottom: 0px;">
13
+ <label for="node-input-template"><i class="fa fa-file-code-o"></i> Template</span></label>
14
+ <input type="hidden" id="node-input-template" autofocus="autofocus">
15
+ <div style="position: absolute; right:0;display:inline-block; text-align: right; font-size: 0.8em;">
16
+ Syntax:
17
+ <select id="node-input-format" style="width:110px; font-size: 10px !important; height: 24px; padding:0;">
18
+ <option value="handlebars">mustache</option>
19
+ <option value="html">HTML</option>
20
+ <option value="json">JSON</option>
21
+ <option value="javascript">JavaScript</option>
22
+ <option value="css">CSS</option>
23
+ <option value="markdown">Markdown</option>
24
+ <option value="php">PHP</option>
25
+ <option value="python">Python</option>
26
+ <option value="sql">SQL</option>
27
+ <option value="yaml">YAML</option>
28
+ <option value="base64">Base64</option>
29
+ <option value="text">Text</option>
30
+ </select>
31
+ <button type="button" id="node-pkgfile-expand-editor" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button>
32
+ </div>
33
+ </div>
34
+
35
+ <div class="form-row node-text-editor-row">
36
+ <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-pkgfile-editor" ></div>
37
+ </div>
38
+
39
+ <div class="form-row">
40
+ <label for="node-input-syntax"><i class="fa fa-code"></i> Syntax</label>
41
+ <select id="node-input-syntax" style="width:180px;">
42
+ <option value="mustache">Mustache</option>
43
+ <option value="plain">Plain</option>
44
+ </select>
45
+ </div>
46
+
47
+ </script>
48
+
49
+ <script type="text/javascript">
50
+ RED.nodes.registerType('PkgFile',{
51
+ color:"#e5e4ef",
52
+ category: 'nodedev',
53
+ defaults: {
54
+ name: {value:""},
55
+ filename: { value: ""},
56
+ format: {value:"handlebars"},
57
+ syntax: {value:"mustache"},
58
+ template: {value:""},
59
+ output: {value:"str"},
60
+ },
61
+ inputs:1,
62
+ outputs:1,
63
+ icon: "font-awesome/fa-file-o",
64
+ label: function() {
65
+ return this.name || this.filename || this._def.paletteLabel;
66
+ },
67
+ labelStyle: function() {
68
+ return this.name?"node_label_italic":"";
69
+ },
70
+ oneditprepare: function() {
71
+ const that = this;
72
+ const stateId = RED.editor.generateViewStateId("node", this, "");
73
+
74
+ if (!this.syntax) {
75
+ this.syntax = 'mustache';
76
+ $("#node-input-syntax").val(this.syntax);
77
+ }
78
+
79
+ this.editor = RED.editor.createEditor({
80
+ id: 'node-input-pkgfile-editor',
81
+ mode: 'ace/mode/html',
82
+ stateId: stateId,
83
+ value: $("#node-input-template").val()
84
+ });
85
+
86
+ $("#node-input-format").on("change", function() {
87
+ var mod = "ace/mode/"+$("#node-input-format").val();
88
+ that.editor.getSession().setMode({
89
+ path: mod,
90
+ v: Date.now()
91
+ });
92
+ });
93
+ RED.popover.tooltip($("#node-pkgfile-expand-editor"), RED._("node-red:common.label.expand"));
94
+ $("#node-pkgfile-expand-editor").on("click", function (e) {
95
+ e.preventDefault();
96
+ const value = that.editor.getValue();
97
+ that.editor.saveView();
98
+ RED.editor.editText({
99
+ mode: $("#node-input-format").val(),
100
+ value: value,
101
+ stateId: stateId,
102
+ width: "Infinity",
103
+ focus: true,
104
+ complete: function (v, cursor) {
105
+ that.editor.setValue(v, -1);
106
+ setTimeout(function () {
107
+ that.editor.restoreView();
108
+ that.editor.focus();
109
+ }, 250);
110
+ }
111
+ })
112
+ })
113
+ },
114
+ oneditsave: function() {
115
+ $("#node-input-template").val(this.editor.getValue());
116
+ this.editor.destroy();
117
+ delete this.editor;
118
+ },
119
+ oneditcancel: function() {
120
+ this.editor.destroy();
121
+ delete this.editor;
122
+ },
123
+ oneditresize: function(size) {
124
+ var rows = $("#dialog-form>div:not(.node-text-editor-row)");
125
+ var height = $("#dialog-form").height();
126
+ for (var i=0; i<rows.length; i++) {
127
+ height -= $(rows[i]).outerHeight(true);
128
+ }
129
+ var editorRow = $("#dialog-form>div.node-text-editor-row");
130
+ height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
131
+ $("#dialog-form .node-text-editor").css("height",height+"px");
132
+ this.editor.resize();
133
+ }
134
+ });
135
+ </script>
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Copyright JS Foundation and other contributors, http://js.foundation
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ **/
16
+
17
+ module.exports = function (RED) {
18
+ "use strict";
19
+ var mustache = require("mustache");
20
+
21
+ function extractTokens(tokens, set) {
22
+ set = set || new Set();
23
+ tokens.forEach(function (token) {
24
+ if (token[0] !== 'text') {
25
+ set.add(token[1]);
26
+ if (token.length > 4) {
27
+ extractTokens(token[4], set);
28
+ }
29
+ }
30
+ });
31
+ return set;
32
+ }
33
+
34
+ function parseContext(key) {
35
+ var match = /^(flow|global)(\[(\w+)\])?\.(.+)/.exec(key);
36
+ if (match) {
37
+ var parts = {};
38
+ parts.type = match[1];
39
+ parts.store = (match[3] === '') ? "default" : match[3];
40
+ parts.field = match[4];
41
+ return parts;
42
+ }
43
+ return undefined;
44
+ }
45
+
46
+ function parseEnv(key) {
47
+ var match = /^env\.(.+)/.exec(key);
48
+ if (match) {
49
+ return match[1];
50
+ }
51
+ return undefined;
52
+ }
53
+
54
+ /**
55
+ * Custom Mustache Context capable to collect message property and node
56
+ * flow and global context
57
+ */
58
+
59
+ function NodeContext(msg, nodeContext, parent, escapeStrings, cachedContextTokens) {
60
+ this.msgContext = new mustache.Context(msg, parent);
61
+ this.nodeContext = nodeContext;
62
+ this.escapeStrings = escapeStrings;
63
+ this.cachedContextTokens = cachedContextTokens;
64
+ }
65
+
66
+ NodeContext.prototype = new mustache.Context();
67
+
68
+ NodeContext.prototype.lookup = function (name) {
69
+ // try message first:
70
+ try {
71
+ var value = this.msgContext.lookup(name);
72
+ if (value !== undefined) {
73
+ if (this.escapeStrings && typeof value === "string") {
74
+ value = value.replace(/\\/g, "\\\\");
75
+ value = value.replace(/\n/g, "\\n");
76
+ value = value.replace(/\t/g, "\\t");
77
+ value = value.replace(/\r/g, "\\r");
78
+ value = value.replace(/\f/g, "\\f");
79
+ value = value.replace(/[\b]/g, "\\b");
80
+ }
81
+ return value;
82
+ }
83
+
84
+ // try env
85
+ if (parseEnv(name)) {
86
+ return this.cachedContextTokens[name];
87
+ }
88
+
89
+ // try flow/global context:
90
+ var context = parseContext(name);
91
+ if (context) {
92
+ var type = context.type;
93
+ var store = context.store;
94
+ var field = context.field;
95
+ var target = this.nodeContext[type];
96
+ if (target) {
97
+ return this.cachedContextTokens[name];
98
+ }
99
+ }
100
+ return '';
101
+ }
102
+ catch (err) {
103
+ throw err;
104
+ }
105
+ }
106
+
107
+ NodeContext.prototype.push = function push(view) {
108
+ return new NodeContext(view, this.nodeContext, this.msgContext, undefined, this.cachedContextTokens);
109
+ };
110
+
111
+ function PkgFileNode(n) {
112
+ RED.nodes.createNode(this, n);
113
+ this.name = n.name;
114
+ this.filename = n.filename;
115
+ this.template = n.template;
116
+ this.format = n.format;
117
+ this.syntax = n.syntax || "mustache";
118
+ this.outputFormat = "str";
119
+
120
+ var node = this;
121
+
122
+ function output(nde, msg, value, send, done) {
123
+ if (!Array.isArray(msg.contents)) {
124
+ RED.util.setMessageProperty(msg, "contents", []);
125
+ }
126
+
127
+ msg.contents.push({
128
+ type: nde.format,
129
+ name: nde.filename,
130
+ contents: value
131
+ })
132
+
133
+ send(msg);
134
+ done();
135
+ }
136
+
137
+
138
+ node.on("input", function (msg, send, done) {
139
+
140
+ try {
141
+ /***
142
+ * Allow template contents to be defined externally
143
+ * through inbound msg.template IFF node.template empty
144
+ */
145
+ var template = node.template;
146
+ if (msg.hasOwnProperty("template")) {
147
+ if (template == "" || template === null) {
148
+ template = msg.template;
149
+ }
150
+ }
151
+
152
+ if (node.syntax === "mustache") {
153
+ var is_json = (node.outputFormat === "json");
154
+ var promises = [];
155
+ var tokens = extractTokens(mustache.parse(template));
156
+ var resolvedTokens = {};
157
+ tokens.forEach(function (name) {
158
+ var env_name = parseEnv(name);
159
+ if (env_name) {
160
+ var promise = new Promise((resolve, reject) => {
161
+ var val = RED.util.evaluateNodeProperty(env_name, 'env', node)
162
+ resolvedTokens[name] = val;
163
+ resolve();
164
+ });
165
+ promises.push(promise);
166
+ return;
167
+ }
168
+
169
+ var context = parseContext(name);
170
+ if (context) {
171
+ var type = context.type;
172
+ var store = context.store;
173
+ var field = context.field;
174
+ var target = node.context()[type];
175
+ if (target) {
176
+ var promise = new Promise((resolve, reject) => {
177
+ target.get(field, store, (err, val) => {
178
+ if (err) {
179
+ reject(err);
180
+ } else {
181
+ resolvedTokens[name] = val;
182
+ resolve();
183
+ }
184
+ });
185
+ });
186
+ promises.push(promise);
187
+ return;
188
+ }
189
+ }
190
+ });
191
+
192
+ Promise.all(promises).then(function () {
193
+ var value = mustache.render(template, new NodeContext(msg, node.context(), null, is_json, resolvedTokens));
194
+ output(node, msg, value, send, done);
195
+ }).catch(function (err) {
196
+ done(err.message);
197
+ });
198
+ } else {
199
+ output(node, msg, template, send, done);
200
+ }
201
+ }
202
+ catch (err) {
203
+ done(err.message);
204
+ }
205
+ });
206
+ }
207
+
208
+ RED.nodes.registerType("PkgFile", PkgFileNode);
209
+ }
@@ -0,0 +1,33 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('NpmTarBall', {
3
+ category: 'nodedev',
4
+ color: "#e5e4ef",
5
+ defaults: {
6
+ name: {
7
+ value: ""
8
+ }
9
+ },
10
+ inputs: 1,
11
+ outputs: 1,
12
+ icon: "font-awesome/fa-birthday-cake",
13
+ label: function() {
14
+ return this.name || this._def.paletteLabel;
15
+ },
16
+ labelStyle: function() {
17
+ return this.name?"node_label_italic": "";
18
+ }
19
+ });
20
+ </script>
21
+
22
+ <script type="text/html" data-template-name="NpmTarBall">
23
+ <div class="form-row">
24
+ <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
25
+ <input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
26
+ </div>
27
+ </script>
28
+
29
+ <script type="text/html" data-help-name="NpmTarBall">
30
+ <p>
31
+ NpmTarballirise msg content.
32
+ </p>
33
+ </script>
@@ -0,0 +1,53 @@
1
+ module.exports = function (RED) {
2
+ function NpmTarBallFunctionality(config) {
3
+ RED.nodes.createNode(this, config);
4
+
5
+ var node = this;
6
+ var cfg = config;
7
+
8
+ const tarStream = require('tar-stream');
9
+ const pakoGzip = require('pako')
10
+
11
+ node.on('close', function () {
12
+ node.status({});
13
+ });
14
+
15
+ node.on("input", function (msg, send, done) {
16
+ const pack = tarStream.pack()
17
+
18
+ var buffer = [];
19
+
20
+ msg.contents.forEach((elem) => {
21
+ if (elem.type == "base64") {
22
+ pack.entry({ name: "package/" + elem.name }, Buffer.from(elem.contents, 'base64'))
23
+ } else {
24
+ pack.entry({ name: "package/" + elem.name }, elem.contents)
25
+ }
26
+ })
27
+
28
+ pack.on('end', function () {
29
+ try {
30
+ var buf = Buffer.from(pakoGzip.gzip(Buffer.concat(buffer)))
31
+ msg.payload = buf;
32
+ send(msg);
33
+ } catch (ex) {
34
+ msg.errors = ex
35
+ done("failed to encode buffer", msg)
36
+ }
37
+ });
38
+
39
+ pack.on('data', function (data) {
40
+ buffer.push(data)
41
+ });
42
+
43
+ pack.on('error', (err) => {
44
+ msg.error = err
45
+ done("tar packing failed", msg)
46
+ })
47
+
48
+ pack.finalize()
49
+ });
50
+ };
51
+
52
+ RED.nodes.registerType("NpmTarBall", NpmTarBallFunctionality);
53
+ }
@@ -0,0 +1,141 @@
1
+ <script type="text/javascript">
2
+ RED.comms.subscribe('nodedev:node-red-install-perform', (event,data) => {
3
+ if ( data.msg == "execfunc" ) {
4
+
5
+ var doSend = (data, nodeid, _msg) => {
6
+ if ( typeof data == "object" ) {
7
+ data = {
8
+ ..._msg,
9
+ ...data
10
+ };
11
+ }
12
+
13
+ $.ajax({
14
+ url: "NodeRedInstall/" + nodeid,
15
+ type: "POST",
16
+ contentType: "application/json; charset=utf-8",
17
+ data: JSON.stringify(data),
18
+
19
+ success: function (resp) {
20
+ },
21
+
22
+ error: function (jqXHR, textStatus, errorThrown) {
23
+ RED.notify("NodeRedInstall Communcation Failure: " +
24
+ nodeid + ": " + textStatus, {
25
+ type: "error",
26
+ id: nodeid,
27
+ timeout: 3000
28
+ });
29
+ }
30
+ });
31
+ };
32
+
33
+ var doError = (msg, nodeid, _msg) => {
34
+ RED.notify("NodeRedInstall Failed: " + nodeid + ": " + msg, {
35
+ type: "error",
36
+ id: nodeid,
37
+ timeout: 3000
38
+ });
39
+
40
+ console.log( "NodeRedInstall: Error with node: " + nodeid + ": " + msg);
41
+ };
42
+
43
+ var nodeid = data.nodeid;
44
+ var _msg = data._msg;
45
+
46
+ var node = {
47
+ send: (dt) => {
48
+ doSend(dt, nodeid, _msg)
49
+ },
50
+ error: (mg) => {
51
+ doError(mg, nodeid, _msg)
52
+ },
53
+ id: data.nodeid
54
+ };
55
+
56
+ var payload = data.payload;
57
+ var topic = data.topic;
58
+
59
+
60
+ var f = new File([new Blob([new Uint8Array(payload.data)])], "tarball.tgz", { type: "application/x-gzip" });
61
+
62
+ var data = new FormData();
63
+ data.append("tarball", f);
64
+ var filename = f.name;
65
+
66
+ $.ajax({
67
+ url: 'nodes',
68
+ data: data,
69
+ cache: false,
70
+ contentType: false,
71
+ processData: false,
72
+ method: 'POST',
73
+ }).always(function (data, textStatus, xhr) {
74
+ }).success(function() {
75
+ RED.notify("Installed new package", { type: "success"})
76
+ }).fail(function (xhr, textStatus, err) {
77
+ var message = textStatus;
78
+ if (xhr.responseJSON) {
79
+ message = xhr.responseJSON.message;
80
+ }
81
+ var notification = RED.notify(RED._('palette.editor.errors.installFailed', { module: filename, message: message }), {
82
+ type: 'error',
83
+ modal: true,
84
+ fixed: true,
85
+ buttons: [
86
+ {
87
+ text: RED._("common.label.close"),
88
+ click: function () {
89
+ notification.close();
90
+ }
91
+ }, {
92
+ text: RED._("eventLog.view"),
93
+ click: function () {
94
+ notification.close();
95
+ RED.actions.invoke("core:show-event-log");
96
+ }
97
+ }
98
+ ]
99
+ });
100
+ })
101
+
102
+ }
103
+ });
104
+
105
+ RED.nodes.registerType('NodeRedInstall',{
106
+ color: '#e5e4ef',
107
+ icon: "icons/subflow.svg",
108
+ category: 'nodedev',
109
+ paletteLabel: 'NRInstall',
110
+ defaults: {
111
+ name: {
112
+ value:"",
113
+ },
114
+ },
115
+ inputs:1,
116
+ outputs:1,
117
+
118
+ label: function() {
119
+ return (this.name || this._def.paletteLabel);
120
+ },
121
+
122
+ labelStyle: function() {
123
+ return this.name?"node_label_italic":"";
124
+ },
125
+ });
126
+ </script>
127
+
128
+ <script type="text/html" data-template-name="NodeRedInstall">
129
+ <div class="form-row">
130
+ <label for="node-input-name">
131
+ <i class="fa fa-tag"></i>
132
+ <span data-i18n="common.label.name">Name</span>
133
+ </label>
134
+ <input type="text" id="node-input-name" placeholder="Name">
135
+ </div>
136
+ </script>
137
+
138
+ <script type="text/html" data-help-name="NodeRedInstall">
139
+ <p>Install NPM tarball into Node-RED as new node package.</p>
140
+ Pass in the results of the NpmTarball node into here and the nodes will be installed into Node-RED.
141
+ </script>
@@ -0,0 +1,48 @@
1
+ module.exports = function (RED) {
2
+ function NodeRedInstallFunctionality(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
+ RED.comms.publish(
14
+ "nodedev:node-red-install-perform",
15
+ RED.util.encodeObject({
16
+ msg: "execfunc",
17
+ payload: msg.payload,
18
+ topic: msg.topic,
19
+ nodeid: node.id,
20
+ _msg: msg
21
+ })
22
+ );
23
+ });
24
+ }
25
+ RED.nodes.registerType("NodeRedInstall", NodeRedInstallFunctionality);
26
+
27
+ RED.httpAdmin.post("/NodeRedInstall/:id",
28
+ RED.auth.needsPermission("NodeRedInstall.write"),
29
+ (req, res) => {
30
+ var node = RED.nodes.getNode(req.params.id);
31
+ if (node != null) {
32
+ try {
33
+ if (req.body && node.type == "NodeRedInstall") {
34
+ node.send(req.body);
35
+ res.sendStatus(200);
36
+ } else {
37
+ res.sendStatus(404);
38
+ }
39
+ } catch (err) {
40
+ res.sendStatus(500);
41
+ node.error("NodeRedInstall: Submission failed: " +
42
+ err.toString())
43
+ }
44
+ } else {
45
+ res.sendStatus(404);
46
+ }
47
+ });
48
+ }