From d47e1ea8fa5239029dffa5479459cb82d8b6b522 Mon Sep 17 00:00:00 2001 From: GermanBluefox Date: Sat, 28 Dec 2024 17:54:04 +0000 Subject: [PATCH] Bump 6.0.1 --- README.md | 4 + admin/jsonConfig.json | 2 +- lib/adapter-config.d.ts | 34 +- main.js | 149 ++++++--- nodes/ioBroker.html | 21 +- nodes/ioBroker.js | 2 +- package-lock.json | 723 ++++++++++++++++++++++++++-------------- package.json | 18 +- 8 files changed, 626 insertions(+), 327 deletions(-) diff --git a/README.md b/README.md index 35146142..f0a7bc9a 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,10 @@ Instantiate the server with Node-RED --> ## Changelog +### **WORK IN PROGRESS** + +- (@GermanBluefox) Restart node-red if admin settings changed + ### 6.0.1 (2024-09-30) - (@GermanBluefox) Corrected the case if `envVars` settings is undefined diff --git a/admin/jsonConfig.json b/admin/jsonConfig.json index 8a2f2b06..8b141f2c 100644 --- a/admin/jsonConfig.json +++ b/admin/jsonConfig.json @@ -6,7 +6,7 @@ "_general": { "type": "panel", "label": "General", - "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tISBGb250IEF3ZXNvbWUgUHJvIDYuNC4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlIChDb21tZXJjaWFsIExpY2Vuc2UpIENvcHlyaWdodCAyMDIzIEZvbnRpY29ucywgSW5jLiAtLT48cGF0aCBkPSJNNDk1LjkgMTY2LjZjMy4yIDguNyAuNSAxOC40LTYuNCAyNC42bC00My4zIDM5LjRjMS4xIDguMyAxLjcgMTYuOCAxLjcgMjUuNHMtLjYgMTcuMS0xLjcgMjUuNGw0My4zIDM5LjRjNi45IDYuMiA5LjYgMTUuOSA2LjQgMjQuNmMtNC40IDExLjktOS43IDIzLjMtMTUuOCAzNC4zbC00LjcgOC4xYy02LjYgMTEtMTQgMjEuNC0yMi4xIDMxLjJjLTUuOSA3LjItMTUuNyA5LjYtMjQuNSA2LjhsLTU1LjctMTcuN2MtMTMuNCAxMC4zLTI4LjIgMTguOS00NCAyNS40bC0xMi41IDU3LjFjLTIgOS4xLTkgMTYuMy0xOC4yIDE3LjhjLTEzLjggMi4zLTI4IDMuNS00Mi41IDMuNXMtMjguNy0xLjItNDIuNS0zLjVjLTkuMi0xLjUtMTYuMi04LjctMTguMi0xNy44bC0xMi41LTU3LjFjLTE1LjgtNi41LTMwLjYtMTUuMS00NC0yNS40TDgzLjEgNDI1LjljLTguOCAyLjgtMTguNiAuMy0yNC41LTYuOGMtOC4xLTkuOC0xNS41LTIwLjItMjIuMS0zMS4ybC00LjctOC4xYy02LjEtMTEtMTEuNC0yMi40LTE1LjgtMzQuM2MtMy4yLTguNy0uNS0xOC40IDYuNC0yNC42bDQzLjMtMzkuNEM2NC42IDI3My4xIDY0IDI2NC42IDY0IDI1NnMuNi0xNy4xIDEuNy0yNS40TDIyLjQgMTkxLjJjLTYuOS02LjItOS42LTE1LjktNi40LTI0LjZjNC40LTExLjkgOS43LTIzLjMgMTUuOC0zNC4zbDQuNy04LjFjNi42LTExIDE0LTIxLjQgMjIuMS0zMS4yYzUuOS03LjIgMTUuNy05LjYgMjQuNS02LjhsNTUuNyAxNy43YzEzLjQtMTAuMyAyOC4yLTE4LjkgNDQtMjUuNGwxMi41LTU3LjFjMi05LjEgOS0xNi4zIDE4LjItMTcuOEMyMjcuMyAxLjIgMjQxLjUgMCAyNTYgMHMyOC43IDEuMiA0Mi41IDMuNWM5LjIgMS41IDE2LjIgOC43IDE4LjIgMTcuOGwxMi41IDU3LjFjMTUuOCA2LjUgMzAuNiAxNS4xIDQ0IDI1LjRsNTUuNy0xNy43YzguOC0yLjggMTguNi0uMyAyNC41IDYuOGM4LjEgOS44IDE1LjUgMjAuMiAyMi4xIDMxLjJsNC43IDguMWM2LjEgMTEgMTEuNCAyMi40IDE1LjggMzQuM3pNMjU2IDMzNmE4MCA4MCAwIDEgMCAwLTE2MCA4MCA4MCAwIDEgMCAwIDE2MHoiLz48L3N2Zz4=", + "icon": "data:image/svg+xml;base64,PHN2ZydefaultPermissionsB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tISBGb250IEF3ZXNvbWUgUHJvIDYuNC4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlIChDb21tZXJjaWFsIExpY2Vuc2UpIENvcHlyaWdodCAyMDIzIEZvbnRpY29ucywgSW5jLiAtLT48cGF0aCBkPSJNNDk1LjkgMTY2LjZjMy4yIDguNyAuNSAxOC40LTYuNCAyNC42bC00My4zIDM5LjRjMS4xIDguMyAxLjcgMTYuOCAxLjcgMjUuNHMtLjYgMTcuMS0xLjcgMjUuNGw0My4zIDM5LjRjNi45IDYuMiA5LjYgMTUuOSA2LjQgMjQuNmMtNC40IDExLjktOS43IDIzLjMtMTUuOCAzNC4zbC00LjcgOC4xYy02LjYgMTEtMTQgMjEuNC0yMi4xIDMxLjJjLTUuOSA3LjItMTUuNyA5LjYtMjQuNSA2LjhsLTU1LjctMTcuN2MtMTMuNCAxMC4zLTI4LjIgMTguOS00NCAyNS40bC0xMi41IDU3LjFjLTIgOS4xLTkgMTYuMy0xOC4yIDE3LjhjLTEzLjggMi4zLTI4IDMuNS00Mi41IDMuNXMtMjguNy0xLjItNDIuNS0zLjVjLTkuMi0xLjUtMTYuMi04LjctMTguMi0xNy44bC0xMi41LTU3LjFjLTE1LjgtNi41LTMwLjYtMTUuMS00NC0yNS40TDgzLjEgNDI1LjljLTguOCAyLjgtMTguNiAuMy0yNC41LTYuOGMtOC4xLTkuOC0xNS41LTIwLjItMjIuMS0zMS4ybC00LjctOC4xYy02LjEtMTEtMTEuNC0yMi40LTE1LjgtMzQuM2MtMy4yLTguNy0uNS0xOC40IDYuNC0yNC42bDQzLjMtMzkuNEM2NC42IDI3My4xIDY0IDI2NC42IDY0IDI1NnMuNi0xNy4xIDEuNy0yNS40TDIyLjQgMTkxLjJjLTYuOS02LjItOS42LTE1LjktNi40LTI0LjZjNC40LTExLjkgOS43LTIzLjMgMTUuOC0zNC4zbDQuNy04LjFjNi42LTExIDE0LTIxLjQgMjIuMS0zMS4yYzUuOS03LjIgMTUuNy05LjYgMjQuNS02LjhsNTUuNyAxNy43YzEzLjQtMTAuMyAyOC4yLTE4LjkgNDQtMjUuNGwxMi41LTU3LjFjMi05LjEgOS0xNi4zIDE4LjItMTcuOEMyMjcuMyAxLjIgMjQxLjUgMCAyNTYgMHMyOC43IDEuMiA0Mi41IDMuNWM5LjIgMS41IDE2LjIgOC43IDE4LjIgMTcuOGwxMi41IDU3LjFjMTUuOCA2LjUgMzAuNiAxNS4xIDQ0IDI1LjRsNTUuNy0xNy43YzguOC0yLjggMTguNi0uMyAyNC41IDYuOGM4LjEgOS44IDE1LjUgMjAuMiAyMi4xIDMxLjJsNC43IDguMWM2LjEgMTEgMTEuNCAyMi40IDE1LjggMzQuM3pNMjU2IDMzNmE4MCA4MCAwIDEgMCAwLTE2MCA4MCA4MCAwIDEgMCAwIDE2MHoiLz48L3N2Zz4=", "items": { "bind": { "newLine": true, diff --git a/lib/adapter-config.d.ts b/lib/adapter-config.d.ts index aec771cf..18b5aa42 100644 --- a/lib/adapter-config.d.ts +++ b/lib/adapter-config.d.ts @@ -2,17 +2,35 @@ // using the actual properties present in io-package.json // in order to provide typings for adapter.config properties -import { type native } from '../io-package.json'; - -type _AdapterConfig = Partial; - // Augment the globally declared type ioBroker.AdapterConfig declare global { namespace ioBroker { - // tslint:disable-next-line:no-empty-interface - // eslint-disable-next-line @typescript-eslint/no-empty-object-type - interface AdapterConfig extends _AdapterConfig { - // Do not enter anything here! + interface AdapterConfig { + bind: string; + port: number | string; + secure: boolean; + certPublic: string; + certPrivate: string; + httpAdminRoot: string; + httpNodeRoot: string; + httpStatic: string; + npmLibs: []; + maxMemory: number; + valueConvert: boolean; + palletmanagerEnabled: boolean; + projectsEnabled: boolean; + allowCreationOfForeignObjects: boolean; + safeMode: boolean; + doNotReadObjectsDynamically: boolean; + authType: 'None' | 'Simple' | 'Extended'; + user: string; + pass: string; + hasDefaultPermissions: boolean; + defaultPermissions: string; + authExt: { username: string; password: string; permissions: string }[]; + editor: 'monaco' | 'ace'; + theme: string; + envVars: { name: string; value: string }[]; } } } diff --git a/main.js b/main.js index 57281f31..12e275bd 100644 --- a/main.js +++ b/main.js @@ -1,4 +1,4 @@ -const utils = require('@iobroker/adapter-core'); +const { Adapter, getAbsoluteDefaultDataDir } = require('@iobroker/adapter-core'); const fs = require('node:fs'); const path = require('node:path'); const spawn = require('node:child_process').spawn; @@ -39,7 +39,7 @@ function getNodeRedEditorPath() { const nodePath = getNodeRedPath(); const editorClientPath = getNodeRedEditorPath(); -class NodeRed extends utils.Adapter { +class NodeRed extends Adapter { constructor(options) { super({ ...options, @@ -50,6 +50,7 @@ class NodeRed extends utils.Adapter { this.systemSecret = null; this.userDataDir = `${__dirname}/userdata/`; this.redProcess = null; + this.adminUrl = ''; this.stopping = false; this.saveTimer = null; @@ -60,6 +61,7 @@ class NodeRed extends utils.Adapter { this.attempts = {}; this.additional = []; + this.on('objectChange', this.onObjectChange.bind(this)); this.on('ready', this.onReady.bind(this)); //this.on('stateChange', this.onStateChange.bind(this)); this.on('message', this.onMessage.bind(this)); @@ -138,6 +140,75 @@ class NodeRed extends utils.Adapter { })};`; } + async onObjectChange(id) { + if (id.startsWith('system.adapter.admin.')) { + const { adminInstanceObj, adminUrl } = await this.getWsConnectionString(); + if (this.adminUrl !== adminUrl) { + // restart node-red to apply new settings + this.log.info('Restarting node-red to apply new settings of admin instance'); + const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); + if (obj) { + await this.setForeignObjectAsync(obj._id, obj); + } + } + } + } + + async getWsConnectionString() { + // get settings for admin + const settings = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); + let adminInstanceObj; + let adminUrl = ''; + + if (settings) { + // read all admin adapters on this host + const admins = await this.getObjectViewAsync( + 'system', + 'instance', + {startkey: 'system.adapter.admin.', endkey: 'system.adapter.admin.\u9999'}, + {}, + ); + let admin = admins.rows.find( + obj => + // admin should run on the same host + obj.value.common.host === settings.common.host && + // admin should not have authentication + !obj.value.native.auth && + // admin should be enabled + obj.value.common.enabled && + // admin should have secure enabled if node-red has secure not enabled + ((!obj.value.native.secure && !!settings.native.secure) || + // or admin should have the same secure settings + !!obj.value.native.secure === !!settings.native.secure), + ); + adminInstanceObj = admin?.value || null; + if (adminInstanceObj) { + // subscribe on changes of admin instance + await this.subscribeForeignObjectsAsync(adminInstanceObj._id); + } + if (this.config.doNotReadObjectsDynamically) { + adminUrl = ''; + } else if (adminInstanceObj && !adminInstanceObj.native.auth) { + if ( + (!adminInstanceObj.native.secure && !!settings.native.secure) || + !!adminInstanceObj.native.secure === !!settings.native.secure + ) { + adminUrl = `ws${adminInstanceObj.native.secure ? 's' : ''}://${adminInstanceObj.native.bind === '0.0.0.0' || adminInstanceObj.native.bind === '127.0.0.1' ? `' + window.location.hostname + '` : adminInstanceObj.native.bind}:${adminInstanceObj.native.port}`; + ` var socket = new WebSocket('ws${adminInstanceObj.native.secure ? 's' : ''}://${adminInstanceObj.native.bind === '0.0.0.0' || adminInstanceObj.native.bind === '127.0.0.1' ? `' + window.location.hostname + '` : adminInstanceObj.native.bind}:${adminInstanceObj.native.port}?sid=' + Date.now()); // THIS LINE WILL BE CHANGED FOR ADMIN`; + } else { + adminUrl = ''; + } + } else if (adminInstanceObj) { + adminUrl = ''; + + } else { + adminUrl = ''; + } + } + + return { adminInstanceObj, adminUrl }; + } + async generateHtml() { const searchText = '// THIS LINE WILL BE CHANGED FOR ADMIN'; const html = fs.readFileSync(`${__dirname}/nodes/ioBroker.html`).toString('utf8'); @@ -146,59 +217,35 @@ class NodeRed extends utils.Adapter { if (pos) { this.log.debug(`Found searched text "${searchText}" of /nodes/ioBroker.html in line ${pos + 1}`); - // get settings for admin - const settings = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); - if (settings) { - // read all admin adapters on this host - const admins = await this.getObjectViewAsync( - 'system', - 'instance', - { startkey: 'system.adapter.admin.', endkey: 'system.adapter.admin.\u9999' }, - {}, - ); - let admin = admins.rows.find( - obj => - obj.value.common.host === settings.common.host && - !obj.value.native.auth && - obj.value.common.enabled && - ((!obj.value.native.secure && !!settings.native.secure) || - !!obj.value.native.secure === !!settings.native.secure), - ); - const adminInstanceObj = admin ? admin.value : null; - - if (this.config.doNotReadObjectsDynamically) { - lines[pos] = ` var socket = null; ${searchText}`; - } else if (adminInstanceObj && !adminInstanceObj.native.auth) { - if ( - (!adminInstanceObj.native.secure && !!settings.native.secure) || - !!adminInstanceObj.native.secure === !!settings.native.secure - ) { - lines[pos] = - ` var socket = new WebSocket('ws${adminInstanceObj.native.secure ? 's' : ''}://${adminInstanceObj.native.bind === '0.0.0.0' || adminInstanceObj.native.bind === '127.0.0.1' ? `' + window.location.hostname + '` : adminInstanceObj.native.bind}:${adminInstanceObj.native.port}?sid=' + Date.now()); // THIS LINE WILL BE CHANGED FOR ADMIN`; - } else { - lines[pos] = ` var socket = null; ${searchText}`; - this.log.warn( - `Cannot enable the dynamic object read as admin is SSL ${adminInstanceObj.native.secure ? 'with' : 'without'} and node-red is ${settings.native.secure ? 'with' : 'without'} SSL`, - ); - } - } else if (adminInstanceObj) { - lines[pos] = ` var socket = null; ${searchText}`; - this.log.warn(`Cannot enable the dynamic object read as admin has authentication`); + const { adminInstanceObj, adminUrl } = await this.getWsConnectionString(); + + if (this.config.doNotReadObjectsDynamically) { + lines[pos] = ` var socket = null; ${searchText}`; + } else if (adminInstanceObj) { + if (adminUrl) { + lines[pos] = + ` var socket = new WebSocket('${adminUrl}?sid=' + Date.now()); // THIS LINE WILL BE CHANGED FOR ADMIN`; } else { lines[pos] = ` var socket = null; ${searchText}`; - this.log.warn(`Cannot enable the dynamic object read as admin has authentication`); + this.log.warn( + `Cannot enable the dynamic object read as admin is SSL ${adminInstanceObj.native.secure ? 'with' : 'without'} and node-red is ${this.config.secure ? 'with' : 'without'} SSL`, + ); } + } else { + lines[pos] = ` var socket = null; ${searchText}`; + this.log.warn(`Cannot enable the dynamic object read as no admin instance found on the same host and wihout authentication`); + } - const searchTextIob = '// THIS LINE WILL BE CHANGED FOR SELECT ID'; - const posIob = lines.findIndex(line => line.includes(searchTextIob)); - if (posIob !== -1 && adminInstanceObj) { - lines[posIob] = ` ${NodeRed.getAdminJson(adminInstanceObj)} ${searchTextIob}`; - } + const searchTextIob = '// THIS LINE WILL BE CHANGED FOR SELECT ID'; + const posIob = lines.findIndex(line => line.includes(searchTextIob)); + if (posIob !== -1 && adminInstanceObj) { + lines[posIob] = ` ${NodeRed.getAdminJson(adminInstanceObj)} ${searchTextIob}`; + } - if (html !== lines.join('\n')) { - fs.writeFileSync(`${__dirname}/nodes/ioBroker.html`, lines.join('\n')); - } + if (html !== lines.join('\n')) { + fs.writeFileSync(`${__dirname}/nodes/ioBroker.html`, lines.join('\n')); } + this.adminUrl = adminUrl; } } @@ -403,9 +450,9 @@ class NodeRed extends utils.Adapter { // Find userdata directory if (this.instance === 0) { - this.userDataDir = path.join(utils.getAbsoluteDefaultDataDir(), 'node-red'); + this.userDataDir = path.join(getAbsoluteDefaultDataDir(), 'node-red'); } else { - this.userDataDir = path.join(utils.getAbsoluteDefaultDataDir(), `node-red.${this.instance}`); + this.userDataDir = path.join(getAbsoluteDefaultDataDir(), `node-red.${this.instance}`); } if (this.config.npmLibs && !this.config.palletmanagerEnabled) { diff --git a/nodes/ioBroker.html b/nodes/ioBroker.html index 33ad14ea..f4ea295b 100644 --- a/nodes/ioBroker.html +++ b/nodes/ioBroker.html @@ -28,12 +28,12 @@