@theotherwillembotha/node-red-plugincore 0.0.55 → 0.2.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 (158) hide show
  1. package/README.md +616 -217
  2. package/build/GenerateNodes.js +3 -6
  3. package/build/Nodes.html +3 -1502
  4. package/build/Nodes.js +5 -15
  5. package/build/Plugins.html +227 -0
  6. package/build/Plugins.js +18 -9
  7. package/build/core/NodeConstructor.d.ts +10 -16
  8. package/build/core/NodeConstructor.d.ts.map +1 -1
  9. package/build/core/NodeConstructor.js +112 -156
  10. package/build/core/NodeGenerator.d.ts.map +1 -1
  11. package/build/core/NodeGenerator.js +79 -29
  12. package/build/core/NodeManagerRuntime.d.ts +44 -0
  13. package/build/core/NodeManagerRuntime.d.ts.map +1 -0
  14. package/build/core/NodeManagerRuntime.js +140 -0
  15. package/build/core/Utils.d.ts.map +1 -1
  16. package/build/core/logger/service/LoggerService.d.ts +6 -5
  17. package/build/core/logger/service/LoggerService.d.ts.map +1 -1
  18. package/build/core/logger/service/LoggerService.js +51 -20
  19. package/build/core/logger/template/LoggerTemplate.d.ts.map +1 -1
  20. package/build/core/metrics/MetricsDecorator.d.ts +1 -2
  21. package/build/core/metrics/MetricsDecorator.d.ts.map +1 -1
  22. package/build/core/metrics/MetricsDecorator.js +89 -116
  23. package/build/core/metrics/node/CounterMetricConfigNode.d.ts.map +1 -1
  24. package/build/core/metrics/node/CounterMetricConfigNode.js +4 -5
  25. package/build/core/metrics/node/GaugeMetricConfigNode.d.ts.map +1 -1
  26. package/build/core/metrics/node/GaugeMetricConfigNode.js +5 -6
  27. package/build/core/metrics/node/MetricsConfigNode.d.ts +19 -9
  28. package/build/core/metrics/node/MetricsConfigNode.d.ts.map +1 -1
  29. package/build/core/metrics/node/MetricsConfigNode.js +14 -45
  30. package/build/core/metrics/node/TimerMetricConfigNode.d.ts.map +1 -1
  31. package/build/core/metrics/node/TimerMetricConfigNode.js +11 -17
  32. package/build/core/metrics/service/MetricsService.d.ts +110 -114
  33. package/build/core/metrics/service/MetricsService.d.ts.map +1 -1
  34. package/build/core/metrics/service/MetricsService.js +122 -339
  35. package/build/core/metrics/template/CounterMetricTemplate.d.ts.map +1 -1
  36. package/build/core/metrics/template/GaugeMetricTemplate.d.ts.map +1 -1
  37. package/build/core/metrics/template/MetricsTemplate.d.ts.map +1 -1
  38. package/build/core/metrics/template/MetricsTemplate.js +1 -2
  39. package/build/core/metrics/template/TimerMetricTemplate.d.ts.map +1 -1
  40. package/build/core/other/node/DelegatedConfigReferenceNode.d.ts +1 -1
  41. package/build/core/other/node/DelegatedConfigReferenceNode.d.ts.map +1 -1
  42. package/build/core/other/node/DelegatedConfigReferenceNode.js +1 -1
  43. package/build/core/other/service/InputService.d.ts.map +1 -1
  44. package/build/core/other/service/SettingsService.d.ts +1 -2
  45. package/build/core/other/service/SettingsService.d.ts.map +1 -1
  46. package/build/core/other/service/SettingsService.js +19 -5
  47. package/build/core/other/template/BasicTemplate.d.ts.map +1 -1
  48. package/build/core/other/template/SettingsTemplate.d.ts.map +1 -1
  49. package/build/core/state/node/InternalStateConfigNode.d.ts +16 -0
  50. package/build/core/state/node/InternalStateConfigNode.d.ts.map +1 -0
  51. package/build/core/state/node/InternalStateConfigNode.js +89 -0
  52. package/build/core/state/node/StateConfigNode.d.ts +24 -0
  53. package/build/core/state/node/StateConfigNode.d.ts.map +1 -0
  54. package/build/core/state/node/StateConfigNode.js +22 -0
  55. package/build/core/state/service/StateService.d.ts +34 -0
  56. package/build/core/state/service/StateService.d.ts.map +1 -0
  57. package/build/core/state/service/StateService.js +201 -0
  58. package/build/core/state/template/StateTemplate.d.ts +8 -0
  59. package/build/core/state/template/StateTemplate.d.ts.map +1 -0
  60. package/build/core/state/template/StateTemplate.js +11 -0
  61. package/build/core/tagging/ServiceDescriptionDecorator.d.ts +10 -0
  62. package/build/core/tagging/ServiceDescriptionDecorator.d.ts.map +1 -0
  63. package/build/core/tagging/ServiceDescriptionDecorator.js +10 -0
  64. package/build/core/tagging/service/NodeTypeService.d.ts +1 -2
  65. package/build/core/tagging/service/NodeTypeService.d.ts.map +1 -1
  66. package/build/core/tagging/service/NodeTypeService.js +19 -5
  67. package/build/core/ui/template/ScriptEditorTemplate.d.ts.map +1 -1
  68. package/build/core/ui/template/UIHelperTemplate.d.ts.map +1 -1
  69. package/build/core/webhook/WebhookDecorator.d.ts.map +1 -1
  70. package/build/core/webhook/WebhookDecorator.js +43 -6
  71. package/build/core/webhook/node/WebhookServerConfigNode.d.ts.map +1 -1
  72. package/build/core/webhook/service/ReverseProxyTypeService.d.ts +1 -0
  73. package/build/core/webhook/service/ReverseProxyTypeService.d.ts.map +1 -1
  74. package/build/core/webhook/service/WebhookServerService.d.ts +3 -4
  75. package/build/core/webhook/service/WebhookServerService.d.ts.map +1 -1
  76. package/build/core/webhook/service/WebhookServerService.js +48 -23
  77. package/build/core/webhook/template/WebhookTemplate.d.ts.map +1 -1
  78. package/build/index.d.ts +5 -2
  79. package/build/index.d.ts.map +1 -1
  80. package/build/index.js +6 -2
  81. package/build/runtime/NodeManagerRuntime.js +140 -0
  82. package/documentation/CounterMetricConfigNode.png +0 -0
  83. package/documentation/GaugeMetricConfigNode.png +0 -0
  84. package/documentation/LoggerTemplate.png +0 -0
  85. package/documentation/MetricsTemplate.png +0 -0
  86. package/documentation/StateTemplate.png +0 -0
  87. package/documentation/TimerMetricConfigNode_histogram.png +0 -0
  88. package/documentation/TimerMetricConfigNode_summary.png +0 -0
  89. package/documentation/WebhookServerConfigNode.png +0 -0
  90. package/documentation/WebhookTemplate.png +0 -0
  91. package/package.json +5 -12
  92. package/src/GenerateNodes.ts +7 -11
  93. package/src/core/NodeConstructor.ts +134 -197
  94. package/src/core/NodeGenerator.ts +88 -34
  95. package/src/core/NodeManagerRuntime.ts +179 -0
  96. package/src/core/logger/service/LoggerService.ts +39 -26
  97. package/src/core/logger/template/LoggerTemplate.html +13 -8
  98. package/src/core/metrics/MetricsDecorator.ts +140 -168
  99. package/src/core/metrics/node/CounterMetricConfigNode.ts +20 -20
  100. package/src/core/metrics/node/GaugeMetricConfigNode.ts +24 -25
  101. package/src/core/metrics/node/MetricsConfigNode.ts +29 -48
  102. package/src/core/metrics/node/TimerMetricConfigNode.html +2 -2
  103. package/src/core/metrics/node/TimerMetricConfigNode.ts +50 -59
  104. package/src/core/metrics/service/MetricsService.ts +214 -489
  105. package/src/core/metrics/template/CounterMetricTemplate.html +0 -1
  106. package/src/core/metrics/template/GaugeMetricTemplate.html +1 -1
  107. package/src/core/metrics/template/MetricsTemplate.html +132 -13
  108. package/src/core/metrics/template/MetricsTemplate.ts +3 -4
  109. package/src/core/other/node/DelegatedConfigReferenceNode.ts +1 -1
  110. package/src/core/other/service/SettingsService.ts +6 -10
  111. package/src/core/other/template/BasicTemplate.html +5 -2
  112. package/src/core/state/node/InternalStateConfigNode.html +77 -0
  113. package/src/core/state/node/InternalStateConfigNode.ts +102 -0
  114. package/src/core/state/node/StateConfigNode.ts +29 -0
  115. package/src/core/state/service/StateService.ts +180 -0
  116. package/src/core/state/template/StateTemplate.html +244 -0
  117. package/src/core/state/template/StateTemplate.ts +17 -0
  118. package/src/core/tagging/ServiceDescriptionDecorator.ts +23 -0
  119. package/src/core/tagging/service/NodeTypeService.ts +6 -11
  120. package/src/core/ui/template/UIHelperTemplate.html +41 -26
  121. package/src/core/webhook/WebhookDecorator.ts +53 -10
  122. package/src/core/webhook/service/ReverseProxyTypeService.ts +1 -0
  123. package/src/core/webhook/service/WebhookServerService.ts +39 -28
  124. package/src/core/webhook/template/WebhookTemplate.html +29 -36
  125. package/src/index.ts +7 -2
  126. package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts +0 -20
  127. package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts.map +0 -1
  128. package/build/core/logger/node/ConsoleLoggerConfigNode.html +0 -90
  129. package/build/core/logger/node/ConsoleLoggerConfigNode.js +0 -62
  130. package/build/core/logger/node/RestLoggerConfigNode.d.ts +0 -40
  131. package/build/core/logger/node/RestLoggerConfigNode.d.ts.map +0 -1
  132. package/build/core/logger/node/RestLoggerConfigNode.html +0 -183
  133. package/build/core/logger/node/RestLoggerConfigNode.js +0 -128
  134. package/build/core/logger/template/LoggerTemplate.html +0 -331
  135. package/build/core/metrics/node/CounterMetricConfigNode.html +0 -48
  136. package/build/core/metrics/node/GaugeMetricConfigNode.html +0 -48
  137. package/build/core/metrics/node/MetricsConfigNode.html +0 -21
  138. package/build/core/metrics/node/TimerMetricConfigNode.html +0 -190
  139. package/build/core/metrics/template/CounterMetricTemplate.html +0 -40
  140. package/build/core/metrics/template/GaugeMetricTemplate.html +0 -39
  141. package/build/core/metrics/template/MetricsTemplate.html +0 -54
  142. package/build/core/metrics/template/TimerMetricTemplate.html +0 -39
  143. package/build/core/other/node/DelegatedConfigReferenceNode.html +0 -1
  144. package/build/core/other/template/BasicTemplate.html +0 -76
  145. package/build/core/other/template/SettingsTemplate.html +0 -128
  146. package/build/core/ui/template/ScriptEditorTemplate.html +0 -50
  147. package/build/core/ui/template/UIHelperTemplate.html +0 -229
  148. package/build/core/webhook/node/WebhookServerConfigNode.html +0 -61
  149. package/build/core/webhook/template/WebhookTemplate.html +0 -442
  150. package/documentation/ConsoleLoggerConfigNode.png +0 -0
  151. package/documentation/MetricsConfigNode.png +0 -0
  152. package/documentation/RestLoggerConfigNode.png +0 -0
  153. package/documentation/TimerMetricConfigNode.png +0 -0
  154. package/documentation/WebhookNode.png +0 -0
  155. package/src/core/logger/node/ConsoleLoggerConfigNode.html +0 -90
  156. package/src/core/logger/node/ConsoleLoggerConfigNode.ts +0 -71
  157. package/src/core/logger/node/RestLoggerConfigNode.html +0 -183
  158. package/src/core/logger/node/RestLoggerConfigNode.ts +0 -170
