diff --git a/.homeycompose/app.json b/.homeycompose/app.json index a8a10da..ee44892 100644 --- a/.homeycompose/app.json +++ b/.homeycompose/app.json @@ -55,6 +55,11 @@ "name": "Ronny Troxler", "email": "ronny.troxler@bluewin.ch", "website": "https://place4fun.ch" + }, + { + "name": "Bert Marcelis", + "email": "homey@bertmarcelis.be", + "website": "https://bertmarcelis.be" } ], "translators": [ diff --git a/app.json b/app.json index 421edbb..c9b432c 100644 --- a/app.json +++ b/app.json @@ -1929,6 +1929,7 @@ }, "class": "sensor", "capabilities": [ + "onoff", "alarm_communication_error", "alarm_filter_state", "last_sync", @@ -1941,6 +1942,22 @@ "web_update_available", "ftl_update_available" ], + "capabilitiesOptions": { + "onoff": { + "titleTrue": { + "en": "Adblocking enabled" + }, + "titleFalse": { + "en": "Adblocking disabled" + }, + "insightsTitleTrue": { + "en": "Adblocking has been enabled" + }, + "insightsTitleFalse": { + "en": "Adblocking has been disabled" + } + } + }, "images": { "small": "/drivers/pihole/assets/images/small.png", "large": "/drivers/pihole/assets/images/large.png", @@ -1954,7 +1971,7 @@ { "id": "manual_input", "prev": "welcome", - "next": "list_devices" + "next": "done" } ], "settings": [ diff --git a/drivers/pihole/device.js b/drivers/pihole/device.js index 6677502..ef8727f 100644 --- a/drivers/pihole/device.js +++ b/drivers/pihole/device.js @@ -1,9 +1,4 @@ const Homey = require('homey'); -const PiholeDevice = require('./device'); // Stellen Sie sicher, dass der Pfad korrekt ist. -const CAPABILITY_DEBOUNCE = 500; - -//Festlegen einer Map für die Task-Verwaltung -let intervalIds = new Map(); //Allgemeine Parameter let absoluteTimestamp; @@ -14,13 +9,15 @@ let formated_blocked_adds_today_percent; class PiHoleDevice extends Homey.Device { + intervalId = undefined + async onInit() { this.log('Gerät wurde initialisiert'); - + //Ansprechen der Einstellungen, damit Zugriff darauf gewähleistet ist const deviceSettings = this.getSettings(); - + // Prüfen, ob Geräteeinstellungen vorhanden sind if (deviceSettings) { @@ -29,7 +26,7 @@ class PiHoleDevice extends Homey.Device { const device_port = deviceSettings.port const device_api = deviceSettings.api const device_interval = deviceSettings.interval - + //Schreiben ins Log File this.log('*******************************************************') this.log('URL ->', device_url); @@ -40,12 +37,12 @@ class PiHoleDevice extends Homey.Device { //Herausfinden welches Gerät das ist this.log('Identifiziert und initialisiert ..') - + //Bereitstellen der nötigen URLs für Aktionen / Abfragen const disable_url = `${device_url}:${device_port}/admin/api.php?disable&auth=${device_api}`; const enable_url = `${device_url}:${device_port}/admin/api.php?enable&auth=${device_api}`; const status_url = `${device_url}:${device_port}/admin/api.php?summaryRaw&auth=${device_api}`; - + //Schreiben ins Log File this.log('URL Disable ->', disable_url); this.log('URL Enable ->', enable_url); @@ -53,58 +50,40 @@ class PiHoleDevice extends Homey.Device { //Capabilities Updaten (Danke Ronny Winkler) await this._updateCapabilities(); + //Task neu erstellen + this.createTask() + + + //Schreibt den Status, bei Veränderung, ins Log File + this.registerCapabilityListener('onoff', async (value) => { + this.log('Eingeschaltet:', value); + + if (value) { + this.log('Eingeschaltet:', value); + this._makeAPICall(enable_url).then( + // update data so it reflects the correct status. Little delay so pihole has time to actually enable + () => setTimeout(() => this._updateDeviceData(), 600) + ) + } else { + this.log('Ausgeschaltet:', value); + this._makeAPICall(disable_url).then( + // update data so it reflects the correct status. Little delay so pihole has time to actually enable + () => setTimeout(() => this._updateDeviceData(), 600) + ) + } + } + ) + + this.registerCapabilityListener('data_refresh', async (value) => { + const api_key = deviceSettings.api + const status_url = `${this.getHost()}/admin/api.php?summaryRaw&auth=${api_key}`; + this._updateDeviceData(status_url); + } + ) + } else { + this.log('Keine Geräteeinstellungen gefunden. Aktionen werden nicht ausgeführt.'); + } - //Capabilities Listener - this.registerMultipleCapabilityListener(this.getCapabilities(), async (capabilityValues, capabilityOptions) => { - // try{ - await this._onCapability( capabilityValues, capabilityOptions); - // } - // catch(error){ - // this.log("_onCapability() Error: ",error); - // } - },CAPABILITY_DEBOUNCE); - - let deviceSettingsArray = [ - { - url: device_url, - port: device_port, - api: device_api, - interval: device_interval - }, - ]; - - deviceSettingsArray.forEach(deviceSettings => { - const status_url = `${device_url}:${device_port}/admin/api.php?summaryRaw&auth=${device_api}`; - - // Erstellen des wiederkehrenden Tasks - const deviceId = this.getId(); - - //Task neu erstellen - this.createTask(deviceId) - }); - - - //Schreibt den Status, bei Veränderung, ins Log File - this.registerCapabilityListener('onoff', async (value) => { - this.log('Eingeschaltet:' ,value); - const deviceId = this.getData().id; - - //Reagiert darauf, wenn das Gerät nicht erreichbar ist - this.setUnavailable(this.homey.__('device.unavailable')).catch(this.error); - - if (value) { - this.log('Eingeschaltet:' ,value); - this._makeAPICall(enable_url) - } else { - this.log('Ausgeschaltet:' ,value); - this._makeAPICall(disable_url) - } - } - ) - } else { - this.log('Keine Geräteeinstellungen gefunden. Aktionen werden nicht ausgeführt.'); - } - } /** @@ -211,7 +190,7 @@ async onDeleted() { const deviceId = this.getId(); //Task löschen - this.deleteTask(deviceId) + this.deleteTask() } async _updateCapabilities(){ @@ -244,50 +223,19 @@ async _updateCapabilities(){ } } -// CAPABILITIES ======================================================================================= - -async _onCapability( capabilityValues, capabilityOptions){ - - //Ansprechen der Einstellungen, damit Zugriff darauf gewähleistet ist - const deviceSettings = this.getSettings(); - - //Die nötigen Einstellungen holen und bereitstellen - const device_url = deviceSettings.url - const device_port = deviceSettings.port - const device_api = deviceSettings.api - const device_Name = this.getName() - - - //Schreiben ins Log File - this.log('*******************************************************') - this.log('ID ->', device_Name); - this.log('URL ->', device_url); - this.log('Port ->', device_port); - this.log('Key ->', device_api); - this.log('*******************************************************') - - //Bereitstellen der nötigen URLs für Aktionen / Abfragen - const status_url = `${device_url}:${device_port}/admin/api.php?summaryRaw&auth=${device_api}`; - const update_url = `${device_url}:${device_port}/admin/api.php??versions`; - - - if( capabilityValues["data_refresh"] != undefined){ - this._updateDeviceData(status_url); - } -} // Helpers ======================================================================================= async _makeAPICall(url) { try { const response = await fetch(url); - + if (!response.ok) { throw new Error(response.statusText); } this.log('API Aufruf erfolgreich'); return { success: true }; // Erfolgsstatus zurückgeben - + } catch (error) { this.log('API Aufruf fehlgeschlagen:'); this.log(errorMessage); @@ -295,25 +243,23 @@ async _makeAPICall(url) { } } -async _updateDeviceData(url) { +async _updateDeviceData() { + const deviceSettings = this.getSettings(); + // Die nötigen Einstellungen holen und bereitstellen + const api_key = deviceSettings.api; + const url = `${this.getHost()}/admin/api.php?summaryRaw&auth=${api_key}`; try { - const response = await fetch(url); - // Überprüft, ob der Statuscode im Erfolgsbereich liegt (200-299) oder 403 ist - if (!response.ok && response.status !== 403) { - throw new Error(`Status Filter: ${response.status}`); - } else { + fetch(url).then(response => { + if (!response.ok && response.status !== 403) { + throw new Error(`Status Filter: ${response.status}`); } + response.json().then(data => { - const data = await response.json(); - - fetch(url).then(response => response.json()) - .then(data => { - //Fülle Variable mit Gerätename ab const deviceName = this.getName() - //Saubere Formatierung des Status + //Saubere Formatierung des Status let PiHoleState = false if(data.status === 'enabled') { @@ -323,14 +269,14 @@ async _updateDeviceData(url) { } //Datum sauber formatieren let syncDate = new Date(); - let day = syncDate.toLocaleDateString('de-DE', { - day: '2-digit', - month: '2-digit', - year: '2-digit', - timeZone: this.homey.clock.getTimezone() + let day = syncDate.toLocaleDateString('de-DE', { + day: '2-digit', + month: '2-digit', + year: '2-digit', + timeZone: this.homey.clock.getTimezone() }); - let time = syncDate.toLocaleTimeString('de-DE', { - hour: '2-digit', + let time = syncDate.toLocaleTimeString('de-DE', { + hour: '2-digit', minute: '2-digit', timeZone: this.homey.clock.getTimezone() }); @@ -349,15 +295,7 @@ async _updateDeviceData(url) { } // Update Prüfung - //Ansprechen der Einstellungen, damit Zugriff darauf gewährleistet ist - const deviceSettings = this.getSettings(); - const device_Name = this.getName() - - //Die nötigen Einstellungen holen und bereitstellen - const device_url = deviceSettings.url - const device_port = deviceSettings.port - - const update_url = `${device_url}:${device_port}/admin/api.php?versions`; + const update_url = `${this.getHost()}/admin/api.php?versions`; this.checkUpdateAvailable(deviceName, update_url) // Geblockte ADDs pro Tag @@ -392,7 +330,7 @@ async _updateDeviceData(url) { const gravity_update_hours = Math.floor((timeDifference % (24 * 3600)) / 3600); const gravity_update_minutes = Math.floor((timeDifference % 3600) / 60); - let gravity_update_string = gravity_update_days + ' ' + this.homey.__('capabilities.gravity_days') + ' ' + gravity_update_hours + ' ' + this.homey.__('capabilities.gravity_hours') + ' ' + gravity_update_minutes + ' ' + this.homey.__('capabilities.gravity_minutes'); + let gravity_update_string = gravity_update_days + ' ' + this.homey.__('capabilities.gravity_days') + ' ' + gravity_update_hours + ' ' + this.homey.__('capabilities.gravity_hours') + ' ' + gravity_update_minutes + ' ' + this.homey.__('capabilities.gravity_minutes'); // Loggen der Werte zwecks Diagnose this.log(''); @@ -444,72 +382,54 @@ async _updateDeviceData(url) { this.log('Fehler --> gravity_last_update ist nicht definiert'); } - // Capabilities für den Rest setzen - this.setCapabilityValue('alarm_communication_error', false); - this.setCapabilityValue('alarm_filter_state', PiHoleState); + // Capabilities für den Rest setzen + this.setCapabilityValue('alarm_communication_error', false); + this.setCapabilityValue('alarm_filter_state', PiHoleState); + this.setCapabilityValue('onoff', data.status === 'enabled'); + }) + }); + } catch (error) { + this.log('Ein Fehler ist aufgetreten ->', error.message); -}); -} catch (error) { - this.log('Ein Fehler ist aufgetreten ->', error.message); - // Jetzt können Sie Capabilities für dieses Gerät setzen - this.setCapabilityValue('alarm_communication_error', true); + this.setCapabilityValue('alarm_communication_error', true); +} } -} //TASK Verwaltung -async deleteTask(deviceId) { - // Überprüfen, ob für die deviceId ein Task existiert - if (intervalIds.has(deviceId)) { - // Hole die intervalId und stoppe das Intervall - clearInterval(intervalIds.get(deviceId)); - this.log('Task für Gerät' ,deviceId, 'gestoppt.'); - - // Entferne die intervalId aus der Map - intervalIds.delete(deviceId); - this.log('Task für Gerät' ,deviceId, 'gelöscht.'); - - } else { - this.log('Kein Task gefunden für Gerät' ,deviceId,); + async deleteTask() { + // Überprüfen, ob für die deviceId ein Task existiert + if (this.intervalId) { + // Hole die intervalId und stoppe das Intervall + clearInterval(this.intervalId); + this.log('Task für Gerät', this.getId(), 'gestoppt.'); + this.intervalId = undefined + this.log('Task für Gerät', this.getId(), 'gelöscht.'); + } else { + this.log('Kein Task gefunden für Gerät', this.getId(),); + } } -} -async createTask(deviceId) { - +async createTask() { + // Vorhandenes Intervall beenden, falls es bereits existiert - const existingIntervalId = intervalIds.get(deviceId); - if (existingIntervalId) { - clearInterval(existingIntervalId); - intervalIds.delete(deviceId); // Entferne das Intervall aus der Map + if (this.intervalId) { + clearInterval(this.intervalId); } - + // Ansprechen der Einstellungen, um darauf zugreifen zu können const deviceSettings = this.getSettings(); - - // Die nötigen Einstellungen holen und bereitstellen - const device_url = deviceSettings.url; - const device_port = deviceSettings.port; - const device_api = deviceSettings.api; - + // Bereitstellen der nötigen URLs für Aktionen / Abfragen - const status_url = `${device_url}:${device_port}/admin/api.php?summaryRaw&auth=${device_api}`; - - // Die nötigen Einstellungen holen und bereitstellen - const device_interval = deviceSettings.interval; - - // Umrechnen der Interval ID in Millisekunden - const device_interval_minutes = device_interval; // Der Wert von device_interval in Minuten - const device_interval_milliseconds = device_interval_minutes * 60000; - + const device_interval_milliseconds = deviceSettings.interval * 60_000; + // Erstelle einen neuen Task (z.B. eine Funktion, die regelmäßig ausgeführt wird) - const intervalId = setInterval(() => { - this._updateDeviceData(status_url); + this.intervalId = setInterval(() => { + this._updateDeviceData(); }, device_interval_milliseconds); // gem. eingestelltem Intervall - - // Speichere die neue intervalId in der Map - intervalIds.set(deviceId, intervalId); - this.log('Neuer Task erstellt für Gerät:', deviceId); + + this.log('Neuer Task erstellt für Gerät:', this.getId()); } async checkUpdateAvailable(device, url) { @@ -517,7 +437,7 @@ async checkUpdateAvailable(device, url) { const response = await fetch(url); const data = await response.json(); - + this.log('Update Prüfung beginnt'); // Überprüfe ob ein Core Update vorhanden ist @@ -526,7 +446,7 @@ async checkUpdateAvailable(device, url) { this.setCapabilityValue('core_update_available', true); } else { this.log(device,': Kein Core Update verfügbar.'); - this.setCapabilityValue('core_update_available', false); + this.setCapabilityValue('core_update_available', false); } // Überprüfe ob ein Core Update vorhanden ist @@ -536,7 +456,7 @@ async checkUpdateAvailable(device, url) { } else { this.log(device,': Kein Web Update verfügbar.'); - this.setCapabilityValue('web_update_available', false); + this.setCapabilityValue('web_update_available', false); } // Überprüfe ob ein Core Update vorhanden ist @@ -545,7 +465,7 @@ async checkUpdateAvailable(device, url) { this.setCapabilityValue('ftl_update_available', true); } else { this.log(device,': Kein FTL Update verfügbar.'); - this.setCapabilityValue('ftl_update_available', false); + this.setCapabilityValue('ftl_update_available', false); } } catch (error) { @@ -555,5 +475,12 @@ async checkUpdateAvailable(device, url) { this.log('Update Prüfung beendet'); } + getHost() { + const deviceSettings = this.getSettings(); + const device_url = deviceSettings.url + const device_port = deviceSettings.port || 80 + return `${device_url}:${device_port}` + } + } module.exports = PiHoleDevice; \ No newline at end of file diff --git a/drivers/pihole/driver.compose.json b/drivers/pihole/driver.compose.json index 4f9030c..b6a3439 100644 --- a/drivers/pihole/driver.compose.json +++ b/drivers/pihole/driver.compose.json @@ -1,19 +1,20 @@ { "id": "pihole", "name": { - "en": "PiHole", - "da": "PiHole", - "de": "PiHole", - "es": "PiHole", - "fr": "PiHole", - "nl": "PiHole", - "no": "PiHole", - "pl": "PiHole", - "ru": "PiHole", - "sv": "PiHole" - }, + "en": "PiHole v5.x", + "da": "PiHole v5.x", + "de": "PiHole v5.x", + "es": "PiHole v5.x", + "fr": "PiHole v5.x", + "nl": "PiHole v5.x", + "no": "PiHole v5.x", + "pl": "PiHole v5.x", + "ru": "PiHole v5.x", + "sv": "PiHole v5.x" + }, "class": "sensor", "capabilities": [ + "onoff", "alarm_communication_error", "alarm_filter_state", "last_sync", @@ -26,20 +27,47 @@ "web_update_available", "ftl_update_available" ], - "images": { + "capabilitiesOptions": { + "onoff": { + "titleTrue": { + "en": "Adblocking enabled" + }, + "titleFalse": { + "en": "Adblocking disabled" + }, + "insightsTitleTrue": { + "en": "Adblocking has been enabled" + }, + "insightsTitleFalse": { + "en": "Adblocking has been disabled" + } + } + }, + "images": { "small": "{{driverAssetsPath}}/images/small.png", "large": "{{driverAssetsPath}}/images/large.png", "xlarge": "{{driverAssetsPath}}/images/xlarge.png" - }, + }, "pair": [ { - "id": "welcome", - "next": "manual_input" + "id": "list_devices", + "template": "list_devices", + "options": { + "singular": true + }, + "navigation": { + "next": "configure_device" + } + }, + { + "id": "configure_device", + "navigation": { + "prev": "list_devices" + } }, { - "id": "manual_input", - "prev": "welcome", - "next": "list_devices" + "id": "add_device", + "template": "add_devices" } - ] + ] } diff --git a/drivers/pihole/driver.js b/drivers/pihole/driver.js index a3b2c8e..70c392c 100644 --- a/drivers/pihole/driver.js +++ b/drivers/pihole/driver.js @@ -1,9 +1,187 @@ const Homey = require('homey'); -const CAPABILITY_DEBOUNCE = 500; -const PiholeDevice = require('./device'); +const crypto = require("crypto"); +const net = require("net"); class PiholeDriver extends Homey.Driver { async onInit() { + this.log("PiholeDriver initialized"); } + + async onPair(session) { + this.log("onPair()"); + + // default device when automatic detection fails + let devices = [{ + name: this.homey.__("pairing.add_manually"), + data: { + id: 'pihole-' + crypto.randomUUID() + }, + settings: { + url: "", + port: 80, + api: "", + interval: 1 + }, + }] + + let autoDiscoverStarted = false + + session.setHandler("list_devices", async () => { + this.log("list_devices"); + if (!autoDiscoverStarted){ + // Prevent multiple searches when navigating back and forth in the pairing process + this.emitAutodetectedPiHoles(session, devices); + autoDiscoverStarted = true; + } + return devices; + }); + + session.setHandler("list_devices_selection", async (devicea) => { + this.selectedDevice = devicea[0]; + this.log("Set selected device, name " + this.selectedDevice.name) + }); + + session.setHandler("get_selected_device", async (device) => { + this.log("Get selected device, name " + this.selectedDevice.name) + return this.selectedDevice; + }); + + session.setHandler("test_device", async (device) => { + this.log("test_device, received settings: " + device.settings); + if ((await this.valid_host(device.settings.url, device.settings.port)) === false) { + return this.homey.__("pairing.test_failed.invalid_host"); + } + if ((await this.valid_key(device)) === false) { + return this.homey.__("pairing.test_failed.invalid_key"); + } + return "ok" + }); + } + + createDeviceStub(ipAddress) { + return { + name: ipAddress, + data: { + id: 'pihole-' + crypto.randomUUID() + }, + iconObj: { + // Reverse-engineered from list-devices template, icons as documented do not work correctly + url: "/app/ch.place4fun.pihole/drivers/pihole/assets/icon.svg", + }, + settings: { + url: "http://" + ipAddress, + port: 80, + api: "", + interval: 1 + }, + }; + } + + async valid_host(url, port, timeout = 5_000) { + let versionUrl = url + ":" + port + "/admin/api.php?versions"; + return fetch(versionUrl, { + signal: AbortSignal.timeout(timeout), + }) + .then(response => { + this.log("Response " + response.status) + return response.json() + }) + .then(json => json["FTL_update"] !== undefined) + .catch(e => { + // this.log("Failed to get a valid response: " + e) + return false; + }); + } + + + async valid_key(device) { + try { + // This page always returns HTTP 200, but with an invalid key it returns an empty array instead of an object + let summaryUrl = device.settings.url + ":" + device.settings.port + + "/admin/api.php?summaryRaw&auth=" + device.settings.api; + this.log("Testing key through " + summaryUrl) + return await fetch(summaryUrl) + .then(response => response.json()) + .then(json => !Array.isArray(json)) + } catch (e) { + this.log("Failed to get a valid response: " + e) + return false; + } + } + + + async getIpsInSubnet() { + // There are better ways to do this, such as obtaining the DNS server through DHCP or getting hosts from ARP + // Homey blocks them all :( + // The dns server is also known in homeys dev tools, but not exposed to apps. + // When getting DNS servers through nodejs homeys hardcoded servers are returned instead of dhcp values + const {networkInterfaces} = require('os'); + const nets = networkInterfaces(); + let localIpAddresses = []; + for (const name of Object.keys(nets)) { + for (const net of nets[name]) { + // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses + // 'IPv4' is in Node <= 17, from 18 it's a number 4 or 6 + const familyV4Value = typeof net.family === 'string' ? 'IPv4' : 4 + if (net.family === familyV4Value && !net.internal) { + let ips = this.getIPsInSubnet(net.address, net.netmask); + // Check 254 addresses at most, so we don't flood the network on large subnets + ips = ips.splice(0, 254) + localIpAddresses.push(...ips); + } + } + } + return localIpAddresses; } + + async emitAutodetectedPiHoles(session, devices) { + return new Promise(async (resolve, reject) => { + let addresses = await this.getIpsInSubnet(); + let result = []; + this.log("Testing " + addresses.length + " ip addresses...") + addresses = ["pi.hole", "pihole.local", ... addresses] // also search two DNS names which are likely matches + for (const address of addresses) { + // this.homey.arp.getMAC(ipAddress) is horribly slow and unusable as help, so we have to brute-force discovery + let isValid = (await this.valid_host("http://" + address, 80, 50)); + if (isValid) { + devices.push(this.createDeviceStub(address)); + session.emit("list_devices", devices); // emit while discovering for faster feedback in the UI + } + } + resolve(result); + }); + } + + +// Get the integer representation of the IP + ipToInteger(ip) { + let [a, b, c, d] = ip.split('.').map(Number); + return (((((a << 8) + b) << 8) + c) << 8) + d; + }; + +// Get the string representation of the IP + integerToIP(i) { + return [(i >> 24) & 255, (i >> 16) & 255, (i >> 8) & 255, i & 255].join('.'); + }; + +// Returns all IP addresses in a subnet + getIPsInSubnet(ip, mask) { + const baseIPInteger = this.ipToInteger(ip); + const maskInteger = this.ipToInteger(mask); + let startIPInteger = baseIPInteger & maskInteger; + let endIPInteger = startIPInteger | ~maskInteger; + + let addressesInSubnet = []; + for (let i = startIPInteger; i <= endIPInteger; i++) { + if ((i & 255) === 255 || (i & 255) === 0) { + // skip broadcast address .255 and .0 + continue + } + addressesInSubnet.push(this.integerToIP(i)); + } + + return addressesInSubnet; + }; +} + module.exports = PiholeDriver; \ No newline at end of file diff --git a/drivers/pihole/pair/configure_device.html b/drivers/pihole/pair/configure_device.html new file mode 100644 index 0000000..ea96495 --- /dev/null +++ b/drivers/pihole/pair/configure_device.html @@ -0,0 +1,135 @@ +
+
+
+

+
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ + + + + diff --git a/drivers/pihole/pair/manual_input.html b/drivers/pihole/pair/manual_input.html deleted file mode 100644 index cd0138d..0000000 --- a/drivers/pihole/pair/manual_input.html +++ /dev/null @@ -1,161 +0,0 @@ -
-
-
-

-
-
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
-
- - - - - diff --git a/drivers/pihole/pair/welcome.html b/drivers/pihole/pair/welcome.html deleted file mode 100644 index c005cae..0000000 --- a/drivers/pihole/pair/welcome.html +++ /dev/null @@ -1,74 +0,0 @@ -
-
-

-

-
-
-

- -
-
-

-
-
- -
-
- - - - diff --git a/locales/en.json b/locales/en.json index 569d2b2..92f7751 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,7 +1,4 @@ { - "settings": { - "information_about_pihole": "IMPORTANT INFORMATION:

The application has undergone a major update. The settings are no longer necessary.

Existing devices will continue to run for a while. The old driver will be removed at some point.

In order to use the new functions, the new driver MUST be used." - }, "warnings": { "deprecated1": "WARNING: The old driver has been removed in version 1.5.0, as announced!" }, @@ -51,5 +48,32 @@ "buttons": { "next": "Add device" } + }, + "settings": { + "title": "Device Settings", + "test_connection": "Test connection", + "name": { + "title": "Device name", + "hint": "How the device should be named in Homey" + }, + "host": { + "title": "Device host", + "hint": "Please use FQDN or IP address with prepended HTTP(s)" + }, + "port": { + "title": "Port", + "hint": "Default is 80" + }, + "api_key": { + "title": "API key", + "hint": "Please enter the PiHole API key here. It can be obtained from the PiHole settings page" + } + }, + "pairing":{ + "add_manually": "Add manually", + "test_failed":{ + "invalid_host": "The hostname or port are incorrect.", + "invalid_key": "The API key is invalid." + } } } \ No newline at end of file