@theotherwillembotha/node-red-plugincore 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +616 -220
- package/build/GenerateNodes.js +9 -33
- package/build/Nodes.html +2 -1505
- package/build/Nodes.js +5 -19
- package/build/Plugins.html +11 -0
- package/build/Plugins.js +17 -12
- package/build/core/NodeConstructor.d.ts +12 -16
- package/build/core/NodeConstructor.d.ts.map +1 -1
- package/build/core/NodeConstructor.js +125 -185
- package/build/core/NodeGenerator.d.ts +3 -1
- package/build/core/NodeGenerator.d.ts.map +1 -1
- package/build/core/NodeGenerator.js +139 -43
- package/build/core/NodeManagerRuntime.d.ts +44 -0
- package/build/core/NodeManagerRuntime.d.ts.map +1 -0
- package/build/core/NodeManagerRuntime.js +140 -0
- package/build/core/Utils.d.ts.map +1 -1
- package/build/core/logger/service/LoggerService.d.ts +8 -6
- package/build/core/logger/service/LoggerService.d.ts.map +1 -1
- package/build/core/logger/service/LoggerService.js +101 -23
- package/build/core/logger/template/LoggerTemplate.d.ts +1 -2
- package/build/core/logger/template/LoggerTemplate.d.ts.map +1 -1
- package/build/core/logger/template/LoggerTemplate.js +18 -5
- package/build/core/metrics/MetricsDecorator.d.ts +1 -2
- package/build/core/metrics/MetricsDecorator.d.ts.map +1 -1
- package/build/core/metrics/MetricsDecorator.js +89 -116
- package/build/core/metrics/node/CounterMetricConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/CounterMetricConfigNode.js +4 -5
- package/build/core/metrics/node/GaugeMetricConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/GaugeMetricConfigNode.js +5 -6
- package/build/core/metrics/node/MetricsConfigNode.d.ts +19 -9
- package/build/core/metrics/node/MetricsConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/MetricsConfigNode.js +14 -45
- package/build/core/metrics/node/TimerMetricConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/TimerMetricConfigNode.js +11 -17
- package/build/core/metrics/service/MetricsService.d.ts +110 -114
- package/build/core/metrics/service/MetricsService.d.ts.map +1 -1
- package/build/core/metrics/service/MetricsService.js +122 -339
- package/build/core/metrics/template/CounterMetricTemplate.d.ts +1 -2
- package/build/core/metrics/template/CounterMetricTemplate.d.ts.map +1 -1
- package/build/core/metrics/template/CounterMetricTemplate.js +16 -5
- package/build/core/metrics/template/GaugeMetricTemplate.d.ts +1 -2
- package/build/core/metrics/template/GaugeMetricTemplate.d.ts.map +1 -1
- package/build/core/metrics/template/GaugeMetricTemplate.js +16 -5
- package/build/core/metrics/template/MetricsTemplate.d.ts +1 -2
- package/build/core/metrics/template/MetricsTemplate.d.ts.map +1 -1
- package/build/core/metrics/template/MetricsTemplate.js +18 -6
- package/build/core/metrics/template/TimerMetricTemplate.d.ts +1 -2
- package/build/core/metrics/template/TimerMetricTemplate.d.ts.map +1 -1
- package/build/core/metrics/template/TimerMetricTemplate.js +16 -5
- package/build/core/other/node/DelegatedConfigReferenceNode.d.ts +1 -1
- package/build/core/other/node/DelegatedConfigReferenceNode.d.ts.map +1 -1
- package/build/core/other/node/DelegatedConfigReferenceNode.js +1 -1
- package/build/core/other/service/InputService.d.ts.map +1 -1
- package/build/core/other/service/SettingsService.d.ts +1 -2
- package/build/core/other/service/SettingsService.d.ts.map +1 -1
- package/build/core/other/service/SettingsService.js +19 -5
- package/build/core/other/template/BasicTemplate.d.ts +1 -2
- package/build/core/other/template/BasicTemplate.d.ts.map +1 -1
- package/build/core/other/template/BasicTemplate.js +15 -5
- package/build/core/other/template/SettingsTemplate.d.ts +1 -2
- package/build/core/other/template/SettingsTemplate.d.ts.map +1 -1
- package/build/core/other/template/SettingsTemplate.js +17 -5
- package/build/core/state/node/InternalStateConfigNode.d.ts +16 -0
- package/build/core/state/node/InternalStateConfigNode.d.ts.map +1 -0
- package/build/core/state/node/InternalStateConfigNode.js +89 -0
- package/build/core/state/node/StateConfigNode.d.ts +24 -0
- package/build/core/state/node/StateConfigNode.d.ts.map +1 -0
- package/build/core/state/node/StateConfigNode.js +22 -0
- package/build/core/state/service/StateService.d.ts +34 -0
- package/build/core/state/service/StateService.d.ts.map +1 -0
- package/build/core/state/service/StateService.js +201 -0
- package/build/core/state/template/StateTemplate.d.ts +7 -0
- package/build/core/state/template/StateTemplate.d.ts.map +1 -0
- package/build/core/state/template/StateTemplate.js +25 -0
- package/build/core/tagging/ServiceDescriptionDecorator.d.ts +10 -0
- package/build/core/tagging/ServiceDescriptionDecorator.d.ts.map +1 -0
- package/build/core/tagging/ServiceDescriptionDecorator.js +10 -0
- package/build/core/tagging/TemplateDescriptionDecorator.d.ts +8 -0
- package/build/core/tagging/TemplateDescriptionDecorator.d.ts.map +1 -0
- package/build/core/tagging/TemplateDescriptionDecorator.js +10 -0
- package/build/core/tagging/service/NodeTypeService.d.ts +1 -2
- package/build/core/tagging/service/NodeTypeService.d.ts.map +1 -1
- package/build/core/tagging/service/NodeTypeService.js +19 -5
- package/build/core/ui/template/ScriptEditorTemplate.d.ts +1 -2
- package/build/core/ui/template/ScriptEditorTemplate.d.ts.map +1 -1
- package/build/core/ui/template/ScriptEditorTemplate.js +15 -5
- package/build/core/ui/template/UIHelperTemplate.d.ts +1 -2
- package/build/core/ui/template/UIHelperTemplate.d.ts.map +1 -1
- package/build/core/ui/template/UIHelperTemplate.js +15 -5
- package/build/core/webhook/node/WebhookServerConfigNode.d.ts.map +1 -1
- package/build/core/webhook/service/ReverseProxyTypeService.d.ts.map +1 -1
- package/build/core/webhook/service/WebhookServerService.d.ts +3 -4
- package/build/core/webhook/service/WebhookServerService.d.ts.map +1 -1
- package/build/core/webhook/service/WebhookServerService.js +48 -18
- package/build/core/webhook/template/WebhookTemplate.d.ts +1 -2
- package/build/core/webhook/template/WebhookTemplate.d.ts.map +1 -1
- package/build/core/webhook/template/WebhookTemplate.js +19 -6
- package/build/index.d.ts +6 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +7 -2
- package/build/runtime/NodeManagerRuntime.js +140 -0
- package/documentation/CounterMetricConfigNode.png +0 -0
- package/documentation/GaugeMetricConfigNode.png +0 -0
- package/documentation/LoggerTemplate.png +0 -0
- package/documentation/MetricsTemplate.png +0 -0
- package/documentation/StateTemplate.png +0 -0
- package/documentation/TimerMetricConfigNode_histogram.png +0 -0
- package/documentation/TimerMetricConfigNode_summary.png +0 -0
- package/documentation/WebhookServerConfigNode.png +0 -0
- package/documentation/WebhookTemplate.png +0 -0
- package/package.json +4 -11
- package/src/GenerateNodes.ts +10 -40
- package/src/core/NodeConstructor.ts +150 -228
- package/src/core/NodeGenerator.ts +156 -47
- package/src/core/NodeManagerRuntime.ts +179 -0
- package/src/core/logger/service/LoggerService.ts +60 -32
- package/src/core/logger/template/LoggerTemplate.html +11 -6
- package/src/core/logger/template/LoggerTemplate.ts +9 -11
- package/src/core/metrics/MetricsDecorator.ts +140 -168
- package/src/core/metrics/node/CounterMetricConfigNode.ts +20 -20
- package/src/core/metrics/node/GaugeMetricConfigNode.ts +24 -25
- package/src/core/metrics/node/MetricsConfigNode.ts +29 -48
- package/src/core/metrics/node/TimerMetricConfigNode.ts +50 -59
- package/src/core/metrics/service/MetricsService.ts +214 -489
- package/src/core/metrics/template/CounterMetricTemplate.html +0 -1
- package/src/core/metrics/template/CounterMetricTemplate.ts +8 -11
- package/src/core/metrics/template/GaugeMetricTemplate.html +1 -1
- package/src/core/metrics/template/GaugeMetricTemplate.ts +8 -11
- package/src/core/metrics/template/MetricsTemplate.html +132 -13
- package/src/core/metrics/template/MetricsTemplate.ts +9 -10
- package/src/core/metrics/template/TimerMetricTemplate.ts +8 -10
- package/src/core/other/node/DelegatedConfigReferenceNode.ts +1 -1
- package/src/core/other/service/SettingsService.ts +6 -10
- package/src/core/other/template/BasicTemplate.html +1 -1
- package/src/core/other/template/BasicTemplate.ts +6 -11
- package/src/core/other/template/SettingsTemplate.ts +8 -13
- package/src/core/state/node/InternalStateConfigNode.html +77 -0
- package/src/core/state/node/InternalStateConfigNode.ts +102 -0
- package/src/core/state/node/StateConfigNode.ts +29 -0
- package/src/core/state/service/StateService.ts +180 -0
- package/src/core/state/template/StateTemplate.html +244 -0
- package/src/core/state/template/StateTemplate.ts +18 -0
- package/src/core/tagging/ServiceDescriptionDecorator.ts +23 -0
- package/src/core/tagging/TemplateDescriptionDecorator.ts +19 -0
- package/src/core/tagging/service/NodeTypeService.ts +6 -11
- package/src/core/ui/template/ScriptEditorTemplate.ts +6 -10
- package/src/core/ui/template/UIHelperTemplate.html +41 -26
- package/src/core/ui/template/UIHelperTemplate.ts +6 -10
- package/src/core/webhook/service/WebhookServerService.ts +39 -23
- package/src/core/webhook/template/WebhookTemplate.html +28 -35
- package/src/core/webhook/template/WebhookTemplate.ts +10 -12
- package/src/index.ts +8 -2
- package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts +0 -20
- package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts.map +0 -1
- package/build/core/logger/node/ConsoleLoggerConfigNode.js +0 -62
- package/build/core/logger/node/RestLoggerConfigNode.d.ts +0 -40
- package/build/core/logger/node/RestLoggerConfigNode.d.ts.map +0 -1
- package/build/core/logger/node/RestLoggerConfigNode.js +0 -128
- package/documentation/ConsoleLoggerConfigNode.png +0 -0
- package/documentation/MetricsConfigNode.png +0 -0
- package/documentation/RestLoggerConfigNode.png +0 -0
- package/documentation/TimerMetricConfigNode.png +0 -0
- package/documentation/WebhookNode.png +0 -0
- package/src/core/logger/node/ConsoleLoggerConfigNode.html +0 -90
- package/src/core/logger/node/ConsoleLoggerConfigNode.ts +0 -71
- package/src/core/logger/node/RestLoggerConfigNode.html +0 -183
- package/src/core/logger/node/RestLoggerConfigNode.ts +0 -170
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
2
3
|
|
|
3
|
-
import { buildNode, Template, NodeDescriptor, BaseService, ServiceDescriptor, BaseNode, DependencyType, TemplateDescriptor } from "./NodeConstructor"
|
|
4
|
+
import { buildNode, buildNodeBuilder, Template, NodeDescriptor, BaseService, ServiceDescriptor, BaseNode, DependencyType, TemplateDescriptor } from "./NodeConstructor"
|
|
4
5
|
|
|
5
6
|
const templatePropertyKeys = ["onIncludeOnce", "onIncludeDefaults", "onIncludeEditPrepare", "onIncludeEditPrepare", "onIncludeEditSave", "onIncludeEditForm"];
|
|
6
7
|
|
|
@@ -57,12 +58,10 @@ class NodeGenerator {
|
|
|
57
58
|
|
|
58
59
|
public registerTemplate(template: ITemplateClass): NodeGenerator {
|
|
59
60
|
try{
|
|
60
|
-
// verify that we dont already have this template.
|
|
61
61
|
let descriptor = template.getTemplateDescriptor();
|
|
62
62
|
if(!this._templates.map(t => t.name()).includes(descriptor.name())){
|
|
63
63
|
this._templates.push(descriptor);
|
|
64
|
-
|
|
65
|
-
// descriptor.dependencies().forEach(dependency => this.addDependency(dependency));
|
|
64
|
+
descriptor.dependencies().forEach(dependency => this.addDependency(dependency));
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
67
|
catch(error){
|
|
@@ -74,14 +73,14 @@ class NodeGenerator {
|
|
|
74
73
|
|
|
75
74
|
public registerNode(node:INodeClass):NodeGenerator{
|
|
76
75
|
try{
|
|
77
|
-
// verify that we have dont already have this node.
|
|
78
76
|
let descriptor = node.getNodeDescriptor();
|
|
79
77
|
if(!this._nodes[descriptor.id()]){
|
|
80
78
|
this._nodes[descriptor.id()] = {nodeClass:node, descriptor:descriptor};
|
|
81
|
-
//
|
|
82
|
-
|
|
79
|
+
// resolve templates declared in @NodeDescription
|
|
80
|
+
descriptor.templates().forEach(t => this.registerTemplate(t.template));
|
|
81
|
+
// resolve explicit dependencies (services, nodes, templates)
|
|
82
|
+
descriptor.dependencies().forEach(dependency => this.addDependency(dependency));
|
|
83
83
|
}
|
|
84
|
-
|
|
85
84
|
}
|
|
86
85
|
catch(error){
|
|
87
86
|
console.log("ERROR:", error);
|
|
@@ -89,13 +88,12 @@ class NodeGenerator {
|
|
|
89
88
|
return this;
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
public registerService(service: IServiceClass):NodeGenerator {
|
|
91
|
+
public registerService(service: IServiceClass):NodeGenerator {
|
|
93
92
|
try{
|
|
94
93
|
let descriptor = service.getServiceDescriptor();
|
|
95
94
|
if(!this._services.map(s => s.name()).includes(descriptor.name())){
|
|
96
95
|
this._services.push(descriptor);
|
|
97
|
-
|
|
98
|
-
//descriptor.dependencies().forEach(dependency => this.addDependency(dependency));
|
|
96
|
+
descriptor.dependencies().forEach(dependency => this.addDependency(dependency));
|
|
99
97
|
}
|
|
100
98
|
}
|
|
101
99
|
catch(error){
|
|
@@ -104,33 +102,22 @@ class NodeGenerator {
|
|
|
104
102
|
return this;
|
|
105
103
|
}
|
|
106
104
|
|
|
107
|
-
public generate(nodesOutputFile:string, pluginsOutputFile:string){
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// STEP 2. compose the HTMLStructure of each of the nodes.
|
|
118
|
-
let nodeHTMLOutut = Object.values(this._nodes)
|
|
119
|
-
.map(node => {
|
|
120
|
-
console.log(`Processing node: ${node.descriptor.id()}`);
|
|
121
|
-
|
|
122
|
-
return buildNode(node.nodeClass);
|
|
123
|
-
})
|
|
124
|
-
.join("\n")
|
|
105
|
+
public generate(nodesOutputFile:string, pluginsOutputFile:string, packageName:string){
|
|
106
|
+
// Partition nodes into owned (belongs to this package) vs shared (from dependencies).
|
|
107
|
+
// Owned nodes get standard registerType + data-template-name in Nodes.html so that
|
|
108
|
+
// Node-RED's registry maps them to this module (enabling palette icons, flow library
|
|
109
|
+
// detection, etc.). Shared nodes keep the deferred pattern in Plugins.html to avoid
|
|
110
|
+
// HTML-scanning conflicts when multiple plugins bundle the same infrastructure nodes.
|
|
111
|
+
const allNodes = Object.values(this._nodes);
|
|
112
|
+
const ownedNodes = allNodes.filter(node => node.descriptor.package() === packageName);
|
|
113
|
+
const sharedNodes = allNodes.filter(node => node.descriptor.package() !== packageName);
|
|
125
114
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
${
|
|
129
|
-
-->
|
|
130
|
-
${templateInclude}
|
|
131
|
-
${nodeHTMLOutut}
|
|
115
|
+
console.log(`\nPackage: ${packageName}`);
|
|
116
|
+
console.log(` Owned nodes (→ Nodes.html): ${ownedNodes.map(n => n.descriptor.id()).join(', ') || '(none)'}`);
|
|
117
|
+
console.log(` Shared nodes (→ Plugins.html): ${sharedNodes.map(n => n.descriptor.id()).join(', ') || '(none)'}\n`);
|
|
132
118
|
|
|
133
|
-
|
|
119
|
+
// STEP 1. Generate Nodes.html with standard client-side definitions for owned nodes.
|
|
120
|
+
this.generateNodesHtml(nodesOutputFile + ".html", ownedNodes);
|
|
134
121
|
|
|
135
122
|
// Step 3. compose the JS structure for each of the nodes.
|
|
136
123
|
// 1. get a list of the files to import.
|
|
@@ -153,15 +140,37 @@ ${nodeHTMLOutut}
|
|
|
153
140
|
${NodeGenerator.warning}
|
|
154
141
|
*/
|
|
155
142
|
"use strict";
|
|
156
|
-
const NodeManager = require("
|
|
143
|
+
const { NodeManager } = require("./runtime/NodeManagerRuntime");
|
|
157
144
|
${importList}
|
|
158
145
|
module.exports = (RED) => {
|
|
159
146
|
let manager = new NodeManager(RED);
|
|
160
147
|
${moduleExports}
|
|
161
|
-
}
|
|
162
|
-
|
|
148
|
+
}
|
|
149
|
+
|
|
163
150
|
`);
|
|
164
151
|
|
|
152
|
+
// Copy NodeManagerRuntime.js into the plugin's build/runtime/ folder so that
|
|
153
|
+
// the generated Nodes.js require("./runtime/NodeManagerRuntime") resolves locally
|
|
154
|
+
// with no dependency on plugincore being installed in the user's environment.
|
|
155
|
+
const outputDir = path.dirname(nodesOutputFile);
|
|
156
|
+
const runtimeDestDir = path.join(outputDir, 'runtime');
|
|
157
|
+
const runtimeDestPath = path.join(runtimeDestDir, 'NodeManagerRuntime.js');
|
|
158
|
+
|
|
159
|
+
let runtimeSrcPath: string;
|
|
160
|
+
try {
|
|
161
|
+
// Resolves when this is a consumer plugin - plugincore is in node_modules.
|
|
162
|
+
runtimeSrcPath = require.resolve('@theotherwillembotha/node-red-plugincore/build/runtime/NodeManagerRuntime');
|
|
163
|
+
} catch {
|
|
164
|
+
// We ARE plugincore - NodeManagerRuntime.js is compiled alongside us in build/core/.
|
|
165
|
+
runtimeSrcPath = path.join(__dirname, 'NodeManagerRuntime.js');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (path.resolve(runtimeSrcPath) !== path.resolve(runtimeDestPath)) {
|
|
169
|
+
fs.mkdirSync(runtimeDestDir, { recursive: true });
|
|
170
|
+
fs.copyFileSync(runtimeSrcPath, runtimeDestPath);
|
|
171
|
+
}
|
|
172
|
+
console.log(`NodeManagerRuntime.js → ${runtimeDestPath}`);
|
|
173
|
+
|
|
165
174
|
console.log(`Done building ${Object.keys(this._nodes).length} nodes\n${Object.values(this._nodes).map(node => " - " + node.descriptor.id() + "\n").join("")}\n`);
|
|
166
175
|
|
|
167
176
|
let serviceExports = this._services.map(service => {
|
|
@@ -196,29 +205,38 @@ module.exports = function (RED) {
|
|
|
196
205
|
|
|
197
206
|
if(addedPlugin && plugin.instantiate && !plugin.instance){
|
|
198
207
|
plugin.instance = new plugin.class();
|
|
199
|
-
|
|
208
|
+
|
|
200
209
|
// instantiate the plugin.
|
|
201
210
|
await plugin.instance.init(RED);
|
|
202
211
|
|
|
212
|
+
// Eagerly call onDeploy with any pre-existing saved flows so that
|
|
213
|
+
// service registries (MetricsService, WebhookServerService, etc.) are
|
|
214
|
+
// populated before config nodes are constructed during flow startup.
|
|
215
|
+
// Without this, MetricsConfigNode._metrics is undefined on first boot.
|
|
216
|
+
const existingFlows = await runtime.flows.getFlows({});
|
|
217
|
+
const hasExistingFlows = existingFlows && existingFlows.flows && existingFlows.flows.length > 0;
|
|
218
|
+
if (hasExistingFlows) {
|
|
219
|
+
await plugin.instance.onDeploy(existingFlows);
|
|
220
|
+
}
|
|
221
|
+
|
|
203
222
|
// publish an event that it has ben deployed.
|
|
204
223
|
RED.events.emit("plugin.instantiated", pluginID);
|
|
205
|
-
|
|
224
|
+
|
|
206
225
|
pluginList.splice(pluginList.findIndex(current => current.id() === addedPlugin.id()), 1);
|
|
207
226
|
|
|
208
227
|
// register a flow deployment listener.
|
|
209
|
-
|
|
228
|
+
// startupDeployment is only needed for a truly fresh Node-RED with no saved flows.
|
|
229
|
+
let startupDeployment = !hasExistingFlows;
|
|
210
230
|
RED.events.on('runtime-event', async (event) => {
|
|
211
|
-
// startup deployment.
|
|
231
|
+
// startup deployment (fresh Node-RED with no pre-existing flows).
|
|
212
232
|
if ("runtime-deploy" === event?.id && startupDeployment) {
|
|
213
|
-
//console.log("STARTUP DEPLOYMENT");
|
|
214
233
|
startupDeployment = false;
|
|
215
234
|
const flows = await runtime.flows.getFlows({});
|
|
216
235
|
await plugin.instance.onDeploy(flows);
|
|
217
236
|
}
|
|
218
|
-
|
|
237
|
+
|
|
219
238
|
// stopping flows on redeployment.
|
|
220
239
|
if ("runtime-state" === event?.id && event?.payload?.state === "stop" && event?.payload?.deploy) {
|
|
221
|
-
//console.log("REDEPLOY!");
|
|
222
240
|
const flows = await runtime.flows.getFlows({});
|
|
223
241
|
await plugin.instance.onDeploy(flows);
|
|
224
242
|
}
|
|
@@ -239,6 +257,97 @@ module.exports = function (RED) {
|
|
|
239
257
|
`
|
|
240
258
|
fs.writeFileSync(pluginsOutputFile + ".js", output);
|
|
241
259
|
console.log(`Done building ${this._services.length} services\n${this._services.map(service => " - " + service.name() + "\n").join("")}\n`);
|
|
260
|
+
|
|
261
|
+
// STEP 4. Generate Plugins.html with deferred client-side definitions for shared nodes.
|
|
262
|
+
this.generatePluginsHtml(pluginsOutputFile + ".html", sharedNodes);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private generateNodesHtml(filePath: string, nodes: {nodeClass: INodeClass, descriptor: NodeDescriptor}[]): void {
|
|
266
|
+
if (nodes.length === 0) {
|
|
267
|
+
fs.writeFileSync(filePath, `
|
|
268
|
+
<!--
|
|
269
|
+
${NodeGenerator.warning}
|
|
270
|
+
No owned nodes in this package. Shared nodes are registered via Plugins.html.
|
|
271
|
+
-->
|
|
272
|
+
`);
|
|
273
|
+
console.log(`Nodes.html: no owned nodes\n`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const seenOnce = new Set<string>();
|
|
278
|
+
const onceHtmlParts: string[] = [];
|
|
279
|
+
const nodeParts: string[] = [];
|
|
280
|
+
|
|
281
|
+
nodes.forEach(node => {
|
|
282
|
+
console.log(`Building Nodes.html for: ${node.descriptor.id()}`);
|
|
283
|
+
const nb = buildNodeBuilder(node.nodeClass);
|
|
284
|
+
|
|
285
|
+
nb.buildOnceHtml().forEach(({source, html}) => {
|
|
286
|
+
if (!seenOnce.has(source)) {
|
|
287
|
+
seenOnce.add(source);
|
|
288
|
+
onceHtmlParts.push(html);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
nodeParts.push([
|
|
293
|
+
nb.buildStandardType(),
|
|
294
|
+
nb.buildHtml(),
|
|
295
|
+
nb.buildDocumentation()
|
|
296
|
+
].join('\n\n'));
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
fs.writeFileSync(filePath, `
|
|
300
|
+
<!--
|
|
301
|
+
${NodeGenerator.warning}
|
|
302
|
+
-->
|
|
303
|
+
${onceHtmlParts.join('\n')}
|
|
304
|
+
${nodeParts.join('\n\n')}
|
|
305
|
+
`);
|
|
306
|
+
console.log(`Done building Nodes.html (${nodes.length} owned nodes)\n`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private generatePluginsHtml(filePath: string, nodes: {nodeClass: INodeClass, descriptor: NodeDescriptor}[]): void {
|
|
310
|
+
if (nodes.length === 0) {
|
|
311
|
+
fs.writeFileSync(filePath, `
|
|
312
|
+
<!--
|
|
313
|
+
${NodeGenerator.warning}
|
|
314
|
+
No shared nodes in this package. All nodes are registered via Nodes.html.
|
|
315
|
+
-->
|
|
316
|
+
`);
|
|
317
|
+
console.log(`Plugins.html: no shared nodes\n`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const seenOnce = new Set<string>();
|
|
322
|
+
const onceHtmlParts: string[] = [];
|
|
323
|
+
const nodePluginParts: string[] = [];
|
|
324
|
+
|
|
325
|
+
nodes.forEach(node => {
|
|
326
|
+
console.log(`Building Plugins.html for: ${node.descriptor.id()}`);
|
|
327
|
+
const nb = buildNodeBuilder(node.nodeClass);
|
|
328
|
+
|
|
329
|
+
nb.buildOnceHtml().forEach(({source, html}) => {
|
|
330
|
+
if (!seenOnce.has(source)) {
|
|
331
|
+
seenOnce.add(source);
|
|
332
|
+
onceHtmlParts.push(html);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
nodePluginParts.push([
|
|
337
|
+
nb.buildDeferredType(),
|
|
338
|
+
nb.buildHtml(),
|
|
339
|
+
nb.buildDocumentation()
|
|
340
|
+
].join('\n\n'));
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
fs.writeFileSync(filePath, `
|
|
344
|
+
<!--
|
|
345
|
+
${NodeGenerator.warning}
|
|
346
|
+
-->
|
|
347
|
+
${onceHtmlParts.join('\n')}
|
|
348
|
+
${nodePluginParts.join('\n\n')}
|
|
349
|
+
`);
|
|
350
|
+
console.log(`Done building Plugins.html (${nodes.length} shared nodes)\n`);
|
|
242
351
|
}
|
|
243
352
|
|
|
244
353
|
private static generateHeader(node:NodeDescriptor){
|
|
@@ -265,7 +374,7 @@ class TypeUtility {
|
|
|
265
374
|
private constructor(){}
|
|
266
375
|
|
|
267
376
|
private static getPrototypes(t:INamedType):string[] {
|
|
268
|
-
if(!t.name){
|
|
377
|
+
if(!t || !t.name){
|
|
269
378
|
return [];
|
|
270
379
|
}
|
|
271
380
|
else{
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NodeManagerRuntime.ts
|
|
3
|
+
*
|
|
4
|
+
* STANDALONE - zero imports from the rest of plugincore.
|
|
5
|
+
*
|
|
6
|
+
* This file is compiled and copied into every plugin's build output so that
|
|
7
|
+
* plugins are self-contained and do not require plugincore to be separately
|
|
8
|
+
* installed in the user's Node-RED environment.
|
|
9
|
+
*
|
|
10
|
+
* When multiple plugins are deployed, each ships its own copy of this file.
|
|
11
|
+
* The first plugin to load registers its NodeManager and nodes; subsequent
|
|
12
|
+
* copies operate independently on their own node types with no conflict.
|
|
13
|
+
*
|
|
14
|
+
* POST_CONSTRUCT_KEY is intentionally a plain string (not a Symbol) so that
|
|
15
|
+
* the decorators (from plugincore's NodeConstructor, loaded via the plugin's
|
|
16
|
+
* dependency on plugincore) and this runtime file (a separate module instance)
|
|
17
|
+
* resolve to the same property key on node instances.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { NodeAPI, NodeAPISettingsWithData } from "node-red";
|
|
21
|
+
|
|
22
|
+
// ─── API version ────────────────────────────────────────────────────────────
|
|
23
|
+
// Semver of the NodeManager API contract - independent of plugincore's package
|
|
24
|
+
// version. Bump when the contract changes; plugins can read this to warn users
|
|
25
|
+
// that they were built against a different NodeManager version.
|
|
26
|
+
export const NODEMANAGER_API_VERSION = "1.0.0";
|
|
27
|
+
|
|
28
|
+
// ─── Shared key for post-construct initializer lists ────────────────────────
|
|
29
|
+
// Must be a plain string so it resolves identically across module instances.
|
|
30
|
+
export const POST_CONSTRUCT_KEY = '__plugincore_postConstructInitializers__';
|
|
31
|
+
|
|
32
|
+
// ─── Minimal types ───────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export interface BaseNodeConfig {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
type: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface INodeDescriptor {
|
|
41
|
+
id(): string;
|
|
42
|
+
tags(): string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface INodeClass {
|
|
46
|
+
new(node: any, config: any): any;
|
|
47
|
+
getNodeDescriptor(): INodeDescriptor;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Post-construct initializer runner ───────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
export function runPostConstructInitializers(instance: any): void {
|
|
53
|
+
const initializers: Function[] = instance[POST_CONSTRUCT_KEY];
|
|
54
|
+
if (!initializers || initializers.length === 0) return;
|
|
55
|
+
|
|
56
|
+
const className = instance.constructor.name;
|
|
57
|
+
initializers.forEach((init, index) => {
|
|
58
|
+
try {
|
|
59
|
+
init(instance);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error(
|
|
62
|
+
`Post-construct initialization failed:\n` +
|
|
63
|
+
` Class: ${className}\n` +
|
|
64
|
+
` Initializer: ${index + 1}/${initializers.length}\n` +
|
|
65
|
+
` Error: ${error instanceof Error ? error.message : String(error)}`
|
|
66
|
+
);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
delete instance[POST_CONSTRUCT_KEY];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─── NodeManager ─────────────────────────────────────────────────────────────
|
|
75
|
+
// RED is stored on `global` rather than as a static class field so that all
|
|
76
|
+
// module instances of NodeManagerRuntime (the local copy bundled into Nodes.js
|
|
77
|
+
// and the copy bundled via plugincore's NodeConstructor) share the same value.
|
|
78
|
+
// Without this, esbuild's two separate inline copies would have independent
|
|
79
|
+
// static fields and the one used by decorators would never see RED being set.
|
|
80
|
+
const _GLOBAL_RED_KEY = '__plugincore_NodeManager_RED__';
|
|
81
|
+
|
|
82
|
+
// ─── Global node-type registration guard ─────────────────────────────────────
|
|
83
|
+
// When multiple self-contained plugins are installed they each bundle plugincore
|
|
84
|
+
// inline and each try to call RED.nodes.registerType for the same shared
|
|
85
|
+
// infrastructure node types (ConsoleLoggerConfigNode, RestLoggerConfigNode, etc.).
|
|
86
|
+
// Node-RED rejects duplicate registrations with a warning and the second
|
|
87
|
+
// plugin's Nodes.js fails to load. This global set tracks which types have
|
|
88
|
+
// already been registered so subsequent plugins silently skip them.
|
|
89
|
+
const _GLOBAL_REGISTERED_NODES_KEY = '__plugincore_registered_nodes__';
|
|
90
|
+
|
|
91
|
+
function getRegisteredNodes(): Set<string> {
|
|
92
|
+
if (!(global as any)[_GLOBAL_REGISTERED_NODES_KEY]) {
|
|
93
|
+
(global as any)[_GLOBAL_REGISTERED_NODES_KEY] = new Set<string>();
|
|
94
|
+
}
|
|
95
|
+
return (global as any)[_GLOBAL_REGISTERED_NODES_KEY];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class NodeManager {
|
|
99
|
+
|
|
100
|
+
// nodeTypeService is resolved dynamically via RED.plugins - no static import needed.
|
|
101
|
+
private nodeTypeService: any;
|
|
102
|
+
private typeBacklog: INodeClass[] = [];
|
|
103
|
+
|
|
104
|
+
public constructor(RED: NodeAPI<NodeAPISettingsWithData>) {
|
|
105
|
+
(global as any)[_GLOBAL_RED_KEY] = RED;
|
|
106
|
+
|
|
107
|
+
const nodeTypeServiceId = "@theotherwillembotha/nodetypeservice";
|
|
108
|
+
|
|
109
|
+
const nodeTypeServiceListener = (pluginID: string) => {
|
|
110
|
+
if (pluginID === nodeTypeServiceId) {
|
|
111
|
+
RED.events.off('plugin.instantiated', nodeTypeServiceListener);
|
|
112
|
+
this.nodeTypeService = (RED.plugins.get(nodeTypeServiceId) as any).instance;
|
|
113
|
+
|
|
114
|
+
for (const nodeType of this.typeBacklog) {
|
|
115
|
+
for (const tag of nodeType.getNodeDescriptor().tags()) {
|
|
116
|
+
this.nodeTypeService.registerNodeType(tag, nodeType);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
this.typeBacklog = [];
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
this.nodeTypeService = (RED.plugins.get(nodeTypeServiceId) as any)?.instance;
|
|
124
|
+
if (!this.nodeTypeService) {
|
|
125
|
+
RED.events.on('plugin.instantiated', nodeTypeServiceListener);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public static get RED(): NodeAPI<NodeAPISettingsWithData> {
|
|
130
|
+
return (global as any)[_GLOBAL_RED_KEY];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public registerNodeType(typeName: string, type: INodeClass): NodeManager {
|
|
134
|
+
if (!type) {
|
|
135
|
+
console.error(
|
|
136
|
+
`[NodeManager v${NODEMANAGER_API_VERSION}] Type "${typeName}" is undefined. ` +
|
|
137
|
+
`Ensure it is exported and included in GenerateNodes.`
|
|
138
|
+
);
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const registeredNodes = getRegisteredNodes();
|
|
143
|
+
if (registeredNodes.has(typeName)) {
|
|
144
|
+
// Already registered by another plugin - skip to avoid Node-RED duplicate-registration error.
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
registeredNodes.add(typeName);
|
|
148
|
+
|
|
149
|
+
const nodeConstructor = function(this: any, config: any) {
|
|
150
|
+
try {
|
|
151
|
+
NodeManager.RED.nodes.createNode(this, config);
|
|
152
|
+
const node = new (type as any)(this, config);
|
|
153
|
+
runPostConstructInitializers(node);
|
|
154
|
+
if (node.onInit) {
|
|
155
|
+
node.onInit();
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`[NodeManager v${NODEMANAGER_API_VERSION}] Failed to construct node "${typeName}":`, error);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
NodeManager.RED.nodes.registerType(typeName, nodeConstructor, { settings: {} });
|
|
164
|
+
|
|
165
|
+
const nodeDescription = type.getNodeDescriptor();
|
|
166
|
+
if (this.nodeTypeService) {
|
|
167
|
+
for (const tag of nodeDescription.tags()) {
|
|
168
|
+
this.nodeTypeService.registerNodeType(tag, type);
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
this.typeBacklog.push(type);
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(`[NodeManager v${NODEMANAGER_API_VERSION}] Failed to register node type "${typeName}":`, error);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
|
|
2
|
-
import
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import {BaseService, ConfigNodeConfig, ConfigNode } from "../../NodeConstructor";
|
|
4
|
+
import { ServiceDescription } from "../../tagging/ServiceDescriptionDecorator";
|
|
3
5
|
import { NodeAPI, NodeAPISettingsWithData, Node } from "node-red";
|
|
4
|
-
import Handlebars from "handlebars";
|
|
5
6
|
import { LoggerTemplate, LoggerTemplateConfig } from "../template/LoggerTemplate";
|
|
6
|
-
var network = require('network');
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
// Build-time / optional runtime deps - lazy so they are never required at bundle load time.
|
|
9
|
+
function getHandlebars(): any { return require('handlebars'); }
|
|
10
|
+
|
|
11
|
+
function getLocalIpAddress(): string {
|
|
12
|
+
const interfaces = os.networkInterfaces();
|
|
13
|
+
for (const name of Object.keys(interfaces)) {
|
|
14
|
+
for (const iface of interfaces[name] ?? []) {
|
|
15
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
16
|
+
return iface.address;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return '127.0.0.1';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let _helpersRegistered = false;
|
|
24
|
+
function ensureHelpersRegistered(): void {
|
|
25
|
+
if (_helpersRegistered) return;
|
|
26
|
+
_helpersRegistered = true;
|
|
27
|
+
// helper for serializing json objects inside handlebars tags.
|
|
28
|
+
// you can then convert it like this: {{{json myobject}}}
|
|
29
|
+
getHandlebars().registerHelper('json', function(context: any) {
|
|
30
|
+
return JSON.stringify(context);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
13
33
|
|
|
14
34
|
export enum Level{
|
|
15
35
|
DEBUG = "DEBUG",
|
|
@@ -40,12 +60,13 @@ export type TagMap = {
|
|
|
40
60
|
|
|
41
61
|
export abstract class Log {
|
|
42
62
|
private _config: LoggerTemplateConfig;
|
|
43
|
-
private
|
|
63
|
+
private _templateStr: string;
|
|
64
|
+
private _compiledTemplate: any = null;
|
|
44
65
|
private tags:TagMap;
|
|
45
66
|
|
|
46
67
|
protected constructor(config:LoggerTemplateConfig){
|
|
47
68
|
this._config = config;
|
|
48
|
-
this.
|
|
69
|
+
this._templateStr = config.template;
|
|
49
70
|
this.tags = {
|
|
50
71
|
id: config.id,
|
|
51
72
|
node: config.name,
|
|
@@ -55,6 +76,14 @@ export abstract class Log {
|
|
|
55
76
|
}
|
|
56
77
|
}
|
|
57
78
|
|
|
79
|
+
private getTemplate(): any {
|
|
80
|
+
if (!this._compiledTemplate) {
|
|
81
|
+
ensureHelpersRegistered();
|
|
82
|
+
this._compiledTemplate = getHandlebars().compile(this._templateStr);
|
|
83
|
+
}
|
|
84
|
+
return this._compiledTemplate;
|
|
85
|
+
}
|
|
86
|
+
|
|
58
87
|
protected config():LoggerTemplateConfig{
|
|
59
88
|
return this._config;
|
|
60
89
|
}
|
|
@@ -62,7 +91,7 @@ export abstract class Log {
|
|
|
62
91
|
public log(payload:{[key:string]:any}|string):void{
|
|
63
92
|
try{
|
|
64
93
|
// step 1. serialize the payload using the template engine.
|
|
65
|
-
let message = (payload instanceof String) ? payload as string : this.
|
|
94
|
+
let message = (payload instanceof String) ? payload as string : this.getTemplate()({msg:payload});
|
|
66
95
|
|
|
67
96
|
// step 2. write to the log appender.
|
|
68
97
|
this.writeToLog(this.config().level, message, this.tags);
|
|
@@ -71,7 +100,7 @@ export abstract class Log {
|
|
|
71
100
|
console.log(e);
|
|
72
101
|
}
|
|
73
102
|
}
|
|
74
|
-
|
|
103
|
+
|
|
75
104
|
protected abstract writeToLog(level:string, message:string, tags:{[key:string]:string|boolean|number}):void;
|
|
76
105
|
}
|
|
77
106
|
|
|
@@ -110,9 +139,22 @@ export abstract class AbstractLogger<BaseLoggerConfig>{
|
|
|
110
139
|
protected abstract createLogger(config: LoggerTemplateConfig):Log;
|
|
111
140
|
}
|
|
112
141
|
|
|
142
|
+
@ServiceDescription({
|
|
143
|
+
id: "@theotherwillembotha/loggerservice",
|
|
144
|
+
sourceFile: "@theotherwillembotha/node-red-plugincore",
|
|
145
|
+
dependencies: [LoggerTemplate]
|
|
146
|
+
})
|
|
113
147
|
export class LoggerService extends BaseService {
|
|
114
148
|
|
|
115
|
-
|
|
149
|
+
// Backed by a global so the value is shared across separately-bundled copies
|
|
150
|
+
// of plugincore (Nodes.js and Plugins.js each inline their own copy).
|
|
151
|
+
public static get instanceID(): string {
|
|
152
|
+
return (global as any).__plugincore_instance_id__ ?? '';
|
|
153
|
+
}
|
|
154
|
+
public static set instanceID(value: string) {
|
|
155
|
+
(global as any).__plugincore_instance_id__ = value;
|
|
156
|
+
}
|
|
157
|
+
|
|
116
158
|
private red!: NodeAPI<NodeAPISettingsWithData>;
|
|
117
159
|
|
|
118
160
|
constructor(){
|
|
@@ -121,26 +163,11 @@ export class LoggerService extends BaseService {
|
|
|
121
163
|
|
|
122
164
|
public init(red: NodeAPI<NodeAPISettingsWithData>): void | Promise<void> {
|
|
123
165
|
this.red = red;
|
|
124
|
-
|
|
125
|
-
return new Promise<void>((resolve) => {
|
|
126
|
-
network.get_active_interface((err:any, obj:any) => {
|
|
127
|
-
LoggerService.instanceID = obj.ip_address;
|
|
128
|
-
});
|
|
129
|
-
});
|
|
166
|
+
LoggerService.instanceID = getLocalIpAddress();
|
|
130
167
|
}
|
|
131
168
|
|
|
132
169
|
public deinit(red: NodeAPI<NodeAPISettingsWithData>): void | Promise<void> {}
|
|
133
170
|
|
|
134
|
-
static override getServiceDescriptor():ServiceDescriptor {
|
|
135
|
-
return new ServiceDescriptor(
|
|
136
|
-
"@theotherwillembotha/loggerservice",
|
|
137
|
-
"LoggerService",
|
|
138
|
-
"services-plugin",
|
|
139
|
-
"@theotherwillembotha/node-red-plugincore",
|
|
140
|
-
LoggerService,
|
|
141
|
-
[LoggerTemplate]
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
171
|
}
|
|
145
172
|
|
|
146
173
|
export type LoggerConfigNodeConfig = ConfigNodeConfig & BaseLoggerConfig & {}
|
|
@@ -160,10 +187,11 @@ export abstract class LoggerConfigNode<CNC extends LoggerConfigNodeConfig, Logge
|
|
|
160
187
|
|
|
161
188
|
export class DoNothingAppender extends Log{
|
|
162
189
|
|
|
163
|
-
private static
|
|
190
|
+
private static _instance: DoNothingAppender | null = null;
|
|
164
191
|
|
|
165
|
-
static get():
|
|
166
|
-
|
|
192
|
+
static get(): DoNothingAppender {
|
|
193
|
+
if (!DoNothingAppender._instance) DoNothingAppender._instance = new DoNothingAppender();
|
|
194
|
+
return DoNothingAppender._instance;
|
|
167
195
|
}
|
|
168
196
|
|
|
169
197
|
private constructor(){
|
|
@@ -108,13 +108,13 @@
|
|
|
108
108
|
</script>
|
|
109
109
|
|
|
110
110
|
<script type="text/javascript" template-section="onIncludeEditPrepare">
|
|
111
|
-
let node = this;
|
|
112
|
-
|
|
113
111
|
// controls.
|
|
114
112
|
let logEnabled = $("#{{defaults.logEnabled}}");
|
|
115
113
|
let logTemplateEnabled = $("#{{defaults.logTemplateOverrideEnabled}}");
|
|
116
114
|
let logTemplateEnabledSection = $("#section_logTemplateOverrideEnabled");
|
|
117
115
|
let logEnabledSection = $("#section_logEnabled");
|
|
116
|
+
let loggerSection = $("#section_loggerconfig");
|
|
117
|
+
loggerSection.hide();
|
|
118
118
|
|
|
119
119
|
// functions.
|
|
120
120
|
let getLoggerTypes = async function(){
|
|
@@ -262,10 +262,15 @@
|
|
|
262
262
|
}
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
265
|
+
// Show the logger section only if at least one logger type is registered.
|
|
266
|
+
// If nodered_logging (or any other logger provider) is not installed, the section stays hidden.
|
|
267
|
+
getLoggerTypes().then(loggerTypes => {
|
|
268
|
+
if (loggerTypes.length === 0) return;
|
|
269
|
+
loggerSection.show();
|
|
270
|
+
updateLoggerSelectorList().then(() => {
|
|
271
|
+
loggerSelector.val(node.logger);
|
|
272
|
+
loggerSelector.trigger('change');
|
|
273
|
+
});
|
|
269
274
|
});
|
|
270
275
|
|
|
271
276
|
</script>
|