@@ -0,0 +1,244 @@
1
+ <script type="text/javascript" template-section="onCompose">
2
+ node
3
+ .addDefault("stateReference", { required: false, value: '', type: "DelegatedConfigReferenceNode" })
4
+ </script>
5
+
6
+ <script type="text/javascript" template-section="onIncludeEditPrepare">
7
+
8
+ // Read name suffix and declared states from the build-time templateConfig injected by NodeConstructor.
9
+ let _stateNameSuffix = (templateConfig && templateConfig.nameSuffix) ? templateConfig.nameSuffix : '-State';
10
+ let _stateStates = (templateConfig && templateConfig.defaultStates) ? templateConfig.defaultStates : null;
11
+
12
+ let stateSection = $("#state-config-section");
13
+ let typeSelector = stateSection.find("#state-type-selector");
14
+ let configureBtn = stateSection.find("#state-configure-btn");
15
+ let stateRefSelector = stateSection.find("#state-ref-selector");
16
+
17
+ // Seed the hidden select with the current value and record the original for cleanup.
18
+ stateRefSelector.empty();
19
+ if (node.stateReference) {
20
+ let existingNode = RED.nodes.node(node.stateReference);
21
+ let label = existingNode ? (existingNode.name || node.stateReference) : node.stateReference;
22
+ stateRefSelector.append($('<option>').val(node.stateReference).text(label));
23
+ stateRefSelector.val(node.stateReference);
24
+ }
25
+ stateRefSelector.data('originalRef', node.stateReference || '');
26
+
27
+ let updateConfigureBtn = function() {
28
+ configureBtn.toggleClass("disabled", !typeSelector.val());
29
+ };
30
+
31
+ let getStateProviderTypes = async function() {
32
+ let types = [];
33
+ await $.ajax({
34
+ url: "nodetypeservice/find?tag=StateProvider",
35
+ type: "GET",
36
+ contentType: "application/json; charset=utf-8",
37
+ success: (r) => { types = r; },
38
+ error: (jqXHR, s, err) => { console.log("StateTemplate: provider types error", jqXHR, s, err); },
39
+ });
40
+ return types;
41
+ };
42
+
43
+ // RED.editor.editConfig captures a reference to the field element before the inner config
44
+ // editor opens. When editing a ConfigNode, the outer editor's DOM is swapped out while the
45
+ // inner editor is active, so the captured reference becomes stale and editConfig's own
46
+ // callback silently fails to update our hidden select.
47
+ //
48
+ // Instead we listen for the 'nodes:add' event which fires via RED.nodes.add() whenever a
49
+ // new node (including config nodes) is added to the workspace - regardless of DOM state.
50
+ let stateNodeAddHandler = function(addedNode) {
51
+ getStateProviderTypes().then(function(types) {
52
+ let typeIds = types.map(function(t) { return t.id; });
53
+ if (typeIds.indexOf(addedNode.type) < 0) { return; }
54
+
55
+ console.log("StateTemplate: nodes:add - state provider added:", addedNode.type, addedNode.id);
56
+
57
+ let prevRef = stateRefSelector.val() || '';
58
+ let originalRef = stateRefSelector.data('originalRef') || '';
59
+
60
+ // Clean up any intermediate node created earlier this session (not the original).
61
+ if (prevRef && prevRef !== originalRef) {
62
+ let inter = RED.nodes.node(prevRef);
63
+ if (inter) { RED.nodes.remove(inter); RED.nodes.dirty(true); }
64
+ }
65
+
66
+ // Update the hidden select with the newly created node.
67
+ stateRefSelector.empty();
68
+ stateRefSelector.append($('<option>').val(addedNode.id).text(addedNode.name || addedNode.id));
69
+ stateRefSelector.val(addedNode.id);
70
+
71
+ // Sync the visible type selector.
72
+ typeSelector.val(addedNode.type);
73
+ updateConfigureBtn();
74
+ });
75
+ };
76
+
77
+ RED.events.on('nodes:add', stateNodeAddHandler);
78
+ stateSection.data('stateNodeAddHandler', stateNodeAddHandler);
79
+
80
+ // When the type dropdown changes, restore the original ref if it matches,
81
+ // or clear so the user must click Configure to create a new instance.
82
+ typeSelector.on('change', function() {
83
+ let selectedType = typeSelector.val() || '';
84
+ let originalRef = stateRefSelector.data('originalRef') || '';
85
+ let currentRef = stateRefSelector.val() || '';
86
+
87
+ if (!selectedType) {
88
+ stateRefSelector.val('');
89
+ updateConfigureBtn();
90
+ return;
91
+ }
92
+ if (originalRef) {
93
+ let originalNode = RED.nodes.node(originalRef);
94
+ if (originalNode && originalNode.type === selectedType) {
95
+ stateRefSelector.val(originalRef);
96
+ updateConfigureBtn();
97
+ return;
98
+ }
99
+ }
100
+ if (currentRef) {
101
+ let currentNode = RED.nodes.node(currentRef);
102
+ if (currentNode && currentNode.type === selectedType) {
103
+ updateConfigureBtn();
104
+ return;
105
+ }
106
+ }
107
+ stateRefSelector.val('');
108
+ updateConfigureBtn();
109
+ });
110
+
111
+ configureBtn.on('click', function(event) {
112
+ event.stopPropagation();
113
+ if (configureBtn.hasClass("disabled")) return;
114
+
115
+ let selectedType = typeSelector.val() || '';
116
+ if (!selectedType) return;
117
+
118
+ let currentRef = stateRefSelector.val() || '';
119
+ let currentNode = currentRef ? RED.nodes.node(currentRef) : null;
120
+
121
+ // Always set __stateTemplateContext so the config node editor can read defaultName and states.
122
+ window.__stateTemplateContext = {
123
+ defaultName: (node.name || node.type) + _stateNameSuffix,
124
+ states: _stateStates
125
+ };
126
+
127
+ if (currentNode && currentNode.type === selectedType) {
128
+ // Editing an existing owned node - nodes:add won't fire, select already has the value.
129
+ RED.editor.editConfig("#state-ref-selector", selectedType, currentRef);
130
+ } else {
131
+ // Creating a new instance - nodes:add handler will capture the new ID.
132
+ RED.editor.editConfig("#state-ref-selector", selectedType, "_ADD_");
133
+ }
134
+ });
135
+
136
+ // Populate the type dropdown, pre-select the current type, or auto-create a default.
137
+ getStateProviderTypes().then(function(providerTypes) {
138
+ providerTypes.forEach(function(t) {
139
+ $('<option>').val(t.id).text(t.name).appendTo(typeSelector);
140
+ });
141
+
142
+ let currentRef = stateRefSelector.val();
143
+ if (currentRef) {
144
+ // Existing reference - just pre-select its type.
145
+ let currentNode = RED.nodes.node(currentRef);
146
+ if (currentNode) typeSelector.val(currentNode.type);
147
+ updateConfigureBtn();
148
+ } else if (providerTypes.length > 0) {
149
+ // No state reference - auto-create a default InternalStateConfigNode so the
150
+ // consumer always has a valid state provider without requiring user action.
151
+ let defaultType = providerTypes.find(function(t) { return t.id === "InternalStateConfigNode"; }) || providerTypes[0];
152
+ let nodeDef = RED.nodes.getType(defaultType.id);
153
+ if (nodeDef) {
154
+ // _def and _users MUST be set explicitly when creating a node programmatically:
155
+ // - _def: RED.nodes.add() triggers updateConfigNodeUsers() which accesses
156
+ // n._def.defaults. Without it: "can't access property '_', e._def is undefined".
157
+ // - _users: When the outer config node is saved, Node-RED updates the referenced
158
+ // config node's user list. Without it: "can't access property 'indexOf',
159
+ // t.users is undefined".
160
+ let newNode = { id: RED.nodes.id(), type: defaultType.id, _def: nodeDef, _users: [], users: [] };
161
+ let defs = nodeDef.defaults || {};
162
+ for (let key in defs) {
163
+ if (defs[key].value !== undefined) { newNode[key] = defs[key].value; }
164
+ }
165
+ newNode.name = (node.name || node.type) + _stateNameSuffix;
166
+ // Pre-select the type immediately; nodes:add handler will set the ref ID.
167
+ typeSelector.val(defaultType.id);
168
+ updateConfigureBtn();
169
+ RED.nodes.add(newNode);
170
+ }
171
+ }
172
+ });
173
+ </script>
174
+
175
+ <script type="text/javascript" template-section="onIncludeEditSave">
176
+ var _stateSection = $("#state-config-section");
177
+ var _stateNodeAddHandler = _stateSection.data('stateNodeAddHandler');
178
+ if (_stateNodeAddHandler) {
179
+ RED.events.off('nodes:add', _stateNodeAddHandler);
180
+ _stateSection.data('stateNodeAddHandler', null);
181
+ }
182
+ var _stateRefSelector = _stateSection.find("#state-ref-selector");
183
+ node.stateReference = _stateRefSelector.val() || '';
184
+ console.log("StateTemplate: save - stateReference:", node.stateReference);
185
+ var _stateOriginalRef = _stateRefSelector.data('originalRef') || '';
186
+ if (_stateOriginalRef && _stateOriginalRef !== node.stateReference) {
187
+ var _stateOldNode = RED.nodes.node(_stateOriginalRef);
188
+ if (_stateOldNode) {
189
+ RED.nodes.remove(_stateOldNode);
190
+ RED.nodes.dirty(true);
191
+ }
192
+ }
193
+ </script>
194
+
195
+ <script type="text/javascript" template-section="onIncludeEditCancel">
196
+ var _stateSection = $("#state-config-section");
197
+ var _stateNodeAddHandler = _stateSection.data('stateNodeAddHandler');
198
+ if (_stateNodeAddHandler) {
199
+ RED.events.off('nodes:add', _stateNodeAddHandler);
200
+ _stateSection.data('stateNodeAddHandler', null);
201
+ }
202
+ var _stateRefSelector = _stateSection.find("#state-ref-selector");
203
+ var _stateCurrentRef = _stateRefSelector.val() || '';
204
+ var _stateOriginalRef = _stateRefSelector.data('originalRef') || '';
205
+ if (_stateCurrentRef && _stateCurrentRef !== _stateOriginalRef) {
206
+ var _stateTempNode = RED.nodes.node(_stateCurrentRef);
207
+ if (_stateTempNode) {
208
+ RED.nodes.remove(_stateTempNode);
209
+ RED.nodes.dirty(true);
210
+ }
211
+ }
212
+ </script>
213
+
214
+ <script type="text/html" template-section="onIncludeEditForm">
215
+ <div id="state-config-section">
216
+ <div class="form-row editorsectionheading">
217
+ <i class="w-16 fa fa-exchange"></i> <span>State</span>
218
+ </div>
219
+ <div class="editorgroupborder">
220
+ <div class="form-row nomargin">
221
+ <label class="towb_editorlabel" for="state-type-selector">Provider</label>
222
+ <div style="width: 70%; display: inline-flex;">
223
+ <select id="state-type-selector" style="flex-grow: 1; min-width: 0;">
224
+ </select>
225
+ <a id="state-configure-btn" class="red-ui-button disabled" style="margin-left: 10px; flex-shrink: 0;" title="Configure state provider">
226
+ <i class="fa fa-pencil"></i>
227
+ </a>
228
+ </div>
229
+ </div>
230
+ <!-- Hidden real select - populated by nodes:add handler when a new state provider is created. -->
231
+ <select id="state-ref-selector" style="display:none;"></select>
232
+ </div>
233
+ </div>
234
+ </script>
235
+
236
+ <script type="text/markdown" template-section="onIncludeDocumentation">
237
+ ### State
238
+
239
+ : *provider* (stateconfig) : Selects the type of state backend for this node. Click the pencil icon to create or configure the provider instance.
240
+
241
+ Available built-in provider: **Internal State Config Node** - stores state in memory with optional persistence across redeployments and restarts.
242
+
243
+ Each node owns its state provider exclusively. Switching provider types during configuration will delete the old provider instance when the editor is saved.
244
+ </script>
@@ -0,0 +1,17 @@
1
+ import { SourceUtility } from "../../NodeGenerator";
2
+ import { BaseNodeConfig, Template, TemplateDescriptor } from "../../NodeConstructor";
3
+
4
+ export interface StateTemplateConfig extends BaseNodeConfig {
5
+ stateReference: string;
6
+ }
7
+
8
+ export class StateTemplate extends Template {
9
+
10
+ public static getTemplateDescriptor(): TemplateDescriptor {
11
+ return new TemplateDescriptor(
12
+ "state",
13
+ StateTemplate,
14
+ SourceUtility.getSourcePath("/build/", "/src/") + "StateTemplate.html",
15
+ []);
16
+ }
17
+ }
@@ -0,0 +1,23 @@
1
+ import { DependencyType, ServiceDescriptor } from "../NodeConstructor";
2
+
3
+ export type ServiceDescriptionConfig = {
4
+ id: string;
5
+ sourceFile: string;
6
+ type?: string;
7
+ name?: string;
8
+ dependencies?: DependencyType[];
9
+ }
10
+
11
+ export function ServiceDescription(config: ServiceDescriptionConfig) {
12
+ return function(target: any) {
13
+ const descriptor = new ServiceDescriptor(
14
+ config.id,
15
+ config.name ?? target.name,
16
+ config.type ?? "services-plugin",
17
+ config.sourceFile,
18
+ target,
19
+ config.dependencies
20
+ );
21
+ target.getServiceDescriptor = (): ServiceDescriptor => descriptor;
22
+ };
23
+ }
@@ -1,8 +1,13 @@
1
1
  import { INodeClass } from "../../NodeGenerator";
