@theotherwillembotha/node-red-nginxproxymanager 0.0.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1386 @@
1
+
2
+ <!--
3
+
4
+ This file was automatically generated using the NODERED Core utility.
5
+ Any modifications to his file will be overwritten the next time the code is regenerated.
6
+
7
+ You have been warned.
8
+
9
+ -->
10
+
11
+ <!-- ui-helper -->
12
+ <style>
13
+ /* ─── PluginCore Dialog overlay ─────────────────────────────────────────── */
14
+ .plugincore-overlay {
15
+ position: fixed;
16
+ top: 0;
17
+ left: 0;
18
+ right: 0;
19
+ bottom: 0;
20
+ background: rgba(0, 0, 0, 0.5);
21
+ z-index: 2000;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ }
26
+
27
+ .plugincore-dialog {
28
+ background: #fff;
29
+ border: 1px solid #aaa;
30
+ border-radius: 4px;
31
+ box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
32
+ min-width: 540px;
33
+ max-width: 85vw;
34
+ max-height: 80vh;
35
+ display: flex;
36
+ flex-direction: column;
37
+ overflow: hidden;
38
+ }
39
+
40
+ /* Title bar — matches Node-RED's main nav bar colour */
41
+ .plugincore-dialog-titlebar {
42
+ display: flex;
43
+ align-items: center;
44
+ background: #3d3d3d;
45
+ color: #fff;
46
+ padding: 6px 8px 6px 12px;
47
+ flex-shrink: 0;
48
+ }
49
+
50
+ .plugincore-dialog-title {
51
+ flex: 1;
52
+ font-weight: 600;
53
+ font-size: 13px;
54
+ }
55
+
56
+ /* Close button — jQuery UI styling as used in Node-RED dialogs */
57
+ .plugincore-dialog-close {
58
+ color: #ccc !important;
59
+ background: transparent !important;
60
+ border-color: transparent !important;
61
+ }
62
+
63
+ .plugincore-dialog-close:hover {
64
+ color: #fff !important;
65
+ background: rgba(255, 255, 255, 0.15) !important;
66
+ border-color: rgba(255, 255, 255, 0.3) !important;
67
+ }
68
+
69
+ /* Scrollable body that contains the jQuery UI tab widget */
70
+ .plugincore-dialog-body {
71
+ flex: 1;
72
+ overflow: auto;
73
+ padding: 10px;
74
+ }
75
+
76
+ /* ─── PluginCore Table ───────────────────────────────────────────────────── */
77
+ .plugincore-table {
78
+ width: 100%;
79
+ border-collapse: collapse;
80
+ font-size: 12px;
81
+ }
82
+
83
+ .plugincore-table th {
84
+ background: #f3f3f3;
85
+ border: 1px solid #ddd;
86
+ padding: 5px 10px;
87
+ text-align: left;
88
+ font-weight: 600;
89
+ white-space: nowrap;
90
+ }
91
+
92
+ .plugincore-table td {
93
+ border: 1px solid #eee;
94
+ padding: 5px 10px;
95
+ vertical-align: middle;
96
+ }
97
+
98
+ .plugincore-table tbody tr:nth-child(even) td {
99
+ background: #fafafa;
100
+ }
101
+
102
+ .plugincore-table tbody tr:hover td {
103
+ background: #f0f6ff;
104
+ }
105
+
106
+ .plugincore-status-enabled {
107
+ color: #27ae60;
108
+ }
109
+
110
+ .plugincore-status-disabled {
111
+ color: #c0392b;
112
+ }
113
+
114
+ </style>
115
+ <script type="text/javascript">
116
+ window.PluginCore = window.PluginCore || {};
117
+ /**
118
+ * PluginCore.table(config) → jQuery <table>
119
+ *
120
+ * config: {
121
+ * columns : [{ key, label, render?(value, row) → string | jQuery }],
122
+ * rows : object[]
123
+ * }
124
+ */
125
+ PluginCore.table = function(config) {
126
+ var $table = $('<table class="plugincore-table">');
127
+ // Header
128
+ var $thead = $('<thead>');
129
+ var $hr = $('<tr>');
130
+ config.columns.forEach(function(col) {
131
+ $hr.append($('<th>').text(col.label));
132
+ });
133
+ $thead.append($hr);
134
+ $table.append($thead);
135
+ // Body
136
+ var $tbody = $('<tbody>');
137
+ (config.rows || []).forEach(function(row) {
138
+ var $tr = $('<tr>');
139
+ config.columns.forEach(function(col) {
140
+ var $td = $('<td>');
141
+ var val = row[col.key];
142
+ if (col.render) {
143
+ var rendered = col.render(val, row);
144
+ if (rendered && typeof rendered === 'object' && rendered.jquery) {
145
+ $td.append(rendered);
146
+ } else {
147
+ $td.html(rendered != null ? String(rendered) : '');
148
+ }
149
+ } else {
150
+ $td.text(val != null ? String(val) : '');
151
+ }
152
+ $tr.append($td);
153
+ });
154
+ $tbody.append($tr);
155
+ });
156
+ $table.append($tbody);
157
+ return $table;
158
+ };
159
+ /**
160
+ * PluginCore.dialog(options) → { close() }
161
+ *
162
+ * options: {
163
+ * title : string,
164
+ * tabs : [{ label: string, render($container) }]
165
+ * }
166
+ *
167
+ * Uses jQuery UI tabs for the tab widget (already bundled with Node-RED).
168
+ * Closes on: close button click, overlay click, or Escape key.
169
+ */
170
+ PluginCore.dialog = function(options) {
171
+ // Only one dialog at a time
172
+ $('.plugincore-overlay').remove();
173
+ var uid = 'plugincore-' + Date.now();
174
+ var $overlay = $('<div class="plugincore-overlay">');
175
+ var $dialog = $('<div class="plugincore-dialog">');
176
+ // ── Title bar ──────────────────────────────────────────────────────
177
+ var $titlebar = $('<div class="plugincore-dialog-titlebar">');
178
+ $titlebar.append($('<span class="plugincore-dialog-title">').text(options.title || ''));
179
+ var $closeBtn = $('<button type="button" class="ui-button ui-corner-all ui-widget plugincore-dialog-close" title="Close">' + '<span class="ui-icon ui-icon-closethick"></span></button>');
180
+ $titlebar.append($closeBtn);
181
+ // ── Body + jQuery UI tabs ──────────────────────────────────────────
182
+ var $body = $('<div class="plugincore-dialog-body">');
183
+ var $tabsEl = $('<div>').attr('id', uid + '-tabs');
184
+ var $ul = $('<ul>');
185
+ (options.tabs || []).forEach(function(tab, i) {
186
+ var paneId = uid + '-pane-' + i;
187
+ $ul.append($('<li>').append($('<a>').attr('href', '#' + paneId).text(tab.label)));
188
+ var $pane = $('<div>').attr('id', paneId);
189
+ if (tab.render) {
190
+ tab.render($pane);
191
+ }
192
+ $tabsEl.append($pane);
193
+ });
194
+ $tabsEl.prepend($ul);
195
+ $body.append($tabsEl);
196
+ // ── Assemble ───────────────────────────────────────────────────────
197
+ $dialog.append($titlebar).append($body);
198
+ $overlay.append($dialog);
199
+ $('body').append($overlay);
200
+ // Initialise jQuery UI tabs (bundled with Node-RED)
201
+ $tabsEl.tabs();
202
+ // ── Close logic ───────────────────────────────────────────────────
203
+ var escNs = 'keydown.plugincore-dialog-' + uid;
204
+
205
+ function close() {
206
+ $overlay.fadeOut(150, function() {
207
+ $overlay.remove();
208
+ });
209
+ $(document).off(escNs);
210
+ }
211
+ $closeBtn.on('click', close);
212
+ $overlay.on('click', function(e) {
213
+ if ($(e.target).is($overlay)) {
214
+ close();
215
+ }
216
+ });
217
+ $(document).on(escNs, function(e) {
218
+ if (e.key === 'Escape') {
219
+ close();
220
+ }
221
+ });
222
+ return {
223
+ close: close
224
+ };
225
+ };
226
+
227
+ </script>
228
+ <script type="text/javascript">
229
+ RED.nodes.registerType('NginxProxyManagerConfigNode', {
230
+ category: 'config',
231
+ label: function() {
232
+ return this.name
233
+ },
234
+ paletteLabel: 'Nginx Proxy Manager Config',
235
+ defaults: {
236
+ name: {
237
+ value: 'NginX Example',
238
+ required: true
239
+ },
240
+ url: {
241
+ value: 'http://nginxproxy:81',
242
+ required: true
243
+ },
244
+ email: {
245
+ value: 'info@example.com',
246
+ required: true
247
+ },
248
+ password: {
249
+ value: '',
250
+ required: true
251
+ },
252
+ },
253
+ oneditprepare: function() {
254
+ // **** NginxProxyManagerConfigNode **** //
255
+ {
256
+ let node = this;
257
+ $("#npm-test-connection-btn").on("click", function() {
258
+ let $btn = $(this);
259
+ let $result = $("#npm-test-connection-result");
260
+ let url = $("#node-config-input-url").val();
261
+ let username = $("#node-config-input-email").val();
262
+ let password = $("#node-config-input-password").val();
263
+ $result.text("Testing...").css("color", "gray");
264
+ $btn.prop("disabled", true);
265
+ $.ajax({
266
+ url: "nginxproxymanagerservice/testconnection",
267
+ type: "POST",
268
+ contentType: "application/json; charset=utf-8",
269
+ data: JSON.stringify({
270
+ connection: {
271
+ url,
272
+ username,
273
+ password
274
+ }
275
+ }),
276
+ success: function(response) {
277
+ $result.text("\u2714 " + response.message).css("color", "green");
278
+ },
279
+ error: function(jqXHR) {
280
+ let msg = "Connection failed";
281
+ try {
282
+ let body = JSON.parse(jqXHR.responseText);
283
+ if (body.error) {
284
+ msg = body.error;
285
+ }
286
+ } catch (e) {}
287
+ $result.text("\u2718 " + msg).css("color", "red");
288
+ },
289
+ complete: function() {
290
+ $btn.prop("disabled", false);
291
+ }
292
+ });
293
+ });
294
+ $("#npm-show-hosts-btn").on("click", function() {
295
+ let $btn = $(this);
296
+ let url = $("#node-config-input-url").val();
297
+ let username = $("#node-config-input-email").val();
298
+ let password = $("#node-config-input-password").val();
299
+ let title = $("#node-config-input-name").val() || "Nginx Proxy Manager";
300
+ $btn.prop("disabled", true);
301
+ $.ajax({
302
+ url: "nginxproxymanagerservice/hosts",
303
+ type: "POST",
304
+ contentType: "application/json; charset=utf-8",
305
+ data: JSON.stringify({
306
+ connection: {
307
+ url,
308
+ username,
309
+ password
310
+ }
311
+ }),
312
+ success: function(response) {
313
+ PluginCore.dialog({
314
+ title: title,
315
+ tabs: [{
316
+ label: "Proxy Hosts",
317
+ render: function($container) {
318
+ var $table = PluginCore.table({
319
+ columns: [{
320
+ key: "id",
321
+ label: "ID"
322
+ }, {
323
+ key: "domain_names",
324
+ label: "Domain Names",
325
+ render: function(v) {
326
+ return v.join("<br>");
327
+ }
328
+ }, {
329
+ key: "forward_host",
330
+ label: "Forward Host"
331
+ }, {
332
+ key: "forward_port",
333
+ label: "Port"
334
+ }, {
335
+ key: "forward_scheme",
336
+ label: "Scheme"
337
+ }, {
338
+ key: "enabled",
339
+ label: "Enabled",
340
+ render: function(v) {
341
+ return $("<span>").addClass(v ? "plugincore-status-enabled" : "plugincore-status-disabled").text(v ? "\u2714 Enabled" : "\u2718 Disabled");
342
+ }
343
+ }],
344
+ rows: response.hosts.proxyHosts
345
+ });
346
+ $container.append($table);
347
+ }
348
+ }]
349
+ });
350
+ },
351
+ error: function(jqXHR) {
352
+ let msg = "Failed to fetch hosts";
353
+ try {
354
+ let body = JSON.parse(jqXHR.responseText);
355
+ if (body.error) {
356
+ msg = body.error;
357
+ }
358
+ } catch (e) {}
359
+ console.error("[NPM] " + msg);
360
+ },
361
+ complete: function() {
362
+ $btn.prop("disabled", false);
363
+ }
364
+ });
365
+ });
366
+ }
367
+ },
368
+ oneditsave: function() {
369
+ // **** NginxProxyManagerConfigNode **** //
370
+ {
371
+ let node = this;
372
+ }
373
+ },
374
+ oneditcancel: function() {},
375
+ oneditdelete: function() {},
376
+ });
377
+
378
+ </script>
379
+
380
+
381
+ <script type="text/html" data-template-name='NginxProxyManagerConfigNode'>
382
+ <div id='section_NginxProxyManagerConfigNode'>
383
+ <div id="NginxProxyManagerConfig">
384
+ <div class="form-row">
385
+ <label class="towb_editorlabel" for="node-config-input-name"><i class="fa fa-font"></i> Name</label>
386
+ <input type="text" id="node-config-input-name" />
387
+ </div>
388
+
389
+ <div class="form-row">
390
+ <label class="towb_editorlabel" for="node-config-input-url"><i class="fa fa-link"></i> URL</label>
391
+ <input type="text" id="node-config-input-url" />
392
+ </div>
393
+
394
+ <div class="form-row">
395
+ <label class="towb_editorlabel" for="node-config-input-email"><i class="fa fa-envelope"></i> Email</label>
396
+ <input type="text" id="node-config-input-email" />
397
+ </div>
398
+
399
+ <div class="form-row">
400
+ <label class="towb_editorlabel" for="node-config-input-password"><i class="fa fa-key"></i> Password</label>
401
+ <input type="password" id="node-config-input-password" />
402
+ </div>
403
+
404
+ <div class="form-row">
405
+ <label class="towb_editorlabel"></label>
406
+ <button type="button" id="npm-test-connection-btn" class="red-ui-button">
407
+ <i class="fa fa-plug"></i> Test Connection
408
+ </button>
409
+ <span id="npm-test-connection-result" style="margin-left: 10px; font-size: 12px;"></span>
410
+ </div>
411
+
412
+ <div class="form-row">
413
+ <label class="towb_editorlabel"></label>
414
+ <button type="button" id="npm-show-hosts-btn" class="red-ui-button">
415
+ <i class="fa fa-list"></i> Show Hosts
416
+ </button>
417
+ </div>
418
+ </div>
419
+
420
+ </div>
421
+ </script>
422
+
423
+ <script type="text/markdown" data-help-name='NginxProxyManagerConfigNode'>
424
+ Connection configuration for an Nginx Proxy Manager instance. Referenced by all other nodes in this plugin.
425
+
426
+ ### Properties
427
+
428
+ : *name* (string) : A descriptive label for this connection.
429
+ : *url* (string) : The base URL of the Nginx Proxy Manager admin API, including port (e.g. `http://192.168.1.10:81`).
430
+ : *email* (string) : The email address used to authenticate with Nginx Proxy Manager.
431
+ : *password* (string) : The password for the above account.
432
+
433
+ ### Actions
434
+
435
+ **Test Connection** — verifies the supplied credentials against the live Nginx Proxy Manager API and shows an inline success or failure message without saving the node.
436
+
437
+ **Show Hosts** — retrieves the current list of proxy hosts and displays them in a dialog with a sortable table (ID, domain names, forward host, port, scheme, enabled status).
438
+
439
+ ### References
440
+ - [Nginx Proxy Manager - Project](https://nginxproxymanager.com/)
441
+ - [Nginx Proxy Manager - GitHub](https://github.com/NginxProxyManager/nginx-proxy-manager)
442
+ </script>
443
+ <!-- UpdateNginxHostNode -->
444
+ <style>
445
+ .node_label_white {
446
+ fill: white
447
+ }
448
+
449
+ </style>
450
+ <!-- logger -->
451
+ <style>
452
+ .editorgroupborder {
453
+ border-width: 1px;
454
+ border-style: solid;
455
+ border-color: lightgray;
456
+ padding: 3px;
457
+ margin-top: 2px;
458
+ margin-bottom: 2px;
459
+ }
460
+
461
+ .editorsectionheading {
462
+ font-weight: 600;
463
+ margin-bottom: 1px !important;
464
+ margin-top: 6px !important;
465
+ }
466
+
467
+ .nomargin {
468
+ margin-bottom: 1px !important;
469
+ margin-top: 1px !important;
470
+ }
471
+
472
+ .slidecontainer {
473
+ width: 100%;
474
+ }
475
+
476
+ .slider {
477
+ -webkit-appearance: none;
478
+ appearance: none;
479
+ width: 100%;
480
+ height: 25px;
481
+ background: #d3d3d3;
482
+ outline: none;
483
+ opacity: 0.7;
484
+ -webkit-transition: .2s;
485
+ transition: opacity .2s;
486
+ }
487
+
488
+ .slider:hover {
489
+ opacity: 1;
490
+ }
491
+
492
+ .slider::-webkit-slider-thumb {
493
+ -webkit-appearance: none;
494
+ appearance: none;
495
+ width: 25px;
496
+ height: 25px;
497
+ background: #04AA6D;
498
+ cursor: pointer;
499
+ }
500
+
501
+ .slider::-moz-range-thumb {
502
+ width: 25px;
503
+ height: 25px;
504
+ background: #04AA6D;
505
+ cursor: pointer;
506
+ }
507
+
508
+ .towb_editorlabel {
509
+ width: 120px !important;
510
+ }
511
+
512
+ .towb_editorfield {
513
+ width: 70% !important;
514
+ }
515
+
516
+ .towb_editorfield_short {
517
+ width: 30% !important;
518
+ }
519
+
520
+ </style>
521
+ <script type="text/javascript">
522
+ // CSS
523
+ dropdownStyles = `
524
+ .loggertype-dropdown {
525
+ position: absolute;
526
+ background: white;
527
+ border: 1px solid #ccc;
528
+ border-radius: 4px;
529
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
530
+ max-height: 150px;
531
+ overflow-y: auto;
532
+ z-index: 1000;
533
+ min-width: 150px;
534
+ }
535
+ .loggertype-dropdown .dropdown-item {
536
+ padding: 6px 12px;
537
+ cursor: pointer;
538
+ }
539
+ .loggertype-dropdown .dropdown-item:hover {
540
+ background: #f0f0f0;
541
+ }
542
+ `;
543
+ $('<style>').text(dropdownStyles).appendTo('head');
544
+
545
+ </script>
546
+ <script type="text/javascript">
547
+ RED.nodes.registerType('UpdateNginxHostNode', {
548
+ category: 'nginxproxymanager',
549
+ icon: 'nginx.png',
550
+ color: '#647791',
551
+ labelStyle: 'node_label_white',
552
+ label: function() {
553
+ return this.name
554
+ },
555
+ paletteLabel: 'Nginx Update Host Node',
556
+ inputs: 1,
557
+ inputLabels: (i) => 'input',
558
+ outputs: 1,
559
+ outputLabels: (i) => ['event'][i],
560
+ defaults: {
561
+ name: {
562
+ value: 'Update Host Node Example',
563
+ required: true
564
+ },
565
+ proxymanagerconfig: {
566
+ type: 'NginxProxyManagerConfigNode',
567
+ required: true
568
+ },
569
+ selectedDevices: {
570
+ value: []
571
+ },
572
+ logEnabled: {
573
+ value: false,
574
+ required: true
575
+ },
576
+ logger: {
577
+ required: false,
578
+ value: '',
579
+ type: 'DelegatedConfigReferenceNode'
580
+ },
581
+ logTemplateOverrideEnabled: {
582
+ value: false
583
+ },
584
+ logTemplateOverride: {
585
+ value: 'message:{{msg}}'
586
+ },
587
+ metricsEnabled: {
588
+ value: false,
589
+ required: true
590
+ },
591
+ metricsReference: {
592
+ type: 'MetricsConfigNode',
593
+ required: false
594
+ },
595
+ },
596
+ oneditprepare: function() {
597
+ // **** UpdateNginxHostNode **** //
598
+ {
599
+ let node = this;
600
+ }
601
+ // **** logger **** //
602
+ {
603
+ let node = this;
604
+ // controls.
605
+ let logEnabled = $("#node-input-logEnabled");
606
+ let logTemplateEnabled = $("#node-input-logTemplateOverrideEnabled");
607
+ let logTemplateEnabledSection = $("#section_logTemplateOverrideEnabled");
608
+ let logEnabledSection = $("#section_logEnabled");
609
+ // functions.
610
+ let getLoggerTypes = async function() {
611
+ let types = [];
612
+ await $.ajax({
613
+ url: "nodetypeservice/find?tag=LoggerType",
614
+ type: "GET",
615
+ contentType: "application/json; charset=utf-8",
616
+ data: JSON.stringify({
617
+ id: node.id
618
+ }),
619
+ success: (response) => {
620
+ types = response;
621
+ },
622
+ error: (jqXHR, textStatus, errorThrown) => {
623
+ console.log(jqXHR, textStatus, errorThrown);
624
+ },
625
+ });
626
+ return types;
627
+ }
628
+ let getLoggers = async () => {
629
+ let loggers = [];
630
+ // get the list of types again.
631
+ let loggerTypes = (await getLoggerTypes()).map(type => type.id);
632
+ // add all of the known loggers.
633
+ RED.nodes.eachConfig(cfg => {
634
+ if (loggerTypes.includes(cfg.type)) {
635
+ loggers.push({
636
+ id: cfg.id,
637
+ name: cfg.label(),
638
+ type: cfg.type
639
+ });
640
+ }
641
+ });
642
+ return loggers;
643
+ };
644
+ let updateLoggerSelectorList = async () => {
645
+ loggerSelector.empty();
646
+ (await getLoggers()).forEach(logger => {
647
+ $('<option value="' + logger.id + '">' + logger.name + '</option>').appendTo(loggerSelector);
648
+ });
649
+ $('<option value="_ADD_">none</option>').appendTo(loggerSelector);
650
+ }
651
+ logEnabled.on('change', function() {
652
+ if (logEnabled.prop("checked")) {
653
+ logEnabledSection.show();
654
+ node._def.defaults.logger.required = true;
655
+ } else {
656
+ logEnabledSection.hide()
657
+ node._def.defaults.logger.required = false;
658
+ }
659
+ });
660
+ logTemplateEnabled.on('change', function() {
661
+ if (logTemplateEnabled.prop("checked")) {
662
+ logTemplateEnabledSection.show();
663
+ node._def.defaults.logTemplateOverride.required = true;
664
+ } else {
665
+ logTemplateEnabledSection.hide()
666
+ node._def.defaults.logTemplateOverride.required = false;
667
+ }
668
+ });
669
+ node.logTemplateOverrideEditor = RED.editor.createEditor({
670
+ id: 'node-input-logTemplateOverrideEditor',
671
+ mode: 'ace/mode/handlebars',
672
+ value: node.logTemplateOverride
673
+ });
674
+ let loggerSelector = $("#logger-selector");
675
+ let loggerEditButton = $("#logger-edit-btn");
676
+ let loggerAddButton = $("#logger-add-btn");
677
+ loggerEditButton.on('click', async function(event) {
678
+ event.stopPropagation();
679
+ if (loggerEditButton.hasClass("disabled")) {
680
+ return;
681
+ }
682
+ // figure out what type of logger this is.
683
+ let selectedLogger = (await getLoggers()).find(logger => logger.id === loggerSelector.val());
684
+ RED.editor.editConfig("#logger-selector", selectedLogger.type, selectedLogger.id);
685
+ });
686
+ loggerAddButton.on('click', function(event) {
687
+ event.stopPropagation();
688
+ let $btn = $(event.target);
689
+ // Remove any existing dropdown
690
+ $('.loggertype-dropdown').remove();
691
+ getLoggerTypes().then(loggerTypes => {
692
+ let $dropdown = $('<div class="loggertype-dropdown red-ui-editor"></div>');
693
+ loggerTypes.forEach(loggerType => {
694
+ $('<div class="dropdown-item"></div>').text(loggerType.name).data('value', loggerType.id).css({
695
+ padding: '4px 8px',
696
+ cursor: 'pointer'
697
+ }).appendTo($dropdown);
698
+ });
699
+ // Append first so outerWidth() is measurable, then position so the
700
+ // right edge of the dropdown aligns with the right edge of the button.
701
+ $('body').append($dropdown);
702
+ $dropdown.css({
703
+ top: $btn.offset().top + $btn.outerHeight(),
704
+ left: $btn.offset().left + $btn.outerWidth() - $dropdown.outerWidth()
705
+ });
706
+ $(document).on('mousedown.dropdown', function(e) {
707
+ if (!$(e.target).closest('.loggertype-dropdown').length) {
708
+ $dropdown.remove();
709
+ $(document).off('mousedown.dropdown'); // Clean up the listener
710
+ }
711
+ });
712
+ // Handle item selection
713
+ $dropdown.on('click', '.dropdown-item', function(e) {
714
+ e.stopPropagation();
715
+ const selectedValue = $(this).data('value');
716
+ const selectedText = $(this).text();
717
+ // Do whatever you need with the selection
718
+ console.log('Selected:', selectedValue, selectedText);
719
+ $dropdown.remove();
720
+ RED.editor.editConfig("#logger-selector", selectedValue, "_ADD_");
721
+ });
722
+ })
723
+ });
724
+ loggerSelector.on("focus", updateLoggerSelectorList);
725
+ loggerSelector.on('change', function(event) {
726
+ if ("_ADD_" === loggerSelector.val()) {
727
+ loggerEditButton.addClass("disabled")
728
+ } else {
729
+ loggerEditButton.removeClass("disabled")
730
+ }
731
+ });
732
+ // lastly, make sure that we set the correct value on the logger selector.
733
+ updateLoggerSelectorList().then(() => {
734
+ loggerSelector.val(node.logger);
735
+ loggerSelector.trigger('change');
736
+ });
737
+ }
738
+ // **** metrics **** //
739
+ {
740
+ let node = this;
741
+ let metricsEnabled = $("#node-input-metricsEnabled");
742
+ let metricsEnabledSection = $("#section_metricsEnabled");
743
+ metricsEnabled.on('change', function() {
744
+ if (metricsEnabled.prop("checked")) {
745
+ metricsEnabledSection.show();
746
+ node._def.defaults.metricsReference.required = true;
747
+ } else {
748
+ metricsEnabledSection.hide()
749
+ node._def.defaults.metricsReference.required = false;
750
+ }
751
+ });
752
+ }
753
+ },
754
+ oneditsave: function() {
755
+ // **** UpdateNginxHostNode **** //
756
+ {
757
+ let node = this;
758
+ }
759
+ // **** logger **** //
760
+ {
761
+ node = this;
762
+ var selectedLogger = $("#logger-selector").val();
763
+ node.logger = (selectedLogger && selectedLogger !== "_ADD_") ? selectedLogger : '';
764
+ node.logTemplateOverride = node.logTemplateOverrideEditor.getValue();
765
+ node.logTemplateOverrideEditor.destroy();
766
+ delete node.logTemplateOverrideEditor;
767
+ }
768
+ // **** metrics **** //
769
+ {}
770
+ },
771
+ oneditcancel: function() {},
772
+ oneditdelete: function() {},
773
+ });
774
+
775
+ </script>
776
+
777
+
778
+ <script type="text/html" data-template-name='UpdateNginxHostNode'>
779
+ <div id='section_UpdateNginxHostNode'>
780
+ <div class="form-row">
781
+ <label class="towb_editorlabel" for="node-input-name"><i class="fa fa-font"></i> Name</label>
782
+ <input type="text" id="node-input-name" />
783
+ </div>
784
+
785
+ <div class="form-row">
786
+ <label class="towb_editorlabel" for="node-input-proxymanagerconfig"><i class="fa fa-server"></i> Proxy Manager</label>
787
+ <input type="text" id="node-input-proxymanagerconfig" />
788
+ </div>
789
+
790
+ </div>
791
+
792
+ <div id='section_logger'>
793
+ <div id="section_loggerconfig">
794
+ <div class="form-row editorsectionheading">
795
+ <i class="w-16 fa fa-eye"></i> <span>Logging</span>
796
+ </div>
797
+ <div class="editorgroupborder">
798
+ <div class="form-row nomargin logEnableCheck">
799
+ <input type="checkbox" id="node-input-logEnabled" placeholder="logEnabled" value="false" style="margin:8px 0 10px 102px; width:20px;">
800
+ <label style="width:auto" for="node-input-logEnabled"><span>Enable Logging</span></label>
801
+ </div>
802
+
803
+ <div id="section_logEnabled">
804
+ <div class="form-row nomargin">
805
+ <label for="logger-selector">Logger</label>
806
+ <div style="width: 70%; display: inline-flex;">
807
+ <select id="logger-selector" style="flex-grow: 1;" class="">
808
+ <option value="_ADD_">none</option>
809
+ </select>
810
+ <a id="logger-edit-btn" class="red-ui-button disabled" style="margin-left: 10px;">
811
+ <i class="fa fa-pencil"></i>
812
+ </a>
813
+ <a id="logger-add-btn" class="red-ui-button" style="margin-left: 10px;">
814
+ <i class="fa fa-plus"></i>
815
+ </a>
816
+ </div>
817
+ </div>
818
+
819
+ <div class="form-row nomargin">
820
+ <input type="checkbox" id="node-input-logTemplateOverrideEnabled" placeholder="" value="false" style="margin:8px 0 10px 102px; width:20px;">
821
+ <label style="width:auto" for="node-input-logTemplateOverrideEnabled"><span>Override Template</span></label>
822
+ </div>
823
+
824
+ <div id="section_logTemplateOverrideEnabled">
825
+ <div class="form-row">
826
+ <label class="towb_editorlabel" for="node-input-logTemplateOverrideEditor"><i class="fa fa-code"></i> Template</label>
827
+ <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-logTemplateOverrideEditor"></div>
828
+ </div>
829
+ </div>
830
+ </div>
831
+ </div>
832
+ </div>
833
+
834
+ </div>
835
+
836
+ <div id='section_metrics'>
837
+ <div id="section_metricsconfig">
838
+ <div class="form-row editorsectionheading">
839
+ <i class="w-16 fa fa-eye"></i> <span>Metrics</span>
840
+ </div>
841
+ <div class="editorgroupborder">
842
+ <div class="form-row nomargin metricsEnableCheck">
843
+ <input type="checkbox" id="node-input-metricsEnabled" value="false" style="margin:8px 0 10px 102px; width:20px;" />
844
+ <label style="width:auto" for="node-input-metricsEnabled"><span>Enable Metrics</span></label>
845
+ </div>
846
+
847
+ <div id="section_metricsEnabled">
848
+ <div class="form-row nomargin">
849
+ <label class="towb_editorlabel" for="node-input-metricsReference">Metric Collector</label>
850
+ <input id="node-input-metricsReference" placeholder="metrics" />
851
+ </div>
852
+ </div>
853
+ </div>
854
+ </div>
855
+
856
+ </div>
857
+ </script>
858
+
859
+ <script type="text/markdown" data-help-name='UpdateNginxHostNode'>
860
+ Creates or updates a proxy host entry in Nginx Proxy Manager.
861
+
862
+ The operation is determined by the presence of an `id` field in `msg.payload`:
863
+ - **No `id`** — creates a new proxy host.
864
+ - **With `id`** — updates the existing proxy host with that ID.
865
+
866
+ ### Properties
867
+
868
+ : *name* (string) : A descriptive label for the node.
869
+ : *proxy manager* (npm config) : The Nginx Proxy Manager connection to use.
870
+
871
+ ### Inputs
872
+
873
+ : *msg.payload* (object) : The host configuration to create or update.
874
+
875
+ **Create a new host:**
876
+
877
+ ```javascript
878
+ {
879
+ domainNames: string[], // domain names to forward (must be unique and not yet registered)
880
+ scheme: "http" | "https", // forwarding scheme
881
+ forwardHost: string, // target host to forward requests to
882
+ forwardPort: number, // target port
883
+ accessList: string, // access list to bind to this host
884
+ cacheAssets: boolean, // optional: cache static assets
885
+ blockCommonExploits: boolean, // optional: block common exploit patterns
886
+ websocketSupport: boolean, // optional: enable WebSocket support
887
+ }
888
+ ```
889
+
890
+ **Update an existing host:**
891
+
892
+ ```javascript
893
+ {
894
+ id: number, // required: ID of the proxy host to update
895
+ domainNames: string[], // optional
896
+ scheme: "http" | "https", // optional
897
+ forwardHost: string, // optional
898
+ forwardPort: number, // optional
899
+ accessList: string, // optional
900
+ cacheAssets: boolean, // optional
901
+ blockCommonExploits: boolean, // optional
902
+ websocketSupport: boolean, // optional
903
+ }
904
+ ```
905
+
906
+ ### Output
907
+
908
+ The original message is forwarded unchanged after the operation completes.
909
+
910
+ ### Example
911
+
912
+ ```json
913
+ {
914
+ "domainNames": ["myservice.example.com"],
915
+ "scheme": "http",
916
+ "forwardHost": "192.168.1.30",
917
+ "forwardPort": 8080,
918
+ "blockCommonExploits": true,
919
+ "websocketSupport": true
920
+ }
921
+ ```
922
+
923
+ ### Logging
924
+ : *enable logging* (boolean) : if checked, then the node will produce logging output to the specified logger.
925
+ : *logger* (logconfig) : the endppoint that the node will log events to.
926
+ : *override template* (logconfig) : if checked, a custom logging template can be provided for the log message.
927
+ : *logger template* (mustache) : a template in mustache format to generate a message for logging purposes (see LoggerConfig node for more information)
928
+
929
+ ### Metrics
930
+ : *enable metrics* (boolean) : if checked, then the node will produce metrics output to the specified metrics provider.
931
+ : *metric collector* (metricconfig) : the collection point or grouping where this particular metric will be attached.
932
+ </script>
933
+ <!-- logger -->
934
+ <style>
935
+ .editorgroupborder {
936
+ border-width: 1px;
937
+ border-style: solid;
938
+ border-color: lightgray;
939
+ padding: 3px;
940
+ margin-top: 2px;
941
+ margin-bottom: 2px;
942
+ }
943
+
944
+ .editorsectionheading {
945
+ font-weight: 600;
946
+ margin-bottom: 1px !important;
947
+ margin-top: 6px !important;
948
+ }
949
+
950
+ .nomargin {
951
+ margin-bottom: 1px !important;
952
+ margin-top: 1px !important;
953
+ }
954
+
955
+ .slidecontainer {
956
+ width: 100%;
957
+ }
958
+
959
+ .slider {
960
+ -webkit-appearance: none;
961
+ appearance: none;
962
+ width: 100%;
963
+ height: 25px;
964
+ background: #d3d3d3;
965
+ outline: none;
966
+ opacity: 0.7;
967
+ -webkit-transition: .2s;
968
+ transition: opacity .2s;
969
+ }
970
+
971
+ .slider:hover {
972
+ opacity: 1;
973
+ }
974
+
975
+ .slider::-webkit-slider-thumb {
976
+ -webkit-appearance: none;
977
+ appearance: none;
978
+ width: 25px;
979
+ height: 25px;
980
+ background: #04AA6D;
981
+ cursor: pointer;
982
+ }
983
+
984
+ .slider::-moz-range-thumb {
985
+ width: 25px;
986
+ height: 25px;
987
+ background: #04AA6D;
988
+ cursor: pointer;
989
+ }
990
+
991
+ .towb_editorlabel {
992
+ width: 120px !important;
993
+ }
994
+
995
+ .towb_editorfield {
996
+ width: 70% !important;
997
+ }
998
+
999
+ .towb_editorfield_short {
1000
+ width: 30% !important;
1001
+ }
1002
+
1003
+ </style>
1004
+ <script type="text/javascript">
1005
+ // CSS
1006
+ dropdownStyles = `
1007
+ .loggertype-dropdown {
1008
+ position: absolute;
1009
+ background: white;
1010
+ border: 1px solid #ccc;
1011
+ border-radius: 4px;
1012
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
1013
+ max-height: 150px;
1014
+ overflow-y: auto;
1015
+ z-index: 1000;
1016
+ min-width: 150px;
1017
+ }
1018
+ .loggertype-dropdown .dropdown-item {
1019
+ padding: 6px 12px;
1020
+ cursor: pointer;
1021
+ }
1022
+ .loggertype-dropdown .dropdown-item:hover {
1023
+ background: #f0f0f0;
1024
+ }
1025
+ `;
1026
+ $('<style>').text(dropdownStyles).appendTo('head');
1027
+
1028
+ </script>
1029
+ <script type="text/javascript">
1030
+ RED.nodes.registerType('NginxGetHostsNode', {
1031
+ category: 'nginxproxymanager',
1032
+ icon: 'nginx.png',
1033
+ color: '#647791',
1034
+ labelStyle: 'node_label_white',
1035
+ label: function() {
1036
+ return this.name
1037
+ },
1038
+ paletteLabel: 'Nginx Get Hosts',
1039
+ inputs: 1,
1040
+ inputLabels: (i) => 'input',
1041
+ outputs: 1,
1042
+ outputLabels: (i) => ['event'][i],
1043
+ defaults: {
1044
+ name: {
1045
+ value: 'Nginx Get Hosts',
1046
+ required: true
1047
+ },
1048
+ proxymanagerconfig: {
1049
+ type: 'NginxProxyManagerConfigNode',
1050
+ required: true
1051
+ },
1052
+ outputpath: {
1053
+ value: 'payload'
1054
+ },
1055
+ logEnabled: {
1056
+ value: false,
1057
+ required: true
1058
+ },
1059
+ logger: {
1060
+ required: false,
1061
+ value: '',
1062
+ type: 'DelegatedConfigReferenceNode'
1063
+ },
1064
+ logTemplateOverrideEnabled: {
1065
+ value: false
1066
+ },
1067
+ logTemplateOverride: {
1068
+ value: 'message:{{msg}}'
1069
+ },
1070
+ metricsEnabled: {
1071
+ value: false,
1072
+ required: true
1073
+ },
1074
+ metricsReference: {
1075
+ type: 'MetricsConfigNode',
1076
+ required: false
1077
+ },
1078
+ },
1079
+ oneditprepare: function() {
1080
+ // **** NginxGetHostsNode **** //
1081
+ {
1082
+ let node = this;
1083
+ $("#node-input-outputpath").typedInput({
1084
+ types: ["msg"]
1085
+ });
1086
+ }
1087
+ // **** logger **** //
1088
+ {
1089
+ let node = this;
1090
+ // controls.
1091
+ let logEnabled = $("#node-input-logEnabled");
1092
+ let logTemplateEnabled = $("#node-input-logTemplateOverrideEnabled");
1093
+ let logTemplateEnabledSection = $("#section_logTemplateOverrideEnabled");
1094
+ let logEnabledSection = $("#section_logEnabled");
1095
+ // functions.
1096
+ let getLoggerTypes = async function() {
1097
+ let types = [];
1098
+ await $.ajax({
1099
+ url: "nodetypeservice/find?tag=LoggerType",
1100
+ type: "GET",
1101
+ contentType: "application/json; charset=utf-8",
1102
+ data: JSON.stringify({
1103
+ id: node.id
1104
+ }),
1105
+ success: (response) => {
1106
+ types = response;
1107
+ },
1108
+ error: (jqXHR, textStatus, errorThrown) => {
1109
+ console.log(jqXHR, textStatus, errorThrown);
1110
+ },
1111
+ });
1112
+ return types;
1113
+ }
1114
+ let getLoggers = async () => {
1115
+ let loggers = [];
1116
+ // get the list of types again.
1117
+ let loggerTypes = (await getLoggerTypes()).map(type => type.id);
1118
+ // add all of the known loggers.
1119
+ RED.nodes.eachConfig(cfg => {
1120
+ if (loggerTypes.includes(cfg.type)) {
1121
+ loggers.push({
1122
+ id: cfg.id,
1123
+ name: cfg.label(),
1124
+ type: cfg.type
1125
+ });
1126
+ }
1127
+ });
1128
+ return loggers;
1129
+ };
1130
+ let updateLoggerSelectorList = async () => {
1131
+ loggerSelector.empty();
1132
+ (await getLoggers()).forEach(logger => {
1133
+ $('<option value="' + logger.id + '">' + logger.name + '</option>').appendTo(loggerSelector);
1134
+ });
1135
+ $('<option value="_ADD_">none</option>').appendTo(loggerSelector);
1136
+ }
1137
+ logEnabled.on('change', function() {
1138
+ if (logEnabled.prop("checked")) {
1139
+ logEnabledSection.show();
1140
+ node._def.defaults.logger.required = true;
1141
+ } else {
1142
+ logEnabledSection.hide()
1143
+ node._def.defaults.logger.required = false;
1144
+ }
1145
+ });
1146
+ logTemplateEnabled.on('change', function() {
1147
+ if (logTemplateEnabled.prop("checked")) {
1148
+ logTemplateEnabledSection.show();
1149
+ node._def.defaults.logTemplateOverride.required = true;
1150
+ } else {
1151
+ logTemplateEnabledSection.hide()
1152
+ node._def.defaults.logTemplateOverride.required = false;
1153
+ }
1154
+ });
1155
+ node.logTemplateOverrideEditor = RED.editor.createEditor({
1156
+ id: 'node-input-logTemplateOverrideEditor',
1157
+ mode: 'ace/mode/handlebars',
1158
+ value: node.logTemplateOverride
1159
+ });
1160
+ let loggerSelector = $("#logger-selector");
1161
+ let loggerEditButton = $("#logger-edit-btn");
1162
+ let loggerAddButton = $("#logger-add-btn");
1163
+ loggerEditButton.on('click', async function(event) {
1164
+ event.stopPropagation();
1165
+ if (loggerEditButton.hasClass("disabled")) {
1166
+ return;
1167
+ }
1168
+ // figure out what type of logger this is.
1169
+ let selectedLogger = (await getLoggers()).find(logger => logger.id === loggerSelector.val());
1170
+ RED.editor.editConfig("#logger-selector", selectedLogger.type, selectedLogger.id);
1171
+ });
1172
+ loggerAddButton.on('click', function(event) {
1173
+ event.stopPropagation();
1174
+ let $btn = $(event.target);
1175
+ // Remove any existing dropdown
1176
+ $('.loggertype-dropdown').remove();
1177
+ getLoggerTypes().then(loggerTypes => {
1178
+ let $dropdown = $('<div class="loggertype-dropdown red-ui-editor"></div>');
1179
+ loggerTypes.forEach(loggerType => {
1180
+ $('<div class="dropdown-item"></div>').text(loggerType.name).data('value', loggerType.id).css({
1181
+ padding: '4px 8px',
1182
+ cursor: 'pointer'
1183
+ }).appendTo($dropdown);
1184
+ });
1185
+ // Append first so outerWidth() is measurable, then position so the
1186
+ // right edge of the dropdown aligns with the right edge of the button.
1187
+ $('body').append($dropdown);
1188
+ $dropdown.css({
1189
+ top: $btn.offset().top + $btn.outerHeight(),
1190
+ left: $btn.offset().left + $btn.outerWidth() - $dropdown.outerWidth()
1191
+ });
1192
+ $(document).on('mousedown.dropdown', function(e) {
1193
+ if (!$(e.target).closest('.loggertype-dropdown').length) {
1194
+ $dropdown.remove();
1195
+ $(document).off('mousedown.dropdown'); // Clean up the listener
1196
+ }
1197
+ });
1198
+ // Handle item selection
1199
+ $dropdown.on('click', '.dropdown-item', function(e) {
1200
+ e.stopPropagation();
1201
+ const selectedValue = $(this).data('value');
1202
+ const selectedText = $(this).text();
1203
+ // Do whatever you need with the selection
1204
+ console.log('Selected:', selectedValue, selectedText);
1205
+ $dropdown.remove();
1206
+ RED.editor.editConfig("#logger-selector", selectedValue, "_ADD_");
1207
+ });
1208
+ })
1209
+ });
1210
+ loggerSelector.on("focus", updateLoggerSelectorList);
1211
+ loggerSelector.on('change', function(event) {
1212
+ if ("_ADD_" === loggerSelector.val()) {
1213
+ loggerEditButton.addClass("disabled")
1214
+ } else {
1215
+ loggerEditButton.removeClass("disabled")
1216
+ }
1217
+ });
1218
+ // lastly, make sure that we set the correct value on the logger selector.
1219
+ updateLoggerSelectorList().then(() => {
1220
+ loggerSelector.val(node.logger);
1221
+ loggerSelector.trigger('change');
1222
+ });
1223
+ }
1224
+ // **** metrics **** //
1225
+ {
1226
+ let node = this;
1227
+ let metricsEnabled = $("#node-input-metricsEnabled");
1228
+ let metricsEnabledSection = $("#section_metricsEnabled");
1229
+ metricsEnabled.on('change', function() {
1230
+ if (metricsEnabled.prop("checked")) {
1231
+ metricsEnabledSection.show();
1232
+ node._def.defaults.metricsReference.required = true;
1233
+ } else {
1234
+ metricsEnabledSection.hide()
1235
+ node._def.defaults.metricsReference.required = false;
1236
+ }
1237
+ });
1238
+ }
1239
+ },
1240
+ oneditsave: function() {
1241
+ // **** NginxGetHostsNode **** //
1242
+ {
1243
+ let node = this;
1244
+ }
1245
+ // **** logger **** //
1246
+ {
1247
+ node = this;
1248
+ var selectedLogger = $("#logger-selector").val();
1249
+ node.logger = (selectedLogger && selectedLogger !== "_ADD_") ? selectedLogger : '';
1250
+ node.logTemplateOverride = node.logTemplateOverrideEditor.getValue();
1251
+ node.logTemplateOverrideEditor.destroy();
1252
+ delete node.logTemplateOverrideEditor;
1253
+ }
1254
+ // **** metrics **** //
1255
+ {}
1256
+ },
1257
+ oneditcancel: function() {},
1258
+ oneditdelete: function() {},
1259
+ });
1260
+
1261
+ </script>
1262
+
1263
+
1264
+ <script type="text/html" data-template-name='NginxGetHostsNode'>
1265
+ <div id='section_NginxGetHostsNode'>
1266
+ <div class="form-row">
1267
+ <label class="towb_editorlabel" for="node-input-name"><i class="fa fa-font"></i> Name</label>
1268
+ <input type="text" id="node-input-name" />
1269
+ </div>
1270
+
1271
+ <div class="form-row">
1272
+ <label class="towb_editorlabel" for="node-input-proxymanagerconfig"><i class="fa fa-server"></i> Proxy Manager</label>
1273
+ <input type="text" id="node-input-proxymanagerconfig" />
1274
+ </div>
1275
+
1276
+ <div class="form-row">
1277
+ <label class="towb_editorlabel" for="node-input-outputpath"><i class="fa fa-sign-out"></i> Output</label>
1278
+ <input type="text" id="node-input-outputpath" />
1279
+ </div>
1280
+
1281
+ </div>
1282
+
1283
+ <div id='section_logger'>
1284
+ <div id="section_loggerconfig">
1285
+ <div class="form-row editorsectionheading">
1286
+ <i class="w-16 fa fa-eye"></i> <span>Logging</span>
1287
+ </div>
1288
+ <div class="editorgroupborder">
1289
+ <div class="form-row nomargin logEnableCheck">
1290
+ <input type="checkbox" id="node-input-logEnabled" placeholder="logEnabled" value="false" style="margin:8px 0 10px 102px; width:20px;">
1291
+ <label style="width:auto" for="node-input-logEnabled"><span>Enable Logging</span></label>
1292
+ </div>
1293
+
1294
+ <div id="section_logEnabled">
1295
+ <div class="form-row nomargin">
1296
+ <label for="logger-selector">Logger</label>
1297
+ <div style="width: 70%; display: inline-flex;">
1298
+ <select id="logger-selector" style="flex-grow: 1;" class="">
1299
+ <option value="_ADD_">none</option>
1300
+ </select>
1301
+ <a id="logger-edit-btn" class="red-ui-button disabled" style="margin-left: 10px;">
1302
+ <i class="fa fa-pencil"></i>
1303
+ </a>
1304
+ <a id="logger-add-btn" class="red-ui-button" style="margin-left: 10px;">
1305
+ <i class="fa fa-plus"></i>
1306
+ </a>
1307
+ </div>
1308
+ </div>
1309
+
1310
+ <div class="form-row nomargin">
1311
+ <input type="checkbox" id="node-input-logTemplateOverrideEnabled" placeholder="" value="false" style="margin:8px 0 10px 102px; width:20px;">
1312
+ <label style="width:auto" for="node-input-logTemplateOverrideEnabled"><span>Override Template</span></label>
1313
+ </div>
1314
+
1315
+ <div id="section_logTemplateOverrideEnabled">
1316
+ <div class="form-row">
1317
+ <label class="towb_editorlabel" for="node-input-logTemplateOverrideEditor"><i class="fa fa-code"></i> Template</label>
1318
+ <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-logTemplateOverrideEditor"></div>
1319
+ </div>
1320
+ </div>
1321
+ </div>
1322
+ </div>
1323
+ </div>
1324
+
1325
+ </div>
1326
+
1327
+ <div id='section_metrics'>
1328
+ <div id="section_metricsconfig">
1329
+ <div class="form-row editorsectionheading">
1330
+ <i class="w-16 fa fa-eye"></i> <span>Metrics</span>
1331
+ </div>
1332
+ <div class="editorgroupborder">
1333
+ <div class="form-row nomargin metricsEnableCheck">
1334
+ <input type="checkbox" id="node-input-metricsEnabled" value="false" style="margin:8px 0 10px 102px; width:20px;" />
1335
+ <label style="width:auto" for="node-input-metricsEnabled"><span>Enable Metrics</span></label>
1336
+ </div>
1337
+
1338
+ <div id="section_metricsEnabled">
1339
+ <div class="form-row nomargin">
1340
+ <label class="towb_editorlabel" for="node-input-metricsReference">Metric Collector</label>
1341
+ <input id="node-input-metricsReference" placeholder="metrics" />
1342
+ </div>
1343
+ </div>
1344
+ </div>
1345
+ </div>
1346
+
1347
+ </div>
1348
+ </script>
1349
+
1350
+ <script type="text/markdown" data-help-name='NginxGetHostsNode'>
1351
+ Retrieves the list of proxy hosts from Nginx Proxy Manager and writes them to the specified output path on the message.
1352
+
1353
+ ### Properties
1354
+
1355
+ : *name* (string) : A descriptive label for the node.
1356
+ : *proxy manager* (npm config) : The Nginx Proxy Manager connection to use.
1357
+ : *output* (msg) : The message property to write the hosts array to. Defaults to `msg.payload`.
1358
+
1359
+ ### Output
1360
+
1361
+ The original message is forwarded with the specified property set to an array of proxy host objects:
1362
+
1363
+ ```javascript
1364
+ [
1365
+ {
1366
+ id: number, // proxy host ID
1367
+ domainNames: string[], // domain names served by this host
1368
+ scheme: string, // forwarding scheme: "http" or "https"
1369
+ forwardHost: string, // target host
1370
+ forwardPort: number, // target port
1371
+ }
1372
+ ]
1373
+ ```
1374
+
1375
+ ### Logging
1376
+ : *enable logging* (boolean) : if checked, then the node will produce logging output to the specified logger.
1377
+ : *logger* (logconfig) : the endppoint that the node will log events to.
1378
+ : *override template* (logconfig) : if checked, a custom logging template can be provided for the log message.
1379
+ : *logger template* (mustache) : a template in mustache format to generate a message for logging purposes (see LoggerConfig node for more information)
1380
+
1381
+ ### Metrics
1382
+ : *enable metrics* (boolean) : if checked, then the node will produce metrics output to the specified metrics provider.
1383
+ : *metric collector* (metricconfig) : the collection point or grouping where this particular metric will be attached.
1384
+ </script>
1385
+
1386
+