iobroker.net-tools
Version:
This adapter cyclic polls configured IPs, can send wake-on-lan packages and scan for open ports.
699 lines (666 loc) • 20.8 kB
JavaScript
const dmUtils = require('@iobroker/dm-utils');
class dmNetTools extends dmUtils.DeviceManagement {
async getInstanceInfo() {
const data = {
...super.getInstanceInfo(),
apiVersion: 'v1',
actions: [
{
id: 'newDevice',
icon: 'fas fa-plus',
title: '',
description: {
en: 'Add new device to Net Tools',
de: 'Neues Gerät zu Net Tools hinzufügen',
ru: 'Добавить новое устройство в Net Tools',
pt: 'Adicionar novo dispositivo ao Net Tools',
nl: 'Voeg nieuw apparaat toe aan Net Tools',
fr: 'Ajouter un nouvel appareil à Net Tools',
it: 'Aggiungi nuovo dispositivo a Net Tools',
es: 'Agregar nuevo dispositivo a Net Tools',
pl: 'Dodaj nowe urządzenie do Net Tools',
'zh-cn': '将新设备添加到Net Tools',
uk: 'Додати новий пристрій до Net Tools'
},
handler: this.handleNewDevice.bind(this)
},
{
id: 'discover',
icon: 'fas fa-search',
title: '',
description: {
en: 'Discover new devices',
de: 'Neue Geräte suchen',
ru: 'Обнаружить новые устройства',
pt: 'Descubra novos dispositivos',
nl: 'Ontdek nieuwe apparaten',
fr: 'Découvrir de nouveaux appareils',
it: 'Scopri nuovi dispositivi',
es: 'Descubrir nuevos dispositivos',
pl: 'Odkryj nowe urządzenia',
'zh-cn': '发现新设备',
uk: 'Виявити нові пристрої'
},
handler: this.handleDiscover.bind(this)
}
],
};
return data;
}
async handleNewDevice(context) {
this.adapter.log.info('handleNewDevice');
const result = await context.showForm({
type : 'panel',
items: {
name: {
type: 'text',
trim: false,
label: {
en: 'Name',
de: 'Name',
ru: 'Имя',
pt: 'Nome',
nl: 'Naam',
fr: 'Nom',
it: 'Nome',
es: 'Nombre',
pl: 'Nazwa',
'zh-cn': '名称',
uk: 'Ім\'я'
}
},
ip: {
type: 'text',
trim: true,
placeholder: '192.168.0.1',
label: {
en: 'IP address',
de: 'IP-Adresse',
ru: 'IP адрес',
pt: 'Endereço de IP',
nl: 'IP adres',
fr: 'Adresse IP',
it: 'Indirizzo IP',
es: 'Dirección IP',
pl: 'Adres IP',
'zh-cn': 'IP地址',
uk: 'IP адреса'
}
},
mac: {
type: 'text',
trim: true,
placeholder: '00:00:00:00:00:00',
label: {
en: 'MAC address',
de: 'MAC-Adresse',
ru: 'MAC адрес',
pt: 'Endereço MAC',
nl: 'MAC adres',
fr: 'Adresse MAC',
it: 'Indirizzo MAC',
es: 'Dirección MAC',
pl: 'Adres MAC',
'zh-cn': 'MAC地址',
uk: 'MAC адреса'
},
},
pingInterval: {
type: 'number',
min: 5,
unit: 's',
label: {
en: 'Ping interval',
de: 'Ping-Intervall',
ru: 'Интервал пинга',
pt: 'Intervalo de ping',
nl: 'Ping-interval',
fr: 'Intervalle de ping',
it: 'Intervallo di ping',
es: 'Intervalo de ping',
pl: 'Interwał ping',
'zh-cn': 'Ping间隔',
uk: 'Інтервал пінгу'
}
},
retries: {
type: 'number',
label: {
en: 'Retries',
de: 'Wiederholungen',
ru: 'Повторы',
pt: 'Tentativas',
nl: 'Pogingen',
fr: 'Essais',
it: 'Tentativi',
es: 'Intentos',
pl: 'Próby',
'zh-cn': '重试',
uk: 'Повтори'
},
},
enabled: {
type: 'checkbox',
label: {
en: 'Ping Enabled',
de: 'Ping aktiviert',
ru: 'Ping включен',
pt: 'Ping ativado',
nl: 'Ping ingeschakeld',
fr: 'Ping activé',
it: 'Ping abilitato',
es: 'Ping habilitado',
pl: 'Ping włączony',
'zh-cn': 'Ping已启用',
uk: 'Ping увімкнено'
}
}
}
},
{
data: {
name: '',
ip: '',
mac: '',
pingInterval: this.adapter.config.pingInterval,
retries: 0,
enabled: true
},
title: {
en: 'Add new device',
de: 'Neues Gerät hinzufügen',
ru: 'Добавить новое устройство',
pt: 'Adicionar novo dispositivo',
nl: 'Voeg nieuw apparaat toe',
fr: 'Ajouter un nouvel appareil',
it: 'Aggiungi nuovo dispositivo',
es: 'Agregar nuevo dispositivo',
pl: 'Dodaj nowe urządzenie',
'zh-cn': '添加新设备',
uk: 'Додати новий пристрій'
}
}
);
if(result === null || result === undefined) {
return { refresh: false };
}
// Check if mac is valid
if(result.mac !== '') {
// Check mac has the right format
if(!result.mac.match(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/)) {
await context.showMessage({
en: `MAC address ${result.mac} is not valid`,
de: `MAC-Adresse ${result.mac} ist ungültig`,
ru: `MAC адрес ${result.mac} недействителен`,
pt: `Endereço MAC ${result.mac} não é válido`,
nl: `MAC-adres ${result.mac} is ongeldig`,
fr: `L'adresse MAC ${result.mac} n'est pas valide`,
it: `L'indirizzo MAC ${result.mac} non è valido`,
es: `La dirección MAC ${result.mac} no es válida`,
pl: `Adres MAC ${result.mac} jest nieprawidłowy`,
'zh-cn': `MAC地址 ${result.mac} 无效`,
uk: `MAC адреса ${result.mac} недійсна`
});
return { refresh: false };
}
}
// Check if ip was entered
if(result.ip === '') {
await context.showMessage({
en: `Please enter an IP address`,
de: `Bitte geben Sie eine IP-Adresse ein`,
ru: `Пожалуйста, введите IP адрес`,
pt: `Por favor, digite um endereço de IP`,
nl: `Voer een IP-adres in`,
fr: `Veuillez saisir une adresse IP`,
it: `Inserisci un indirizzo IP`,
es: `Por favor ingrese una dirección IP`,
pl: `Proszę wprowadzić adres IP`,
'zh-cn': `请输入IP地址`,
uk: `Будь ласка, введіть IP адресу`
});
return { refresh: false };
}
// Check if ip is valid
if(result.ip !== '') {
// Check ip has the right format
if(!result.ip.match(/^(\d{1,3}\.){3}\d{1,3}$/)) {
await context.showMessage({
en: `IP address ${result.ip} is not valid`,
de: `IP-Adresse ${result.ip} ist ungültig`,
ru: `IP адрес ${result.ip} недействителен`,
pt: `Endereço de IP ${result.ip} não é válido`,
nl: `IP-adres ${result.ip} is ongeldig`,
fr: `L'adresse IP ${result.ip} n'est pas valide`,
it: `L'indirizzo IP ${result.ip} non è valido`,
es: `La dirección IP ${result.ip} no es válida`,
pl: `Adres IP ${result.ip} jest nieprawidłowy`,
'zh-cn': `IP地址 ${result.ip} 无效`,
uk: `IP адреса ${result.ip} недійсна`
});
return { refresh: false };
}
}
this.adapter.addDevice(result.ip, result.name, result.enabled, result.mac, result.pingInterval, result.retries);
return { refresh: true };
}
async handleDiscover(context) {
context.showMessage(
'Dicovery started. This process will take a few minutes until all devices are discovered. You can close this dialog and continue working in the meantime.');
this.adapter.discover();
return { refresh: false };
}
async listDevices() {
const devices = await this.adapter.getDevicesAsync();
const arrDevices = [];
for (const i in devices) {
const status = {};
let hasDetails = false;
if(devices[i].native.ip || devices[i].native.mac || devices[i].native.vendor || devices[i]._id.includes('localhost')) {
hasDetails = true;
}
const alive = await this.adapter.getStateAsync(`${devices[i]._id}.alive`);
if(alive !== null && alive !== undefined) {
status.connection = alive.val ? 'connected' : 'disconnected';
}
const res = {
id: devices[i]._id,
name: devices[i].common.name,
icon: devices[i].common.icon ? devices[i].common.icon : null,
manufacturer: devices[i].native.manufacturer ? devices[i].native.manufacturer : '',
model: devices[i].native.name ? devices[i].native.name : '',
status: status,
hasDetails: hasDetails,
actions: [
{
id: 'delete',
icon: 'fa-solid fa-trash-can',
description: {
en: 'Delete this device',
de: 'Gerät löschen',
ru: 'Удалить это устройство',
pt: 'Excluir este dispositivo',
nl: 'Verwijder dit apparaat',
fr: 'Supprimer cet appareil',
it: 'Elimina questo dispositivo',
es: 'Eliminar este dispositivo',
pl: 'Usuń to urządzenie',
'zh-cn': '删除此设备',
uk: 'Видалити цей пристрій'
},
handler: this.handleDeleteDevice.bind(this)
},
{
id: 'rename',
icon: 'fa-solid fa-pen',
description: {
en: 'Rename this device',
de: 'Gerät umbenennen',
ru: 'Переименовать это устройство',
pt: 'Renomear este dispositivo',
nl: 'Hernoem dit apparaat',
fr: 'Renommer cet appareil',
it: 'Rinomina questo dispositivo',
es: 'Renombrar este dispositivo',
pl: 'Zmień nazwę tego urządzenia',
'zh-cn': '重命名此设备',
uk: 'Перейменуйте цей пристрій'
},
handler: this.handleRenameDevice.bind(this)
},
{
id: 'settings',
icon: 'settings',
description: {
en: 'Settings',
de: 'Einstellungen',
ru: 'Настройки',
pt: 'Configurações',
nl: 'Instellingen',
fr: 'Paramètres',
it: 'Impostazioni',
es: 'Configuraciones',
pl: 'Ustawienia',
'zh-cn': '设定值',
uk: 'Налаштування'
},
handler: this.handleSettingsDevice.bind(this)
}
]
};
if(devices[i]._id.includes('localhost')) {
res.actions = [];
}
arrDevices.push(res);
}
return arrDevices;
}
/**
* Handle delete device
* @param {string} id - id of device
* @param {object} context - context object
* @returns {Promise<{refresh: boolean}>}
*/
async handleDeleteDevice(id, context) {
// Remove namespace from context
const name = id.replace(/net-tools\.\d\./, '');
const response = await context.showConfirmation({
en: `Do you really want to delete the device ${name}?`,
de: `Möchten Sie das Gerät ${name} wirklich löschen?`,
ru: `Вы действительно хотите удалить устройство ${name}?`,
pt: `Você realmente deseja excluir o dispositivo ${name}?`,
nl: `Weet u zeker dat u het apparaat ${name} wilt verwijderen?`,
fr: `Voulez-vous vraiment supprimer l'appareil ${name} ?`,
it: `Vuoi davvero eliminare il dispositivo ${name}?`,
es: `¿Realmente desea eliminar el dispositivo ${name}?`,
pl: `Czy na pewno chcesz usunąć urządzenie ${name}?`,
'zh-cn': `您真的要删除设备 ${name} 吗?`,
uk: `Ви дійсно бажаєте видалити пристрій ${name}?`
});
// delete device
if(response === false) {
return {refresh: false};
}
const result = this.adapter.delDevice(name);
if(result === false) {
await context.showMessage({
en: `Can not delete device ${name}`,
de: `Gerät ${name} kann nicht gelöscht werden`,
ru: `Невозможно удалить устройство ${name}`,
pt: `Não é possível excluir o dispositivo ${name}`,
nl: `Kan apparaat ${name} niet verwijderen`,
fr: `Impossible de supprimer l'appareil ${name}`,
it: `Impossibile eliminare il dispositivo ${name}`,
es: `No se puede eliminar el dispositivo ${name}`,
pl: `Nie można usunąć urządzenia ${name}`,
'zh-cn': `无法删除设备 ${name}`,
uk: `Не вдалося видалити пристрій ${name}`
});
return {refresh: false};
} else {
return {refresh: true};
}
}
async handleRenameDevice(id, context) {
const result = await context.showForm({
type : 'panel',
items: {
newName: {
type: 'text',
trim: false,
placeholder: '',
}
}}, {
data: {
newName: ''
},
title: {
en: 'Enter new name',
de: 'Neuen Namen eingeben',
ru: 'Введите новое имя',
pt: 'Digite um novo nome',
nl: 'Voer een nieuwe naam in',
fr: 'Entrez un nouveau nom',
it: 'Inserisci un nuovo nome',
es: 'Ingrese un nuevo nombre',
pl: 'Wpisz nowe imię',
'zh-cn': '输入新名称',
uk: 'Введіть нове ім\'я'
}
});
if(result === null || result === undefined) {
return {refresh: false};
}
if(result.newName === undefined || result.newName === '') {
return {refresh: false};
}
const obj = {
common: {
name: result.newName
}
};
const res = await this.adapter.extendObjectAsync(id, obj);
this.adapter.log.info(JSON.stringify(res));
if(res === null) {
this.adapter.log.warn(`Can not rename device ${context.id}: ${JSON.stringify(res)}`);
return {refresh: false};
}
return {refresh: true};
}
async handleSettingsDevice(id, context) {
// Get settings from device native object
const device = await this.adapter.getObjectAsync(id);
if(device === null) {
this.adapter.log.warn(`Can not find device ${id}`);
return {refresh: false};
}
this.log.info(JSON.stringify(device));
const result = await context.showForm({
type : 'panel',
style: {width: '200px'},
items: {
ip: {
type: 'text',
newLine: true,
label: {
en: 'IP address',
de: 'IP-Adresse',
ru: 'IP адрес',
pt: 'Endereço de IP',
nl: 'IP adres',
fr: 'Adresse IP',
it: 'Indirizzo IP',
es: 'Dirección IP',
pl: 'Adres IP',
'zh-cn': 'IP地址',
uk: 'IP адреса'
}
},
pingInterval: {
type: 'number',
min: 5,
unit: 's',
newLine: true,
label: {
en: 'Ping interval',
de: 'Ping-Intervall',
ru: 'Интервал пинга',
pt: 'Intervalo de ping',
nl: 'Ping-interval',
fr: 'Intervalle de ping',
it: 'Intervallo di ping',
es: 'Intervalo de ping',
pl: 'Interwał ping',
'zh-cn': 'Ping间隔',
uk: 'Інтервал пінгу'
}
},
retries: {
type: 'number',
newLine: true,
label: {
en: 'Retries',
de: 'Wiederholungen',
ru: 'Повторы',
pt: 'Tentativas',
nl: 'Pogingen',
fr: 'Essais',
it: 'Tentativi',
es: 'Intentos',
pl: 'Próby',
'zh-cn': '重试',
uk: 'Повтори'
},
tooltip: {
en: 'Retries before set alive to false',
de: 'Wiederholungen, bevor alive auf false gesetzt wird',
ru: 'Повторы перед установкой alive в false',
pt: 'Tentativas antes de definir vivo como falso',
nl: 'Pogingen voordat alive op false wordt ingesteld',
fr: 'Essais avant de définir en vie sur faux',
it: 'Tentativi prima di impostare vivo su falso',
es: 'Intentos antes de establecer vivo como falso',
pl: 'Próby przed ustawieniem żywego na fałszywe',
'zh-cn': '重试之前将活动设置为false',
uk: 'Повтори перед встановленням alive в false'
}
},
wakeWithIp: {
type: 'checkbox',
newLine: true,
label: {
en: 'Wake-on-LAN with IP',
de: 'Wake-on-LAN mit IP',
ru: 'Пробуждение по локальной сети с помощью IP',
pt: 'Wake-on-LAN com IP',
nl: 'Wake-on-LAN met IP',
fr: 'Wake-on-LAN avec IP',
it: 'Wake-on-LAN con IP',
es: 'Wake-on-LAN con IP',
pl: 'Wake-on-LAN z IP',
'zh-cn': 'IP 网络唤醒',
uk: 'Wake-on-LAN з IP'
},
tooltip: {
en: 'Use this for devices on other subnets then the host.',
de: 'Verwenden Sie dies für Geräte in anderen Subnetzen als dem des Hosts.',
ru: 'Используйте этот параметр для устройств, находящихся в других подсетях, чем хост.',
pt: 'Utilize esta opção para dispositivos noutras sub-redes que não a do anfitrião.',
nl: 'Gebruik dit voor apparaten op andere subnetten dan de host.',
fr: 'A utiliser pour les appareils situés sur d\'autres sous-réseaux que celui de l\'hôte.',
it: 'Utilizzare questa opzione per i dispositivi che si trovano su altre sottoreti rispetto all\'host.',
es: 'Utilícelo para dispositivos que se encuentren en otras subredes distintas a la del host.',
pl: 'Użyj tego dla urządzeń w innych podsieciach niż host.',
'zh-cn': '用于主机以外其他子网的设备。',
uk: 'Використовуйте цей параметр для пристроїв в інших підмережах, ніж хост.'
}
},
note: {
type: 'staticText',
text: {
en: 'The changed settings only have an effect after the next ping.',
de: 'Die geänderten Einstellungen haben erst einen Effekt nachdem nächsten Ping.'
}
}
}
}, {
data: {
ip: device.native.ip ? device.native.ip : '',
pingInterval: device.native.pingInterval ? device.native.pingInterval : this.adapter.config.pingInterval,
retries: device.native.retries ? device.native.retries : 0,
wakeWithIp: device.native.wakeWithIp ? device.native.wakeWithIp : false
},
title: {
en: 'Settings',
de: 'Einstellungen',
ru: 'Настройки',
pt: 'Configurações',
nl: 'Instellingen',
fr: 'Paramètres',
it: 'Impostazioni',
es: 'Configuraciones',
pl: 'Ustawienia',
'zh-cn': '设定值',
uk: 'Налаштування'
}
}
);
if(result === null || result === undefined) {
return {refresh: false};
}
// Set settings to device native object if changed
if(result.ip !== device.native.ip || result.pingInterval !== device.native.pingInterval || result.retries !== device.native.retries || result.wakeWithIp !== device.native.wakeWithIp) {
const obj = {
native: {
ip: result.ip,
pingInterval: result.pingInterval,
retries: result.retries,
wakeWithIp: result.wakeWithIp
}
};
const res = await this.adapter.extendObject(id, obj);
if(res === null) {
this.adapter.log.warn(`Can not set settings for device ${context.id}: ${JSON.stringify(res)}`);
return {refresh: false};
}
return {refresh: true};
} else {
return {refresh: false};
}
}
async getDeviceDetails(id, action, context) {
const devices = await this.adapter.getDevicesAsync();
const device = devices.find(d => d._id === id);
if(!device) {
return {error: 'Device not found'};
}
const data = {
id: device._id,
schema: {
type: 'panel',
items: {
mac: {
type: 'staticText',
text: device.native.mac ? `<b>MAC:</b> ${device.native.mac}` : ``,
newLine: true
},
vendor: {
type: 'staticText',
text: device.native.vendor ? `<b>Vendor:</b> ${device.native.vendor}` : ``,
newLine: true
}
}
}
};
if(device._id.includes('localhost')) {
const interfaces = await this.adapter.getLocalNetworkInterfaces();
const items = transformNetworkInterfaces(interfaces).items;
data.schema = {
type: 'tabs',
items: items,
};
}
return data;
}
}
function transformNetworkInterfaces(networkInterfaces) {
const items = {};
for (const interfaceName in networkInterfaces) {
const addresses = networkInterfaces[interfaceName];
// Erstellen eines neuen Panel-Tabs für jedes Interface
items[interfaceName] = {
type: 'panel',
label: interfaceName,
items: {}
};
for (const addressInfo of addresses) {
items[interfaceName].items[`family_${interfaceName}_${addressInfo.family}`] = {
type: 'header',
text: `${addressInfo.family}`,
size: 2
};
items[interfaceName].items[`mac_${interfaceName}_${addressInfo.family}`] = {
type: 'staticText',
text: `<b>MAC:</b> ${addressInfo.mac}`,
newLine: true
};
items[interfaceName].items[`ip_${interfaceName}_${addressInfo.family}`] = {
type: 'staticText',
text: `<b>IP-Adresse:</b> ${addressInfo.address}`,
newLine: true
};
items[interfaceName].items[`netmask_${interfaceName}_${addressInfo.family}`] = {
type: 'staticText',
text: `<b>Netmask:</b> ${addressInfo.netmask}`,
newLine: true
};
items[interfaceName].items[`cidr_${interfaceName}_${addressInfo.family}`] = {
type: 'staticText',
text: `<b>CIDR:</b> ${addressInfo.cidr}`,
newLine: true
};
}
}
return { items };
}
module.exports = dmNetTools;