Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified front/src/assets/integrations/logos/logo_zigbee2mqtt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions front/src/config/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,8 @@
"mqtt": "MQTT",
"status": "Status",
"connectionUrl": "URL der Zigbee2mqtt-Schnittstelle: <a href=\"{{url}}\" target=\"_blank\">{{url}}</a>",
"z2mFrontendUrlLabel": "URL der Zigbee2mqtt-Schnittstelle",
"z2mFrontendUrlPlaceholder": "http://192.168.1.x:8080",
"reset": {
"title": "Integration zurücksetzen",
"description": "Verwenden Sie diese Option, wenn Sie mit Zigbee2mqtt von vorne beginnen möchten. Dies kann nützlich sein, wenn Ihre Konfiguration beschädigt ist oder wenn Sie Ihren Zigbee-Koordinator wechseln möchten.",
Expand Down Expand Up @@ -1012,6 +1014,8 @@
"nameLabel": "Gerätename",
"namePlaceholder": "Gib den Namen deines Geräts ein",
"roomLabel": "Zimmer",
"ieeeAddressLabel": "IEEE-Adresse",
"openInZ2mButton": "In Zigbee2mqtt öffnen",
"topicLabel": "Topic",
"topicPlaceholder": "%topic% Zigbee2MQTT-MQTT-Wert",
"featuresLabel": "Funktionen",
Expand Down
4 changes: 4 additions & 0 deletions front/src/config/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,8 @@
"mqtt": "MQTT",
"status": "Status",
"connectionUrl": "Zigbee2mqtt Interface URL: <a href=\"{{url}}\" target=\"_blank\">{{url}}</a>",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add rel="noopener noreferrer" to this external link.

This new target="_blank" link reintroduces an opener relationship. Please keep it aligned with the rest of the file’s external links.

Suggested fix
-        "connectionUrl": "Zigbee2mqtt Interface URL: <a href=\"{{url}}\" target=\"_blank\">{{url}}</a>",
+        "connectionUrl": "Zigbee2mqtt Interface URL: <a href=\"{{url}}\" target=\"_blank\" rel=\"noopener noreferrer\">{{url}}</a>",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"connectionUrl": "Zigbee2mqtt Interface URL: <a href=\"{{url}}\" target=\"_blank\">{{url}}</a>",
"connectionUrl": "Zigbee2mqtt Interface URL: <a href=\"{{url}}\" target=\"_blank\" rel=\"noopener noreferrer\">{{url}}</a>",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/config/i18n/en.json` at line 876, The translation string
"connectionUrl" contains an external anchor with target="_blank" but lacks
rel="noopener noreferrer"; update the value for the "connectionUrl" key so the
anchor tag becomes <a href="{{url}}" target="_blank" rel="noopener noreferrer">
(i.e., add rel="noopener noreferrer" to the anchor) to match other external
links and avoid introducing an opener relationship.

"z2mFrontendUrlLabel": "Zigbee2mqtt Interface URL",
"z2mFrontendUrlPlaceholder": "http://192.168.1.x:8080",
"reset": {
"title": "Reset Integration",
"description": "Use this option if you want to start from scratch with Zigbee2mqtt. This can be useful if your configuration is corrupted or if you want to change your Zigbee coordinator.",
Expand Down Expand Up @@ -907,6 +909,8 @@
"nameLabel": "Device Name",
"namePlaceholder": "Enter the name of your device",
"roomLabel": "Room",
"ieeeAddressLabel": "IEEE Address",
"openInZ2mButton": "Open in Zigbee2mqtt",
"topicLabel": "Topic",
"topicPlaceholder": "%topic% Zigbee2mqtt MQTT value",
"featuresLabel": "Features",
Expand Down
4 changes: 4 additions & 0 deletions front/src/config/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,8 @@
"mqtt": "MQTT",
"status": "Statut",
"connectionUrl": "URL de l'interface Zigbee2mqtt : <a href=\"{{url}}\" target=\"_blank\">{{url}}</a>",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add rel="noopener noreferrer" to the new external link.

Line 1111 opens a configured URL in a new tab with target="_blank" but no rel, so the Zigbee2MQTT frontend keeps access to window.opener.

Suggested fix
-        "connectionUrl": "URL de l'interface Zigbee2mqtt : <a href=\"{{url}}\" target=\"_blank\">{{url}}</a>",
+        "connectionUrl": "URL de l'interface Zigbee2mqtt : <a href=\"{{url}}\" target=\"_blank\" rel=\"noopener noreferrer\">{{url}}</a>",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"connectionUrl": "URL de l'interface Zigbee2mqtt : <a href=\"{{url}}\" target=\"_blank\">{{url}}</a>",
"connectionUrl": "URL de l'interface Zigbee2mqtt : <a href=\"{{url}}\" target=\"_blank\" rel=\"noopener noreferrer\">{{url}}</a>",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/config/i18n/fr.json` at line 1111, The translation string
"connectionUrl" currently opens an external link with target="_blank" but omits
rel attributes; update the value for the "connectionUrl" key to include
rel="noopener noreferrer" in the anchor tag so the external page cannot access
window.opener (i.e., modify the string for the "connectionUrl" key to add
rel="noopener noreferrer" to the <a> tag).

"z2mFrontendUrlLabel": "URL de l'interface Zigbee2mqtt",
"z2mFrontendUrlPlaceholder": "http://192.168.1.x:8080",
"reset": {
"title": "Réinitialiser l'intégration",
"description": "Utilisez cette option si vous souhaitez repartir de zéro avec Zigbee2mqtt. Cela peut être utile si votre configuration est corrompue ou si vous souhaitez changer de coordinateur Zigbee.",
Expand Down Expand Up @@ -1145,6 +1147,8 @@
"nameLabel": "Nom",
"namePlaceholder": "Donner un nom à l'appareil",
"roomLabel": "Pièce",
"ieeeAddressLabel": "Adresse IEEE",
"openInZ2mButton": "Ouvrir dans Zigbee2mqtt",
"topicLabel": "Sujet MQTT",
"topicPlaceholder": "%topic% zigbee2mqtt MQTT value",
"featuresLabel": "Fonctionnalités",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import style from './style.css';
import { DEVICE_FEATURE_CATEGORIES } from '../../../../../../../server/utils/constants';
import DeviceFeatures from '../../../../../components/device/view/DeviceFeatures';
import BatteryLevelFeature from '../../../../../components/device/view/BatteryLevelFeature';
import logoZigbee2mqtt from '../../../../../assets/integrations/logos/logo_zigbee2mqtt.png';

class Zigbee2mqttBox extends Component {
updateName = e => {
Expand Down Expand Up @@ -164,6 +165,26 @@ class Zigbee2mqttBox extends Component {
</select>
</div>

{props.device.ieee_address && (
<div class="form-group">
<label class="form-label">
<Text id="integration.zigbee2mqtt.ieeeAddressLabel" />
</label>
<input type="text" class="form-control" value={props.device.ieee_address} disabled />
{props.z2mUrl && (
Comment on lines +169 to +174
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use readOnly instead of disabled for the IEEE address field.

A disabled input can't be focused or copied normally, which makes the new identifier harder to reuse for support/debug flows. readOnly keeps it non-editable without blocking selection.

Suggested fix
-                  <div class="form-group">
-                    <label class="form-label">
+                  <div class="form-group">
+                    <label class="form-label" for={`ieee_${props.deviceIndex}`}>
                       <Text id="integration.zigbee2mqtt.ieeeAddressLabel" />
                     </label>
-                    <input type="text" class="form-control" value={props.device.ieeeAddress} disabled />
+                    <input
+                      id={`ieee_${props.deviceIndex}`}
+                      type="text"
+                      class="form-control"
+                      value={props.device.ieeeAddress}
+                      readOnly
+                    />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="form-group">
<label class="form-label">
<Text id="integration.zigbee2mqtt.ieeeAddressLabel" />
</label>
<input type="text" class="form-control" value={props.device.ieeeAddress} disabled />
{props.z2mUrl && (
<div class="form-group">
<label class="form-label" for={`ieee_${props.deviceIndex}`}>
<Text id="integration.zigbee2mqtt.ieeeAddressLabel" />
</label>
<input
id={`ieee_${props.deviceIndex}`}
type="text"
class="form-control"
value={props.device.ieeeAddress}
readOnly
/>
{props.z2mUrl && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/routes/integration/all/zigbee2mqtt/device-page/Zigbee2mqttBox.jsx`
around lines 169 - 174, The IEEE address input in Zigbee2mqttBox.jsx is using
the disabled attribute which prevents focus and copying; change the input
rendering that references props.device.ieeeAddress to use the readOnly attribute
instead of disabled (i.e., replace disabled with readOnly on the input element)
so the value remains non-editable but selectable/focusable for support/debug
flows.

<a
href={`${props.z2mUrl}/#/device/0/${props.device.ieee_address}`}
target="_blank"
rel="noopener noreferrer"
class={`${style.z2mDeviceLink} mt-2 text-muted`}
>
<img src={logoZigbee2mqtt} alt="Zigbee2mqtt" class={style.z2mDeviceLinkLogo} />
<Text id="integration.zigbee2mqtt.openInZ2mButton" />
</a>
)}
</div>
)}

<div class="form-group">
<label class="form-label">
<Text id="integration.zigbee2mqtt.featuresLabel" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,28 @@ import uuid from 'uuid';
import debounce from 'debounce';
import createActionsIntegration from '../../../../../actions/integration';
import createActionsHouse from '../../../../../actions/house';
import config from '../../../../../config';

function createActions(store) {
const integrationActions = createActionsIntegration(store);
const houseActions = createActionsHouse(store);
const actions = {
async getZ2mUrl(state) {
try {
const configuration = await state.httpClient.get('/api/v1/service/zigbee2mqtt/setup');
if (configuration.Z2M_MQTT_MODE === 'local') {
if (configuration.z2mTcpPort && state.session.gatewayClient === undefined) {
const url = new URL(config.localApiUrl);
const z2mUrl = `${url.protocol}//${url.hostname}:${configuration.z2mTcpPort}`;
store.setState({ z2mUrl });
}
} else if (configuration.Z2M_MQTT_MODE === 'external' && configuration.Z2M_FRONTEND_URL) {
store.setState({ z2mUrl: configuration.Z2M_FRONTEND_URL });
}
} catch (e) {
// z2mUrl stays undefined, link won't be shown
}
Comment on lines +13 to +27
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clear stale z2mUrl values before loading setup.

Lines 13-27 only write z2mUrl in success branches. In the SPA store, a value from a previous visit survives when setup no longer exposes a frontend URL or this request fails, so the “Open in Zigbee2mqtt” link can keep pointing to an invalid target. The same logic is duplicated in front/src/routes/integration/all/zigbee2mqtt/discover-page/actions.js, Lines 10-25, so a shared helper would keep both pages consistent.

Suggested fix
     async getZ2mUrl(state) {
+      store.setState({ z2mUrl: undefined });
       try {
         const configuration = await state.httpClient.get('/api/v1/service/zigbee2mqtt/setup');
         if (configuration.Z2M_MQTT_MODE === 'local') {
           if (configuration.z2mTcpPort && state.session.gatewayClient === undefined) {
             const url = new URL(config.localApiUrl);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async getZ2mUrl(state) {
try {
const configuration = await state.httpClient.get('/api/v1/service/zigbee2mqtt/setup');
if (configuration.Z2M_MQTT_MODE === 'local') {
if (configuration.z2mTcpPort && state.session.gatewayClient === undefined) {
const url = new URL(config.localApiUrl);
const z2mUrl = `${url.protocol}//${url.hostname}:${configuration.z2mTcpPort}`;
store.setState({ z2mUrl });
}
} else if (configuration.Z2M_MQTT_MODE === 'external' && configuration.Z2M_FRONTEND_URL) {
store.setState({ z2mUrl: configuration.Z2M_FRONTEND_URL });
}
} catch (e) {
// z2mUrl stays undefined, link won't be shown
}
async getZ2mUrl(state) {
store.setState({ z2mUrl: undefined });
try {
const configuration = await state.httpClient.get('/api/v1/service/zigbee2mqtt/setup');
if (configuration.Z2M_MQTT_MODE === 'local') {
if (configuration.z2mTcpPort && state.session.gatewayClient === undefined) {
const url = new URL(config.localApiUrl);
const z2mUrl = `${url.protocol}//${url.hostname}:${configuration.z2mTcpPort}`;
store.setState({ z2mUrl });
}
} else if (configuration.Z2M_MQTT_MODE === 'external' && configuration.Z2M_FRONTEND_URL) {
store.setState({ z2mUrl: configuration.Z2M_FRONTEND_URL });
}
} catch (e) {
// z2mUrl stays undefined, link won't be shown
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/routes/integration/all/zigbee2mqtt/device-page/actions.js` around
lines 13 - 27, Clear any stale z2mUrl before attempting to load setup and DRY
the logic into a shared helper: in getZ2mUrl (and the duplicate in
discover-page/actions.js) immediately call store.setState({ z2mUrl: undefined })
or use a shared async helper (e.g., fetchZ2mUrlFromSetup) that performs
state.httpClient.get('/api/v1/service/zigbee2mqtt/setup'), returns the resolved
z2mUrl or undefined, and then call store.setState({ z2mUrl }) with that result;
replace the duplicated URL-construction branches in getZ2mUrl and the
discover-page action with a single call to the helper so both pages consistently
clear stale values and set the new z2mUrl only on success.

},
async getZigbee2mqttDevices(state, take, skip) {
store.setState({
getZigbee2mqttStatus: RequestStatus.Getting
Expand All @@ -23,12 +40,26 @@ function createActions(store) {
options.search = state.zigbee2mqttSearch;
}

const zigbee2mqttsReceived = await state.httpClient.get('/api/v1/service/zigbee2mqtt/device', options);
const [zigbee2mqttsReceived, discoveredDevices] = await Promise.all([
state.httpClient.get('/api/v1/service/zigbee2mqtt/device', options),
state.httpClient.get('/api/v1/service/zigbee2mqtt/discovered', { filter_existing: false })
]);
Comment on lines +43 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t make IEEE enrichment a hard dependency for loading devices.

Lines 46-49 reject the whole action if /discovered is unavailable, even though that response is only used to attach ieeeAddress. A transient MQTT/discovery failure would now blank the saved-device list as well.

Suggested fix
-        const [zigbee2mqttsReceived, discoveredDevices] = await Promise.all([
-          state.httpClient.get('/api/v1/service/zigbee2mqtt/device', options),
-          state.httpClient.get('/api/v1/service/zigbee2mqtt/discovered', { filter_existing: false })
-        ]);
+        const [zigbee2mqttsReceived, discoveredDevices] = await Promise.all([
+          state.httpClient.get('/api/v1/service/zigbee2mqtt/device', options),
+          state.httpClient
+            .get('/api/v1/service/zigbee2mqtt/discovered', { filter_existing: false })
+            .catch(() => [])
+        ]);

Based on learnings: degraded-mode return is preferred over throwing errors because preserving available data is more useful than failing fast; discovery should remain resilient.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [zigbee2mqttsReceived, discoveredDevices] = await Promise.all([
state.httpClient.get('/api/v1/service/zigbee2mqtt/device', options),
state.httpClient.get('/api/v1/service/zigbee2mqtt/discovered', { filter_existing: false })
]);
const [zigbee2mqttsReceived, discoveredDevices] = await Promise.all([
state.httpClient.get('/api/v1/service/zigbee2mqtt/device', options),
state.httpClient
.get('/api/v1/service/zigbee2mqtt/discovered', { filter_existing: false })
.catch(() => [])
]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/routes/integration/all/zigbee2mqtt/device-page/actions.js` around
lines 46 - 49, The action currently fails entirely if the discovered devices
call errors; wrap the second Promise
(state.httpClient.get('/api/v1/service/zigbee2mqtt/discovered', {
filter_existing: false })) in a try/catch or resolve it separately so that
zigbee2mqttsReceived still returns; set discoveredDevices to an empty array on
error and only attach ieeeAddress when discoveredDevices contains a match (use
the existing discoveredDevices variable and the zigbee2mqttsReceived result),
thereby preserving loaded devices even when discovery/IEEE enrichment is
unavailable.


const discoveredMap = {};
discoveredDevices.forEach(d => {
discoveredMap[d.external_id] = d;
});

zigbee2mqttsReceived.forEach(device => {
const model = device.params.find(p => p.name === 'model');
if (model) {
device.model = model.value;
}

const discovered = discoveredMap[device.external_id];
if (discovered && discovered.ieee_address) {
device.ieee_address = discovered.ieee_address;
}
});

let zigbee2mqttDevices;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Zigbee2mqttIntegration extends Component {
this.props.getZigbee2mqttDevices(100, 0);
this.props.getHouses();
this.props.getIntegrationByName('zigbee2mqtt');
this.props.getZ2mUrl();
}

render(props, {}) {
Expand All @@ -21,6 +22,6 @@ class Zigbee2mqttIntegration extends Component {
}

export default connect(
'user,zigbee2mqttDevices,houses,getZigbee2mqttStatus,zigbee2mqttSearch,getZigbee2mqttOrderDir',
'user,zigbee2mqttDevices,houses,getZigbee2mqttStatus,zigbee2mqttSearch,getZigbee2mqttOrderDir,z2mUrl',
actions
)(Zigbee2mqttIntegration);
13 changes: 13 additions & 0 deletions front/src/routes/integration/all/zigbee2mqtt/device-page/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,16 @@
.z2mActionButtons > a button {
width: 100%;
}

.z2mDeviceLink {
display: inline-flex;
align-items: center;
gap: 6px;
text-decoration: none;
}

.z2mDeviceLinkLogo {
width: 20px;
height: 20px;
object-fit: contain;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import cx from 'classnames';
import { RequestStatus } from '../../../../../utils/consts';
import DeviceFeatures from '../../../../../components/device/view/DeviceFeatures';
import { DEVICE_FEATURE_CATEGORIES } from '../../../../../../../server/utils/constants';
import logoZigbee2mqtt from '../../../../../assets/integrations/logos/logo_zigbee2mqtt.png';
import style from './style.css';

const GITHUB_BASE_URL = 'https://github.com/GladysAssistant/Gladys/issues/new';

Expand Down Expand Up @@ -42,8 +44,8 @@ class DiscoveredBox extends Component {
});
};

render({ device = {}, deviceIndex, houses = [] }, { loading, saveError }) {
const { features = [] } = device;
render({ device = {}, deviceIndex, houses = [], z2mUrl }, { loading, saveError }) {
const { features = [], ieee_address = null } = device;
const enableSaveButton = !device.created_at;
const enableUpdateButton = device.updatable;
const supportedDevice = features.findIndex(f => f.category !== DEVICE_FEATURE_CATEGORIES.BATTERY) >= 0;
Expand Down Expand Up @@ -129,6 +131,26 @@ class DiscoveredBox extends Component {
</Localizer>
</div>

{ieee_address && (
<div class="form-group">
<label class="form-label">
<Text id="integration.zigbee2mqtt.ieeeAddressLabel" />
</label>
<input type="text" class="form-control" value={ieee_address} disabled />
{z2mUrl && (
<a
href={`${z2mUrl}/#/device/0/${ieee_address}`}
target="_blank"
rel="noopener noreferrer"
class={`${style.z2mDeviceLink} mt-2 text-muted`}
>
<img src={logoZigbee2mqtt} alt="Zigbee2mqtt" class={style.z2mDeviceLinkLogo} />
<Text id="integration.zigbee2mqtt.openInZ2mButton" />
</a>
)}
</div>
)}

<div class="form-group">
<label class="form-label">
<Text id="integration.zigbee2mqtt.featuresLabel" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
import update from 'immutability-helper';
import createActionsIntegration from '../../../../../actions/integration';
import createActionsHouse from '../../../../../actions/house';
import config from '../../../../../config';

function createActions(store) {
const integrationActions = createActionsIntegration(store);
const houseActions = createActionsHouse(store);
const actions = {
async getZ2mUrl(state) {
try {
const configuration = await state.httpClient.get('/api/v1/service/zigbee2mqtt/setup');
if (configuration.Z2M_MQTT_MODE === 'local') {
if (configuration.z2mTcpPort && state.session.gatewayClient === undefined) {
const url = new URL(config.localApiUrl);
const z2mUrl = `${url.protocol}//${url.hostname}:${configuration.z2mTcpPort}`;
store.setState({ z2mUrl });
}
} else if (configuration.Z2M_MQTT_MODE === 'external' && configuration.Z2M_FRONTEND_URL) {
store.setState({ z2mUrl: configuration.Z2M_FRONTEND_URL });
}
} catch (e) {
// z2mUrl stays undefined, link won't be shown
}
},
async getDiscoveredDevices(state) {
store.setState({
discoverZigbee2mqtt: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Zigbee2mqttIntegration extends Component {
this.props.getHouses();
this.props.getIntegrationByName('zigbee2mqtt');
this.props.getDiscoveredDevices();
this.props.getZ2mUrl();
}

componentWillUnmount() {
Expand All @@ -47,7 +48,7 @@ class Zigbee2mqttIntegration extends Component {

export default withIntlAsProp(
connect(
'user,session,houses,zigbee2mqttDevices,discoverZigbee2mqtt,discoverZigbee2mqttError,permitJoin,gladysConnected,zigbee2mqttConnected,usbConfigured,z2mEnabled,filterExisting',
'user,session,houses,zigbee2mqttDevices,discoverZigbee2mqtt,discoverZigbee2mqttError,permitJoin,gladysConnected,zigbee2mqttConnected,usbConfigured,z2mEnabled,filterExisting,z2mUrl',
actions
)(Zigbee2mqttIntegration)
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,16 @@
.zigbee2mqttListBody {
min-height: 200px
}

.z2mDeviceLink {
display: inline-flex;
align-items: center;
gap: 6px;
text-decoration: none;
}

.z2mDeviceLinkLogo {
width: 20px;
height: 20px;
object-fit: contain;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const VARIABLE_MAP = {
Z2M_MQTT_MODE: 'mqttMode',
Z2M_MQTT_URL: 'mqttUrl',
GLADYS_MQTT_USERNAME: 'mqttUsername',
GLADYS_MQTT_PASSWORD: 'mqttPassword'
GLADYS_MQTT_PASSWORD: 'mqttPassword',
Z2M_FRONTEND_URL: 'z2mFrontendUrl'
};

class Zigbee2mqttSetupPage extends Component {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ class SetupRemoteOptions extends Component {
this.setState({ mqttUrl: e.target.value });
};

updateZ2mFrontendUrl = e => {
this.setState({ z2mFrontendUrl: e.target.value });
};

updateMqttUsername = e => {
this.setState({ mqttUsername: e.target.value });
};
Expand All @@ -22,12 +26,13 @@ class SetupRemoteOptions extends Component {
};

saveConfiguration = () => {
const { mqttMode, mqttUrl, mqttUsername, mqttPassword } = this.state;
const { mqttMode, mqttUrl, mqttUsername, mqttPassword, z2mFrontendUrl } = this.state;
this.props.saveConfiguration({
mqttMode,
mqttUrl,
mqttUsername,
mqttPassword
mqttPassword,
z2mFrontendUrl
});
};

Expand All @@ -43,18 +48,19 @@ class SetupRemoteOptions extends Component {
super(props);

const { configuration } = props;
const { mqttUrl, mqttPassword, mqttUsername } = configuration;
const { mqttUrl, mqttPassword, mqttUsername, z2mFrontendUrl } = configuration;

this.state = {
mqttMode: MQTT_MODE.EXTERNAL,
mqttUrl,
mqttPassword,
mqttUsername,
z2mFrontendUrl,
showPassword: false
};
}

render({ disabled }, { mqttMode, mqttUrl, mqttUsername, mqttPassword, showPassword }) {
render({ disabled }, { mqttMode, mqttUrl, mqttUsername, mqttPassword, z2mFrontendUrl, showPassword }) {
return (
<div>
<p>
Expand All @@ -63,6 +69,22 @@ class SetupRemoteOptions extends Component {
<div class="form-group">
{mqttMode === MQTT_MODE.EXTERNAL && (
<form>
<div class="form-group">
<label for="z2mFrontendUrl" class="form-label">
<Text id="integration.zigbee2mqtt.setup.z2mFrontendUrlLabel" />
</label>
<Localizer>
<input
id="z2mFrontendUrl"
name="z2mFrontendUrl"
placeholder={<Text id="integration.zigbee2mqtt.setup.z2mFrontendUrlPlaceholder" />}
value={z2mFrontendUrl}
class="form-control"
onInput={this.updateZ2mFrontendUrl}
/>
</Localizer>
</div>

<div class="form-group">
<label for="mqttUrl" class="form-label">
<Text id={`integration.mqtt.setup.urlLabel`} />
Expand Down
2 changes: 2 additions & 0 deletions server/services/zigbee2mqtt/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const CONFIGURATION = {
GLADYS_MQTT_PASSWORD_KEY: 'GLADYS_MQTT_PASSWORD',
DOCKER_MQTT_VERSION: 'DOCKER_MQTT_VERSION', // Variable to identify last version of MQTT docker file is installed
DOCKER_Z2M_VERSION: 'DOCKER_Z2M_VERSION', // Variable to identify last version of Z2M docker file is installed
Z2M_FRONTEND_URL: 'Z2M_FRONTEND_URL',
};

const MQTT_MODE = {
Expand All @@ -29,6 +30,7 @@ const SETUP_VARIABLES = [
CONFIGURATION.GLADYS_MQTT_USERNAME_KEY,
CONFIGURATION.GLADYS_MQTT_PASSWORD_KEY,
CONFIGURATION.Z2M_MQTT_MODE,
CONFIGURATION.Z2M_FRONTEND_URL,
];

const DEFAULT = {
Expand Down
Loading
Loading