iobroker.zigbee 1.6.3 → 1.6.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/admin/admin.js CHANGED
@@ -129,6 +129,7 @@ function getGroupCard(dev) {
129
129
  title = dev.common.name,
130
130
  lq = '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>',
131
131
  rooms = [],
132
+ numid = parseInt(id.replace(namespace+'.group_', '')),
132
133
  lang = systemLang || 'en';
133
134
  for (const r in dev.rooms) {
134
135
  if (dev.rooms[r].hasOwnProperty(lang)) {
@@ -137,16 +138,17 @@ function getGroupCard(dev) {
137
138
  rooms.push(dev.rooms[r]);
138
139
  }
139
140
  }
141
+ devGroups[numid] = dev;
140
142
  const room = rooms.join(',') || '&nbsp';
141
143
  let memberCount = 0;
142
144
  let info = `<div style="min-height:88px; font-size: 0.8em; height: 98px; overflow-y: auto" class="truncate">
143
145
  <ul>`;
144
- info = info.concat(`<li><span class="labelinfo">Group ${id.replace(namespace+'.group_', '')}</span></li>`);
146
+ info = info.concat(`<li><span class="labelinfo">Group ${numid}</span></li>`);
145
147
  if (dev.memberinfo === undefined) {
146
148
  info = info.concat(`<li><span class="labelinfo">No devices in group</span></li>`);
147
149
  } else {
148
150
  for (let m=0;m < dev.memberinfo.length; m++) {
149
- info = info.concat(`<li><span class="labelinfo">${dev.memberinfo[m].name}</span><span> ...${dev.memberinfo[m].ieee.slice(-4)}</span></li>`);
151
+ info = info.concat(`<li><span align:"left">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid}</span><span align:"right"> ...${dev.memberinfo[m].ieee.slice(-4)}</span></li>`);
150
152
  }
151
153
  memberCount = (dev.memberinfo.length<8?dev.memberinfo.length:7);
152
154
  };
@@ -154,7 +156,7 @@ function getGroupCard(dev) {
154
156
  </div>`);
155
157
  const image = `<img src="img/group_${memberCount}.png" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`;
156
158
  const dashCard = getDashCard(dev,`img/group_${memberCount}.png` );
157
- const card = `<div id="${id}" class="device">
159
+ const card = `<div id="${id}" class="device group">
158
160
  <div class="card hoverable flipable">
159
161
  <div class="front face">${dashCard}</div>
160
162
  <div class="back face">
@@ -187,14 +189,19 @@ function getGroupCard(dev) {
187
189
  return card;
188
190
  }
189
191
 
190
-
192
+ function sanitizeModelParameter(parameter) {
193
+ const replaceByUnderscore = /[\s/]/g;
194
+ return parameter.replace(replaceByUnderscore, '_');
195
+ }
191
196
 
192
197
  function getCard(dev) {
193
198
  const title = dev.common.name,
194
199
  id = dev._id,
195
- type = (dev.common.type ? dev.common.type.replace('/','_'):'unknown'),
200
+ type = (dev.common.type ? dev.common.type : 'unknown'),
201
+ type_url = (dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown'),
196
202
  img_src = dev.icon || dev.common.icon,
197
203
  rooms = [],
204
+ isActive = (dev.common.deactivated ? false : true),
198
205
  lang = systemLang || 'en';
199
206
  for (const r in dev.rooms) {
200
207
  if (dev.rooms[r].hasOwnProperty(lang)) {
@@ -206,14 +213,14 @@ function getCard(dev) {
206
213
  const room = rooms.join(',') || '&nbsp';
207
214
  const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
208
215
  const rid = id.split('.').join('_');
209
- const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
216
+ const modelUrl = (!type) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${type_url}.html" target="_blank" rel="noopener noreferrer">${type}</a>`;
210
217
  const image = `<img src="${img_src}" width="80px" onerror="this.onerror=null;this.src='img/unavailable.png';">`,
211
218
  nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
212
- battery_cls = getBatteryCls(dev.battery),
219
+ battery_cls = (isActive ? getBatteryCls(dev.battery):''),
213
220
  lqi_cls = getLQICls(dev.link_quality),
214
- battery = (dev.battery) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
215
- lq = (dev.link_quality) > 0 ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>` : '',
216
- status = (dev.link_quality) > 0 ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`,
221
+ battery = (dev.battery && isActive) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
222
+ lq = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>` : '',
223
+ status = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (isActive ? `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`:''),
217
224
  info = `<div style="min-height:88px; font-size: 0.8em" class="truncate">
218
225
  <ul>
219
226
  <li><span class="labelinfo">ieee:</span><span>0x${id.replace(namespace+'.', '')}</span></li>
@@ -223,10 +230,11 @@ function getCard(dev) {
223
230
  </ul>
224
231
  </div>`,
225
232
  permitJoinBtn = (dev.info && dev.info.device._type == 'Router') ? '<button name="join" class="btn-floating btn-small waves-effect waves-light right hoverable green"><i class="material-icons tiny">leak_add</i></button>' : '',
233
+ deactBtn = `<button name="swapactive" class="right btn-flat btn-small tooltipped" title="${(isActive?'Deactivate':'Activate')}"><i class="material-icons icon-${(isActive?'red':'green')}">power_settings_new</i></button>`,
226
234
  infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '';
227
235
  const dashCard = getDashCard(dev);
228
236
  const card = `<div id="${id}" class="device">
229
- <div class="card hoverable flipable">
237
+ <div class="card hoverable flipable ${isActive?'':'bg_red'}">
230
238
  <div class="front face">${dashCard}</div>
231
239
  <div class="back face">
232
240
  <div class="card-content zcard">
@@ -254,6 +262,10 @@ function getCard(dev) {
254
262
  <button name="edit" class="right btn-flat btn-small">
255
263
  <i class="material-icons icon-green">edit</i>
256
264
  </button>
265
+ <button name="reconfigure" class="right btn-flat btn-small tooltipped" title="Reconfigure">
266
+ <i class="material-icons icon-red">sync</i>
267
+ </button>
268
+ ${deactBtn}
257
269
  ${permitJoinBtn}
258
270
  </div>
259
271
  </div>
@@ -344,25 +356,96 @@ function cleanConfirmation() {
344
356
  Materialize.updateTextFields();
345
357
  }
346
358
 
359
+ function EndPointIDfromEndPoint(ep)
360
+ {
361
+ if (ep && ep.deviceIeeeAddress && ep.ID)
362
+ return `${ep.deviceIeeeAddress}:${ep.ID}`;
363
+ return 'unidentified';
364
+ }
365
+
347
366
  function editName(id, name) {
367
+ console.log('editName called with '+name);
348
368
  const dev = devices.find((d) => d._id == id);
349
369
  $('#modaledit').find("input[id='d_name']").val(name);
350
- if (dev.info && dev.info.device._type == 'Router') {
351
- list2select('#d_groups', groups, devGroups[id] || []);
352
- $('#modaledit').find('.input-field.groups').removeClass('hide');
353
- } else {
354
- $('#modaledit').find('.input-field.groups').addClass('hide');
355
- }
370
+ // if (dev.info && dev.info.device._type == 'Router') {
371
+ const groupables = [];
372
+ if (dev && dev.info && dev.info.endpoints) {
373
+ for (const ep of dev.info.endpoints) {
374
+ if (ep.inputClusters.includes(4)) {
375
+ groupables.push({ epid:EndPointIDfromEndPoint(ep), ep:ep, memberOf:[]});
376
+ }
377
+ }
378
+ }
379
+ const numEP = groupables.length;
380
+ // console.log('groupables: '+JSON.stringify(groupables));
381
+ $('#modaledit').find('.row.epid0').addClass('hide');
382
+ $('#modaledit').find('.row.epid1').addClass('hide');
383
+ $('#modaledit').find('.row.epid2').addClass('hide');
384
+ $('#modaledit').find('.row.epid3').addClass('hide');
385
+ if (numEP > 0) {
386
+ // go through all the groups. Find the ones to list for each groupable
387
+ if (numEP == 1) {
388
+ $('#modaledit').find('.endpointid').addClass('hide');
389
+ }
390
+ else {
391
+ $('#modaledit').find('.endpointid').removeClass('hide');
392
+ }
393
+ for (const d of devices) {
394
+ if (d && d.common && d.common.type == 'group') {
395
+ if (d.hasOwnProperty("memberinfo")) {
396
+ for (const member of d.memberinfo) {
397
+ const epid = EndPointIDfromEndPoint(member.ep)
398
+ for (var i=0;i<groupables.length;i++) {
399
+ if (groupables[i].epid == epid) {
400
+ groupables[i].memberOf.push(d.native.id.replace('group_', ''));
401
+ }
402
+ }
403
+ }
404
+ }
405
+ }
406
+ }
407
+ console.log("groupables: " + JSON.stringify(groupables));
408
+ for (var i = 0;i<groupables.length;i++)
409
+ {
410
+ if (i > 1) {
411
+ $('#modaledit').find("translate.device_with_endpoint").innerHtml = name + ' ' + groupables[i].epid;
412
+ }
413
+ $('#modaledit').find('.row.epid'+i).removeClass('hide');
414
+ list2select('#d_groups_ep'+i, groups, groupables[i].memberOf || []);
415
+ }
416
+ }
417
+ // } else {
418
+ // $('#modaledit').find('.input-field.endpoints').addClass('hide');
419
+ // $('#modaledit').find('.input-field.groups').addClass('hide');
420
+ // }
356
421
  $("#modaledit a.btn[name='save']").unbind('click');
357
422
  $("#modaledit a.btn[name='save']").click(() => {
358
- const newName = $('#modaledit').find("input[id='d_name']").val(),
359
- newGroups = $('#d_groups').val();
360
- updateDev(id, newName, newGroups);
423
+ const newName = $('#modaledit').find("input[id='d_name']").val();
424
+ const groupsbyid = {};
425
+ if (groupables.length > 0) {
426
+ for (var i = 0;i<groupables.length;i++) {
427
+ const ng = $('#d_groups_ep'+i).val();
428
+ if (ng.toString() != groupables[i].memberOf.toString())
429
+ groupsbyid[groupables[i].ep.ID] = GenerateGroupChange(groupables[i].memberOf, ng);
430
+ }
431
+ }
432
+ console.log('grpid ' + JSON.stringify(groupsbyid));
433
+ updateDev(id, newName, groupsbyid);
361
434
  });
362
435
  $('#modaledit').modal('open');
363
436
  Materialize.updateTextFields();
364
437
  }
365
438
 
439
+ function GenerateGroupChange(oldmembers, newmembers)
440
+ {
441
+ let grpchng = [];
442
+ for (const oldg of oldmembers)
443
+ if (!newmembers.includes(oldg)) grpchng.push('-'+oldg);
444
+ for (const newg of newmembers)
445
+ if (!oldmembers.includes(newg)) grpchng.push(newg)
446
+ return grpchng;
447
+ }
448
+
366
449
  function deleteDevice(id, force) {
367
450
  sendTo(namespace, 'deleteDevice', {id: id, force: force}, function (msg) {
368
451
  closeWaitingDialog();
@@ -434,7 +517,6 @@ function showDevices() {
434
517
  }
435
518
  return 0;
436
519
  });
437
- devGroups = {};
438
520
  for (let i=0;i < devices.length; i++) {
439
521
  const d = devices[i];
440
522
  if (!d.info) {
@@ -451,7 +533,7 @@ function showDevices() {
451
533
  } else {
452
534
  //if (d.groups && d.info && d.info.device._type == "Router") {
453
535
  if (d.groups) {
454
- devGroups[d._id] = d.groups;
536
+ // devGroups[d._id] = d.groups;
455
537
  if (typeof d.groups.map == 'function') {
456
538
  d.groupNames = d.groups.map(item=>{
457
539
  return groups[item] || '';
@@ -525,7 +607,7 @@ function showDevices() {
525
607
  const dev_block = $(this).parents('div.device'),
526
608
  id = dev_block.attr('id').replace(namespace+'.group_', ''),
527
609
  name = getDevName(dev_block);
528
- editGroupName(id, name);
610
+ editGroupName(id, name, false);
529
611
  });
530
612
  $("button.btn-floating[name='join']").click(function() {
531
613
  const dev_block = $(this).parents('div.device');
@@ -544,6 +626,14 @@ function showDevices() {
544
626
  $("a.btn-flat[name='close']").click((e) => {
545
627
  closeReval(e);
546
628
  });
629
+ $(".card-reveal-buttons button[name='reconfigure']").click(function() {
630
+ const dev_block = $(this).parents('div.device');
631
+ reconfigureDlg(getDevId(dev_block));
632
+ });
633
+ $(".card-reveal-buttons button[name='swapactive']").click(function() {
634
+ const dev_block = $(this).parents('div.device');
635
+ swapActive(getDevId(dev_block));
636
+ });
547
637
 
548
638
  showNetworkMap(devices, map);
549
639
  translateAll();
@@ -656,7 +746,7 @@ function getDevices() {
656
746
  }
657
747
 
658
748
  function getDeviceCards() {
659
- return $('#devices .device');
749
+ return $('#devices .device').not(".group");
660
750
  }
661
751
 
662
752
  function getDeviceCard(devId) {
@@ -758,13 +848,14 @@ function load(settings, onChange) {
758
848
  });
759
849
 
760
850
  $('#add_group').click(function() {
851
+ // showGroupList(true);
761
852
  const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
762
- editGroupName(maxind+1, '');
853
+ editGroupName(maxind+1, 'Group ' + maxind+1, true);
763
854
  });
764
855
  $('#add_grp_btn').click(function() {
856
+ // showGroupList(true);
765
857
  const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
766
- editGroupName(maxind+1, '');
767
- getDevices();
858
+ editGroupName(maxind+1, 'Group ' + maxind+1, true);
768
859
  });
769
860
 
770
861
  $(document).ready(function() {
@@ -1702,7 +1793,7 @@ function showGroups() {
1702
1793
  $("a.btn-floating[name='groupedit']").click(function() {
1703
1794
  const index = $(this).attr('id'),
1704
1795
  name = groups[index];
1705
- editGroupName(index, name);
1796
+ editGroupName(index, name, false);
1706
1797
  });
1707
1798
  $("a.btn-floating[name='groupdelete']").click(function() {
1708
1799
  const index = $(this).attr('id'),
@@ -1711,8 +1802,35 @@ function showGroups() {
1711
1802
  });
1712
1803
  }
1713
1804
 
1714
- function editGroupName(id, name) {
1805
+ function editGroupName(id, name, isnew) {
1806
+ //const dev = devices.find((d) => d._id == id);
1807
+ //console.log('devices: '+ JSON.stringify(devices));
1808
+ const groupables = [];
1809
+ for (const d of devices) {
1810
+ if (d && d.info && d.info.endpoints) {
1811
+ for (const ep of d.info.endpoints) {
1812
+ if (ep.inputClusters.includes(4))
1813
+ {
1814
+ groupables.push(ep);
1815
+ }
1816
+ }
1817
+ }
1818
+ //console.log('device ' + JSON.stringify(d));
1819
+ }
1820
+
1715
1821
  //var text = 'Enter new name for "'+name+'" ('+id+')?';
1822
+ if (isnew) {
1823
+ $('#groupedit').find('.editgroup').addClass('hide');
1824
+ $('#groupedit').find('.addgroup').removeClass('hide');
1825
+ $('#groupedit').find('.input-field.members').addClass('hide');
1826
+ $('#groupedit').find('.input-field.groupid').removeClass('hide');
1827
+ }
1828
+ else {
1829
+ $('#groupedit').find('.editgroup').removeClass('hide');
1830
+ $('#groupedit').find('.addgroup').addClass('hide');
1831
+ $('#groupedit').find('.input-field.members').removeClass('hide');
1832
+ $('#groupedit').find('.input-field.groupid').addClass('hide');
1833
+ }
1716
1834
  $('#groupedit').find("input[id='g_index']").val(id);
1717
1835
  $('#groupedit').find("input[id='g_name']").val(name);
1718
1836
  $("#groupedit a.btn[name='save']").unbind('click');
@@ -1721,7 +1839,7 @@ function editGroupName(id, name) {
1721
1839
  newName = $('#groupedit').find("input[id='g_name']").val();
1722
1840
  updateGroup(id, newId, newName);
1723
1841
  // showGroups();
1724
- getDevices();
1842
+ // getDevices();
1725
1843
  });
1726
1844
  $('#groupedit').modal('open');
1727
1845
  Materialize.updateTextFields();
@@ -1735,7 +1853,7 @@ function deleteGroupConfirmation(id, name) {
1735
1853
  $("#modaldelete a.btn[name='yes']").click(() => {
1736
1854
  deleteGroup(id);
1737
1855
  // showGroups();
1738
- getDevices();
1856
+ // getDevices();
1739
1857
  });
1740
1858
  $('#modaldelete').modal('open');
1741
1859
  }
@@ -1743,12 +1861,22 @@ function deleteGroupConfirmation(id, name) {
1743
1861
  function updateGroup(id, newId, newName) {
1744
1862
  delete groups[id];
1745
1863
  groups[newId] = newName;
1746
- sendTo(namespace, 'renameGroup', { id: newId, name: newName} );
1864
+ sendTo(namespace, 'renameGroup', { id: newId, name: newName}, function(msg) {
1865
+ if (msg && ms.error) {
1866
+ showMessage(msg.error, _('Error'));
1867
+ }
1868
+ getDevices();
1869
+ });
1747
1870
  }
1748
1871
 
1749
1872
  function deleteGroup(id) {
1750
1873
  delete groups[id];
1751
- sendTo(namespace, 'deleteGroup', id );
1874
+ sendTo(namespace, 'deleteGroup', id , function(msg) {
1875
+ if (msg && ms.error) {
1876
+ showMessage(msg.error, _('Error'));
1877
+ }
1878
+ getDevices();
1879
+ });
1752
1880
  }
1753
1881
 
1754
1882
  function updateDev(id, newName, newGroups) {
@@ -1756,6 +1884,24 @@ function updateDev(id, newName, newGroups) {
1756
1884
  if (dev && dev.common.name != newName) {
1757
1885
  renameDevice(id, newName);
1758
1886
  }
1887
+ const keys = Object.keys(newGroups)
1888
+ if (keys && keys.length)
1889
+ {
1890
+ sendTo(namespace, 'updateGroupMembership', { id: id, groups: newGroups }, function (msg) {
1891
+ closeWaitingDialog();
1892
+ if (msg && msg.error) {
1893
+ showMessage(msg.error, _('Error'));
1894
+ }
1895
+ else {
1896
+ // save dev-groups on success
1897
+ dev.groups = newGroups;
1898
+ }
1899
+ showDevices();
1900
+ });
1901
+ showWaitingDialog('Updating group memberships', 10);
1902
+
1903
+ }
1904
+ /*
1759
1905
  if (dev.info.device._type == 'Router') {
1760
1906
  const oldGroups = devGroups[id] || [];
1761
1907
  if (oldGroups.toString() != newGroups.toString()) {
@@ -1772,6 +1918,7 @@ function updateDev(id, newName, newGroups) {
1772
1918
  });
1773
1919
  }
1774
1920
  }
1921
+ */
1775
1922
  }
1776
1923
 
1777
1924
  function resetConfirmation() {
@@ -2166,7 +2313,7 @@ function genDevInfo(device) {
2166
2313
  const dev = (device && device.info) ? device.info.device : undefined;
2167
2314
  const mapped = (device && device.info) ? device.info.mapped : undefined;
2168
2315
  if (!dev) return `<div class="truncate">No info</div>`;
2169
- const genRow = function(name, value) {
2316
+ const genRow = function(name, value, refresh) {
2170
2317
  if (value === undefined) {
2171
2318
  return '';
2172
2319
  } else {
@@ -2185,7 +2332,7 @@ function genDevInfo(device) {
2185
2332
  }).join('');
2186
2333
  }
2187
2334
  };
2188
- const modelUrl = (!mapped) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${mapped.model}.html" target="_blank" rel="noopener noreferrer">${mapped.model}</a>`;
2335
+ const modelUrl = (!mapped) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${sanitizeModelParameter(mapped.model)}.html" target="_blank" rel="noopener noreferrer">${mapped.model}</a>`;
2189
2336
  const mappedInfo = (!mapped) ? '' :
2190
2337
  `<div style="font-size: 0.9em">
2191
2338
  <ul>
@@ -2230,7 +2377,7 @@ function genDevInfo(device) {
2230
2377
  ${genRow('date code', dev._dateCode)}
2231
2378
  ${genRow('build', dev._softwareBuildID)}
2232
2379
  ${genRow('interviewed', dev._interviewCompleted)}
2233
- ${genRow('configured', (dev.meta.configured === 1))}
2380
+ ${genRow('configured', (dev.meta.configured === 1), true)}
2234
2381
  </ul>
2235
2382
  </div>
2236
2383
  </div>
@@ -2246,17 +2393,60 @@ function showDevInfo(id){
2246
2393
  $('#modaldevinfo').modal('open');
2247
2394
  }
2248
2395
 
2396
+ function showGroupList(show){
2397
+ const htmlsections = [];
2398
+ for (const groupid in devGroups) {
2399
+ const dev = devGroups[groupid];
2400
+ const grpname = (dev.common && dev.common.name?dev.common.name:'Group '+groupid);
2401
+ const selectables = [];
2402
+ const members = [];
2403
+ if (dev && dev.memberinfo)
2404
+ {
2405
+ selectables.push(`<select id="members_${groupid}" multiple>`)
2406
+ for (let m=0;m<dev.memberinfo.length; m++) {
2407
+ members.push(`${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (${dev.memberinfo[m].ieee})`)
2408
+ selectables.push(`<option value="${m}">${dev.memberinfo[m].device}.${dev.memberinfo[m].epid} (...${dev.memberinfo[m].ieee.slice(-4)})</option>`);
2409
+ }
2410
+ selectables.push('</select>');
2411
+ }
2412
+ htmlsections.push(`
2413
+ <div class="row">
2414
+ <div class="col s4 m4 l4">
2415
+ <h5>${grpname}<h5>
2416
+ </div>
2417
+ <div class=col s7 m7 l7">
2418
+ ${members.join('<br>')}
2419
+ </div>
2420
+ </div>
2421
+ `);
2422
+ }
2423
+
2424
+ $('#grouplist').html(htmlsections.join(''));
2425
+ $('#add').click(function() {
2426
+ const maxind = parseInt(Object.getOwnPropertyNames(groups).reduce((a,b) => a>b ? a : b, 0));
2427
+ editGroupName(maxind+1, 'Group ' + maxind+1, true);
2428
+ showGroupList(false);
2429
+ });
2430
+
2431
+ $("#modalgrouplist a.btn[name='save']").unbind('click');
2432
+ $("#modalgrouplist a.btn[name='save']").click(() => {
2433
+ });
2434
+ if (show) $('#modalgrouplist').modal('open');
2435
+ }
2436
+
2437
+ let waitingTimeout, waitingInt;
2249
2438
 
2250
2439
  function showWaitingDialog(text, timeout){
2251
2440
  let countDown = timeout;
2252
- const waitingInt = setInterval(function() {
2441
+ waitingInt = setInterval(function() {
2253
2442
  countDown -= 1;
2254
2443
  const percent = 100-100*countDown/timeout;
2255
2444
  $('#waiting_progress_line').css('width', `${percent}%`);
2256
2445
  }, 1000);
2257
- setTimeout(function() {
2446
+ waitingTimeout = setTimeout(function() {
2258
2447
  $('#waiting_progress_line').css('width', `0%`);
2259
2448
  clearTimeout(waitingInt);
2449
+ clearTimeout(waitingTimeout);
2260
2450
  $('#modalWaiting').modal('close');
2261
2451
  }, timeout*1000);
2262
2452
  $('#waiting_message').text(text);
@@ -2264,6 +2454,8 @@ function showWaitingDialog(text, timeout){
2264
2454
  }
2265
2455
 
2266
2456
  function closeWaitingDialog(){
2457
+ if (waitingInt) clearTimeout(waitingInt);
2458
+ if (waitingTimeout) clearTimeout(waitingTimeout);
2267
2459
  $('#modalWaiting').modal('close');
2268
2460
  }
2269
2461
 
@@ -2427,10 +2619,10 @@ function showExclude() {
2427
2619
  const exclude_dev = devices.find((d) => d.common.type == exclude_id) || {common: {name: exclude_id}};
2428
2620
  // exclude_icon = (exclude_dev.icon) ? `<img src="${exclude_dev.icon}" width="64px">` : '';
2429
2621
 
2430
- const modelUrl = (!exclude_id) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${exclude_id}.html" target="_blank" rel="noopener noreferrer">${exclude_id}</a>`;
2622
+ const modelUrl = (!exclude_id) ? '' : `<a href="https://www.zigbee2mqtt.io/devices/${sanitizeModelParameter(exclude_id)}.html" target="_blank" rel="noopener noreferrer">${exclude_id}</a>`;
2431
2623
 
2432
2624
  const card = `
2433
- <div id="${exclude_id}" class="exclude col s12 m6 l4 xl3">
2625
+ <div id="${exclude_id}" class="exclude col s12 m6 l4 xl3" style="height: 135px;padding-bottom: 10px;">
2434
2626
  <div class="card hoverable">
2435
2627
  <div class="card-content zcard">
2436
2628
  <i class="left"><img src="${exclude_dev.icon}" width="64px" onerror="this.onerror=null;this.src='img/unavailable.png';"></i>
@@ -2546,6 +2738,7 @@ function getDashCard(dev, groupImage) {
2546
2738
  id = dev._id,
2547
2739
  type = dev.common.type,
2548
2740
  img_src = (groupImage ? groupImage: dev.icon || dev.common.icon),
2741
+ isActive = (dev.common.deactivated ? false : true),
2549
2742
  rooms = [],
2550
2743
  lang = systemLang || 'en';
2551
2744
  const paired = (dev.paired) ? '' : '<i class="material-icons right">leak_remove</i>';
@@ -2555,12 +2748,12 @@ function getDashCard(dev, groupImage) {
2555
2748
  nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined,
2556
2749
  battery_cls = getBatteryCls(dev.battery),
2557
2750
  lqi_cls = getLQICls(dev.link_quality),
2558
- battery = (dev.battery) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
2559
- lq = (dev.link_quality) > 0 ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>` : '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>',
2560
- status = (dev.link_quality) > 0 ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (groupImage ? '': `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`),
2561
- permitJoinBtn = (dev.info && dev.info.device._type == 'Router') ? '<button name="join" class="btn-floating btn-small waves-effect waves-light right hoverable green"><i class="material-icons tiny">leak_add</i></button>' : '',
2751
+ battery = (dev.battery && isActive) ? `<div class="col tool"><i id="${rid}_battery_icon" class="material-icons ${battery_cls}">battery_std</i><div id="${rid}_battery" class="center" style="font-size:0.7em">${dev.battery}</div></div>` : '',
2752
+ lq = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_icon" class="material-icons ${lqi_cls}">network_check</i><div id="${rid}_link_quality" class="center" style="font-size:0.7em">${dev.link_quality}</div></div>` : (isActive ? '<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>':''),
2753
+ status = (dev.link_quality > 0 && isActive) ? `<div class="col tool"><i class="material-icons icon-green">check_circle</i></div>` : (groupImage || !isActive ? '': `<div class="col tool"><i class="material-icons icon-black">leak_remove</i></div>`),
2754
+ permitJoinBtn = (isActive && dev.info && dev.info.device._type == 'Router') ? '<button name="join" class="btn-floating btn-small waves-effect waves-light right hoverable green"><i class="material-icons tiny">leak_add</i></button>' : '',
2562
2755
  infoBtn = (nwk) ? `<button name="info" class="left btn-flat btn-small"><i class="material-icons icon-blue">info</i></button>` : '',
2563
- idleTime = (dev.link_quality_lc > 0) ? `<div class="col tool"><i id="${rid}_link_quality_lc_icon" class="material-icons idletime">access_time</i><div id="${rid}_link_quality_lc" class="center" style="font-size:0.7em">${getIdleTime(dev.link_quality_lc)}</div></div>` : '';
2756
+ idleTime = (dev.link_quality_lc > 0 && isActive) ? `<div class="col tool"><i id="${rid}_link_quality_lc_icon" class="material-icons idletime">access_time</i><div id="${rid}_link_quality_lc" class="center" style="font-size:0.7em">${getIdleTime(dev.link_quality_lc)}</div></div>` : '';
2564
2757
  const info = (dev.statesDef) ? dev.statesDef.map((stateDef)=>{
2565
2758
  const id = stateDef.id;
2566
2759
  const sid = id.split('.').join('_');
@@ -2587,7 +2780,7 @@ function getDashCard(dev, groupImage) {
2587
2780
  return `<li><span class="label dash truncate">${stateDef.name}</span><span id=${sid} oid=${id} class="state">${val}</span></li>`;
2588
2781
  }).join('') : '';
2589
2782
  const dashCard = `
2590
- <div class="card-content zcard">
2783
+ <div class="card-content zcard ${isActive?'':'bg_red'}">
2591
2784
  <div class="flip" style="cursor: pointer">
2592
2785
  <span class="top right small" style="border-radius: 50%">
2593
2786
  ${idleTime}
@@ -2600,7 +2793,7 @@ function getDashCard(dev, groupImage) {
2600
2793
  <i class="left">${image}</i>
2601
2794
  <div style="min-height:88px; font-size: 0.8em; height: 130px; overflow-y: auto" class="truncate">
2602
2795
  <ul>
2603
- ${info}
2796
+ ${(isActive?info:'Device deactivated')}
2604
2797
  </ul>
2605
2798
  </div>
2606
2799
  <div class="footer right-align"></div>
@@ -2698,3 +2891,36 @@ function removeDevice(id) {
2698
2891
  }
2699
2892
  }
2700
2893
  }
2894
+
2895
+ function swapActive(id) {
2896
+ const dev = getDeviceByID(id);
2897
+ if (dev && dev.common) {
2898
+ dev.common.deactivated = !(dev.common.deactivated)
2899
+ sendTo(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {
2900
+ showDevices();
2901
+ });
2902
+ }
2903
+ }
2904
+
2905
+ function reconfigureDlg(id) {
2906
+ const text = translateWord(`Do you really want to reconfigure device?`);
2907
+ $('#modalreconfigure').find('p').text(text);
2908
+ $("#modalreconfigure a.btn[name='yes']").unbind('click');
2909
+ $("#modalreconfigure a.btn[name='yes']").click(() => {
2910
+ reconfigureDevice(id, force);
2911
+ });
2912
+ $('#modalreconfigure').modal('open');
2913
+ Materialize.updateTextFields();
2914
+ }
2915
+
2916
+ function reconfigureDevice(id) {
2917
+ sendTo(namespace, 'reconfigure', {id: id}, function (msg) {
2918
+ closeWaitingDialog();
2919
+ if (msg) {
2920
+ if (msg.error) {
2921
+ showMessage(msg.error, _('Error'));
2922
+ }
2923
+ }
2924
+ });
2925
+ showWaitingDialog('Device is being reconfigure', 30);
2926
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file