iobroker.zigbee 3.2.5 → 3.3.1-alpha.0

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
@@ -19,10 +19,12 @@ let devices = [],
19
19
  networkEvents,
20
20
  responseCodes = false,
21
21
  localConfigData = {},
22
+ shownMap = 0,
22
23
  groups = {},
23
24
  devGroups = {}, // eslint-disable-line prefer-const
24
25
  binding = [],
25
26
  excludes = [],
27
+ tabShown = 0,
26
28
  coordinatorinfo = {
27
29
  installSource: 'IADefault_1',
28
30
  channel: '-1',
@@ -109,6 +111,7 @@ function UpdateAdapterAlive(state) {
109
111
  $('#ErrorNotificationBtn').removeClass('disabled');
110
112
  $('#show_errors_btn').removeClass('disabled');
111
113
  $('#download_icons_btn').removeClass('disabled');
114
+ $('#rebuild_states_btn').removeClass('disabled');
112
115
  $('#pairing').removeClass('disabled');
113
116
  }
114
117
  else {
@@ -121,6 +124,7 @@ function UpdateAdapterAlive(state) {
121
124
  $('#show_errors_btn').addClass('disabled');
122
125
  $('#pairing').addClass('disabled');
123
126
  $('#download_icons_btn').addClass('disabled');
127
+ $('#rebuild_states_btn').addClass('disabled');
124
128
  }
125
129
  connectionStatus.connected = state;
126
130
  }
@@ -237,12 +241,15 @@ function getModelData(data, models, keys) {
237
241
  const e_btn_tip = `edit model ${key}`;
238
242
  const d_btn = btnParam(d_btn_name, d_btn_tip, foldData.devices ? 'expand_less' : 'expand_more', false);
239
243
  const e_btn = btnParam(e_btn_name, e_btn_tip, 'edit', 'green', false)
244
+ const legacy = model.setOptions?.options?.legacy ? 'Legacy' : 'Exposed'
245
+
240
246
  LocalDataDisplayValues.buttonSet.add(d_btn_name);
241
247
  LocalDataDisplayValues.buttonSet.add(e_btn_name);
242
- const devtxt = (model.devices.length && !foldData.devices) ? `${model.devices.length} ${model.model.type}${model.devices.length > 1 ? 's' : ''}` : '';
248
+ const devtxt = (model.devices.length) ? `${model.devices.length} ${model.model.type}${model.devices.length > 1 ? 's' : ''}` : '';
243
249
  Html.push(`<tr id="datarowodd">
244
- <td rowspan="${numrows}" width="15%"><img src=${model.model.icon} class="dev_list"></td>
245
- <td colspan="2">Model ${key}</td><td>${devtxt}</td>
250
+ <td rowspan="${numrows}" width="10%"><img src=${model.model.icon} class="dev_list"></td>
251
+ <td rowspan="${numrows}" width="15%">${legacy} model<br>${key}</td>
252
+ <td colspan="3">${devtxt}</td>
246
253
  <td>${d_btn}&nbsp;${e_btn}</td></tr>`)
247
254
  let cnt = 0;
248
255
  if (foldData.devices) {
@@ -258,7 +265,7 @@ function getModelData(data, models, keys) {
258
265
  //const bn = btnParam(`d_delete_${devieee}`, `delete device ${devieee}`, 'delete', 'red darken-4', false);
259
266
  const bna = btnParam(`d_delall_${k}-${devieee}`, `completely delete device ${devieee}`, 'delete_forever', 'red accent-4', false);
260
267
  const bta = !dev.common.deactivated ? btnParam(`d_disen_${k}-${devieee}`, `disable device ${devieee}`, 'power_settings_new', 'green accent-4', false) : btnParam(`d_disen_${k}-${devieee}`, `enable device ${devieee}`, 'power_settings_new', 'red accent-4', false);
261
- Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}${dev.common.deactivated ? '_red' : ''}"><td width="1%"><i class="material-icons small">devices</i></td><td width="25%">${devieee}</td><td width="45%">${dev.common.name}</td><td width="10%">${bna}${bta}<td></tr>`)
268
+ Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}${dev.common.deactivated ? '_red' : ''}"><td width="1%"><i class="material-icons small">devices</i></td><td>${devieee}</td><td>${dev.common.name}</td><td width="10%">${bna}${bta}</td></tr>`)
262
269
  isOdd = !isOdd;
263
270
  }
264
271
  }
@@ -266,10 +273,10 @@ function getModelData(data, models, keys) {
266
273
  const o_btn_name = `o_toggle_${k}`;
267
274
  const o_btn_tip = `fold / unfold options for Model ${key}`;
268
275
  LocalDataDisplayValues.buttonSet.add(o_btn_name);
269
- const opttxt = (numOptions > 0 && !(foldData.options)) ? `${numOptions} global option${numOptions > 1 ? 's' : ''}` :''
276
+ const opttxt = (numOptions > 0) ? `${numOptions} global option${numOptions > 1 ? 's' : ''}` :''
270
277
  Html.push(`<tr id="datarowodd">
271
- <td colspan="2">Model ${key}</td><td>${opttxt}</td>
272
- <td>${btnParam(o_btn_name, o_btn_tip, foldData.options ? 'expand_less' : 'expand_more')}</td></tr>`)
278
+ <td colspan="3">${opttxt}</td>
279
+ <td>${btnParam(o_btn_name, o_btn_tip, foldData.options ? 'expand_less' : 'expand_more')}&nbsp;${e_btn}</td></tr>`)
273
280
  if (foldData.options) {
274
281
  let isOdd = false;
275
282
  for (const key of Object.keys(model.setOptions)) {
@@ -278,7 +285,7 @@ function getModelData(data, models, keys) {
278
285
  for (const ok of Object.keys(oo)) {
279
286
  LocalDataDisplayValues.buttonSet.add(`o_delete_${k}-${ok}`);
280
287
  const btn = btnParam(`o_delete_${k}-${ok}`, `delete option ${ok}`, 'delete', 'red darken-4', false);
281
- Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}"><td width="1%"><i class="material-icons small">blur_circular</i></td><td width="25%">${ok}</td><td width="45%" ${oo[ok] === undefined ? 'id="datared">"not set on model"' : '>'+oo[ok]}</td><td>${btn}</td></tr>`)
288
+ Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}"><td width="1%"><i class="material-icons small">blur_circular</i></td><td>${ok}</td><td${oo[ok] === undefined ? 'id="datared">"not set on model"' : '>'+oo[ok]}</td><td width="10%">${btn}</td></tr>`)
282
289
  isOdd = !isOdd;
283
290
  }
284
291
  }
@@ -288,10 +295,10 @@ function getModelData(data, models, keys) {
288
295
  if (key==='icon') {
289
296
  const icontext = model.setOptions[key] === undefined ? 'id="datared">"not set on model"' : `>${model.setOptions[key]}`;
290
297
  const icon = model.setOptions[key]=== undefined ? '' : `<img src=${model.setOptions[key]} height="32px" class="sml_list">`;
291
- Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}"><td width="1%"><i class="material-icons small">blur_circular</i></td><td width="25%">${key}</td><td valign="middle" width="45%" ${icontext}</td><td>${btn}${icon}</td></tr>`)
298
+ Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}"><td width="1%"><i class="material-icons small">blur_circular</i></td><td>${key}</td><td valign="middle" ${icontext}</td><td width="10%">${btn}${icon}</td></tr>`)
292
299
  }
293
300
  else