2
- import { BaseService, ConfigNode, FlowDeployment, ServiceDescriptor } from "../../NodeConstructor";
2
+ import { BaseService, ConfigNode, FlowDeployment } from "../../NodeConstructor";
3
+ import { ServiceDescription } from "../ServiceDescriptionDecorator";
3
4
  import { NodeAPI, NodeAPISettingsWithData } from "node-red";
4
5
 
5
6
 
7
+ @ServiceDescription({
8
+ id: "@theotherwillembotha/nodetypeservice",
9
+ sourceFile: "@theotherwillembotha/node-red-plugincore"
10
+ })
6
11
  export class NodeTypeService extends BaseService {
7
12
  private red!: NodeAPI<NodeAPISettingsWithData>;
8
13
  private nodeTypes:{[key:string]:INodeClass[]} = {};
@@ -54,14 +59,4 @@ export class NodeTypeService extends BaseService {
54
59
  return this.nodeTypes[tag] ? this.nodeTypes[tag] : [];
55
60
  }
56
61
 
57
- static override getServiceDescriptor():ServiceDescriptor {
58
- return new ServiceDescriptor(
59
- "@theotherwillembotha/nodetypeservice",
60
- "NodeTypeService",
61
- "services-plugin",
62
- "@theotherwillembotha/node-red-plugincore",
63
- NodeTypeService,
64
- []
65
- );
66
- }
67
62
  }
