@flowfuse/node-red-dashboard 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.
Files changed (67) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +53 -0
  3. package/dist/css/app.d047b42b.css +1 -0
  4. package/dist/css/chunk-vendors.2378ce49.css +24 -0
  5. package/dist/fonts/materialdesignicons-webfont.3de8526e.woff +0 -0
  6. package/dist/fonts/materialdesignicons-webfont.477c6ab0.woff2 +0 -0
  7. package/dist/fonts/materialdesignicons-webfont.48a1ce0c.eot +0 -0
  8. package/dist/fonts/materialdesignicons-webfont.dfd403cf.ttf +0 -0
  9. package/dist/index.html +1 -0
  10. package/dist/js/app.854a8cd5.js +2 -0
  11. package/dist/js/app.854a8cd5.js.map +1 -0
  12. package/dist/js/chunk-vendors.174e8921.js +43 -0
  13. package/dist/js/chunk-vendors.174e8921.js.map +1 -0
  14. package/nodes/config/locales/en-US/ui_base.json +19 -0
  15. package/nodes/config/locales/en-US/ui_group.html +4 -0
  16. package/nodes/config/locales/en-US/ui_group.json +16 -0
  17. package/nodes/config/ui_base.html +807 -0
  18. package/nodes/config/ui_base.js +678 -0
  19. package/nodes/config/ui_group.html +55 -0
  20. package/nodes/config/ui_group.js +34 -0
  21. package/nodes/config/ui_page.html +84 -0
  22. package/nodes/config/ui_page.js +33 -0
  23. package/nodes/config/ui_theme.html +101 -0
  24. package/nodes/config/ui_theme.js +15 -0
  25. package/nodes/store/index.js +34 -0
  26. package/nodes/utils/index.js +35 -0
  27. package/nodes/widgets/locales/en-US/ui_button.html +7 -0
  28. package/nodes/widgets/locales/en-US/ui_button.json +24 -0
  29. package/nodes/widgets/locales/en-US/ui_chart.html +41 -0
  30. package/nodes/widgets/locales/en-US/ui_chart.json +17 -0
  31. package/nodes/widgets/locales/en-US/ui_dropdown.html +24 -0
  32. package/nodes/widgets/locales/en-US/ui_form.html +16 -0
  33. package/nodes/widgets/locales/en-US/ui_form.json +36 -0
  34. package/nodes/widgets/locales/en-US/ui_markdown.html +10 -0
  35. package/nodes/widgets/locales/en-US/ui_slider.html +9 -0
  36. package/nodes/widgets/locales/en-US/ui_switch.html +32 -0
  37. package/nodes/widgets/locales/en-US/ui_template.html +59 -0
  38. package/nodes/widgets/locales/en-US/ui_template.json +18 -0
  39. package/nodes/widgets/locales/en-US/ui_text.html +16 -0
  40. package/nodes/widgets/locales/en-US/ui_text_input.html +19 -0
  41. package/nodes/widgets/ui_button.html +146 -0
  42. package/nodes/widgets/ui_button.js +65 -0
  43. package/nodes/widgets/ui_chart.html +314 -0
  44. package/nodes/widgets/ui_chart.js +195 -0
  45. package/nodes/widgets/ui_dropdown.html +199 -0
  46. package/nodes/widgets/ui_dropdown.js +19 -0
  47. package/nodes/widgets/ui_form.html +368 -0
  48. package/nodes/widgets/ui_form.js +18 -0
  49. package/nodes/widgets/ui_markdown.html +134 -0
  50. package/nodes/widgets/ui_markdown.js +14 -0
  51. package/nodes/widgets/ui_notification.html +139 -0
  52. package/nodes/widgets/ui_notification.js +14 -0
  53. package/nodes/widgets/ui_radio_group.html +186 -0
  54. package/nodes/widgets/ui_radio_group.js +20 -0
  55. package/nodes/widgets/ui_slider.html +162 -0
  56. package/nodes/widgets/ui_slider.js +31 -0
  57. package/nodes/widgets/ui_switch.html +194 -0
  58. package/nodes/widgets/ui_switch.js +98 -0
  59. package/nodes/widgets/ui_table.html +149 -0
  60. package/nodes/widgets/ui_table.js +16 -0
  61. package/nodes/widgets/ui_template.html +283 -0
  62. package/nodes/widgets/ui_template.js +19 -0
  63. package/nodes/widgets/ui_text.html +358 -0
  64. package/nodes/widgets/ui_text.js +98 -0
  65. package/nodes/widgets/ui_text_input.html +141 -0
  66. package/nodes/widgets/ui_text_input.js +37 -0
  67. package/package.json +114 -0
