Conversation
Co-authored-by: DutchmanNL <7318445+DutchmanNL@users.noreply.github.com>
Co-authored-by: DutchmanNL <7318445+DutchmanNL@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Migrates the adapter’s admin UI from the legacy Materialize page to an Admin 5 (React) JSON-Config interface, and updates backend sendTo handlers to support the new device management workflow.
Changes:
- Added a new Admin 5 JSON-Config UI with tabs for main settings and device management.
- Updated
addDevice/deleteDevicemessage parsing for JSON-Config compatibility and added a newloadDeviceshandler for populating the devices table. - Switched
io-package.jsontoadminUI.config = "json"and added EN/DE i18n strings; updated README + changelog entries.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| main.js | Adds loadDevices sendTo handler and updates message parsing for addDevice/deleteDevice. |
| io-package.json | Switches admin UI from Materialize to JSON-Config (Admin 5). |
| admin/jsonConfig.json5 | Introduces the new Admin 5 configuration UI (tabs, device table, sendTo buttons). |
| admin/index_m.html.backup | Adds a backup of the legacy Materialize admin page. |
| admin/i18n/en/translations.json | Adds new translation keys for the JSON-Config UI. |
| admin/i18n/de/translations.json | Adds German translations for the JSON-Config UI. |
| README.md | Updates configuration instructions and adds changelog entries for the migration. |
| case 'deleteDevice': | ||
| // Delete device by IP address or device ID | ||
| // eslint-disable-next-line no-case-declarations | ||
| const deviceIP = obj.message; | ||
| const deviceIP = typeof obj.message === 'string' ? obj.message : obj.message.ip; | ||
| this.log.debug(`Delete device request received for IP: ${deviceIP}`); |
There was a problem hiding this comment.
deleteDevice now supports JSON-Config message formats, but the delete logic further down uses this.devices[ip].name as the object ID to delete. handleBasicStates() creates device objects keyed by MAC (deviceData.info.mac) and stores it in this.devices[deviceIP].mac, while .name is the WLED device name. As a result, deletion by IP can fail to remove the correct device tree. Use this.devices[deviceIP].mac (the actual object-id) when calling delDevice().
| const stateObj = await this.getStateAsync(`${device.name}._info._online`); | ||
| connectionState = stateObj && stateObj.val ? 'Connected' : 'Offline'; | ||
| } catch (stateError) { | ||
| this.log.debug(`Could not get state for ${device.name}: ${stateError.message}`); | ||
| } | ||
|
|
||
| devicesList.push({ | ||
| mac: device.name, |
There was a problem hiding this comment.
loadDevices checks the online state using ${device.name}._info._online, but online states are keyed by MAC (${this.devices[ip].mac}._info._online), and device.name is the WLED device name (see handleBasicStates). This will cause the status column to be wrong/missing. Use device.mac for the state ID.
| const stateObj = await this.getStateAsync(`${device.name}._info._online`); | |
| connectionState = stateObj && stateObj.val ? 'Connected' : 'Offline'; | |
| } catch (stateError) { | |
| this.log.debug(`Could not get state for ${device.name}: ${stateError.message}`); | |
| } | |
| devicesList.push({ | |
| mac: device.name, | |
| const idBase = device.mac || device.name; | |
| const stateObj = await this.getStateAsync(`${idBase}._info._online`); | |
| connectionState = stateObj && stateObj.val ? 'Connected' : 'Offline'; | |
| } catch (stateError) { | |
| this.log.debug(`Could not get state for ${device.name}: ${stateError.message}`); | |
| } | |
| devicesList.push({ | |
| mac: device.mac || device.name, |
| devicesList.push({ | ||
| mac: device.name, | ||
| name: device.clientName || device.name, | ||
| ip: ip, | ||
| connected: connectionState, | ||
| }); |
There was a problem hiding this comment.
In the devicesList.push(...) mapping, mac: device.name is incorrect because .name is the WLED device name, not the MAC address. The MAC is stored as device.mac, and the UI column is labeled "MAC Address". Consider using { mac: device.mac, name: device.name, ... } (and drop device.clientName unless it is actually set elsewhere).
| // Main settings tab | ||
| "_options": { | ||
| "type": "panel", | ||
| "label": "Main settings", |
There was a problem hiding this comment.
With "i18n": true, tab labels should be translation keys to avoid always showing English. "label": "Main settings" is not present in the i18n files, so it won’t be translated in other languages. Consider switching this to a key (e.g. lblMainSettingsTab) and adding it to EN/DE translations.
| "label": "Main settings", | |
| "label": "lblMainSettingsTab", |
| // Devices tab | ||
| "devices": { | ||
| "type": "panel", | ||
| "label": "Devices", |
There was a problem hiding this comment.
The second tab uses "label": "Devices" while i18n is enabled, but there is no translation entry for this literal label. Use an i18n key for the tab label (and add EN/DE translations) so the tab name is localized consistently with the rest of the UI.
| "label": "Devices", | |
| "label": "lblDevicesHeader", |
| case 'loadDevices': | ||
| // Load and return all devices with their current state | ||
| this.log.debug('Load devices request received'); | ||
| try { | ||
| const devicesList = []; | ||
|
|
There was a problem hiding this comment.
loadDevices introduces a new sendTo command used by the Admin 5 UI, but there is no integration test covering its response format (e.g., returning { native: { devicesTable: [] } } when no devices are configured, and a populated table when devices exist). Since this repo already has integration tests for other sendTo commands, please add a similar test to prevent regressions in the Admin 5 device table refresh.
| const deviceIPToAdd = typeof obj.message === 'string' ? obj.message : obj.message.ip; | ||
| // eslint-disable-next-line no-case-declarations | ||
| const result = await this.getDeviceJSON(deviceIPToAdd); |
There was a problem hiding this comment.
deviceIPToAdd extraction can throw if obj.message is null/undefined (because typeof null === 'object' and then obj.message.ip crashes). Please guard this by checking obj.message && typeof obj.message === 'object' before reading .ip, and return a clear failure response when no IP was provided.
| // eslint-disable-next-line no-case-declarations | ||
| const deviceIP = obj.message; | ||
| const deviceIP = typeof obj.message === 'string' ? obj.message : obj.message.ip; | ||
| this.log.debug(`Delete device request received for IP: ${deviceIP}`); |
There was a problem hiding this comment.
Similar to addDevice, const deviceIP = typeof obj.message === 'string' ? obj.message : obj.message.ip; can throw when obj.message is null/undefined. Add a guard and fail early with a helpful response if no IP was provided.
Replaces Materialize HTML admin interface with Admin 5 React-based JSON-Config format, following the pattern established by ioBroker.esphome.
Changes
Admin Interface (
admin/jsonConfig.json5)Backend (
main.js)loadDevicessendTo handler returning devices in JSON-Config table format:addDevice/deleteDevicehandlers to parse both legacy string format (obj.message) and JSON-Config object format (obj.message.ip)Configuration
io-package.json: ChangedadminUI.configfrom"materialize"to"json"admin/index_m.html.backupDocumentation
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.