@@ -27,7 +27,7 @@
27
27
  overflow: hidden;
28
28
  }
29
29
 
30
- /* Title bar matches Node-RED's main nav bar colour */
30
+ /* Title bar - matches Node-RED's main nav bar colour */
31
31
  .plugincore-dialog-titlebar {
32
32
  display: flex;
33
33
  align-items: center;
@@ -43,7 +43,7 @@
43
43
  font-size: 13px;
44
44
  }
45
45
 
46
- /* Close button jQuery UI styling as used in Node-RED dialogs */
46
+ /* Close button - jQuery UI styling as used in Node-RED dialogs */
47
47
  .plugincore-dialog-close {
48
48
  color: #ccc !important;
49
49
  background: transparent !important;
@@ -56,9 +56,9 @@
56
56
  border-color: rgba(255, 255, 255, 0.3) !important;
57
57
  }
58
58
 
59
- /* Scrollable body that contains the jQuery UI tab widget */
59
+ /* Scrollable body that contains the jQuery UI tab widget or plain content */
60
60
  .plugincore-dialog-body {
61
- flex: 1;
61
+ flex: 1 1 auto;
62
62
  overflow: auto;
63
63
  padding: 10px;
64
64
  }
@@ -148,15 +148,45 @@
148
148
  return $table;