@@ -0,0 +1,194 @@
1
+ <script type="text/javascript">
2
+ (function () {
3
+ function hasProperty (obj, prop) {
4
+ return Object.prototype.hasOwnProperty.call(obj, prop)
5
+ }
6
+ RED.nodes.registerType('ui-switch', {
7
+ category: RED._('@flowforge/node-red-dashboard/ui-base:ui-base.label.category'),
8
+ color: RED._('@flowforge/node-red-dashboard/ui-base:ui-base.colors.light'),
9
+ defaults: {
10
+ name: { value: '' },
11
+ label: { value: 'switch' },
12
+ // tooltip: {value: ''},
13
+ group: { type: 'ui-group', required: true },
14
+ order: { value: 0 },
15
+ width: {
16
+ value: 0,
17
+ validate: function (v) {
18
+ const width = v || 0
19
+ const currentGroup = $('#node-input-group').val() || this.group
20
+ const groupNode = RED.nodes.node(currentGroup)
21
+ const valid = !groupNode || +width <= +groupNode.width
22
+ $('#node-input-size').toggleClass('input-error', !valid)
23
+ return valid
24
+ }
25
+ },
26
+ height: { value: 0 },
27
+ passthru: { value: false },
28
+ topic: { value: 'topic', validate: (hasProperty(RED.validators, 'typedInput') ? RED.validators.typedInput('topicType') : function (v) { return true }) },
29
+ topicType: { value: 'msg' },
30
+ style: { value: '' },
31
+ className: { value: '' },
32
+ // on state
33
+ onvalue: { value: true, validate: (hasProperty(RED.validators, 'typedInput') ? RED.validators.typedInput('onvalueType') : function (v) { return true }) },
34
+ onvalueType: { value: 'bool' },
35
+ onicon: { value: '' },
36
+ oncolor: { value: '' },
37
+ // off state
38
+ offvalue: { value: false, validate: (hasProperty(RED.validators, 'typedInput') ? RED.validators.typedInput('offvalueType') : function (v) { return true }) },
39
+ offvalueType: { value: 'bool' },
40
+ officon: { value: '' },
41
+ offcolor: { value: '' }
42
+ },
43
+ inputs: 1,
44
+ outputs: 1,
45
+ icon: 'font-awesome/fa-toggle-on',
46
+ paletteLabel: 'switch',
47
+ label: function () { return this.name || (~this.label.indexOf('{' + '{') ? null : this.label) || 'switch' },
48
+ labelStyle: function () { return this.name ? 'node_label_italic' : '' },
49
+ oneditprepare: function () {
50
+ $('#node-input-size').elementSizer({
51
+ width: '#node-input-width',
52
+ height: '#node-input-height',
53
+ group: '#node-input-group'
54
+ })
55
+ $('#node-input-custom-icons').on('change', function () {
56
+ if ($('#node-input-custom-icons').val() === 'default') {
57
+ $('.form-row-custom-icons').hide()
58
+ } else {
59
+ $('.form-row-custom-icons').show()
60
+ }
61
+ })
62
+
63
+ if (this.onicon !== '' || this.oncolor !== '' || this.officon !== '' || this.offcolor !== '') {
64
+ $('#node-input-custom-icons').val('custom')
65
+ } else {
66
+ $('.form-row-custom-icons').hide()
67
+ $('#node-input-custom-icons').change()
68
+ }
69
+
70
+ $('#node-input-onvalue').typedInput({
71
+ default: 'str',
72
+ typeField: $('#node-input-onvalueType'),
73
+ types: ['str', 'num', 'bool', 'json', 'bin', 'date', 'flow', 'global']
74
+ })
75
+
76
+ $('#node-input-offvalue').typedInput({
77
+ default: 'str',
78
+ typeField: $('#node-input-offvalueType'),
79
+ types: ['str', 'num', 'bool', 'json', 'bin', 'date', 'flow', 'global']
80
+ })
81
+
82
+ $('#node-input-topic').typedInput({
83
+ default: 'str',
84
+ typeField: $('#node-input-topicType'),
85
+ types: ['str', 'msg', 'flow', 'global']
86
+ })
87
+
88
+ $('#node-input-passthru').on('change', function () {
89
+ if (this.checked) {
90
+ $('.form-row-decouple').hide()
91
+ $('#node-input-decouple').val('false')
92
+ } else {
93
+ $('.form-row-decouple').show()
94
+ }
95
+ })
96
+
97
+ // use jQuery UI tooltip to convert the plain old title attribute to a nice tooltip
98
+ $('.ui-node-popover-title').tooltip({
99
+ show: {
100
+ effect: 'slideDown',
101
+ delay: 150
102
+ }
103
+ })
104
+ },
105
+ oneditsave: function () {
106
+ if ($('#node-input-custom-icons').val() === 'default') {
107
+ $('#node-input-onicon').val('')
108
+ $('#node-input-officon').val('')
109
+ $('#node-input-oncolor').val('')
110
+ $('#node-input-offcolor').val('')
111
+ }
112
+ }
113
+ })
114
+ })()
115
+ </script>
116
+
117
+ <script type="text/html" data-template-name="ui-switch">
118
+ <div class="form-row">
119
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
120
+ <input type="text" id="node-input-name">
121
+ </div>
122
+ <div class="form-row">
123
+ <label for="node-input-group"><i class="fa fa-table"></i> Group</label>
124
+ <input type="text" id="node-input-group">
125
+ </div>
126
+ <div class="form-row">
127
+ <label><i class="fa fa-object-group"></i> Size</label>
128
+ <input type="hidden" id="node-input-width">
129
+ <input type="hidden" id="node-input-height">
130
+ <button class="editor-button" id="node-input-size"></button>
131
+ </div>
132
+ <div class="form-row">
133
+ <label for="node-input-label"><i class="fa fa-i-cursor"></i> Label</label>
134
+ <input type="text" id="node-input-label">
135
+ </div>
136
+ <div class="form-row">
137
+ <label for="node-input-className"><i class="fa fa-code"></i> Class</label>
138
+ <div style="display: inline;">
139
+ <input style="width: 70%;" type="text" id="node-input-className" placeholder="Optional CSS class name(s)" style="flex-grow: 1;">
140
+ <a
141
+ data-html="true"
142
+ title="Dynamic Property: Class names can also be set by sending a message to the node with a msg.topic of 'ui-property:class' and a payload containing the class name(s) to be applied. NOTE: classes set at runtime will be applied in addition to any class(es) set in the nodes class field."
143
+ class="red-ui-button ui-node-popover-title"
144
+ style="margin-left: 4px; cursor: help; font-size: 0.625rem; border-radius: 50%; width: 24px; height: 24px; display: inline-flex; justify-content: center; align-items: center;">
145
+ <i style="font-family: ui-serif;">fx</i>
146
+ </a>
147
+ </div>
148
+ </div>
149
+ <!--<div class="form-row">
150
+ <label for="node-input-tooltip"><i class="fa fa-info-circle"></i> Tooltip</label>
151
+ <input type="text" id="node-input-tooltip" placeholder="optional tooltip">
152
+ </div>-->
153
+ <div class="form-row">
154
+ <label for="node-input-custom-icons"><i class="fa fa-picture-o"></i> Icon</label>
155
+ <select id="node-input-custom-icons" style="width:35%">
156
+ <option value="default">Default</option>
157
+ <option value="custom">Custom</option>
158
+ </select>
159
+ </div>
160
+ <div class="form-row form-row-custom-icons">
161
+ <label for="node-input-onicon" style="text-align:right;"><i class="fa fa-toggle-on"></i> On Icon</label>
162
+ <input type="text" id="node-input-onicon" style="width:120px">
163
+ <label for="node-input-oncolor" style="width:50px; text-align:right;">Colour</label>
164
+ <input type="text" id="node-input-oncolor" style="width:120px">
165
+ </div>
166
+ <div class="form-row form-row-custom-icons">
167
+ <label for="node-input-officon" style="text-align:right;"><i class="fa fa-toggle-off"></i> Off Icon</label>
168
+ <input type="text" id="node-input-officon" style="width:120px">
169
+ <label for="node-input-offcolor" style="width:50px; text-align:right;">Colour</label>
170
+ <input type="text" id="node-input-offcolor" style="width:120px">
171
+ </div>
172
+ <div class="form-row">
173
+ <label style="width:auto" for="node-input-passthru"><i class="fa fa-arrow-right"></i> If <code>msg</code> arrives on input, pass through to output: </label>
174
+ <input type="checkbox" checked id="node-input-passthru" style="display:inline-block; width:auto; vertical-align:top;">
175
+ </div>
176
+ <div class="form-row">
177
+ <label style="width:auto" for="node-input-onvalue"><i class="fa fa-envelope-o"></i> When clicked, send:</label>
178
+ </div>
179
+ <div class="form-row">
180
+ <label for="node-input-onvalue" style="padding-left:25px; margin-right:-25px">On Payload</label>
181
+ <input type="text" id="node-input-onvalue" style="width:70%">
182
+ <input type="hidden" id="node-input-onvalueType">
183
+ </div>
184
+ <div class="form-row">
185
+ <label for="node-input-offvalue" style="padding-left:25px; margin-right:-25px">Off Payload</label>
186
+ <input type="text" id="node-input-offvalue" style="width:70%">
187
+ <input type="hidden" id="node-input-offvalueType">
188
+ </div>
189
+ <div class="form-row">
190
+ <label for="node-input-topic" style="padding-left:25px; margin-right:-25px">Topic</label>
191
+ <input type="text" id="node-input-topic">
192
+ <input type="hidden" id="node-input-topicType">
193
+ </div>
194
+ </script>
@@ -0,0 +1,98 @@
1
+ const datastore = require('../store/index.js')
2
+ const { appendTopic } = require('../utils/index.js')
3
+
4
+ module.exports = function (RED) {
5
+ function SwitchNode (config) {
6
+ // create node in Node-RED
7
+ RED.nodes.createNode(this, config)
8
+
9
+ const node = this
10
+ node.status({})
11
+
12
+ const states = ['off', 'on']
13
+
14
+ // which group are we rendering this widget
15
+ const group = RED.nodes.getNode(config.group)
16
+
17
+ const evts = {
18
+ // runs on UI interaction
19
+ // value = true | false from the ui-switch
20
+ onChange: async function (value) {
21
+ // ensure we have latest instance of the widget's node
22
+ const wNode = RED.nodes.getNode(node.id)
23
+ const msg = datastore.get(node.id) || {}
24
+
25
+ node.status({
26
+ fill: value ? 'green' : 'red',
27
+ shape: 'ring',
28
+ text: value ? states[1] : states[0]
29
+ })
30
+
31
+ // retrieve the assigned on/off value
32
+ const on = RED.util.evaluateNodeProperty(config.onvalue, config.onvalueType, wNode)
33
+ const off = RED.util.evaluateNodeProperty(config.offvalue, config.offvalueType, wNode)
34
+ msg.payload = value ? on : off
35
+
36
+ datastore.save(node.id, msg)
37
+
38
+ // simulate Node-RED node receiving an input
39
+ wNode.send(msg)
40
+ },
41
+ onInput: async function (msg, send) {
42
+ let error = null
43
+ // ensure we have latest instance of the widget's node
44
+ const wNode = RED.nodes.getNode(node.id)
45
+
46
+ // retrieve the assigned on/off value
47
+ const on = RED.util.evaluateNodeProperty(config.onvalue, config.onvalueType, wNode)
48
+ const off = RED.util.evaluateNodeProperty(config.offvalue, config.offvalueType, wNode)
49
+ if (msg.payload === true || msg.payload === on) {
50
+ msg.payload = on
51
+ } else if (msg.payload === false || msg.payload === off) {
52
+ msg.payload = off
53
+ } else {
54
+ // throw Node-RED error
55
+ error = 'Invalid payload value'
56
+ }
57
+ if (!error) {
58
+ // store the latest msg passed to node
59
+ datastore.save(node.id, msg)
60
+
61
+ node.status({
62
+ fill: msg.payload ? 'green' : 'red',
63
+ shape: 'ring',
64
+ text: msg.payload ? states[1] : states[0]
65
+ })
66
+
67
+ if (config.passthru) {
68
+ send(msg)
69
+ }
70
+ } else {
71
+ const err = new Error(error)
72
+ err.type = 'warn'
73
+ throw err
74
+ }
75
+ },
76
+ beforeSend: async function (msg) {
77
+ // ensure we have latest instance of the widget's node
78
+ const wNode = RED.nodes.getNode(node.id)
79
+
80
+ msg = await appendTopic(RED, config, wNode, msg)
81
+ return msg
82
+ }
83
+ }
84
+
85
+ const on = RED.util.evaluateNodeProperty(config.onvalue, config.onvalueType, node)
86
+ const off = RED.util.evaluateNodeProperty(config.offvalue, config.offvalueType, node)
87
+
88
+ config.evaluated = {
89
+ on,
90
+ off
91
+ }
92
+
93
+ // inform the dashboard UI that we are adding this node
94
+ group.register(node, config, evts)
95
+ }
96
+
97
+ RED.nodes.registerType('ui-switch', SwitchNode)
98
+ }
@@ -0,0 +1,149 @@
1
+ <script type="text/javascript">
2
+ (function () {
3
+ RED.nodes.registerType('ui-table', {
4
+ category: RED._('@flowforge/node-red-dashboard/ui-base:ui-base.label.category'),
5
+ color: RED._('@flowforge/node-red-dashboard/ui-base:ui-base.colors.medium'),
6
+ defaults: {
7
+ group: { type: 'ui-group', required: true },
8
+ name: { value: '' },
9
+ label: { value: 'text' },
10
+ order: { value: 0 },
11
+ width: {
12
+ value: 0,
13
+ validate: function (v) {
14
+ const width = v || 0
15
+ const currentGroup = $('#node-input-group').val() || this.group
16
+ const groupNode = RED.nodes.node(currentGroup)
17
+ const valid = !groupNode || +width <= +groupNode.width
18
+ $('#node-input-size').toggleClass('input-error', !valid)
19
+ return valid
20
+ }
21
+ },
22
+ height: { value: 0 },
23
+ maxrows: { value: 0, validate: RED.validators.number() },
24
+ autocols: { value: true },
25
+ columns: {
26
+ // value: [{ key: '', label: '' }]
27
+ value: null
28
+ }
29
+ },
30
+ inputs: 1,
31
+ outputs: 0,
32
+ outputLabels: function () { return this.mode },
33
+ icon: 'font-awesome/fa-table',
34
+ paletteLabel: 'table',
35
+ label: function () {
36
+ return this.name || 'table'
37
+ },
38
+ oneditprepare: function () {
39
+ $('#node-input-size').elementSizer({
40
+ width: '#node-input-width',
41
+ height: '#node-input-height',
42
+ group: '#node-input-group'
43
+ })
44
+ const autocols = $('#node-input-autocols')
45
+
46
+ $(autocols).change(() => {
47
+ const val = autocols.is(':checked')
48
+ const customColsDiv = $('#node-input-columns-container')
49
+ if (val) {
50
+ customColsDiv.hide()
51
+ } else {
52
+ customColsDiv.show()
53
+ }
54
+ })
55
+
56
+ // Columns Editor
57
+ function generateColumn (i, col) {
58
+ const container = $('<li/>', { style: 'background: var(--red-ui-secondary-background, #fff); margin:0; padding:8px 0px 0px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc);' })
59
+ const row = $('<div/>').appendTo(container)
60
+ $('<div/>', { style: 'padding-top:5px; padding-left:175px;' }).appendTo(container)
61
+ $('<div/>', { style: 'padding-top:5px; padding-left:120px;' }).appendTo(container)
62
+
63
+ $('<i style="color: var(--red-ui-form-text-color, #eee); cursor:move; margin-left:3px;" class="node-input-column-handle fa fa-bars"></i>').appendTo(row)
64
+
65
+ $('<input/>', { class: 'node-input-column-key', type: 'text', style: 'margin-left:7px; width:calc(50% - 32px);', placeholder: 'Key', value: col.key }).appendTo(row)
66
+ $('<input/>', { class: 'node-input-column-label', type: 'text', style: 'margin-left:7px; width:calc(50% - 32px);', placeholder: 'Label', value: col.label }).appendTo(row)
67
+
68
+ const finalSpan = $('<span/>', { style: 'float:right; margin-right:8px;' }).appendTo(row)
69
+ const deleteButton = $('<a/>', { href: '#', class: 'editor-button editor-button-small', style: 'margin-top:7px; margin-left:5px;' }).appendTo(finalSpan)
70
+ $('<i/>', { class: 'fa fa-remove' }).appendTo(deleteButton)
71
+
72
+ deleteButton.click(function () {
73
+ container.css({ background: 'var(--red-ui-secondary-background-inactive, #fee)' })
74
+ container.fadeOut(300, function () {
75
+ $(this).remove()
76
+ })
77
+ })
78
+
79
+ $('#node-input-column-container').append(container)
80
+ }
81
+
82
+ $('#node-input-add-column').click(function () {
83
+ generateColumn($('#node-input-column-container').children().length + 1, {})
84
+ $('#node-input-column-container-div').scrollTop($('#node-input-column-column-div').get(0).scrollHeight)
85
+ })
86
+
87
+ for (let i = 0; i < this.columns?.length; i++) {
88
+ const col = this.columns[i]
89
+ generateColumn(i + 1, col)
90
+ }
91
+
92
+ $('#node-input-column-container').sortable({
93
+ axis: 'y',
94
+ handle: '.node-input-column-handle',
95
+ cursor: 'move'
96
+ })
97
+ },
98
+ oneditsave: function () {
99
+ const columns = $('#node-input-column-container').children()
100
+ const node = this
101
+ node.columns = []
102
+ columns.each(function (i) {
103
+ const column = $(this)
104
+ const o = {
105
+ label: column.find('.node-input-column-label').val(),
106
+ key: column.find('.node-input-column-key').val()
107
+ }
108
+ node.columns.push(o)
109
+ })
110
+ }
111
+ })
112
+ })()
113
+ </script>
114
+
115
+ <script type="text/html" data-template-name="ui-table">
116
+ <div class="form-row">
117
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
118
+ <input type="text" id="node-input-name">
119
+ </div>
120
+ <div class="form-row">
121
+ <label for="node-input-group"><i class="fa fa-table"></i> Group</label>
122
+ <input type="text" id="node-input-group">
123
+ </div>
124
+ <div class="form-row">
125
+ <label><i class="fa fa-object-group"></i> Size</label>
126
+ <input type="hidden" id="node-input-width">
127
+ <input type="hidden" id="node-input-height">
128
+ <button class="editor-button" id="node-input-size"></button>
129
+ </div>
130
+ <div class="form-row">
131
+ <label for="node-input-maxrows"><i class="fa fa-tag"></i> Max Rows</label>
132
+ <input type="number" id="node-input-maxrows">
133
+ </div>
134
+ <div class="form-row" style="display:flex;">
135
+ <label for="node-input-width" style="vertical-align:top"><i class="fa fa-list-alt"></i> Columns</label>
136
+ <div class="form-row node-input-column-container-row" style="margin-bottom: 0px; width:calc(70% + 15px);">
137
+ <div>
138
+ <input type="checkbox" checked id="node-input-autocols" style="display: inline-block; width: auto; margin: 0px 0px 0px 4px;">
139
+ <label style="width:auto" for="node-input-autocols">Auto Calculate Columns</label>
140
+ </div>
141
+ <div id="node-input-columns-container">
142
+ <div id="node-input-column-container-div" style="box-sizing:border-box; border-radius:5px; height:257px; padding:5px; border:1px solid var(--red-ui-form-input-border-color, #ccc); overflow-y:scroll; display:inline-block; width: 100%;">
143
+ <ol id="node-input-column-container" style="list-style-type:none; margin:0;"></ol>
144
+ </div>
145
+ <a href="#" class="editor-button editor-button-small" id="node-input-add-column" style="margin-top:4px;"><i class="fa fa-plus"></i> <span>column</span></a>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </script>
@@ -0,0 +1,16 @@
1
+ module.exports = function (RED) {
2
+ function TableNode (config) {
3
+ const node = this
4
+
5
+ // create node in Node-RED
6
+ RED.nodes.createNode(this, config)
7
+
8
+ // which group are we rendering this widget
9
+ const group = RED.nodes.getNode(config.group)
10
+
11
+ // inform the dashboard UI that we are adding this node
12
+ group.register(node, config)
13
+ }
14
+
15
+ RED.nodes.registerType('ui-table', TableNode)
16
+ }