diff --git a/admin/i18n/de/translations.json b/admin/i18n/de/translations.json index c739dcb..72ad808 100644 --- a/admin/i18n/de/translations.json +++ b/admin/i18n/de/translations.json @@ -7,5 +7,8 @@ "Password": "Passwort", "Realm": "Realm", "Additional information": "Weitere Informationen", - "Request disk information": "Festplatteninformationen anfordern" + "Request disk information": "Festplatteninformationen anfordern", + "Request Ceph information": "Ceph Informationen", + "Request HA information": "HA Informationen", + "new tree structure": "neue Baumstruktur" } diff --git a/admin/i18n/en/translations.json b/admin/i18n/en/translations.json index b0357d9..fa5bcad 100644 --- a/admin/i18n/en/translations.json +++ b/admin/i18n/en/translations.json @@ -7,5 +7,8 @@ "Password": "Password", "Realm": "Realm", "Additional information": "Additional information", - "Request disk information": "Request disk information" -} \ No newline at end of file + "Request disk information": "Request disk information", + "Request Ceph information": "Request Ceph information", + "Request HA information": "Request HA information", + "new tree structure": "new tree structure" +} diff --git a/admin/i18n/es/translations.json b/admin/i18n/es/translations.json index 8f19ead..b5cf10d 100644 --- a/admin/i18n/es/translations.json +++ b/admin/i18n/es/translations.json @@ -7,5 +7,8 @@ "Password": "Contraseña", "Realm": "Reino", "Additional information": "Información adicional", - "Request disk information": "Solicitar información del disco" + "Request disk information": "Solicitar información del disco", + "Request Ceph information": "Solicitud de información de Ceph", + "Request HA information": "Solicitud de información de HA", + "new tree structure": "nueva estructura" } diff --git a/admin/i18n/fr/translations.json b/admin/i18n/fr/translations.json index 9517441..0cd9186 100644 --- a/admin/i18n/fr/translations.json +++ b/admin/i18n/fr/translations.json @@ -7,5 +7,8 @@ "Password": "Mot de passe", "Realm": "Royaume", "Additional information": "Informations Complémentaires", - "Request disk information": "Demander des informations sur le disque" + "Request disk information": "Demander des informations sur le disque", + "Request Ceph information": "Demande de renseignements sur le ceph", + "Request HA information": "Demande de renseignements sur le HA", + "new tree structure": "nouvelle structure" } diff --git a/admin/i18n/it/translations.json b/admin/i18n/it/translations.json index 523568b..320a011 100644 --- a/admin/i18n/it/translations.json +++ b/admin/i18n/it/translations.json @@ -7,5 +7,8 @@ "Password": "Parola d'ordine", "Realm": "Regno", "Additional information": "Informazioni aggiuntive", - "Request disk information": "Richiedi informazioni sul disco" + "Request disk information": "Richiedi informazioni sul disco", + "Request Ceph information": "Richiedi informazioni su Ceph", + "Request HA information": "Richiedi informazioni su HA", + "new tree structure": "nuova struttura" } diff --git a/admin/i18n/nl/translations.json b/admin/i18n/nl/translations.json index b1be48f..bbdc2c1 100644 --- a/admin/i18n/nl/translations.json +++ b/admin/i18n/nl/translations.json @@ -7,5 +7,8 @@ "Password": "Wachtwoord", "Realm": "Rijk", "Additional information": "Extra informatie", - "Request disk information": "Schijfinformatie opvragen" + "Request disk information": "Schijfinformatie opvragen", + "Request Ceph information": "Verzoek Ceph informatie", + "Request HA information": "Verzoek HA informatie", + "new tree structure": "nieuwe boomstructuur" } diff --git a/admin/i18n/pl/translations.json b/admin/i18n/pl/translations.json index 5ac9e35..a48c616 100644 --- a/admin/i18n/pl/translations.json +++ b/admin/i18n/pl/translations.json @@ -1,11 +1,14 @@ { "Options": "Opcje", - "IP Aaddress": "Adres IP", + "IP Aaddress": "Adresy IP", "Port": "Port", "Request Interval": "Interwał żądania", "User name": "Nazwa użytkownika", "Password": "Hasło", - "Realm": "Królestwo", + "Realm": "Realm", "Additional information": "Dodatkowe informacje", - "Request disk information": "Poproś o informacje o dysku" + "Request disk information": "informacje o dysku", + "Request Ceph information": "informacje o Ceph", + "Request HA information": "informacje o HA", + "new tree structure": "nowa struktura" } diff --git a/admin/i18n/pt/translations.json b/admin/i18n/pt/translations.json index 028f0a4..27dee3b 100644 --- a/admin/i18n/pt/translations.json +++ b/admin/i18n/pt/translations.json @@ -7,5 +7,8 @@ "Password": "Senha", "Realm": "Reino", "Additional information": "Informações adicionais", - "Request disk information": "Solicitar informações do disco" + "Request disk information": "Solicitar informações do disco", + "Request Ceph information": "Solicitar informações sobre Ceph", + "Request HA information": "Solicitar informações sobre HA", + "new tree structure": "nova estrutura da árvore" } diff --git a/admin/i18n/ru/translations.json b/admin/i18n/ru/translations.json index 579e89a..1c15d70 100644 --- a/admin/i18n/ru/translations.json +++ b/admin/i18n/ru/translations.json @@ -7,5 +7,8 @@ "Password": "Пароль", "Realm": "Область", "Additional information": "Дополнительная информация", - "Request disk information": "Запросить информацию о диске" + "Request disk information": "Запросить информацию о диске", + "Request Ceph information": "Запросить информацию Ceph", + "Request HA information": "Запросить информацию HA", + "new tree structure": "новая структура дерева" } diff --git a/admin/i18n/uk/translations.json b/admin/i18n/uk/translations.json index a1f487c..98b3f12 100644 --- a/admin/i18n/uk/translations.json +++ b/admin/i18n/uk/translations.json @@ -7,5 +7,8 @@ "Password": "Пароль", "Realm": "Королівство", "Additional information": "Додаткова інформація", - "Request disk information": "Запит інформації про диск" + "Request disk information": "Запит інформації про диск", + "Request Ceph information": "Запит інформації Ceph", + "Request HA information": "Запит інформації HA", + "new tree structure": "нова структура дерева" } diff --git a/admin/i18n/zh-cn/translations.json b/admin/i18n/zh-cn/translations.json index 392998c..05e2c1c 100644 --- a/admin/i18n/zh-cn/translations.json +++ b/admin/i18n/zh-cn/translations.json @@ -7,5 +7,8 @@ "Password": "密码", "Realm": "领域", "Additional information": "附加信息", - "Request disk information": "请求磁盘信息" + "Request disk information": "请求磁盘信息", + "Request Ceph information": "请求资料", + "Request HA information": "请求人道协调厅提供资料", + "new tree structure": "新树结构" } diff --git a/admin/jsonConfig.json b/admin/jsonConfig.json index 3c4bd07..7f97d3f 100644 --- a/admin/jsonConfig.json +++ b/admin/jsonConfig.json @@ -79,6 +79,27 @@ "sm": 12, "md": 6, "lg": 6 + }, + "requestCephInformation": { + "type": "checkbox", + "label": "Request Ceph information", + "sm": 12, + "md": 6, + "lg": 6 + }, + "requestHAInformation": { + "type": "checkbox", + "label": "Request HA information", + "sm": 12, + "md": 6, + "lg": 6 + }, + "newTreeStructure": { + "type": "checkbox", + "label": "new tree structure", + "sm": 12, + "md": 6, + "lg": 6 } } } diff --git a/admin/words.js b/admin/words.js index 34a0d6c..58bfd8b 100644 --- a/admin/words.js +++ b/admin/words.js @@ -9,12 +9,15 @@ systemDictionary = { "Options": { "en": "Options", "de": "Optionen", "ru": "Параметры", "pt": "Opções", "nl": "Opties", "fr": "Possibilités", "it": "Opzioni", "es": "Opciones", "pl": "Opcje", "uk": "Опції", "zh-cn": "选项"}, - "IP Aaddress": { "en": "IP Address", "de": "IP Adresse", "ru": "Айпи адрес", "pt": "Endereço de IP", "nl": "IP adres", "fr": "Adresse IP", "it": "Indirizzo IP", "es": "Dirección IP", "pl": "Adres IP", "uk": "IP-адреса", "zh-cn": "IP地址"}, + "IP Aaddress": { "en": "IP Address", "de": "IP Adresse", "ru": "Айпи адрес", "pt": "Endereço de IP", "nl": "IP adres", "fr": "Adresse IP", "it": "Indirizzo IP", "es": "Dirección IP", "pl": "Adresy IP", "uk": "IP-адреса", "zh-cn": "IP地址"}, "Port": { "en": "Port", "de": "Port", "ru": "Порт", "pt": "Porta", "nl": "Haven", "fr": "Port", "it": "Porta", "es": "Puerto", "pl": "Port", "uk": "Порт", "zh-cn": "港口"}, "Request Interval": { "en": "Request Interval", "de": "Anfrage-Intervall", "ru": "Интервал запроса", "pt": "Intervalo de solicitação", "nl": "Interval aanvragen", "fr": "Intervalle de demande", "it": "Richiedi intervallo", "es": "Intervalo de solicitud", "pl": "Interwał żądania", "uk": "Інтервал запиту", "zh-cn": "请求间隔"}, "User name": { "en": "User name", "de": "Nutzername", "ru": "Имя пользователя", "pt": "Nome de usuário", "nl": "Gebruikersnaam", "fr": "Nom d'utilisateur", "it": "Nome utente", "es": "Nombre de usuario", "pl": "Nazwa użytkownika", "uk": "Ім'я користувача", "zh-cn": "用户名"}, "Password": { "en": "Password", "de": "Passwort", "ru": "Пароль", "pt": "Senha", "nl": "Wachtwoord", "fr": "Mot de passe", "it": "Parola d'ordine", "es": "Contraseña", "pl": "Hasło", "uk": "Пароль", "zh-cn": "密码"}, - "Realm": { "en": "Realm", "de": "Realm", "ru": "Область", "pt": "Reino", "nl": "Rijk", "fr": "Royaume", "it": "Regno", "es": "Reino", "pl": "Królestwo", "uk": "Королівство", "zh-cn": "领域"}, + "Realm": { "en": "Realm", "de": "Realm", "ru": "Область", "pt": "Reino", "nl": "Rijk", "fr": "Royaume", "it": "Regno", "es": "Reino", "pl": "Realm", "uk": "Королівство", "zh-cn": "领域"}, "Additional information": { "en": "Additional information", "de": "Weitere Informationen", "ru": "Дополнительная информация", "pt": "Informações adicionais", "nl": "Extra informatie", "fr": "Informations Complémentaires", "it": "Informazioni aggiuntive", "es": "Información adicional", "pl": "Dodatkowe informacje", "uk": "Додаткова інформація", "zh-cn": "附加信息"}, - "Request disk information": { "en": "Request disk information", "de": "Festplatteninformationen anfordern", "ru": "Запросить информацию о диске", "pt": "Solicitar informações do disco", "nl": "Schijfinformatie opvragen", "fr": "Demander des informations sur le disque", "it": "Richiedi informazioni sul disco", "es": "Solicitar información del disco", "pl": "Poproś o informacje o dysku", "uk": "Запит інформації про диск", "zh-cn": "请求磁盘信息"}, + "Request disk information": { "en": "Request disk information", "de": "Festplatteninformationen anfordern", "ru": "Запросить информацию о диске", "pt": "Solicitar informações do disco", "nl": "Schijfinformatie opvragen", "fr": "Demander des informations sur le disque", "it": "Richiedi informazioni sul disco", "es": "Solicitar información del disco", "pl": "informacje o dysku", "uk": "Запит інформації про диск", "zh-cn": "请求磁盘信息"}, + "Request Ceph information": { "en": "Request Ceph information", "de": "Ceph Informationen", "ru": "Запросить информацию Ceph", "pt": "Solicitar informações sobre Ceph", "nl": "Verzoek Ceph informatie", "fr": "Demande de renseignements sur le ceph", "it": "Richiedi informazioni su Ceph", "es": "Solicitud de información de Ceph", "pl": "informacje o Ceph", "uk": "Запит інформації Ceph", "zh-cn": "请求资料"}, + "Request HA information": { "en": "Request HA information", "de": "HA Informationen", "ru": "Запросить информацию HA", "pt": "Solicitar informações sobre HA", "nl": "Verzoek HA informatie", "fr": "Demande de renseignements sur le HA", "it": "Richiedi informazioni su HA", "es": "Solicitud de información de HA", "pl": "informacje o HA", "uk": "Запит інформації HA", "zh-cn": "请求人道协调厅提供资料"}, + "new tree structure": { "en": "new tree structure", "de": "neue Baumstruktur", "ru": "новая структура дерева", "pt": "nova estrutura da árvore", "nl": "nieuwe boomstructuur", "fr": "nouvelle structure", "it": "nuova struttura", "es": "nueva estructura", "pl": "nowa struktura", "uk": "нова структура дерева", "zh-cn": "新树结构"}, }; \ No newline at end of file diff --git a/io-package.json b/io-package.json index 80324d9..8cd6737 100644 --- a/io-package.json +++ b/io-package.json @@ -168,7 +168,8 @@ { "admin": ">=6.0.0" } - ] + ], + "installedFrom": "iobroker-community-adapters/ioBroker.proxmox#4444513cc28cbbe60377f31ec85057b6ee597390" }, "protectedNative": [ "pwd", @@ -211,15 +212,15 @@ "type": "state", "common": { "name": { - "en": "Connected to LaMetric", - "de": "Verbindung mit LaMetric", - "ru": "При подключении к LaMetric", - "pt": "Se conectado ao LaMetric", - "nl": "Indien verbonden met LaMetric", - "fr": "Si connecté à LaMetric", - "it": "Se connesso a LaMetric", - "es": "Si está conectado a LaMetric", - "pl": "W przypadku połączenia z LaMetric", + "en": "Connected to Proxmox", + "de": "Verbindung mit Proxmox", + "ru": "При подключении к Proxmox", + "pt": "Se conectado ao Proxmox", + "nl": "Indien verbonden met Proxmox", + "fr": "Si connecté à Proxmox", + "it": "Se connesso a Proxmox", + "es": "Si está conectado a Proxmox", + "pl": "W przypadku połączenia z Proxmox", "zh-cn": "如果连接到 LaMetric" }, "role": "indicator.reachable", @@ -229,7 +230,31 @@ "def": false }, "native": {} + }, + { + "_id": "info.offlineMachines", + "type": "state", + "common": { + "name": { + "en": "string for offline Machines", + "de": "string für Offline-Maschinen", + "ru": "string для оффлайн машин", + "pt": "string para máquinas off-line", + "nl": "vertaling:", + "fr": "chaîne pour machines hors ligne", + "it": "stringa per macchine offline", + "es": "cadena para máquinas sin conexión", + "pl": "offline Machine", + "uk": "рядок для автономних машин", + "zh-cn": "a. 通 束" + }, + "role": "state", + "type": "string", + "read": true, + "write": false + }, + "native": {} } ], "objects": [] -} +} \ No newline at end of file diff --git a/lib/proxmox.js b/lib/proxmox.js index 256a494..a91a5f8 100644 --- a/lib/proxmox.js +++ b/lib/proxmox.js @@ -19,7 +19,8 @@ class ProxmoxUtils { this.currentIpId = 0; - this.setNextUrl(0); + this.setNextUrlMain(0); + this.adapter.log.debug(`Using Proxmox API: ${this.URL}`); this.responseCache = {}; @@ -30,7 +31,7 @@ class ProxmoxUtils { this.communicationErrorCounter = 0; } - setNextUrl(id) { + setNextUrlMain(id) { if (typeof id === 'number') { this.currentIpId = id - 1; } @@ -57,9 +58,9 @@ class ProxmoxUtils { if (this.responseCache['/nodes']) { resolve(JSON.parse(JSON.stringify(this.responseCache['/nodes']))); } else { - this._get('/nodes', 'get') + this._getData('/nodes', 'get') .then((data) => { - if (data !== null && typeof data === 'object') { + if (data !== '' && data !== null && typeof data === 'object') { this.responseCache['/nodes'] = data.data; resolve(data.data); } else { @@ -76,9 +77,9 @@ class ProxmoxUtils { if (useCache && this.responseCache[`/nodes/${node}/status`]) { resolve(JSON.parse(JSON.stringify(this.responseCache[`/nodes/${node}/status`]))); } else { - this._get(`/nodes/${node}/status`, 'get') + this._getData(`/nodes/${node}/status`, 'get') .then((data) => { - if (data !== null && typeof data === 'object') { + if (data !== '' && data !== null && typeof data === 'object') { this.responseCache[`/nodes/${node}/status`] = data.data; resolve(data.data); } else { @@ -95,9 +96,9 @@ class ProxmoxUtils { if (useCache && this.responseCache[`/nodes/${node}/disks/list`]) { resolve(JSON.parse(JSON.stringify(this.responseCache[`/nodes/${node}/disks/list`]))); } else { - this._get(`/nodes/${node}/disks/list`, 'get') + this._getData(`/nodes/${node}/disks/list`, 'get') .then((data) => { - if (data !== null && typeof data === 'object') { + if (data !== '' && data !== null && typeof data === 'object') { this.responseCache[`/nodes/${node}/disks/list`] = data.data; resolve(data.data); } else { @@ -110,7 +111,15 @@ class ProxmoxUtils { } async getNodeDisksSmart(node, disk) { - return this._get(`/nodes/${node}/disks/smart?disk=${disk}`, 'get'); + return this._getData(`/nodes/${node}/disks/smart?disk=${disk}`, 'get'); + } + + async getCephInformation() { + return this._getData(`/cluster/ceph/status`, 'get','' ,'','ceph'); + } + + async getHAStatusInformation() { + return this._getData(`/cluster/ha/status/current`, 'get','' ,'','ha'); } async getClusterResources(useCache) { @@ -118,9 +127,9 @@ class ProxmoxUtils { if (useCache && this.responseCache['/cluster/resources']) { resolve(JSON.parse(JSON.stringify(this.responseCache['/cluster/resources']))); } else { - return this._get('/cluster/resources', 'get') + return this._getData('/cluster/resources', 'get') .then((data) => { - if (data !== null && typeof data === 'object') { + if (data !== '' && data !== null && typeof data === 'object') { this.responseCache['/cluster/resources'] = data.data; resolve(data.data); } else { @@ -137,9 +146,9 @@ class ProxmoxUtils { if (useCache && this.responseCache[`/nodes/${node}/${type}/${ID}/status/current`]) { resolve(JSON.parse(JSON.stringify(this.responseCache[`/nodes/${node}/${type}/${ID}/status/current`]))); } else { - this._get(`/nodes/${node}/${type}/${ID}/status/current`, 'get') + this._getData(`/nodes/${node}/${type}/${ID}/status/current`, 'get') .then((data) => { - if (data !== null && typeof data === 'object') { + if (data !== '' && data !== null && typeof data === 'object') { this.responseCache[`/nodes/${node}/${type}/${ID}/status/current`] = data.data; resolve(data.data); } else { @@ -162,9 +171,10 @@ class ProxmoxUtils { this.sharedMap[`/nodes/SHARED/storage/${ID}/status`] = node; } } - this._get(`/nodes/${node}/storage/${ID}/status`, 'get') + + this._getData(`/nodes/${node}/storage/${ID}/status`, 'get') .then((data) => { - if (data !== null && typeof data === 'object') { + if (data !== '' && data !== null && typeof data === 'object') { this.responseCache[`/nodes/${shared ? 'SHARED' : node}/storage/${ID}/status`] = data.data; resolve(data.data); } @@ -175,39 +185,39 @@ class ProxmoxUtils { } async qemuStart(node, type, ID) { - return this._get(`/nodes/${node}/${type}/${ID}/status/start`, 'post'); + return this._getData(`/nodes/${node}/${type}/${ID}/status/start`, 'post'); } async qemuStop(node, type, ID) { - return this._get(`/nodes/${node}/${type}/${ID}/status/stop`, 'post'); + return this._getData(`/nodes/${node}/${type}/${ID}/status/stop`, 'post'); } async qemuShutdown(node, type, ID) { - return this._get(`/nodes/${node}/${type}/${ID}/status/shutdown`, 'post'); + return this._getData(`/nodes/${node}/${type}/${ID}/status/shutdown`, 'post'); } async qemuReset(node, type, ID) { - return this._get(`/nodes/${node}/${type}/${ID}/status/reset`, 'post'); + return this._getData(`/nodes/${node}/${type}/${ID}/status/reset`, 'post'); } async qemuSuspend(node, type, ID) { - return this._get(`/nodes/${node}/${type}/${ID}/status/suspend`, 'post'); + return this._getData(`/nodes/${node}/${type}/${ID}/status/suspend`, 'post'); } async qemuResume(node, type, ID) { - return this._get(`/nodes/${node}/${type}/${ID}/status/resume`, 'post'); + return this._getData(`/nodes/${node}/${type}/${ID}/status/resume`, 'post'); } async qemuReboot(node, type, ID) { - return this._get(`/nodes/${node}/${type}/${ID}/status/reboot`, 'post'); + return this._getData(`/nodes/${node}/${type}/${ID}/status/reboot`, 'post'); } async nodeReboot(node) { - return this._get(`/nodes/${node}/status`, 'post', 'command=reboot'); + return this._getData(`/nodes/${node}/status`, 'post', 'command=reboot'); } async nodeShutdown(node) { - return this._get(`/nodes/${node}/status`, 'post', 'command=shutdown'); + return this._getData(`/nodes/${node}/status`, 'post', 'command=shutdown'); } ticket(cb) { @@ -225,7 +235,7 @@ class ProxmoxUtils { }); } - async _get(url, method, data, retry) { + async _getData(url, method, data, retry, additional) { return new Promise((resolve, reject) => { if (this.stopped) { reject('STOPPED'); @@ -262,34 +272,39 @@ class ProxmoxUtils { this.communicationErrorCounter = 0; if (response.status === 401 && !retry) { - this.ticket(() => { - // Retry with new ticket - this._get(url, method, data, true).then(resolve).catch(reject); - }); + if (additional == null) { + this.ticket(() => { + // Retry with new ticket + this._getData(url, method, data, true).then(resolve).catch(reject); + }); + } } else { if (response.status === 200) { resolve(response.data); + } else { reject(response.status + ' - ' + response.data); } } }) .catch((error) => { - this.adapter.log.error(`received ${error.response.status} response from ${url} with content: ${JSON.stringify(error.response.data)}`); + this.adapter.log.error(`Error received response from ${url}`); this.communicationErrorCounter++; // We experienced error the same as we have servers available, so no chance to retry another server - this.setNextUrl(); + this.setNextUrlMain(); if (this.communicationErrorCounter >= this.ipList.length) { - reject(error.response.data); + reject(error.response.statusText); + this.adapter.log.error(`Error received response from ${error.response.statusText}`); } - this.adapter.log.info(`Use Next Proxmox Host because of communication failure (${this.communicationErrorCounter}): ${this.URL}`); + this.adapter.log.info(`Use Next Proxmox Host because of communication failure (${this.communicationErrorCounter}): ${url}`); - this._get(url, method, data, true); + this._getData(url, method, data, true); }); }); } + async _getTicket() { return new Promise((resolve, reject) => { const url = `/access/ticket?username=${encodeURIComponent(this.name)}@${encodeURIComponent(this.server)}&password=${encodeURIComponent(this.password)}`; @@ -318,7 +333,7 @@ class ProxmoxUtils { .catch((error) => { if (error.response) { // The request was made and the server responded with a status code - this.adapter.log.warn(`received ${error.response.status} response from ${url} with content: ${JSON.stringify(error.response.data)}`); + this.adapter.log.warn(`Error received ${error.response.status} response from ${url} with content: ${JSON.stringify(error.response.data)}`); reject(error.response.status); } else if (error.request) { diff --git a/main.js b/main.js index 00b0d09..6c2b11e 100644 --- a/main.js +++ b/main.js @@ -76,8 +76,12 @@ class Proxmox extends utils.Adapter { if (obj && obj?.native?.type) { const type = obj.native.type; const node = obj.native.node; - const command = id.split('.')[3]; + let command = id.split('.')[3]; + + if (this.config.newTreeStructure && type !== 'node') { + command = id.split('.')[4]; + } this.log.debug(`state changed: "${command}" type: "${type}" node: "${node}"`); if (type === 'lxc' || type === 'qemu') { @@ -237,9 +241,11 @@ class Proxmox extends utils.Adapter { * @private */ async createNodes(nodes) { - const nodesAll = Object.keys(this.objects) + let nodesAll = Object.keys(this.objects) .map(this.removeNamespace.bind(this)) .filter((id) => id.startsWith('node_')); + + const nodesKeep = []; /** @@ -263,7 +269,12 @@ class Proxmox extends utils.Adapter { const nodeName = this.prepareNameForId(node.node); this.log.debug(`Node: ${JSON.stringify(node)}`); - nodesKeep.push(`node_${nodeName}`); + +// if (this.config.newTreeStructure) { +// nodesKeep.push(`node.${nodeName}`); +// } else { + nodesKeep.push(`node_${nodeName}`); +// } const sid = `${this.namespace}.${node.type}_${nodeName}`; @@ -309,6 +320,8 @@ class Proxmox extends utils.Adapter { }, }); + this.subscribeForeignStates(`${sid}.shutdown`); + await this.extendObjectAsync(`${sid}.reboot`, { type: 'state', common: { @@ -336,6 +349,8 @@ class Proxmox extends utils.Adapter { }, }); + this.subscribeForeignStates(`${sid}.reboot`); + // type has changed so extend no matter if yet exists await this.extendObjectAsync(`${sid}.status`, { type: 'state', @@ -361,111 +376,122 @@ class Proxmox extends utils.Adapter { native: {}, }); - await this.setStateChangedAsync(`${sid}.status`, { val: node.status, ack: true }); + await this.setStateChangedAsync(`${sid}.status`, {val: node.status, ack: true}); - if (node.cpu) { - await this.createCustomState(sid, 'cpu', 'level', parseInt(node.cpu * 10000) / 100); - } - if (node.maxcpu) { - await this.createCustomState(sid, 'cpu_max', 'default_num', node.maxcpu); - } + if (node.status == 'online') { // node is offline no infomration available + if (node.cpu) { + await this.createCustomState(sid, 'cpu', 'level', parseInt(node.cpu * 10000) / 100); + } + if (node.maxcpu) { + await this.createCustomState(sid, 'cpu_max', 'default_num', node.maxcpu); + } - this.log.debug(`Requesting states for node ${node.node}`); - try { - const nodeStatus = await this.proxmox?.getNodeStatus(node.node); - if (nodeStatus) { - if (nodeStatus.uptime !== undefined) { - await this.createCustomState(sid, 'uptime', 'time', nodeStatus.uptime); - } - if (nodeStatus.wait !== undefined) { - await this.createCustomState(sid, 'iowait', 'level', parseInt(nodeStatus.wait * 10000) / 100); - } + this.log.debug(`Requesting states for node ${node.node}`); + try { + const nodeStatus = await this.proxmox?.getNodeStatus(node.node); + if (nodeStatus) { + if (nodeStatus.uptime !== undefined) { + await this.createCustomState(sid, 'uptime', 'time', nodeStatus.uptime); + } + if (nodeStatus.wait !== undefined) { + await this.createCustomState(sid, 'iowait', 'level', parseInt(nodeStatus.wait * 10000) / 100); + } - if (nodeStatus.memory.used !== undefined) { - await this.createCustomState(sid, 'memory.used', 'size', BtoMb(nodeStatus.memory.used)); - } - if (nodeStatus.memory.used !== undefined) { - await this.createCustomState(sid, 'memory.used_lev', 'level', p(nodeStatus.memory.used, nodeStatus.memory.total)); - } - if (nodeStatus.memory.total !== undefined) { - await this.createCustomState(sid, 'memory.total', 'size', BtoMb(nodeStatus.memory.total)); - } - if (nodeStatus.memory.free !== undefined) { - await this.createCustomState(sid, 'memory.free', 'size', BtoMb(nodeStatus.memory.free)); - } + if (nodeStatus.memory.used !== undefined) { + await this.createCustomState(sid, 'memory.used', 'size', BtoMb(nodeStatus.memory.used)); + } + if (nodeStatus.memory.used !== undefined) { + await this.createCustomState(sid, 'memory.used_lev', 'level', p(nodeStatus.memory.used, nodeStatus.memory.total)); + } + if (nodeStatus.memory.total !== undefined) { + await this.createCustomState(sid, 'memory.total', 'size', BtoMb(nodeStatus.memory.total)); + } + if (nodeStatus.memory.free !== undefined) { + await this.createCustomState(sid, 'memory.free', 'size', BtoMb(nodeStatus.memory.free)); + } - if (nodeStatus.loadavg[0] !== undefined) { - await this.createCustomState(sid, 'loadavg.0', 'default_num', parseFloat(nodeStatus.loadavg[0])); - } - if (nodeStatus.loadavg[1] !== undefined) { - await this.createCustomState(sid, 'loadavg.1', 'default_num', parseFloat(nodeStatus.loadavg[1])); - } - if (nodeStatus.loadavg[2] !== undefined) { - await this.createCustomState(sid, 'loadavg.2', 'default_num', parseFloat(nodeStatus.loadavg[2])); - } + if (nodeStatus.loadavg[0] !== undefined) { + await this.createCustomState(sid, 'loadavg.0', 'default_num', parseFloat(nodeStatus.loadavg[0])); + } + if (nodeStatus.loadavg[1] !== undefined) { + await this.createCustomState(sid, 'loadavg.1', 'default_num', parseFloat(nodeStatus.loadavg[1])); + } + if (nodeStatus.loadavg[2] !== undefined) { + await this.createCustomState(sid, 'loadavg.2', 'default_num', parseFloat(nodeStatus.loadavg[2])); + } - if (nodeStatus.swap.used !== undefined) { - await this.createCustomState(sid, 'swap.used', 'size', BtoMb(nodeStatus.swap.used)); - } - if (nodeStatus.swap.free !== undefined) { - await this.createCustomState(sid, 'swap.free', 'size', BtoMb(nodeStatus.swap.free)); - } - if (nodeStatus.swap.total !== undefined) { - await this.createCustomState(sid, 'swap.total', 'size', BtoMb(nodeStatus.swap.total)); - } - if (nodeStatus.swap.free !== undefined && nodeStatus.swap.total !== undefined) { - await this.createCustomState(sid, 'swap.used_lev', 'level', p(nodeStatus.swap.used, nodeStatus.swap.total)); + if (nodeStatus.swap.used !== undefined) { + await this.createCustomState(sid, 'swap.used', 'size', BtoMb(nodeStatus.swap.used)); + } + if (nodeStatus.swap.free !== undefined) { + await this.createCustomState(sid, 'swap.free', 'size', BtoMb(nodeStatus.swap.free)); + } + if (nodeStatus.swap.total !== undefined) { + await this.createCustomState(sid, 'swap.total', 'size', BtoMb(nodeStatus.swap.total)); + } + if (nodeStatus.swap.free !== undefined && nodeStatus.swap.total !== undefined) { + await this.createCustomState(sid, 'swap.used_lev', 'level', p(nodeStatus.swap.used, nodeStatus.swap.total)); + } } + } catch (err) { + this.log.warn(`Unable to get status of node ${node.node}: ${err}`); } - } catch (err) { - this.log.warn(`Unable to get status of node ${node.node}: ${err}`); - } - - if (this.config.requestDiskInformation) { - try { - const nodeDisks = await this.proxmox?.getNodeDisks(node.node); - if (nodeDisks) { - for (const disk of nodeDisks) { - const diskPath = 'disk_' + String(disk.devpath).replace('/dev/', ''); - - await this.setObjectNotExistsAsync(`${sid}.${diskPath}`, { - type: 'folder', - common: { - name: disk.devpath, - }, - native: {}, - }); - if (disk.type !== undefined) { - await this.createCustomState(sid, `${diskPath}.type`, 'text', disk.type); - } - if (disk.size !== undefined) { - await this.createCustomState(sid, `${diskPath}.size`, 'size', disk.size); - } - if (disk.health !== undefined) { - await this.createCustomState(sid, `${diskPath}.health`, 'text', disk.health); - } - if (disk.wearout !== undefined && !isNaN(disk.wearout)) { - await this.createCustomState(sid, `${diskPath}.wearout`, 'level', disk.wearout); - } - if (disk.model !== undefined) { - await this.createCustomState(sid, `${diskPath}.model`, 'text', disk.model); - } + if (this.config.requestDiskInformation) { + try { + const nodeDisks = await this.proxmox?.getNodeDisks(node.node); + if (nodeDisks) { + for (const disk of nodeDisks) { + const diskPath = 'disk_' + String(disk.devpath).replace('/dev/', ''); + + await this.setObjectNotExistsAsync(`${sid}.${diskPath}`, { + type: 'folder', + common: { + name: disk.devpath, + }, + native: {}, + }); - const nodeDiskSmart = await this.proxmox?.getNodeDisksSmart(node.node, disk.devpath); - if (nodeDiskSmart?.data?.text) { - await this.createCustomState(sid, `${diskPath}.smart`, 'text', nodeDiskSmart.data.text); + if (disk.type !== undefined) { + await this.createCustomState(sid, `${diskPath}.type`, 'text', disk.type); + } + if (disk.size !== undefined) { + await this.createCustomState(sid, `${diskPath}.size`, 'size', disk.size); + } + if (disk.health !== undefined) { + await this.createCustomState(sid, `${diskPath}.health`, 'text', disk.health); + } + if (disk.wearout !== undefined && !isNaN(disk.wearout)) { + await this.createCustomState(sid, `${diskPath}.wearout`, 'level', disk.wearout); + } + if (disk.model !== undefined) { + await this.createCustomState(sid, `${diskPath}.model`, 'text', disk.model); + } + + const nodeDiskSmart = await this.proxmox?.getNodeDisksSmart(node.node, disk.devpath); + if (nodeDiskSmart?.data?.text) { + await this.createCustomState(sid, `${diskPath}.smart`, 'text', nodeDiskSmart.data.text); + } } } + } catch (err) { + this.log.warn(`Unable to get disk for node ${node.node}: ${err}`); } - } catch (err) { - this.log.warn(`Unable to get disk for node ${node.node}: ${err}`); } } + } - await this.createVM(); + if (this.config.requestCephInformation) { + await this.createCeph(); } + if (this.config.requestHAInformation) { + await this.createHA(); + } + + await this.createVM(); + + // Delete non existent nodes for (const node of nodesAll) { if (!nodesKeep.includes(node)) { @@ -476,6 +502,118 @@ class Proxmox extends utils.Adapter { } } + /** + * Create CEPH + * @private + */ + async createHA() { + const haid = `${this.namespace}.ha`; + + await this.setObjectNotExistsAsync(`${haid}`, { + type: 'chanel', + common: { + name: 'ha', + }, + native: {}, + }); + + const haInformation = await this.proxmox?.getHAStatusInformation(); + + for (let lpEntry in haInformation.data) { + let lpType = typeof haInformation.data[lpEntry]; // get Type of Variable as String, like string/number/boolean + const lpData = haInformation.data[lpEntry]; + if (lpType == 'object') { + for (let lpEntry2 in lpData) { + let lpType2 = typeof lpData[lpEntry2]; + const lpData2 = lpData[lpEntry2]; + let lpData2Id = lpData.id; + + if (lpEntry2 == 'id') { + continue; + } + + lpData2Id = lpData2Id.replace(/:/g, '_'); + + await this.extendObjectAsync(`${haid}.${lpData2Id}_${lpEntry2}`, { + type: 'state', + common: { + name: lpEntry2, + type: lpType2, + read: true, + write: false, + role: 'value', + }, + native: {}, + }); + await this.setStateChangedAsync(`${haid}.${lpData2Id}_${lpEntry2}`, lpData2, true); + } + } + } + } + + + async createCeph() { + const cephid = `${this.namespace}.ceph`; + + await this.setObjectNotExistsAsync(`${cephid}`, { + type: 'chanel', + common: { + name: 'ceph', + }, + native: {}, + }); + + const cephInformation = await this.proxmox?.getCephInformation(); + + for (let lpEntry in cephInformation.data) { + let lpType = typeof cephInformation.data[lpEntry]; // get Type of Variable as String, like string/number/boolean + const lpData = cephInformation.data[lpEntry]; + if (lpType == 'object') { + await this.setObjectNotExistsAsync(`${cephid}.${lpEntry}`, { + type: 'folder', + common: { + name: lpEntry, + }, + native: {}, + }); + + for (let lpEntry2 in cephInformation.data[lpEntry]) { + let lpType2 = typeof cephInformation.data[lpEntry][lpEntry2]; + const lpData2 = cephInformation.data[lpEntry][lpEntry2]; + if (lpType2 == 'object') { + continue; + } + + await this.extendObjectAsync(`${cephid}.${lpEntry}.${lpEntry2}`, { + type: 'state', + common: { + name: lpEntry2, + type: lpType2, + read: true, + write: false, + role: 'value', + }, + native: {}, + }); + await this.setStateChangedAsync(`${cephid}.${lpEntry}.${lpEntry2}`, lpData2, true); + } + } else { + await this.extendObjectAsync(`${cephid}.${lpEntry}`, { + type: 'state', + common: { + name: lpEntry, + type: lpType, + read: true, + write: false, + role: 'value', + }, + native: {}, + }); + await this.setStateChangedAsync(`${cephid}.${lpEntry}`, lpData, true); + } + } + } + async createVM() { const resourcesAll = Object.keys(this.objects) .map(this.removeNamespace.bind(this)) @@ -486,23 +624,26 @@ class Proxmox extends utils.Adapter { const resources = await this.proxmox?.getClusterResources(); for (const res of resources) { let sid = ''; - if (res.type === 'qemu' || res.type === 'lxc') { + + if (res.status !== 'unknown' && (res.type === 'qemu' || res.type === 'lxc')) { // if status offline or stopped no infos available const type = res.type; - const resourceStatus = await this.proxmox?.getResourceStatus(res.node, type, res.vmid); - const resName = this.prepareNameForId(resourceStatus.name); + const resName = this.prepareNameForId(res.name); - resourcesKeep.push(`${type}_${resName}`); - sid = `${this.namespace}.${type}_${resName}`; + if (this.config.newTreeStructure) { + sid = `${this.namespace}.${type}.${resName}`; + } else { + sid = `${this.namespace}.${type}_${resName}`; + } - this.log.debug(`new ${type}: ${resourceStatus.name} - ${JSON.stringify(resourceStatus)}`); + resourcesKeep.push(`${type}.${resName}`); if (!this.objects[sid]) { // add to objects in RAM this.objects[sid] = { type: 'channel', common: { - name: resourceStatus.name, + name: res.name, }, native: { type: type, @@ -540,6 +681,8 @@ class Proxmox extends utils.Adapter { }, }); + this.subscribeForeignStates(`${sid}.start`); + await this.extendObjectAsync(`${sid}.stop`, { type: 'state', common: { @@ -568,6 +711,8 @@ class Proxmox extends utils.Adapter { }, }); + this.subscribeForeignStates(`${sid}.stop`); + await this.extendObjectAsync(`${sid}.shutdown`, { type: 'state', common: { @@ -596,6 +741,8 @@ class Proxmox extends utils.Adapter { }, }); + this.subscribeForeignStates(`${sid}.shutdown`); + await this.extendObjectAsync(`${sid}.reboot`, { type: 'state', common: { @@ -624,6 +771,7 @@ class Proxmox extends utils.Adapter { }, }); + this.subscribeForeignStates(`.${sid}.reboot`); // type was boolean but has been corrected to string -> extend await this.extendObjectAsync(`${sid}.status`, { type: 'state', @@ -649,25 +797,34 @@ class Proxmox extends utils.Adapter { native: {}, }); - await this.findState(sid, resourceStatus, async (states) => { - for (const s of states) { - try { - await this.createCustomState(s[0], s[1], s[2], s[3]); - } catch (e) { - this.log.error(`Could not create state for ${JSON.stringify(s)}: ${e.message}`); + await this.setStateChangedAsync(`${sid}.status`, {val: res.status, ack: true}); + + if (res.status === 'running') { + const resourceStatus = await this.proxmox?.getResourceStatus(res.node, type, res.vmid); + + this.log.debug(`new ${type}: ${resourceStatus.name} - ${JSON.stringify(resourceStatus)}`); + + await this.findState(sid, resourceStatus, async (states) => { + for (const s of states) { + try { + await this.createCustomState(s[0], s[1], s[2], s[3]); + } catch (e) { + this.log.error(`Could not create state for ${JSON.stringify(s)}: ${e.message}`); + } } - } - }); + }); + } } else if (res.type === 'storage') { const type = res.type; - - const storageStatus = await this.proxmox?.getStorageStatus(res.node, res.storage, !!res.shared); const storageName = this.prepareNameForId(res.storage); - resourcesKeep.push(`${type}_${storageName}`); - sid = `${this.namespace}.${type}_${storageName}`; + resourcesKeep.push(`${type}.${storageName}`); - this.log.debug(`new storage: ${res.storage} - ${JSON.stringify(storageStatus)}`); + if (this.config.newTreeStructure) { + sid = `${this.namespace}.${type}.${storageName}`; + } else { + sid = `${this.namespace}.${type}_${storageName}`; + } if (!this.objects[sid]) { // add to objects in RAM @@ -683,15 +840,25 @@ class Proxmox extends utils.Adapter { this.setObjectNotExists(sid, this.objects[sid]); } - await this.findState(sid, storageStatus, async (states) => { - for (const s of states) { - try { - await this.createCustomState(s[0], s[1], s[2], s[3]); - } catch (e) { - this.log.error(`Could not create state for ${JSON.stringify(s)}: ${e.message}`); - } + try { + if (res.status !== 'unknown') { + const storageStatus = await this.proxmox?.getStorageStatus(res.node, res.storage, !!res.shared); + + this.log.debug(`new storage: ${res.storage} - ${JSON.stringify(storageStatus)}`); + + await this.findState(sid, storageStatus, async (states) => { + for (const s of states) { + try { + await this.createCustomState(s[0], s[1], s[2], s[3]); + } catch (e) { + this.log.error(`Could not create state for ${JSON.stringify(s)}: ${e.message}`); + } + } + }); } - }); + } catch (err) { + this.log.error(`Storage: ${res.storage} on ${res.storage} not available`); + } } } @@ -704,7 +871,7 @@ class Proxmox extends utils.Adapter { } } } catch (err) { - this.log.warn(`Unable to get cluster resources: ${err}`); + this.log.debug(`Unable to get cluster resources: ${err}`); } } @@ -717,94 +884,159 @@ class Proxmox extends utils.Adapter { const sid = `${this.namespace}.${node.type}_${node.node}`; // check if the item is already in RAM - if not it's newly created - if (!knownObjIds.includes(sid)) { + + if (!knownObjIds.includes(sid) && node.status == 'online') { // new node restart adapter to create objects this.log.info(`Detected new node "${node.node}" - restarting instance`); return void this.restart(); } - await this.setStateChangedAsync(`${sid}.cpu`, { val: parseInt(node.cpu * 10000) / 100, ack: true }); - if (node.maxcpu) { - await this.setStateChangedAsync(`${sid}.cpu_max`, { val: node.maxcpu, ack: true }); - } - await this.setStateChangedAsync(`${sid}.status`, { val: node.status, ack: true }); - - this.log.debug(`Requesting states for node ${node.node}`); - try { - const nodeStatus = await this.proxmox?.getNodeStatus(node.node, true); - if (nodeStatus) { - if (nodeStatus.uptime !== undefined) { - await this.setStateChangedAsync(sid + '.uptime', { val: nodeStatus.uptime, ack: true }); - } - if (nodeStatus.wait !== undefined) { - await this.setStateChangedAsync(sid + '.iowait', { val: parseInt(nodeStatus.wait * 10000) / 100, ack: true }); - } + await this.setStateChangedAsync(`${sid}.status`, {val: node.status, ack: true}); - if (nodeStatus.memory.used !== undefined) { - await this.setStateChangedAsync(sid + '.memory.used', { val: BtoMb(nodeStatus.memory.used), ack: true }); - } - if (nodeStatus.memory.used !== undefined) { - await this.setStateChangedAsync(sid + '.memory.used_lev', { val: p(nodeStatus.memory.used, nodeStatus.memory.total), ack: true }); - } - if (nodeStatus.memory.total !== undefined) { - await this.setStateChangedAsync(sid + '.memory.total', { val: BtoMb(nodeStatus.memory.total), ack: true }); - } - if (nodeStatus.memory.free !== undefined) { - await this.setStateChangedAsync(sid + '.memory.free', { val: BtoMb(nodeStatus.memory.free), ack: true }); - } + if (node.status !== 'offline') { + await this.setStateChangedAsync(`${sid}.cpu`, {val: parseInt(node.cpu * 10000) / 100, ack: true}); + if (node.maxcpu) { + await this.setStateChangedAsync(`${sid}.cpu_max`, {val: node.maxcpu, ack: true}); + } + + this.log.debug(`Requesting states for node ${node.node}`); + try { + const nodeStatus = await this.proxmox?.getNodeStatus(node.node, true); + if (nodeStatus) { + if (nodeStatus.uptime !== undefined) { + await this.setStateChangedAsync(sid + '.uptime', {val: nodeStatus.uptime, ack: true}); + } + if (nodeStatus.wait !== undefined) { + await this.setStateChangedAsync(sid + '.iowait', { + val: parseInt(nodeStatus.wait * 10000) / 100, + ack: true + }); + } + + if (nodeStatus.memory.used !== undefined) { + await this.setStateChangedAsync(sid + '.memory.used', { + val: BtoMb(nodeStatus.memory.used), + ack: true + }); + } + if (nodeStatus.memory.used !== undefined) { + await this.setStateChangedAsync(sid + '.memory.used_lev', { + val: p(nodeStatus.memory.used, nodeStatus.memory.total), + ack: true + }); + } + if (nodeStatus.memory.total !== undefined) { + await this.setStateChangedAsync(sid + '.memory.total', { + val: BtoMb(nodeStatus.memory.total), + ack: true + }); + } + if (nodeStatus.memory.free !== undefined) { + await this.setStateChangedAsync(sid + '.memory.free', { + val: BtoMb(nodeStatus.memory.free), + ack: true + }); + } + + if (nodeStatus.loadavg[0] !== undefined) { + await this.setStateChangedAsync(sid + '.loadavg.0', { + val: parseFloat(nodeStatus.loadavg[0]), + ack: true + }); + } + if (nodeStatus.loadavg[1] !== undefined) { + await this.setStateChangedAsync(sid + '.loadavg.1', { + val: parseFloat(nodeStatus.loadavg[1]), + ack: true + }); + } + if (nodeStatus.loadavg[2] !== undefined) { + await this.setStateChangedAsync(sid + '.loadavg.2', { + val: parseFloat(nodeStatus.loadavg[2]), + ack: true + }); + } + + if (nodeStatus.swap.used !== undefined) { + await this.setStateChangedAsync(sid + '.swap.used', { + val: BtoMb(nodeStatus.swap.used), + ack: true + }); + } + if (nodeStatus.swap.free !== undefined) { + await this.setStateChangedAsync(sid + '.swap.free', { + val: BtoMb(nodeStatus.swap.free), + ack: true + }); + } + if (nodeStatus.swap.total !== undefined) { + await this.setStateChangedAsync(sid + '.swap.total', { + val: BtoMb(nodeStatus.swap.total), + ack: true + }); + } + if (nodeStatus.swap.used !== undefined && nodeStatus.swap.total !== undefined) { + await this.setStateChangedAsync(sid + '.swap.used_lev', { + val: p(nodeStatus.swap.used, nodeStatus.swap.total), + ack: true + }); + } - if (nodeStatus.loadavg[0] !== undefined) { - await this.setStateChangedAsync(sid + '.loadavg.0', { val: parseFloat(nodeStatus.loadavg[0]), ack: true }); - } - if (nodeStatus.loadavg[1] !== undefined) { - await this.setStateChangedAsync(sid + '.loadavg.1', { val: parseFloat(nodeStatus.loadavg[1]), ack: true }); - } - if (nodeStatus.loadavg[2] !== undefined) { - await this.setStateChangedAsync(sid + '.loadavg.2', { val: parseFloat(nodeStatus.loadavg[2]), ack: true }); - } - if (nodeStatus.swap.used !== undefined) { - await this.setStateChangedAsync(sid + '.swap.used', { val: BtoMb(nodeStatus.swap.used), ack: true }); - } - if (nodeStatus.swap.free !== undefined) { - await this.setStateChangedAsync(sid + '.swap.free', { val: BtoMb(nodeStatus.swap.free), ack: true }); - } - if (nodeStatus.swap.total !== undefined) { - await this.setStateChangedAsync(sid + '.swap.total', { val: BtoMb(nodeStatus.swap.total), ack: true }); - } - if (nodeStatus.swap.used !== undefined && nodeStatus.swap.total !== undefined) { - await this.setStateChangedAsync(sid + '.swap.used_lev', { val: p(nodeStatus.swap.used, nodeStatus.swap.total), ack: true }); } + + } catch (err) { + this.log.warn(`Unable to get status of node ${node.node}: ${err}`); } - } catch (err) { - this.log.warn(`Unable to get status of node ${node.node}: ${err}`); + } else { + await this.setStateChangedAsync(`${sid}.status`, {val: 'offline', ack: true}); } if (this.config.requestDiskInformation) { try { - const nodeDisks = await this.proxmox?.getNodeDisks(node.node); - if (nodeDisks) { - for (const disk of nodeDisks) { - const diskPath = 'disk_' + String(disk.devpath).replace('/dev/', ''); - if (disk.type !== undefined) { - await this.setStateChangedAsync(`${sid}.${diskPath}.type`, { val: disk.type, ack: true }); - } - if (disk.size !== undefined) { - await this.setStateChangedAsync(`${sid}.${diskPath}.size`, { val: disk.size, ack: true }); - } - if (disk.health !== undefined) { - await this.setStateChangedAsync(`${sid}.${diskPath}.health`, { val: disk.health, ack: true }); - } - if (disk.wearout !== undefined && !isNaN(disk.wearout)) { - await this.setStateChangedAsync(`${sid}.${diskPath}.wearout`, { val: disk.wearout, ack: true }); - } - if (disk.model !== undefined) { - await this.setStateChangedAsync(`${sid}.${diskPath}.model`, { val: disk.model, ack: true }); - } - - const nodeDiskSmart = await this.proxmox?.getNodeDisksSmart(node.node, disk.devpath); - if (nodeDiskSmart?.data?.text) { - await this.setStateChangedAsync(`${sid}.${diskPath}.smart`, { val: nodeDiskSmart.data.text, ack: true }); + if (node.status !== 'offline') { + const nodeDisks = await this.proxmox?.getNodeDisks(node.node); + if (nodeDisks) { + for (const disk of nodeDisks) { + const diskPath = 'disk_' + String(disk.devpath).replace('/dev/', ''); + if (disk.type !== undefined) { + await this.setStateChangedAsync(`${sid}.${diskPath}.type`, { + val: disk.type, + ack: true + }); + } + if (disk.size !== undefined) { + await this.setStateChangedAsync(`${sid}.${diskPath}.size`, { + val: disk.size, + ack: true + }); + } + if (disk.health !== undefined) { + await this.setStateChangedAsync(`${sid}.${diskPath}.health`, { + val: disk.health, + ack: true + }); + } + if (disk.wearout !== undefined && !isNaN(disk.wearout)) { + await this.setStateChangedAsync(`${sid}.${diskPath}.wearout`, { + val: disk.wearout, + ack: true + }); + } + if (disk.model !== undefined) { + await this.setStateChangedAsync(`${sid}.${diskPath}.model`, { + val: disk.model, + ack: true + }); + } + + const nodeDiskSmart = await this.proxmox?.getNodeDisksSmart(node.node, disk.devpath); + if (nodeDiskSmart?.data?.text) { + await this.setStateChangedAsync(`${sid}.${diskPath}.smart`, { + val: nodeDiskSmart.data.text, + ack: true + }); + } } } } @@ -814,52 +1046,145 @@ class Proxmox extends utils.Adapter { } } + if (this.config.requestCephInformation) { + await this.setCeph(); + } + + if (this.config.requestHAInformation) { + await this.setHA(); + } + await this.setVM(); } + async setCeph() { + try { + const cephid = `${this.namespace}.ceph`; + + + const cephInformation = await this.proxmox?.getCephInformation(); + + if (cephInformation !== null) { + for (let lpEntry in cephInformation.data) { + const lpType = typeof cephInformation.data[lpEntry]; // get Type of Variable as String, like string/number/boolean + const lpData = cephInformation.data[lpEntry]; + if (lpType == 'object') { + for (let lpEntry2 in cephInformation.data[lpEntry]) { + const lpType2 = typeof cephInformation.data[lpEntry][lpEntry2]; + const lpData2 = cephInformation.data[lpEntry][lpEntry2]; + if (lpType2 == 'object') { + continue; + } + await this.setStateChangedAsync(`${cephid}.${lpEntry}.${lpEntry2}`, lpData2, true); + } + } else { + await this.setStateChangedAsync(`${cephid}.${lpEntry}`, lpData, true); + } + } + } + } catch (err) { + this.log.debug(`Unable to get Ceph resources: ${err.message} `); + } + } + + async setHA() { + try { + const haid = `${this.namespace}.ha`; + const haInformation = await this.proxmox?.getHAStatusInformation(); + + for (let lpEntry in haInformation.data) { + let lpType = typeof haInformation.data[lpEntry]; // get Type of Variable as String, like string/number/boolean + const lpData = haInformation.data[lpEntry]; + if (lpType == 'object') { + for (let lpEntry2 in lpData) { + let lpType2 = typeof lpData[lpEntry2]; + const lpData2 = lpData[lpEntry2]; + let lpData2Id = lpData.id; + + if (lpEntry2 == 'id') { + continue; + } + + lpData2Id = lpData2Id.replace(/:/g, '_'); + + await this.setStateChangedAsync(`${haid}.${lpData2Id}_${lpEntry2}`, lpData2, true); + } + } + } + } catch (err) { + this.log.debug(`Unable to get HA resources: ${err.message} `); + } + } async setVM() { try { const resources = await this.proxmox?.getClusterResources(); const knownObjIds = Object.keys(this.objects); + let offlineMachines = {}; + + this.setStateAsync(`info.offlineMachines`, JSON.stringify(offlineMachines), true); for (const res of resources) { let sid = ''; - if (res.type === 'qemu' || res.type === 'lxc') { - const type = res.type; + if (res.type === 'qemu' || res.type === 'lxc') { // if status unknown then no infos available + const resName = this.prepareNameForId(res.name); - const resourceStatus = await this.proxmox?.getResourceStatus(res.node, type, res.vmid, true); - const resName = this.prepareNameForId(resourceStatus.name); + if (this.config.newTreeStructure) { + sid = `${this.namespace}.${res.type}.${resName}`; + } else { + sid = `${this.namespace}.${res.type}_${resName}`; + } - sid = `${this.namespace}.${type}_${resName}`; + if (res.status == 'unknown') { + res.status = 'offline'; + } - if (!knownObjIds.includes(sid)) { - // new node restart adapter to create objects - this.log.info(`Detected new VM/storage "${resourceStatus.name}" (${resName}) - restarting instance`); - return void this.restart(); + if (resName == 'undefined') { // überspringe maschiene falls knoten offline und diese auf dem knoten liegt + offlineMachines[res.id]++; + offlineMachines[res.id] = 'offline'; + this.setStateAsync(`info.offlineMachines`, JSON.stringify(offlineMachines), true); + continue; } - await this.findState(sid, resourceStatus, async (states) => { - for (const element of states) { - await this.setStateChangedAsync(element[0] + '.' + element[1], element[3], true); - } - }); - } else if (res.type === 'storage') { - const type = res.type; + await this.setStateChangedAsync(`${sid}.status`, {val: res.status, ack: true}); - const storageStatus = await this.proxmox?.getStorageStatus(res.node, res.storage, !!res.shared); + if (res.status === 'running') { + const type = res.type; + const resourceStatus = await this.proxmox?.getResourceStatus(res.node, type, res.vmid, true); - sid = this.namespace + '.' + type + '_' + res.storage; + if (!knownObjIds.includes(sid)) { + // new node restart adapter to create objects + this.log.info(`Detected new VM/storage "${resourceStatus.name}" (${resName}) - restarting instance`); + return void this.restart(); + } - await this.findState(sid, storageStatus, async (states) => { - for (const element of states) { - await this.setStateChangedAsync(element[0] + '.' + element[1], element[3], true); + await this.findState(sid, resourceStatus, async (states) => { + for (const element of states) { + await this.setStateChangedAsync(element[0] + '.' + element[1], element[3], true); + } + }); } - }); + } else if (res.type === 'storage') { + if (res.status !== 'unknown') { + try { + const storageStatus = await this.proxmox?.getStorageStatus(res.node, res.storage, !!res.shared); + await this.findState(sid, storageStatus, async (states) => { + for (const element of states) { + if (element[0] == '') { + continue; + } else { + await this.setStateChangedAsync(element[0] + '.' + element[1], element[3], true); + } + } + }); + } catch (err) { + this.log.error(`Storage: ${res.storage} on ${res.storage} not available`); + } + } } } } catch (err) { - this.log.warn(`Unable to get cluster resources: ${err}`); + this.log.debug(`Unable to get cluster resources: ${err.message} `); } } @@ -889,7 +1214,7 @@ class Proxmox extends utils.Adapter { result.push([sid, key, 'level', parseInt(value * 10000) / 100]); } else if (key === 'pid' || key === 'cpus' || key === 'shared' || key === 'enabled' || key === 'active' || key === 'shared') { result.push([sid, key, 'default_num', parseInt(value)]); // parseInt, because pid would be string - } else if (key === 'content' || key === 'type' || key === 'status') { + } else if (key === 'content' || key === 'type' || key === 'status' || key === 'vmid') { result.push([sid, key, 'text', value]); } }