149
149
  };
150
150
 
151
+ /**
152
+ * PluginCore.tabs(tabs) → jQuery element
153
+ *
154
+ * tabs: [{ label: string, render($container) }]
155
+ *
156
+ * Builds and returns a jQuery UI tab widget element.
157
+ * Can be embedded in any container, including inside PluginCore.dialog render().
158
+ */
159
+ PluginCore.tabs = function(tabs) {
160
+ var uid = 'plugincore-tabs-' + Date.now();
161
+ var $tabsEl = $('<div>').attr('id', uid);
162
+ var $ul = $('<ul>');
163
+
164
+ (tabs || []).forEach(function(tab, i) {
165
+ var paneId = uid + '-pane-' + i;
166
+ $ul.append(
167
+ $('<li>').append($('<a>').attr('href', '#' + paneId).text(tab.label))
168
+ );
169
+ var $pane = $('<div>').attr('id', paneId);
170
+ if (tab.render) { tab.render($pane); }
171
+ $tabsEl.append($pane);
172
+ });
173
+
174
+ $tabsEl.prepend($ul);
175
+ $tabsEl.tabs();
176
+
177
+ return $tabsEl;
178
+ };
179
+
151
180
  /**
152
181
  * PluginCore.dialog(options) → { close() }
153
182
  *
154
183
  * options: {
155
- * title : string,
156
- * tabs : [{ label: string, render($container) }]
184
+ * title : string,
185
+ * render : function($container) - simple dialog; body passed directly
186
+ * tabs : [{ label: string, render($c) }] - tabbed dialog; delegates to PluginCore.tabs
157
187
  * }
158
188
  *
159
- * Uses jQuery UI tabs for the tab widget (already bundled with Node-RED).
189
+ * render and tabs can coexist: render is called first, then the tab widget is appended.
160
190
  * Closes on: close button click, overlay click, or Escape key.
161
191
  */