294
- Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}"><td width="1%"><i class="material-icons small">blur_circular</i></td><td width="25%">${key}</td><td width="45%" ${model.setOptions[key] === undefined ? 'id="datared">"not set on model"' : '>'+model.setOptions[key]}</td><td>${btn}</td></tr>`)
301
+ Html.push(`<tr id="datarow${isOdd ? 'opt':'even'}"><td width="1%"><i class="material-icons small">blur_circular</i></td><td>${key}</td><td ${model.setOptions[key] === undefined ? 'id="datared">"not set on model"' : '>'+model.setOptions[key]}</td><td>${btn}</td></tr>`)
295
302
  isOdd = !isOdd;
296
303
  }
297
304
  }
@@ -336,26 +343,26 @@ function getDeviceData(deviceList, withIcon) {
336
343
 
337
344
  function sortAndFilter(filter, sort) {
338
345
  const fFun = filter || LocalDataDisplayValues.filterMethod;
339
- console.warn('once:='+JSON.stringify(models['m_0'].setOptions))
340
- console.warn('twice:='+ JSON.stringify(models['m_1'].setOptions))
346
+ //console.warn('once:='+JSON.stringify(models['m_0'].setOptions))
347
+ //console.warn('twice:='+ JSON.stringify(models['m_1'].setOptions))
341
348
  let filterMap = LocalDataDisplayValues.sortedKeys = Object.keys(models);
342
349
  if (LocalDataDisplayValues.searchVal && LocalDataDisplayValues.searchVal.length) {
343
350
  filterMap = filterMap.filter((a) => {
344
351
  return models[a]?.model?.model?.toLowerCase().includes(LocalDataDisplayValues.searchVal)
345
352
  });
346
- console.warn(`${JSON.stringify(LocalDataDisplayValues.searchVal)} - ${JSON.stringify(models['m_1'].model)}`);
353
+ //console.warn(`${JSON.stringify(LocalDataDisplayValues.searchVal)} - ${JSON.stringify(models['m_1'].model)}`);
347
354
  }
348
355
  if (typeof fFun == 'function') {
349
- console.warn(`${JSON.stringify(filterMap)} - ${JSON.stringify(models['m_1'].model)}`);
356
+ //console.warn(`${JSON.stringify(filterMap)} - ${JSON.stringify(models['m_1'].model)}`);
350
357
  filterMap = filterMap.filter(fFun);
351
358
  }
352
- console.warn(JSON.stringify(filterMap));
359
+ //console.warn(JSON.stringify(filterMap));
353
360
  const sFun = sort || LocalDataDisplayValues.sortMethod;
354
361
  if (typeof sFun == 'function') {
355
- console.warn(`${JSON.stringify(filterMap)} - ${JSON.stringify(models['m_1'].model)}`);
362
+ //console.warn(`${JSON.stringify(filterMap)} - ${JSON.stringify(models['m_1'].model)}`);
356
363
  filterMap = filterMap.sort(sFun);
357
364
  }
358
- console.warn(JSON.stringify(filterMap));
365
+ //console.warn(JSON.stringify(filterMap));
359
366
  if (typeof filter == 'function') LocalDataDisplayValues.filterMethod = filter;
360
367
  if (typeof sort == 'function') LocalDataDisplayValues.sortMethod = sort;
361
368
  return filterMap;
@@ -373,7 +380,7 @@ function showLocalData() {
373
380
  const Html = [];
374
381
 
375
382
  if (sm) {
376
- Html.push(`<table style="width:100%"><tr id="datatable"><th rowspan="${RowSpan}">&nbsp;</th><th colspan=4></th><th></th><th rowspan="${RowSpan}">&nbsp;</th></tr>`);
383
+ Html.push(`<table style="width:100%"><tr id="datatable"><th rowspan="${RowSpan}" width="10px">&nbsp;</th><th colspan=5></th><th></th><th rowspan="${RowSpan}" width="10px"">&nbsp;</th></tr>`);
377
384
  Html.push(ModelHtml.join(''));
378
385
  }
379
386
  /*else {
@@ -419,16 +426,16 @@ function showLocalData() {
419
426
  editDeviceOptions(models[key], true);
420
427
  })
421
428
  if (item.startsWith('o_delete_')) {
422
- console.warn(`adding click to ${item}`)
429
+ //console.warn(`adding click to ${item}`)
423
430
  $(`#${item}`).click(function () {
424
- console.warn(`clicked ${item}`);
431
+ //console.warn(`clicked ${item}`);
425
432
  const keys = item.replace('o_delete_', '').split('-');
426
433
  const model = models[keys[0]]?.model.model;
427
434
  const option = keys[1];
428
435
  const sOptions = models[keys[0]]?.setOptions || {};
429
436
  const options = models[keys[0]]?.setOptions?.options || {};
430
437
  //options[option] = '##REMOVE##';
431
- console.warn(`clicked ${item} - options are ${JSON.stringify(options)}`);
438
+ //console.warn(`clicked ${item} - options are ${JSON.stringify(options)}`);
432
439
  delete options[option];
433
440
  updateLocalConfigItems(model, sOptions || {}, true);
434
441
  showLocalData();
@@ -440,15 +447,15 @@ function showLocalData() {
440
447
  const option = keys[1];
441
448
  const options = models[keys[0]].setOptions;
442
449
  options[option] = '##REMOVE##';
443
- console.warn(`clicked ${item} - options are ${JSON.stringify(options)}`);
450
+ //console.warn(`clicked ${item} - options are ${JSON.stringify(options)}`);
444
451
  updateLocalConfigItems(model, options || {}, true)
445
452
  delete options[option];
446
453
  showLocalData();
447
454
  })
448
455
  if (item.startsWith('d_disen_')) {
449
- console.warn(`adding click to ${item}`)
456
+ //console.warn(`adding click to ${item}`)
450
457
  $(`#${item}`).click(function () {
451
- console.warn(`clicked ${item}`);
458
+ //console.warn(`clicked ${item}`);
452
459
  const keys = item.replace('d_disen_', '').split('-');
453
460
  const model = models[keys[0]];
454
461
  const device = model.devices.find( (d) => d.native.id === keys[1]);
@@ -460,13 +467,13 @@ function showLocalData() {
460
467
  });
461
468
  }
462
469
  if (item.startsWith('d_delall_')) {
463
- console.warn(`adding click to ${item}`)
470
+ //console.warn(`adding click to ${item}`)
464
471
  $(`#${item}`).click(function () {
465
- console.warn(`clicked ${item}`);
472
+ //console.warn(`clicked ${item}`);
466
473
  const keys = item.replace('d_delall_', '').split('-');
467
474
  const model = models[keys[0]];
468
475
  const device = model.devices.find( (d) => d.native.id === keys[1]);
469
- console.warn(`setting delete confirmation with ${keys[1]} ${models[keys[0]].devices?.length} ${models[keys[0]]?.model.model} `);
476
+ //console.warn(`setting delete confirmation with ${keys[1]} ${models[keys[0]].devices?.length} ${models[keys[0]]?.model.model} `);
470
477
  deleteConfirmation(keys[1], device.common.name, keys[1], models[keys[0]]?.devices?.length <=1 ? models[keys[0]]?.model?.model : undefined);
471
478
  });
472
479
  }
@@ -1002,7 +1009,7 @@ function cleanConfirmation() {
1002
1009
  $('#modalclean a.btn[name=\'yes\']').unbind('click');
1003
1010
  $('#modalclean a.btn[name=\'yes\']').click(() => {
1004
1011
  const force = $('#cforce').prop('checked');
1005
- cleanDeviceStates(force);
1012
+ modifyDeviceStates('clean', force, `${force ? 'Completely r' : 'R'}emoving orphaned states.`);
1006
1013
  });
1007
1014
  $('#modalclean').modal('open');
1008
1015
  Materialize.updateTextFields();
@@ -1029,7 +1036,7 @@ function editGroupMembers(id, name) {
1029
1036
  }
1030
1037
  $('#modaledit').find('.endpoints_for_groups').html(html.join(''));
1031
1038
  for (const groupable of groupables) {
1032
- console.warn(`list 2 select called with ${groupable.ep.ID}, groups ${JSON.stringify(groups)}, groupable ${JSON.stringify(groupable)}`);
1039
+ //console.warn(`list 2 select called with ${groupable.ep.ID}, groups ${JSON.stringify(groups)}, groupable ${JSON.stringify(groupable)}`);
1033
1040
  list2select(`#gk_${groupable.ep.ID || -1}`, groups, groupable.memberOf || []);
1034
1041
  }
1035
1042
  }
@@ -1087,7 +1094,7 @@ function deleteZigbeeDevice(id, force, devOpts, modelOpts) {
1087
1094
  sendToWrapper(namespace, 'deleteZigbeeDevice', {id: id, force: force, dev:devOpts, model:modelOpts}, function (msg) {
1088
1095
  closeWaitingDialog();
1089
1096
  if (msg) {
1090
- if (msg.error) {
1097
+ if (msg.error && msg.error.length) {
1091
1098
  showMessage(msg.error, _('Error'));
1092
1099
  } else {
1093
1100
  getDevices();
@@ -1098,8 +1105,8 @@ function deleteZigbeeDevice(id, force, devOpts, modelOpts) {
1098
1105
  }
1099
1106
 
1100
1107
 
1101
- function cleanDeviceStates(force) {
1102
- sendToWrapper(namespace, 'cleanDeviceStates', {force: force}, function (msg) {
1108
+ function modifyDeviceStates(action, force, message, timeout) {
1109
+ sendToWrapper(namespace, 'modifyDeviceStates', { action, force}, function (msg) {
1103
1110
  closeWaitingDialog();
1104
1111
  if (msg) {
1105
1112
  if (msg.error) {
@@ -1112,7 +1119,7 @@ function cleanDeviceStates(force) {
1112
1119
  }
1113
1120
  }
1114
1121
  });
1115
- showWaitingDialog('Orphaned states are being removed', 10);
1122
+ showWaitingDialog(message, timeout);
1116
1123
  }
1117
1124
 
1118
1125
  function renameDevice(id, name) {
@@ -1490,12 +1497,12 @@ async function editDeviceOptions(id, isModel) {
1490
1497
  device_options[key] = { key:optionName, value:'', isCustom:optionName==='custom', expose:getExposeFromOptions(optionName)};
1491
1498
  idx = dialogData.availableOptions.indexOf(optionName);
1492
1499
  if (idx > -1 && !device_options[key].isCustom) dialogData.availableOptions.splice(idx, 1);
1493
- console.warn(`addOption added ${JSON.stringify(device_options)}`)
1500
+ //console.warn(`addOption added ${JSON.stringify(device_options)}`)
1494
1501
  }
1495
1502
 
1496
1503
 
1497
1504
  function updateOptions(candidates) {
1498
- console.warn(`update Options with ${JSON.stringify(candidates)}`)
1505
+ //console.warn(`update Options with ${JSON.stringify(candidates)}`)
1499
1506
  if (candidates.length > 0) {
1500
1507
  $('#chooseimage').find('.new_options_available').removeClass('hide');
1501
1508
  list2select('#option_Selector', candidates, [], (key, val) => { return val; }, (key, val) => { return val; })
@@ -1509,7 +1516,7 @@ async function editDeviceOptions(id, isModel) {
1509
1516
  for (const k of Object.keys(device_options)) {
1510
1517
  const expose = device_options[k].expose === undefined ? getExposeFromOptions(device_options[k].key) : device_options[k].expose;
1511
1518
  const disabled = device_options[k]?.isCustom ? '' : 'disabled ';
1512
- console.warn(`option for ${k} is ${JSON.stringify(device_options[k])}`);
1519
+ //console.warn(`option for ${k} is ${JSON.stringify(device_options[k])}`);
1513
1520
  html_options.push(`<div class="row">`);
1514
1521
  switch (expose.type) {
1515
1522
  case 'numeric':
@@ -1545,7 +1552,7 @@ async function editDeviceOptions(id, isModel) {
1545
1552
  const oval = $(`#option_value_${key}`).html();
1546
1553
  const val = $(`#option_value_${key}`).html()=== dok.vOn ? dok.vOff : dok.vOn;
1547
1554
  dok.value = val;
1548
- console.warn(`${item} clicked: ${JSON.stringify(dok)} => ${val} from ${oval}`);
1555
+ //console.warn(`${item} clicked: ${JSON.stringify(dok)} => ${val} from ${oval}`);
1549
1556
  $(`#${item}`).html(val);
1550
1557
  });
1551
1558
  }
@@ -1557,7 +1564,7 @@ async function editDeviceOptions(id, isModel) {
1557
1564
  if (device_options[k].expose?.type != 'binary') {
1558
1565
  const value = $(`#option_value_${k}.value`);
1559
1566
  /* if (value.attr('type') === 'checkbox') {
1560
- console.warn(`oval for ${k} : ${device_options[k].value}`);
1567
+ //console.warn(`oval for ${k} : ${device_options[k].value}`);
1561
1568
  value.prop('checked', Boolean(device_options[k].value));
1562
1569
  }
1563
1570
  else*/
@@ -1574,7 +1581,7 @@ async function editDeviceOptions(id, isModel) {
1574
1581
 
1575
1582
  function getExposeFromOptions(option) {
1576
1583
  const rv = dialogData.model.optionExposes.find((expose) => expose.name === option);
1577
- console.warn(`GEFO: ${option} results in ${JSON.stringify(rv)}`);
1584
+ //console.warn(`GEFO: ${option} results in ${JSON.stringify(rv)}`);
1578
1585
  if (rv) return rv;
1579
1586
  return { type:option === 'legacy' ? 'binary' : 'string' };
1580
1587
  }
@@ -1582,15 +1589,15 @@ async function editDeviceOptions(id, isModel) {
1582
1589
  function getOptionsFromUI(_do, _so) {
1583
1590
  const _no = {};
1584
1591
  let changed = false;
1585
- console.warn(`${changed} : ${JSON.stringify(_do)} - ${JSON.stringify(_no)}`)
1592
+ //console.warn(`${changed} : ${JSON.stringify(_do)} - ${JSON.stringify(_no)}`)
1586
1593
  for (const k of Object.keys(_do)) {
1587
1594
  const key = $(`#option_key_${k}`).val();
1588
1595
  if (_do[k].isCustom) _do[k].key = key;
1589
1596
  else if (_do[k].key != key) {
1590
- console.warn(`_illegal Keys: ${key}, ${_do[k].key}`)
1597
+ //console.warn(`_illegal Keys: ${key}, ${_do[k].key}`)
1591
1598
  continue;
1592
1599
  }
1593
- console.warn(`_legal Keys: ${key}, ${_do[k].key}`)
1600
+ //console.warn(`_legal Keys: ${key}, ${_do[k].key}`)
1594
1601
  if (_do[k].expose?.type === 'binary') {
1595
1602
  _do[k].value = $(`#option_value_${k}`).html();
1596
1603
  }
@@ -1599,13 +1606,13 @@ async function editDeviceOptions(id, isModel) {
1599
1606
  _do[k].value = $(`#option_value_${k}`).val();
1600
1607
  }
1601
1608
  if (_do[k].key.length > 0) {
1602
- console.warn(`dok: ${_do[k].key} : ${_do[k].value}`);
1609
+ //console.warn(`dok: ${_do[k].key} : ${_do[k].value}`);
1603
1610
  _no[key] = _do[k].value;
1604
1611
  changed |= (_no[key] != _so[key]);
1605
1612
  }
1606
1613
  }
1607
1614
  changed |= (Object.keys(_no).length != Object.keys(_so).length);
1608
- console.warn(`${changed ? 'changed': 'unchanged'} : ${JSON.stringify(_so)} - ${JSON.stringify(_no)}`)
1615
+ //console.warn(`${changed ? 'changed': 'unchanged'} : ${JSON.stringify(_so)} - ${JSON.stringify(_no)}`)
1609
1616
  if (changed) return _no;
1610
1617
  return undefined;
1611
1618
  }
@@ -1654,6 +1661,7 @@ async function editDeviceOptions(id, isModel) {
1654
1661
  else dialogData.setOptions[k] = id.setOptions[k];
1655
1662
  dialogData.name = id.setOptions.name || id.name || 'unset';
1656
1663
  dialogData.icon = id.setOptions.icon || model.icon || 'img/dummyDevice.jpg';
1664
+ dialogData.defaultIcon = model.icon || `img/${model.model.replace(/\//g, '-')}.png`;
1657
1665
  dialogData.legacyIcon = id.devices[0].legacyIcon;
1658
1666
  id = id.model.model;
1659
1667
  } else
@@ -1664,7 +1672,7 @@ async function editDeviceOptions(id, isModel) {
1664
1672
  dialogData.availableOptions.push(...adapterDefinedOptions)
1665
1673
  dialogData.name = dev.common.name;
1666
1674
  dialogData.icon = dev.common.icon || dev.icon;
1667
- dialogData.default_icon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
1675
+ dialogData.defaultIcon = (dev.common.type === 'group' ? dev.common.modelIcon : `img/${dev.common.type.replace(/\//g, '-')}.png`);
1668
1676
  dialogData.legacyIcon = dev.legacyIcon;
1669
1677
  }
1670
1678
 
@@ -1709,7 +1717,7 @@ async function editDeviceOptions(id, isModel) {
1709
1717
  for (const key in msg.options)
1710
1718
  {
1711
1719
  const idx = dialogData.availableOptions.indexOf(key);
1712
- console.warn(`key ${key} : index : ${idx}`);
1720
+ //console.warn(`key ${key} : index : ${idx}`);
1713
1721
  if (idx > -1) dialogData.availableOptions.splice(idx,1);
1714
1722
  received_options[key]=msg.options[key];
1715
1723
  device_options[`o${cnt}`] = { key:key, value:msg.options[key]}
@@ -1759,7 +1767,7 @@ function HtmlFromInDebugMessages(messages, devID, filter) {
1759
1767
  const buttonList = [];
1760
1768
  const idRed = ' id="dbgred"'
1761
1769
  if (dbgMsghide.has('i_'+devID)) {
1762
- console.warn('in all filtered out')
1770
+ //console.warn('in all filtered out')
1763
1771
  Html.push('&nbsp;')
1764
1772
  } else for (const item of messages) {
1765
1773
  if (item.states.length > 0) {
@@ -1798,7 +1806,7 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1798
1806
  let isodd=true;
1799
1807
  const buttonList = [];
1800
1808
  if (dbgMsghide.has('o_'+devID)) {
1801
- console.warn('out all filtered out')
1809
+ //console.warn('out all filtered out')
1802
1810
  Html.push('&nbsp;')
1803
1811
  }
1804
1812
  else for (const item of messages) {
@@ -1834,7 +1842,7 @@ function HtmlFromOutDebugMessages(messages, devID, filter) {
1834
1842
  }
1835
1843
 
1836
1844
  function displayDebugMessages(msg) {
1837
- console.warn('displayDebugMessages called with '+ JSON.stringify(msg));
1845
+ //console.warn('displayDebugMessages called with '+ JSON.stringify(msg));
1838
1846
  const buttonNames = [];
1839
1847
  const idButtons = [];
1840
1848
  if (msg.byId) {
@@ -2064,6 +2072,7 @@ function getDevices() {
2064
2072
  }
2065
2073
 
2066
2074
  function extractDevicesData(msg) {
2075
+ //console.warn(JSON.stringify(msg.errors));
2067
2076
  devices = msg.devices ? msg.devices : [];
2068
2077
  // check if stashed error messages are sent alongside
2069
2078
  if (msg.clean)
@@ -2103,25 +2112,36 @@ function getNamedColors() {
2103
2112
  });
2104
2113
  }
2105
2114
 
2106
-
2115
+ let map_errors = [];
2107
2116
  function getMap(rebuild) {
2108
- $('#refresh').addClass('disabled');
2109
- if (isHerdsmanRunning) {
2117
+ if (rebuild) $('#refresh').addClass('disabled');
2118
+ if (isHerdsmanRunning || !rebuild) {
2110
2119
  sendToWrapper(namespace, 'getMap', { forcebuild:rebuild}, function (msg) {
2111
2120
  $('#refresh').removeClass('disabled');
2112
2121
  if (msg) {
2113
- if (msg.error) {
2114
- //errorData.push(msg.error);
2115
- isHerdsmanRunning = false;
2116
- updateStartButton();
2117
- } else {
2118
- isHerdsmanRunning = true;
2119
- updateStartButton();
2120
- if (msg.errors.length > 0 && $('#errorCollectionOn').is(':checked')) {
2121
- showMessage(msg.errors.join('<br>'), 'Map generation messages');
2122
+ if (!msg.hasMap) $('#refresh').removeClass('hide');
2123
+ else {
2124
+ $('#refresh').addClass('hide');
2125
+ if (msg.error) {
2126
+ //errorData.push(msg.error);
2127
+ isHerdsmanRunning = false;
2128
+ updateStartButton();
2129
+ } else {
2130
+ isHerdsmanRunning = true;
2131
+ updateStartButton();
2132
+ if (msg.errors.length > 0 && $('#errorCollectionOn').is(':checked')) {
2133
+ $('#map_errors_btn').removeClass('hide');
2134
+ $('#map_errors_btn').unbind('click');
2135
+ map_errors=msg.errors;
2136
+ $('#map_errors_btn').click(() => {
2137
+ showMessage(map_errors.join('<br>'), 'Map generation messages');
2138
+ $('#map_errors_btn').addClass('hide');
2139
+ })
2140
+ }
2141
+ if (map?.timestamp != msg?.timestamp)
2142
+ map = msg;
2143
+ if (rebuild) showNetworkMap(devices, map);
2122
2144
  }
2123
- map = msg;
2124
- showNetworkMap(devices, map);
2125
2145
  }
2126
2146
  }
2127
2147
  });
@@ -2207,6 +2227,7 @@ function load(settings, onChange) {
2207
2227
  //const keepAliveHandle = startKeepalive();
2208
2228
  keepAlive(() => {
2209
2229
  getDevices();
2230
+ getMap(false);
2210
2231
  getNamedColors();
2211
2232
  readNVRamBackup(false);
2212
2233
  sendToWrapper(namespace, 'getGroups', {}, function (data) {
@@ -2257,26 +2278,32 @@ function load(settings, onChange) {
2257
2278
  });
2258
2279
  $('#show_errors_btn').click(function () {
2259
2280
  const errMsgTable = [];
2260
- console.warn(JSON.stringify(errorData));
2281
+ //console.warn(JSON.stringify(errorData));
2261
2282
  if (Object.keys(errorData.errors).length > 0) {
2262
- errMsgTable.push(`<table><tr><th>Message</th><th>#</th><th>last seen</th></tr>`)
2283
+ errMsgTable.push(`<table><tr><th>Message</th><th>#</th><th>first seen</th><th>last seen</th></tr>`)
2263
2284
  for (const err of Object.values(errorData.errors))
2264
- if (err) errMsgTable.push(`<tr><td>${err.message}</td><td>${err.ts.length}</td><td>${new Date(err.ts[err.ts.length-1]).toLocaleTimeString()}</td></tr>`)
2285
+ if (err && err.ts && err.count) {
2286
+ const erridx = err.ts.length > 1 ? 1 : 0
2287
+ errMsgTable.push(`<tr><td>${err.message}</td><td>${err.count}</td><td>${new Date(err.ts[0]).toLocaleTimeString()}</td><td>${new Date(err.ts[erridx]).toLocaleTimeString()}</td></tr>`)
2288
+ }
2265
2289
  errMsgTable.push('</table>');
2266
2290
  }
2267
2291
  if (Object.keys(errorData.unknownModels).length > 0) {
2268
- errMsgTable.push(`<table><tr><th>Unknown Models</th><th>#</th><th>last seen</th></tr>`)
2292
+ errMsgTable.push(`<table><tr><th>Unknown Models</th><th>#</th><th>first seen</th><th>last seen</th></tr>`)
2269
2293
  for (const err of Object.values(errorData.unknownModels))
2270
- errMsgTable.push(`<tr><td>${err.message}</td><td>${err.ts.length}</td><td>${new Date(err.ts[err.ts.length-1]).toLocaleTimeString()}</td></tr>`)
2294
+ if (err && err.ts && err.count) {
2295
+ const erridx = err.ts.length > 1 ? 1 : 0
2296
+ errMsgTable.push(`<tr><td>${err.message}</td><td>${err.count}</td><td>${new Date(err.ts[0]).toLocaleTimeString()}</td><td>${new Date(err.ts[erridx]).toLocaleTimeString()}</td></tr>`)
2297
+ }
2271
2298
  errMsgTable.push('</table>');
2272
2299
  }
2273
- console.warn(JSON.stringify(errMsgTable));
2300
+ //console.warn(JSON.stringify(errMsgTable));
2274
2301
  showMessage(errMsgTable.join(''), 'Stashed error messages', '<a id="delete_errors_btn" class="btn-floating waves-effect waves-light tooltipped center-align hoverable translateT" title="delete Errors"></i class="material-icons icon-black">delete_sweep</i></a>');
2275
2302
  $('#delete_errors_btn').unbind('click')
2276
2303
  $('#delete_errors_btn').click(function () {
2277
2304
  sendToWrapper(namespace, 'clearErrors', {}, function(msg) {
2278
2305
  if (msg) {
2279
- console.warn('msg is ' + JSON.stringify(msg));
2306
+ //console.warn('msg is ' + JSON.stringify(msg));
2280
2307
  errorData = msg;
2281
2308
  $('#show_errors_btn').addClass('hide');
2282
2309
  }
@@ -2288,6 +2315,20 @@ function load(settings, onChange) {
2288
2315
  $('#download_icons_btn').click(function () {
2289
2316
  showMessage(downloadIcons());
2290
2317
  });
2318
+ $('#rebuild_states_btn').click(function () {
2319
+ const text = translateWord('Do you really want to recreate all states ?');
2320
+ $('#modalrebuild').find('p').text(text);
2321
+ $('#cforce_rebuild').prop('checked', true);
2322
+ $('#cforce_rebuild').removeClass('hide');
2323
+ $('#cforcediv').removeClass('hide');
2324
+ $('#modalrebuild a.btn[name=\'yes\']').unbind('click');
2325
+ $('#modalrebuild a.btn[name=\'yes\']').click(() => {
2326
+ const force = $('#cforce_rebuild').prop('checked');
2327
+ modifyDeviceStates('rebuild', force, `${force ? 'Completely r':'R'}ebuilding all device states`, 10);
2328
+ });
2329
+ $('#modalrebuild').modal('open');
2330
+ Materialize.updateTextFields();
2331
+ });
2291
2332
  $('#fw_check_btn').click(function () {
2292
2333
  checkFwUpdate();
2293
2334
  });
@@ -2304,7 +2345,7 @@ function load(settings, onChange) {
2304
2345
  });
2305
2346
 
2306
2347
  $('#refresh').click(function () {
2307
- getMap(false);
2348
+ getMap(true);
2308
2349
  });
2309
2350
  $('#regenerate').click(function () {
2310
2351
  getMap(true);
@@ -2374,7 +2415,17 @@ function load(settings, onChange) {
2374
2415
  Materialize.updateTextFields();
2375
2416
  $('.collapsible').collapsible();
2376
2417
 
2377
- Materialize.Tabs.init($('.tabs'));
2418
+ function new_tab_show_callback() {
2419
+
2420
+ tabShown = M.Tabs.getInstance($('.tabs')).index;
2421
+ if (tabShown === 1 && shownMap === 0) {
2422
+ console.log(`tabShown set to ${tabShown} - showing map for the first time`);
2423
+ showNetworkMap(devices, map);
2424
+ }
2425
+ else console.log(`tabShown set to ${tabShown}`);
2426
+ }
2427
+
2428
+ Materialize.Tabs.init($('.tabs'), {duration: 600, onShow: new_tab_show_callback});
2378
2429
  $('#device-search').keyup(function (event) {
2379
2430
  doFilter(event.target.value.toLowerCase());
2380
2431
  });
@@ -2670,10 +2721,25 @@ socket.on('stateChange', function (id, state) {
2670
2721
  if (state.val === 'Closing network.') {
2671
2722
  getDevices();
2672
2723
  }
2724
+ if (state.val.startsWith('Map')) {
2725
+ if (state.val === 'Map invalidated.') {
2726
+ $('#refresh').removeClass('hide');
2727
+ return;
2728
+ }
2729
+ const numDev = Number(state.val.split(':').pop()) || 0;
2730
+ if (numDev > 0) {
2731
+ $(`#map_generating_btn`).removeClass('hide');
2732
+ if (numDev < 10) $(`#map_generating_btn`).html(`<i class="material-icons large icon-blue">filter_${numDev}</i>`);
2733
+ else $(`#map_generating_btn`).html(`<i class="material-icons large icon-blue">${numDev%2 ? 'filter_9_plus' : 'queue'}</i>`);
2734
+ }
2735
+ else {
2736
+ $('#map_generating_btn').addClass('hide');
2737
+ }
2738
+ }
2673
2739
  }
2674
2740
  } else if (id.match(/\.info\.lasterror$/)) {
2675
2741
  try {
2676
- console.warn(`lasterror is ${JSON.stringify(state)}`)
2742
+ //console.warn(`lasterror is ${JSON.stringify(state)}`)
2677
2743
  const errobj = JSON.parse(state.val);
2678
2744
  let changed = false;
2679
2745
  if (errobj.error) {
@@ -2768,94 +2834,151 @@ function showNetworkMap(devices, map) {
2768
2834
  // create an array with edges
2769
2835
  const edges = [];
2770
2836
 
2771
- if (map.lqis == undefined || map.lqis.length === 0) { // first init
2772
- $('#filterParent, #filterSibl, #filterPrvChild, #filterMesh, #physicsOn').change(function () {
2773
- updateMapFilter();
2774
- });
2837
+ // if (map.lqis == undefined || map.lqis.length === 0) { // first init
2838
+ $('#filterParent, #filterSibl, #filterPrvChild, #filterMesh, #physicsOn').unbind('change');
2839
+ $('#filterParent, #filterSibl, #filterPrvChild, #filterMesh, #physicsOn').change(function () {
2840
+ updateMapFilter();
2841
+ });
2842
+ // }
2843
+ if (tabShown != 1) {
2844
+ console.log(`tabShown is ${tabShown} - map is not visible so we dont generate it.`);
2845
+ return;
2775
2846
  }
2776
2847
 
2777
- const createNode = function (dev, mapEntry) {
2778
- if (dev.common && (dev.common.type == 'group' || dev.common.deactivated)) return undefined;
2779
- const extInfo = (mapEntry && mapEntry.networkAddress) ? `\n (nwkAddr: 0x${mapEntry.networkAddress.toString(16)} | ${mapEntry.networkAddress})` : '';
2780
- const t = dev._id.replace(namespace + '.', '');
2781
- const node = {
2782
- id: dev._id,
2783
- label: (dev.link_quality > 0 ? dev.common.name : `${dev.common.name}\n(disconnected)`),
2784
- title: `${t} ${extInfo}`,
2785
- shape: 'circularImage',
2786
- image: dev.common.icon || dev.icon,
2787
- imagePadding: {top: 5, bottom: 5, left: 5, right: 5},
2788
- color: {background: '#cccccc', highlight: {background: 'white'}},
2789
- font: {color: '#00bb00'},
2790
- borderWidth: 1,
2791
- borderWidthSelected: 4,
2848
+ console.log(`showNetwork Map (previous: ${shownMap} - new: ${map.timestamp} for ${devices.length} devices.`);
2849
+ if (devices.length == 0) return;
2850
+ if (shownMap != map.timestamp) {
2851
+ shownMap = map.timestamp;
2852
+
2853
+ const createNode = function (dev, mapEntry) {
2854
+ if (dev.common && (dev.common.type == 'group' || dev.common.deactivated)) return undefined;
2855
+ const extInfo = (mapEntry && mapEntry.networkAddress) ? `\n (nwkAddr: 0x${mapEntry.networkAddress.toString(16)} | ${mapEntry.networkAddress})` : '';
2856
+ const t = dev._id.replace(namespace + '.', '');
2857
+ const node = {
2858
+ id: dev._id,
2859
+ label: (dev.link_quality > 0 ? dev.common.name : `${dev.common.name}\n(disconnected)`),
2860
+ title: `${t} ${extInfo}`,
2861
+ shape: 'circularImage',
2862
+ image: dev.common.icon || dev.icon,
2863
+ imagePadding: {top: 5, bottom: 5, left: 5, right: 5},
2864
+ color: {background: '#cccccc', highlight: {background: 'white'}},
2865
+ font: {color: '#00bb00'},
2866
+ borderWidth: 1,
2867
+ borderWidthSelected: 4,
2868
+ };
2869
+ if (dev.common && dev.common.type === 'Coordinator') {
2870
+ // node.shape = 'star';
2871
+ node.image = 'zigbee.png';
2872
+ node.label = 'Coordinator';
2873
+ // delete node.color;
2874
+ }
2875
+ //console.warn(`node for device ${JSON.stringify(node)}`)
2876
+ return node;
2792
2877
  };
2793
- if (dev.common && dev.common.type === 'Coordinator') {
2794
- // node.shape = 'star';
2795
- node.image = 'zigbee.png';
2796
- node.label = 'Coordinator';
2797
- // delete node.color;
2798
- }
2799
- //console.warn(`node for device ${JSON.stringify(node)}`)
2800
- return node;
2801
- };
2802
2878
 
2803
- if (map.lqis) {
2804
- map.lqis.forEach((mapEntry) => {
2805
- const dev = getDeviceByIEEE(mapEntry.ieeeAddr);
2806
- if (!dev) {
2807
- return;
2808
- }
2879
+ if (map.lqis) {
2880
+ map.lqis.forEach((mapEntry) => {
2881
+ const dev = getDeviceByIEEE(mapEntry.ieeeAddr);
2882
+ if (!dev) {
2883
+ return;
2884
+ }
2809
2885
 
2810
- let node;
2811
- if (!nodes.hasOwnProperty(mapEntry.ieeeAddr)) { // add node only once
2812
- node = createNode(dev, mapEntry);
2813
- if (node) {
2814
- nodes[mapEntry.ieeeAddr] = node;
2886
+ let node;
2887
+ if (!nodes.hasOwnProperty(mapEntry.ieeeAddr)) { // add node only once
2888
+ node = createNode(dev, mapEntry);
2889
+ if (node) {
2890
+ nodes[mapEntry.ieeeAddr] = node;
2891
+ }
2892
+ } else {
2893
+ node = nodes[mapEntry.ieeeAddr];
2815
2894
  }
2816
- } else {
2817
- node = nodes[mapEntry.ieeeAddr];
2818
- }
2819
- if (node) {
2820
- const parentDev = getDeviceByIEEE(mapEntry.parent);
2821
- const to = parentDev ? parentDev._id : undefined;
2822
- const from = dev._id;
2823
- let label = mapEntry.lqi.toString();
2824
- let linkColor = '#0000ff';
2825
- let edge = edges.find((edge) => {
2826
- return (edge.to == to && edge.from == from);
2827
- });
2828
- const reverse = edges.find((edge) => {
2829
- return (edge.to == from && edge.from == to);
2830
- });
2895
+ if (node) {
2896
+ const parentDev = getDeviceByIEEE(mapEntry.parent);
2897
+ const to = parentDev ? parentDev._id : undefined;
2898
+ const from = dev._id;
2899
+ let label = mapEntry.lqi.toString();
2900
+ let linkColor = '#0000ff';
2901
+ let edge = edges.find((edge) => {
2902
+ return (edge.to == to && edge.from == from);
2903
+ });
2904
+ const reverse = edges.find((edge) => {
2905
+ return (edge.to == from && edge.from == to);
2906
+ });
2831
2907
 
2832
- if (mapEntry.relationship === 0 || mapEntry.relationship === 1) { // 0 - parent, 1 - child
2833
- // // parent/child
2834
- if (mapEntry.status !== 'online') {
2835
- label = label + ' (off)';
2836
- linkColor = '#660000';
2908
+ if (mapEntry.relationship === 0 || mapEntry.relationship === 1) { // 0 - parent, 1 - child
2909
+ // // parent/child
2910
+ if (mapEntry.status !== 'online') {
2911
+ label = label + ' (off)';
2912
+ linkColor = '#660000';
2913
+ }
2914
+ if (mapEntry.lqi < 10) {
2915
+ linkColor = '#ff0000';
2916
+ }
2917
+ } else if (mapEntry.relationship === 2) { // sibling
2918
+ linkColor = '#00bb00';
2919
+ } else if (mapEntry.relationship === 3 && !reverse) { // unknown
2920
+ linkColor = '#aaaaff';
2921
+ } else if (mapEntry.relationship === 4) { // previous child
2922
+ linkColor = '#555555';
2837
2923
  }
2838
- if (mapEntry.lqi < 10) {
2839
- linkColor = '#ff0000';
2924
+ if (reverse) {
2925
+ // update reverse edge
2926
+ edge = reverse;
2927
+ edge.label += '\n' + label;
2928
+ edge.arrows.from = {enabled: false, scaleFactor: 0.5}; // start hidden if node is not selected
2929
+ if (mapEntry.relationship == 1) { //
2930
+ edge.color.color = linkColor;
2931
+ edge.color.highlight = linkColor;
2932
+ }
2933
+ } else if (!edge) {
2934
+ edge = {
2935
+ from: from,
2936
+ to: to,
2937
+ label: label,
2938
+ font: {
2939
+ align: 'middle',
2940
+ size: 0, // start hidden
2941
+ color: linkColor
2942
+ },
2943
+ arrows: {to: {enabled: false, scaleFactor: 0.5}},
2944
+ //arrowStrikethrough: false,
2945
+ color: {
2946
+ color: linkColor,
2947
+ opacity: 0, // start hidden
2948
+ highlight: linkColor
2949
+ },
2950
+ chosen: {
2951
+ edge: (values) => {
2952
+ values.opacity = 1.0;
2953
+ values.toArrow = true; // always existing
2954
+ values.fromArrow = values.fromArrowScale != 1 ? true : false; // simplified, arrow existing if scale is not default value
2955
+ },
2956
+ label: () => {
2957
+ // see onMapSelect workaround
2958
+ // values.size = 10;
2959
+ }
2960
+ },
2961
+ selectionWidth: 0,
2962
+ physics: mapEntry.relationship === 1 ? true : false,
2963
+ relationship: mapEntry.relationship
2964
+ };
2965
+ edges.push(edge);
2840
2966
  }
2841
- } else if (mapEntry.relationship === 2) { // sibling
2842
- linkColor = '#00bb00';
2843
- } else if (mapEntry.relationship === 3 && !reverse) { // unknown
2844
- linkColor = '#aaaaff';
2845
- } else if (mapEntry.relationship === 4) { // previous child
2846
- linkColor = '#555555';
2847
2967
  }
2848
- if (reverse) {
2849
- // update reverse edge
2850
- edge = reverse;
2851
- edge.label += '\n' + label;
2852
- edge.arrows.from = {enabled: false, scaleFactor: 0.5}; // start hidden if node is not selected
2853
- if (mapEntry.relationship == 1) { //
2854
- edge.color.color = linkColor;
2855
- edge.color.highlight = linkColor;
2856
- }
2857
- } else if (!edge) {
2858
- edge = {
2968
+ });
2969
+ }
2970
+ /*
2971
+ if (map.routing) {
2972
+ map.routing.forEach((route)=>{
2973
+ if (!route.nextHop) return;
2974
+ const routeSource = getDeviceByNetwork(route.nextHop);
2975
+ const routeDest = getDeviceByNetwork(route.destination);
2976
+ if (routeSource && routeDest) {
2977
+ const to = routeDest._id;
2978
+ const from = routeSource._id;
2979
+ const label = route.status;
2980
+ const linkColor = '#ff55ff';
2981
+ const edge = {
2859
2982
  from: from,
2860
2983
  to: to,
2861
2984
  label: label,
@@ -2864,13 +2987,14 @@ function showNetworkMap(devices, map) {
2864
2987
  size: 0, // start hidden
2865
2988
  color: linkColor
2866
2989
  },
2867
- arrows: {to: {enabled: false, scaleFactor: 0.5}},
2990
+ arrows: { to: { enabled: false, scaleFactor: 0.5 }},
2868
2991
  //arrowStrikethrough: false,
2869
2992
  color: {
2870
2993
  color: linkColor,
2871
- opacity: 0, // start hidden
2994
+ //opacity: 0, // start hidden
2872
2995
  highlight: linkColor
2873
2996
  },
2997
+ dashes: true,
2874
2998
  chosen: {
2875
2999
  edge: (values) => {
2876
3000
  values.opacity = 1.0;
@@ -2878,129 +3002,82 @@ function showNetworkMap(devices, map) {
2878
3002
  values.fromArrow = values.fromArrowScale != 1 ? true : false; // simplified, arrow existing if scale is not default value
2879
3003
  },
2880
3004
  label: () => {
2881
- // see onMapSelect workaround
3005
+ // see onMapSelect workaround
2882
3006
  // values.size = 10;
2883
3007
  }
2884
3008
  },
2885
3009
  selectionWidth: 0,
2886
- physics: mapEntry.relationship === 1 ? true : false,
2887
- relationship: mapEntry.relationship
3010
+ physics: false,
2888
3011
  };
2889
3012
  edges.push(edge);
2890
3013
  }
2891
- }
2892
- });
2893
- }
2894
- /*
2895
- if (map.routing) {
2896
- map.routing.forEach((route)=>{
2897
- if (!route.nextHop) return;
2898
- const routeSource = getDeviceByNetwork(route.nextHop);
2899
- const routeDest = getDeviceByNetwork(route.destination);
2900
- if (routeSource && routeDest) {
2901
- const to = routeDest._id;
2902
- const from = routeSource._id;
2903
- const label = route.status;
2904
- const linkColor = '#ff55ff';
2905
- const edge = {
2906
- from: from,
2907
- to: to,
2908
- label: label,
2909
- font: {
2910
- align: 'middle',
2911
- size: 0, // start hidden
2912
- color: linkColor
2913
- },
2914
- arrows: { to: { enabled: false, scaleFactor: 0.5 }},
2915
- //arrowStrikethrough: false,
2916
- color: {
2917
- color: linkColor,
2918
- //opacity: 0, // start hidden
2919
- highlight: linkColor
2920
- },
2921
- dashes: true,
2922
- chosen: {
2923
- edge: (values) => {
2924
- values.opacity = 1.0;
2925
- values.toArrow = true; // always existing
2926
- values.fromArrow = values.fromArrowScale != 1 ? true : false; // simplified, arrow existing if scale is not default value
2927
- },
2928
- label: () => {
2929
- // see onMapSelect workaround
2930
- // values.size = 10;
2931
- }
2932
- },
2933
- selectionWidth: 0,
2934
- physics: false,
2935
- };
2936
- edges.push(edge);
2937
- }
2938
- });
2939
- }
2940
- */
3014
+ });
3015
+ }
3016
+ */
2941
3017
 
2942
- const nodesArray = Object.values(nodes);
2943
- // add devices without network links to map
2944
- devices.forEach((dev) => {
2945
- const node = nodesArray.find((node) => {
2946
- return node.id == dev._id;
2947
- });
2948
- if (!node) {
2949
- const node = createNode(dev);
3018
+ const nodesArray = Object.values(nodes);
3019
+ // add devices without network links to map
3020
+ devices.forEach((dev) => {
3021
+ const node = nodesArray.find((node) => {
3022
+ return node.id == dev._id;
3023
+ });
3024
+ if (!node) {
3025
+ const node = createNode(dev);
2950
3026
 
2951
- if (node) {
2952
- node.font = {color: '#ff0000'};
2953
- if (dev.info && dev.info.device && dev.info.device.type == 'Coordinator') {
2954
- node.font = {color: '#00ff00'};
3027
+ if (node) {
3028
+ node.font = {color: '#ff0000'};
3029
+ if (dev.info && dev.info.device && dev.info.device.type == 'Coordinator') {
3030
+ node.font = {color: '#00ff00'};
3031
+ }
3032
+ nodesArray.push(node);
2955
3033
  }
2956
- nodesArray.push(node);
2957
3034
  }
2958
- }
2959
- });
3035
+ });
2960
3036
 
2961
- // create a network
2962
- const container = document.getElementById('map');
2963
- mapEdges = new vis.DataSet(edges);
2964
- const data = {
2965
- nodes: nodesArray,
2966
- edges: mapEdges
2967
- };
3037
+ // create a network
3038
+ const container = document.getElementById('map');
3039
+ mapEdges = new vis.DataSet(edges);
3040
+ const data = {
3041
+ nodes: nodesArray,
3042
+ edges: mapEdges
3043
+ };
2968
3044
 
2969
- network = new vis.Network(container, data, networkOptions);
2970
-
2971
- const onMapSelect = function (event) {
2972
- // workaround for https://github.com/almende/vis/issues/4112
2973
- // may be moved to edge.chosen.label if fixed
2974
- function doSelection(select, edges, data) {
2975
- edges.forEach((edgeId => {
2976
- const id = (typeof edgeId === 'string') ? edgeId : edgeId.id;
2977
- const options = data.edges._data.get(id);
2978
- if (select) {
2979
- options.font.size = 15;
2980
- } else {
2981
- options.font.size = 0;
2982
- }
2983
- network.clustering.updateEdge(id, options);
2984
- }));
2985
- }
3045
+ network = new vis.Network(container, data, networkOptions);
3046
+
3047
+ const onMapSelect = function (event) {
3048
+ // workaround for https://github.com/almende/vis/issues/4112
3049
+ // may be moved to edge.chosen.label if fixed
3050
+ function doSelection(select, edges, data) {
3051
+ edges.forEach((edgeId => {
3052
+ const id = (typeof edgeId === 'string') ? edgeId : edgeId.id;
3053
+ const options = data.edges._data.get(id);
3054
+ if (select) {
3055
+ options.font.size = 15;
3056
+ } else {
3057
+ options.font.size = 0;
3058
+ }
3059
+ network.clustering.updateEdge(id, options);
3060
+ }));
3061
+ }
2986
3062
 
2987
- if (event.hasOwnProperty('previousSelection')) { // unselect previous selected
2988
- doSelection(false, event.previousSelection.edges, this.body.data);
2989
- }
2990
- doSelection(true, event.edges, this.body.data);
2991
- /*
2992
- if (event.nodes) {
2993
- event.nodes.forEach((node)=>{
2994
- //const options = network.clustering.findNode[node];
2995
- network.clustering.updateClusteredNode(
2996
- node, {size: 50}
2997
- );
2998
- });
2999
- }
3000
- */
3001
- };
3002
- network.on('selectNode', onMapSelect);
3003
- network.on('deselectNode', onMapSelect);
3063
+ if (event.hasOwnProperty('previousSelection')) { // unselect previous selected
3064
+ doSelection(false, event.previousSelection.edges, this.body.data);
3065
+ }
3066
+ doSelection(true, event.edges, this.body.data);
3067
+ /*
3068
+ if (event.nodes) {
3069
+ event.nodes.forEach((node)=>{
3070
+ //const options = network.clustering.findNode[node];
3071
+ network.clustering.updateClusteredNode(
3072
+ node, {size: 50}
3073
+ );
3074
+ });
3075
+ }
3076
+ */
3077
+ };
3078
+ network.on('selectNode', onMapSelect);
3079
+ network.on('deselectNode', onMapSelect);
3080
+ }
3004
3081
  redrawMap();
3005
3082
  updateMapFilter();
3006
3083
 
@@ -3651,19 +3728,19 @@ function selectBackup() {
3651
3728
  const candidates = {};
3652
3729
  for (const fn of msg.files) {
3653
3730
  const m = fn.matchAll(/backup_([0-9]+)_([0-9]+)_([0-9]+)-([0-9]+)_([0-9]+)_([0-9]+)/gm);
3654
- console.warn(`m is ${JSON.stringify(m)}`);
3731
+ //console.warn(`m is ${JSON.stringify(m)}`);
3655
3732
  if (m) {
3656
3733
  candidates[`${m[3]}.${m[2]}.${m[1]} ${m[4]}:${m[5]}`] = fn;
3657
3734
  }
3658
3735
  }
3659
- console.warn('candidates is ' + JSON.stringify(candidates));
3736
+ //console.warn('candidates is ' + JSON.stringify(candidates));
3660
3737
  list2select('#backup_Selector', msg.files, [], (key, val) => { return val; }, (key, val) => { return val; })
3661
3738
  $('#modalrestore').modal('open');
3662
3739
  const btn = $('#modalrestore .modal-content a.btn-large');
3663
3740
  btn.unbind('click')
3664
3741
  btn.click(function (e) {
3665
3742
  const name = $('#backup_Selector').val();
3666
- console.warn(` filename is ${name}`);
3743
+ //console.warn(` filename is ${name}`);
3667
3744
  $('#modalrestore').modal('close');
3668
3745
  showWaitingDialog(`Attempting to restore the backup from ${name}`, 180000);
3669
3746
  const start = Date.now();
@@ -4164,7 +4241,7 @@ function showWaitingDialog(text, timeout) {
4164
4241
  clearTimeout(waitingTimeout);
4165
4242
  $('#modalWaiting').modal('close');
4166
4243
  }, timeout * 1000);
4167
- $('#waiting_message').text(text);
4244
+ $('#waiting_message').text(translateWord(text));
4168
4245
  $('#modalWaiting').modal('open');
4169
4246
  }
4170
4247
 
@@ -4460,6 +4537,14 @@ function doSort() {
4460
4537
  shuffleInstance.sort({
4461
4538
  by: sortByTitle
4462
4539
  });
4540
+ } else if (sortOrder === 'range') {
4541
+ shuffleInstance.sort({
4542
+ by: sortByRange
4543
+ });
4544
+ } else if (sortOrder === 'load') {
4545
+ shuffleInstance.sort({
4546
+ by: sortByLoad
4547
+ });
4463
4548
  }
4464
4549
  }
4465
4550
  }
@@ -4467,6 +4552,30 @@ function doSort() {
4467
4552
  function sortByTitle(element) {
4468
4553
  return element.querySelector('.card-title').textContent.toLowerCase().trim();
4469
4554
  }
4555
+ function sortByRange(element) {
4556
+ try {
4557
+ const lqNode = element.querySelector('[id$="_link_quality"]');
4558
+ if (!lqNode) return 0; // kein Wert -> ans Ende
4559
+ const txt = lqNode.textContent || lqNode.innerText || '';
4560
+ const m = txt.match(/-?\d+(.\d+)?/);
4561
+ const val = m ? parseFloat(m[0]) : 0;
4562
+ return -val;
4563
+ } catch (e) {
4564
+ return 0;
4565
+ }
4566
+ }
4567
+ function sortByLoad(element) {
4568
+ try {
4569
+ const battNode = element.querySelector('[id$="_battery"]');
4570
+ if (!battNode) return 0;
4571
+ const txt = battNode.textContent || battNode.innerText || '';
4572
+ const m = txt.match(/-?\d+(.\d+)?/);
4573
+ const val = m ? parseFloat(m[0]) : 0;
4574
+ return -val;
4575
+ } catch (e) {
4576
+ return 0;
4577
+ }
4578
+ }
4470
4579
 
4471
4580
 
4472
4581
  function updateDevice(id) {
@@ -4500,7 +4609,7 @@ function removeDevice(id) {
4500
4609
 
4501
4610
  function swapActive(id) {
4502
4611
  const dev = getDeviceByID(id) || getDeviceByIEEE(`0x${id}`);
4503
- console.warn(`swap_active for ${id} -> ${JSON.stringify(dev)}`);
4612
+ //console.warn(`swap_active for ${id} -> ${JSON.stringify(dev)}`);
4504
4613
  if (dev && dev.common) {
4505
4614
  dev.common.deactivated = !(dev.common.deactivated);
4506
4615
  sendToWrapper(namespace, 'setDeviceActivated', {id: id, deactivated: dev.common.deactivated}, function () {