162
192
  PluginCore.dialog = function(options) {
@@ -179,32 +209,17 @@
179
209
  );
180
210
  $titlebar.append($closeBtn);
181
211
 
182
- // ── Body + jQuery UI tabs ──────────────────────────────────────────
183
- var $body = $('<div class="plugincore-dialog-body">');
184
- var $tabsEl = $('<div>').attr('id', uid + '-tabs');
185
- var $ul = $('<ul>');
212
+ // ── Body ──────────────────────────────────────────────────────────
213
+ var $body = $('<div class="plugincore-dialog-body">');
186
214
 
187
- (options.tabs || []).forEach(function(tab, i) {
188
- var paneId = uid + '-pane-' + i;
189
- $ul.append(
190
- $('<li>').append($('<a>').attr('href', '#' + paneId).text(tab.label))
191
- );
192
- var $pane = $('<div>').attr('id', paneId);
193
- if (tab.render) { tab.render($pane); }
194
- $tabsEl.append($pane);
195
- });
196
-
197
- $tabsEl.prepend($ul);
198
- $body.append($tabsEl);
215
+ if (options.render) { options.render($body); }
216
+ if (options.tabs) { $body.append(PluginCore.tabs(options.tabs)); }
199
217
 
200
218
  // ── Assemble ───────────────────────────────────────────────────────
201
219
  $dialog.append($titlebar).append($body);
202
220
  $overlay.append($dialog);
203
221
  $('body').append($overlay);
204
222
 
205
- // Initialise jQuery UI tabs (bundled with Node-RED)
206
- $tabsEl.tabs();
207
-
208
223
  // ── Close logic ───────────────────────────────────────────────────
209
224
  var escNs = 'keydown.plugincore-dialog-' + uid;
210
225
 
@@ -1,6 +1,7 @@
1
+ import os from "os";
1
2
  import { RestAuthType } from "../logger/service/LoggerService";
2
3
  import { BaseNode, createPostConstructDecorator, NodeManager } from "../NodeConstructor";
3
- import { ProxyManagerClient } from "./service/ReverseProxyTypeService";
4
+ import { HTTPScheme, ProxyManagerClient } from "./service/ReverseProxyTypeService";
4
5
  import { ApiKeyAuthenticationConfig, BasicAuthenticationConfig, EndpointConfig, EndpointMethodType, NoAuthenticationConfig, WebhookServer } from "./service/WebhookServerService";
5
6
  import { WebhookTemplateConfig } from "./template/WebhookTemplate";
6
7
 
@@ -10,6 +11,14 @@ type WebhookConfig = {
10
11
  methods:EndpointMethodType[],
11
12
  }
12
13
 
14
+ type ManagedProxyHost = {
15
+ proxyNodeId: string;
16
+ hostId: number;
17
+ }
18
+
19
+ // In-memory registry: nodeId → managed proxy host entries from the last deploy.
20
+ const managedProxyHosts = new Map<string, ManagedProxyHost[]>();
21
+
13
22
  const Webhook = createPostConstructDecorator<WebhookConfig>('Webhook')
14
23
  .withInitLogic(async (instance, propertyKey, webhookconfig:WebhookConfig) => {
15
24
  //console.log(`Registering webhook callback`, "PROPERTYKEY", propertyKey, "WEBHOOK CONFIG", webhookconfig, "THIS", this);
@@ -35,24 +44,58 @@ const Webhook = createPostConstructDecorator<WebhookConfig>('Webhook')
35
44
 
36
45
  if(nodeConfig.reverseProxies_enabled){
37
46
 
38
- await Promise.all(nodeConfig.reverseProxies_connections.map(async proxy => {
47
+ let targetPort = webhookServer.config().port;
48
+ let targetHost = os.hostname();
49
+ let nodeId = nodeConfig.id;
50
+ let newlyManaged: ManagedProxyHost[] = [];
39
51
 
52
+ await Promise.all(nodeConfig.reverseProxies_connections.map(async proxy => {
40
53
  try{
41
- // get a n instance of reverseProxNode.
42
- let reverseProxyNode = (NodeManager.RED.nodes.getNode(proxy.proxy) as any).node();
43
-
44
54
  // TODO: ReverseProxyConfigNodes need to become their own subtype of config nodes so we can enforce the ".client()" method.
55
+ let reverseProxyNode = (NodeManager.RED.nodes.getNode(proxy.proxy) as any).node();
45
56
  let proxyClient = reverseProxyNode.client() as ProxyManagerClient;
46
57
 
47
- console.log("List of known hosts: ", await proxyClient.getHosts());
48
- //console.log("ADD REVERSE PROXY:" + proxy.proxy + " to " + node.id(), proxyClient);
58
+ let hosts = await proxyClient.getHosts();
59
+ let matchingHost = hosts.find(h => h.domainNames?.includes(proxy.domainname));
60
+
61
+ if (!matchingHost) {
62
+ await proxyClient.updateHost({ domainNames: [proxy.domainname], forwardHost: targetHost, forwardPort: targetPort, scheme: HTTPScheme.HTTP });
63
+ // Fetch again to get the assigned ID of the newly created entry.
64
+ let updatedHosts = await proxyClient.getHosts();
65
+ matchingHost = updatedHosts.find(h => h.domainNames?.includes(proxy.domainname));
66
+ }
67
+ else if (matchingHost.forwardHost !== targetHost || matchingHost.forwardPort !== targetPort) {
68
+ await proxyClient.updateHost({ id: matchingHost.id, forwardHost: targetHost, forwardPort: targetPort, scheme: HTTPScheme.HTTP });
69
+ }
70
+
71
+ if (matchingHost?.id !== undefined) {
72
+ newlyManaged.push({ proxyNodeId: proxy.proxy, hostId: matchingHost.id });
73
+ }
49
74
  }
50
75
  catch(e){
51
- console.log(e);
76
+ console.error(e);
52
77
  }
53
-
54
- return Promise.resolve();
55
78
  }));
79
+
80
+ // Remove any stale proxy host entries that were managed last deploy but are no longer in the current config.
81
+ let previouslyManaged = managedProxyHosts.get(nodeId) ?? [];
82
+ let newHostIds = new Set(newlyManaged.map(m => m.hostId));
83
+
84
+ await Promise.all(previouslyManaged
85
+ .filter(m => !newHostIds.has(m.hostId))
86
+ .map(async stale => {
87
+ try{
88
+ let reverseProxyNode = (NodeManager.RED.nodes.getNode(stale.proxyNodeId) as any).node();
89
+ let proxyClient = reverseProxyNode.client() as ProxyManagerClient;
90
+ await proxyClient.deleteHost(stale.hostId);
91
+ }
92
+ catch(e){
93
+ console.error(e);
94
+ }
95
+ })
96
+ );
97
+
98
+ managedProxyHosts.set(nodeId, newlyManaged);
56
99
  }
57
100
 
58
101
  // register a close listener to the node.
@@ -22,6 +22,7 @@ export abstract class ReverseProxyType {
22
22
  export interface ProxyManagerClient {
23
23
  updateHost(data: Partial<HostEntry>):Promise<void>;
24
24
  getHosts():Promise<Partial<HostEntry>[]>;
25
+ deleteHost(id: number):Promise<void>;
25
26
  }
26
27
 
27
28
  export type HostEntry = {