iobroker.lovelace 4.0.12 → 4.1.1
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/README.md +10 -7
- package/admin/i18n/de/translations.json +2 -1
- package/admin/i18n/en/translations.json +2 -1
- package/admin/i18n/es/translations.json +2 -1
- package/admin/i18n/fr/translations.json +2 -1
- package/admin/i18n/it/translations.json +2 -1
- package/admin/i18n/nl/translations.json +2 -1
- package/admin/i18n/pl/translations.json +2 -1
- package/admin/i18n/pt/translations.json +2 -1
- package/admin/i18n/ru/translations.json +2 -1
- package/admin/i18n/uk/translations.json +2 -1
- package/admin/i18n/zh-cn/translations.json +2 -1
- package/admin/index_m.html +14 -5
- package/admin/words.js +1 -0
- package/io-package.json +10 -18
- package/lib/converters/climate.js +6 -2
- package/lib/converters/genericConverter.js +8 -7
- package/lib/modules/browser_mod.js +3 -0
- package/lib/modules/history.js +12 -9
- package/lib/modules/person.js +38 -0
- package/lib/modules/todo.js +3 -0
- package/lib/server.js +11 -31
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -473,6 +473,15 @@ After that checkout modified version in `./build` folder. Then.
|
|
|
473
473
|
PLACEHOLDER for next version:
|
|
474
474
|
### **WORK IN PROGRESS**
|
|
475
475
|
-->
|
|
476
|
+
### 4.1.1 (2024-01-02)
|
|
477
|
+
* (Garfonso) changed: determining user id
|
|
478
|
+
* (Garfosno) changed: history attributes handling
|
|
479
|
+
* (Garfonso) added: handle browser_mod/recall_id service call.
|
|
480
|
+
* (Garfonso) changed: all states are strings (fixes #483)
|
|
481
|
+
|
|
482
|
+
### 4.1.0 (2023-12-18)
|
|
483
|
+
* (Garfons) add an option to show users on login screen (off by default)
|
|
484
|
+
|
|
476
485
|
### 4.0.12 (2023-12-15)
|
|
477
486
|
* (Garfonso) fix lint errors
|
|
478
487
|
|
|
@@ -485,15 +494,9 @@ After that checkout modified version in `./build` folder. Then.
|
|
|
485
494
|
* (Garfonso) fix: login & authorization
|
|
486
495
|
* (Garfonso) added: user images & names on login screen
|
|
487
496
|
|
|
488
|
-
### 4.0.9 (2023-12-12)
|
|
489
|
-
* (Garfonso) fixed: timestamp in legacy history data
|
|
490
|
-
|
|
491
|
-
### 4.0.8 (2023-12-12)
|
|
492
|
-
* (Garfonso) re-add legacy history for custom cards
|
|
493
|
-
|
|
494
497
|
## License
|
|
495
498
|
|
|
496
|
-
Copyright 2019-
|
|
499
|
+
Copyright 2019-2024, bluefox <dogafox@gmail.com>
|
|
497
500
|
|
|
498
501
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
499
502
|
you may not use this file except in compliance with the License.
|
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
"Unsecure_Auth": "Wenn Sie HTTPS nicht aktivieren oder andere Mittel zur Verschlüsselung der Kommunikation verwenden, wird Ihr Passwort unverschlüsselt über das Netzwerk gesendet. Öffnen Sie nicht einfach einen Port für Lovelace!",
|
|
178
178
|
"Warning!": "Warnung!",
|
|
179
179
|
"Ignore warning": "Warnung ignorieren",
|
|
180
|
-
"Disable authentication": "Deaktivieren Sie die Authentifizierung"
|
|
180
|
+
"Disable authentication": "Deaktivieren Sie die Authentifizierung",
|
|
181
|
+
"showUsersOnLoginScreen": "Benutzer auf dem Anmeldebildschirm anzeigen"
|
|
181
182
|
}
|
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
"Unsecure_Auth": "If you don't enable HTTPS or use other means to encrypt communication, your password will be sent unencrypted over the network. Do not just open a port for lovelace!",
|
|
178
178
|
"Warning!": "Warning!",
|
|
179
179
|
"Ignore warning": "Ignore warning",
|
|
180
|
-
"Disable authentication": "Disable authentication"
|
|
180
|
+
"Disable authentication": "Disable authentication",
|
|
181
|
+
"showUsersOnLoginScreen": "Show users on login screen"
|
|
181
182
|
}
|
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
"Unsecure_Auth": "Si no habilita HTTPS o utiliza otros medios para cifrar la comunicación, su contraseña se enviará sin cifrar a través de la red. ¡No se limite a abrir un puerto para Lovelace!",
|
|
178
178
|
"Warning!": "¡Advertencia!",
|
|
179
179
|
"Ignore warning": "ignorar advertencia",
|
|
180
|
-
"Disable authentication": "Deshabilitar la autenticación"
|
|
180
|
+
"Disable authentication": "Deshabilitar la autenticación",
|
|
181
|
+
"showUsersOnLoginScreen": "Mostrar usuarios en la pantalla de inicio de sesión"
|
|
181
182
|
}
|
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
"Unsecure_Auth": "Si vous n'activez pas HTTPS ou n'utilisez pas d'autres moyens pour crypter la communication, votre mot de passe sera envoyé en clair sur le réseau. Ne vous contentez pas d'ouvrir un port pour Lovelace !",
|
|
178
178
|
"Warning!": "Avertissement!",
|
|
179
179
|
"Ignore warning": "Ignorer l'avertissement",
|
|
180
|
-
"Disable authentication": "Désactiver l'authentification"
|
|
180
|
+
"Disable authentication": "Désactiver l'authentification",
|
|
181
|
+
"showUsersOnLoginScreen": "Afficher les utilisateurs sur l'écran de connexion"
|
|
181
182
|
}
|
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
"Unsecure_Auth": "Se non abiliti HTTPS o non utilizzi altri mezzi per crittografare la comunicazione, la tua password verrà inviata non crittografata sulla rete. Non limitarti ad aprire una porta per l'amore!",
|
|
178
178
|
"Warning!": "Avvertimento!",
|
|
179
179
|
"Ignore warning": "Ignora l'avviso",
|
|
180
|
-
"Disable authentication": "Disabilita l'autenticazione"
|
|
180
|
+
"Disable authentication": "Disabilita l'autenticazione",
|
|
181
|
+
"showUsersOnLoginScreen": "Mostra gli utenti nella schermata di accesso"
|
|
181
182
|
}
|
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
"Unsecure_Auth": "Als u HTTPS niet inschakelt of andere middelen gebruikt om de communicatie te versleutelen, wordt uw wachtwoord onversleuteld over het netwerk verzonden. Open niet zomaar een poort voor lovelace!",
|
|
178
178
|
"Warning!": "Waarschuwing!",
|
|
179
179
|
"Ignore warning": "Negeer waarschuwing",
|
|
180
|
-
"Disable authentication": "Schakel authenticatie uit"
|
|
180
|
+
"Disable authentication": "Schakel authenticatie uit",
|
|
181
|
+
"showUsersOnLoginScreen": "Toon gebruikers op inlogscherm"
|
|
181
182
|
}
|
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
"Unsecure_Auth": "Jeśli nie włączysz protokołu HTTPS lub nie użyjesz innych sposobów szyfrowania komunikacji, Twoje hasło zostanie przesłane przez sieć w formie niezaszyfrowanej. Nie otwieraj portu tylko dla lovelace!",
|
|
178
178
|
"Warning!": "Ostrzeżenie!",
|
|
179
179
|
"Ignore warning": "Zignoruj ostrzeżenie",
|
|
180
|
-
"Disable authentication": "Wyłącz uwierzytelnianie"
|
|
180
|
+
"Disable authentication": "Wyłącz uwierzytelnianie",
|
|
181
|
+
"showUsersOnLoginScreen": "Pokaż użytkowników na ekranie logowania"
|
|
181
182
|
}
|
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
"Unsecure_Auth": "Se você não ativar o HTTPS ou usar outros meios para criptografar a comunicação, sua senha será enviada sem criptografia pela rede. Não basta abrir uma porta para lovelace!",
|
|
178
178
|
"Warning!": "Aviso!",
|
|
179
179
|
"Ignore warning": "Ignorar aviso",
|
|
180
|
-
"Disable authentication": "Desabilitar autenticação"
|
|
180
|
+
"Disable authentication": "Desabilitar autenticação",
|
|
181
|
+
"showUsersOnLoginScreen": "Mostrar usuários na tela de login"
|
|
181
182
|
}
|
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
"Unsecure_Auth": "Если вы не включите HTTPS или не используете другие средства шифрования связи, ваш пароль будет отправлен по сети в незашифрованном виде. Не открывайте порт просто так для ловеласа!",
|
|
178
178
|
"Warning!": "Предупреждение!",
|
|
179
179
|
"Ignore warning": "Игнорировать предупреждение",
|
|
180
|
-
"Disable authentication": "Отключить аутентификацию"
|
|
180
|
+
"Disable authentication": "Отключить аутентификацию",
|
|
181
|
+
"showUsersOnLoginScreen": "Показывать пользователей на экране входа в систему"
|
|
181
182
|
}
|
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
"Unsecure_Auth": "Якщо ви не ввімкнете HTTPS або не використаєте інші засоби шифрування зв’язку, ваш пароль буде надіслано через мережу в незашифрованому вигляді. Не просто відкривайте порт для ловеласа!",
|
|
178
178
|
"Warning!": "УВАГА!",
|
|
179
179
|
"Ignore warning": "Ігнорувати попередження",
|
|
180
|
-
"Disable authentication": "Вимкнути автентифікацію"
|
|
180
|
+
"Disable authentication": "Вимкнути автентифікацію",
|
|
181
|
+
"showUsersOnLoginScreen": "Показати користувачів на екрані входу"
|
|
181
182
|
}
|
|
@@ -177,5 +177,6 @@
|
|
|
177
177
|
"Unsecure_Auth": "如果您未启用 HTTPS 或使用其他方式加密通信,您的密码将通过网络以未加密的方式发送。不要只为lovelace打开一个端口!",
|
|
178
178
|
"Warning!": "警告!",
|
|
179
179
|
"Ignore warning": "忽略警告",
|
|
180
|
-
"Disable authentication": "禁用身份验证"
|
|
180
|
+
"Disable authentication": "禁用身份验证",
|
|
181
|
+
"showUsersOnLoginScreen": "在登录屏幕上显示用户"
|
|
181
182
|
}
|
package/admin/index_m.html
CHANGED
|
@@ -288,8 +288,11 @@
|
|
|
288
288
|
$('.tab-login').removeClass('disabled');
|
|
289
289
|
$('#defaultUser').val('admin');
|
|
290
290
|
$('.col-defaultUser').hide();
|
|
291
|
+
$('.col-showUsersOnLoginScreen').show();
|
|
292
|
+
$('#showUsersOnLoginScreen').show();
|
|
291
293
|
$('.col-ttl').show();
|
|
292
294
|
|
|
295
|
+
|
|
293
296
|
if ((id === 'auth' || id === 'secure') && !$secure.prop('checked')) {
|
|
294
297
|
confirmMessage(_('Unsecure_Auth'), _('Warning!'), 'security', [_('Ignore warning'), _('Disable authentication')], function (result) {
|
|
295
298
|
if (result === 1) {
|
|
@@ -301,7 +304,9 @@
|
|
|
301
304
|
} else {
|
|
302
305
|
$('.tab-login').addClass('disabled');
|
|
303
306
|
$('.col-defaultUser').show();
|
|
304
|
-
|
|
307
|
+
$('.col-showUsersOnLoginScreen').hide();
|
|
308
|
+
$('#showUsersOnLoginScreen').hide();
|
|
309
|
+
$('.col-ttl').hide();
|
|
305
310
|
}
|
|
306
311
|
if ($('#loginBackgroundImage').prop('checked')) {
|
|
307
312
|
$('.background').show();
|
|
@@ -748,10 +753,6 @@
|
|
|
748
753
|
</div>
|
|
749
754
|
</div>
|
|
750
755
|
<div class="row">
|
|
751
|
-
<div class="input-field col s12 m6 l2 col-ttl">
|
|
752
|
-
<input class="value" type="number" id="ttl" />
|
|
753
|
-
<label class="translate" for="ttl">Login timeout(sec)</label>
|
|
754
|
-
</div>
|
|
755
756
|
<div class="input-field col s12 m6 l2">
|
|
756
757
|
<input class="value" id="auth" type="checkbox" />
|
|
757
758
|
<label class="translate" for="auth">Authentication</label>
|
|
@@ -760,6 +761,14 @@
|
|
|
760
761
|
<select class="value" id="defaultUser"></select>
|
|
761
762
|
<label class="translate" for="defaultUser">Run as</label>
|
|
762
763
|
</div>
|
|
764
|
+
<div class="input-field col s12 m6 l2 col-ttl">
|
|
765
|
+
<input class="value" type="number" id="ttl" />
|
|
766
|
+
<label class="translate" for="ttl">Login timeout(sec)</label>
|
|
767
|
+
</div>
|
|
768
|
+
<div class="input-field col s12 m6 l2 col-showUsersOnLoginScreen">
|
|
769
|
+
<input class="value" id="showUsersOnLoginScreen" type="checkbox" />
|
|
770
|
+
<label class="translate" for="showUsersOnLoginScreen">showUsersOnLoginScreen</label>
|
|
771
|
+
</div>
|
|
763
772
|
</div>
|
|
764
773
|
<div class="row">
|
|
765
774
|
<div class="input-field col s12 m6 l2 col-language">
|
package/admin/words.js
CHANGED
|
@@ -187,6 +187,7 @@ const lovelace_systemDictionary = {
|
|
|
187
187
|
"Warning!": { "en": "Warning!", "de": "Warnung!", "ru": "Предупреждение!", "pt": "Aviso!", "nl": "Waarschuwing!", "fr": "Avertissement!", "it": "Avvertimento!", "es": "¡Advertencia!", "pl": "Ostrzeżenie!", "uk": "УВАГА!", "zh-cn": "警告!"},
|
|
188
188
|
"Ignore warning": { "en": "Ignore warning", "de": "Warnung ignorieren", "ru": "Игнорировать предупреждение", "pt": "Ignorar aviso", "nl": "Negeer waarschuwing", "fr": "Ignorer l'avertissement", "it": "Ignora l'avviso", "es": "ignorar advertencia", "pl": "Zignoruj ostrzeżenie", "uk": "Ігнорувати попередження", "zh-cn": "忽略警告"},
|
|
189
189
|
"Disable authentication": { "en": "Disable authentication", "de": "Deaktivieren Sie die Authentifizierung", "ru": "Отключить аутентификацию", "pt": "Desabilitar autenticação", "nl": "Schakel authenticatie uit", "fr": "Désactiver l'authentification", "it": "Disabilita l'autenticazione", "es": "Deshabilitar la autenticación", "pl": "Wyłącz uwierzytelnianie", "uk": "Вимкнути автентифікацію", "zh-cn": "禁用身份验证"},
|
|
190
|
+
"showUsersOnLoginScreen": { "en": "Show users on login screen", "de": "Benutzer auf dem Anmeldebildschirm anzeigen", "ru": "Показывать пользователей на экране входа в систему", "pt": "Mostrar usuários na tela de login", "nl": "Toon gebruikers op inlogscherm", "fr": "Afficher les utilisateurs sur l'écran de connexion", "it": "Mostra gli utenti nella schermata di accesso", "es": "Mostrar usuarios en la pantalla de inicio de sesión", "pl": "Pokaż użytkowników na ekranie logowania", "uk": "Показати користувачів на екрані входу", "zh-cn": "在登录屏幕上显示用户"},
|
|
190
191
|
};
|
|
191
192
|
|
|
192
193
|
if (typeof module !== 'undefined' && module.parent) { module.exports = lovelace_systemDictionary; }
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "lovelace",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.1.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"4.1.1": {
|
|
7
|
+
"en": "changed: determining user id\nchanged: history attributes handling\nadded: handle browser_mod/recall_id service call.\nchanged: all states are strings!",
|
|
8
|
+
"de": "geändert: bestimmung der benutzer-id\ngeändert: historie für attribute\nhinzugefügt: handle browser_mod/recall_id service call.\ngeändert: alle zustände sind strings!"
|
|
9
|
+
},
|
|
10
|
+
"4.1.0": {
|
|
11
|
+
"en": "add an option to show users on login screen (off by default)",
|
|
12
|
+
"de": "Neu Option um Benutzer auf dem Login-Bildschirm anzuzeigen"
|
|
13
|
+
},
|
|
6
14
|
"4.0.12": {
|
|
7
15
|
"en": "fix lint errors",
|
|
8
16
|
"de": "lint fehler beheben",
|
|
@@ -40,23 +48,6 @@
|
|
|
40
48
|
"4.0.8": {
|
|
41
49
|
"en": "re-add legacy history for custom cards",
|
|
42
50
|
"de": "re-add die alten history methoden für benutzerdefinierte karten"
|
|
43
|
-
},
|
|
44
|
-
"4.0.7": {
|
|
45
|
-
"en": "history should be working again.",
|
|
46
|
-
"de": "history sollte wieder funktionieren.",
|
|
47
|
-
"ru": "история должна работать снова.",
|
|
48
|
-
"pt": "a história deve estar a trabalhar de novo.",
|
|
49
|
-
"nl": "de geschiedenis zou weer moeten werken.",
|
|
50
|
-
"fr": "l'histoire devrait retravailler.",
|
|
51
|
-
"it": "la storia dovrebbe funzionare di nuovo.",
|
|
52
|
-
"es": "la historia debe estar trabajando de nuevo.",
|
|
53
|
-
"pl": "historia powinna być kontynuowana.",
|
|
54
|
-
"uk": "історія повинна працювати знову.",
|
|
55
|
-
"zh-cn": "历史应该再次发挥作用。."
|
|
56
|
-
},
|
|
57
|
-
"4.0.6": {
|
|
58
|
-
"en": "fixed: thermostat card for thermostats without mode / off state\nfixed: history and history graph missing for some stuff",
|
|
59
|
-
"de": "repariert: thermostatkarte für thermostate ohne modus / aus zustand\nrepariert: history und history diagramm fehlt für einige sachen"
|
|
60
51
|
}
|
|
61
52
|
},
|
|
62
53
|
"title": "Visualization with Lovelace-UI",
|
|
@@ -209,6 +200,7 @@
|
|
|
209
200
|
"defaultUser": "admin",
|
|
210
201
|
"onlyAllowWhenUserIsOwner": false,
|
|
211
202
|
"ttl": 3600,
|
|
203
|
+
"showUsersOnLoginScreen": false,
|
|
212
204
|
"themes": "",
|
|
213
205
|
"defaultTheme": "default",
|
|
214
206
|
"defaultThemeDark": "default",
|
|
@@ -237,11 +237,15 @@ function fillClimateEntityFromStates(states, objects, entity, iobType) {
|
|
|
237
237
|
|
|
238
238
|
//controls hvac_mode which can be 'off' but not 'on', so translate 'on' to heat / cool depending on type.
|
|
239
239
|
if (states.state || states.stateRead) {
|
|
240
|
+
if (!entity.attributes.hvac_modes) {
|
|
241
|
+
entity.attributes.hvac_modes = [];
|
|
242
|
+
}
|
|
243
|
+
entity.attributes.hvac_modes.push('off');
|
|
240
244
|
if (!states.hvac_mode) {
|
|
241
245
|
if (iobType === typeDetector.Types.airCondition) {
|
|
242
|
-
entity.attributes.hvac_modes
|
|
246
|
+
entity.attributes.hvac_modes.push('cool');
|
|
243
247
|
} else {
|
|
244
|
-
entity.attributes.hvac_modes
|
|
248
|
+
entity.attributes.hvac_modes.push('heat');
|
|
245
249
|
}
|
|
246
250
|
}
|
|
247
251
|
entity.context.STATE.getParser = function (entity, attr, state) {
|
|
@@ -48,8 +48,6 @@ exports.numericDeviceClasses = [
|
|
|
48
48
|
'distance'
|
|
49
49
|
];
|
|
50
50
|
|
|
51
|
-
const numericDeviceClassesIob = exports.numericDeviceClasses.concat(['timestamp']);
|
|
52
|
-
|
|
53
51
|
exports.iobState2EntityState = function (entity, val, attribute) {
|
|
54
52
|
let type = entity.context.type;
|
|
55
53
|
const pos = type.lastIndexOf('.');
|
|
@@ -61,14 +59,17 @@ exports.iobState2EntityState = function (entity, val, attribute) {
|
|
|
61
59
|
return val ? 'on' : 'off';
|
|
62
60
|
} else if (type === 'binary_sensor') {
|
|
63
61
|
return val ? 'on' : 'off';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
}else if (typeof val === 'number' && entity.attributes && ['date', 'timestamp'].includes(entity.attributes.device_class)) {
|
|
63
|
+
//convert to date string
|
|
64
|
+
const date = new Date(val);
|
|
65
|
+
let dateStr = date.toDateString();
|
|
66
|
+
if (attribute === 'timestamp') {
|
|
67
|
+
dateStr = date.toISOString();
|
|
68
|
+
}
|
|
68
69
|
return dateStr === 'Invalid Date' ? 'unknown' : dateStr;
|
|
69
70
|
} else if (type === 'lock') {
|
|
70
71
|
return val ? 'unlocked' : 'locked';
|
|
71
|
-
} else if (typeof val === 'boolean' && type !== 'media_player') {
|
|
72
|
+
} else if (typeof val === 'boolean' && type !== 'media_player' && !attribute) { //attributes can have true/false.
|
|
72
73
|
return val ? 'on' : 'off';
|
|
73
74
|
} else if (typeof val === 'number' && entity.context.STATE.map2lovelace) {
|
|
74
75
|
return entity.context.STATE.map2lovelace[val] || val;
|
|
@@ -344,6 +344,7 @@ class BrowserModModule {
|
|
|
344
344
|
instance: message.browserID,
|
|
345
345
|
ws
|
|
346
346
|
};
|
|
347
|
+
ws.browserID = message.browserID; //store browserID in ws object to handle recall service call later.
|
|
347
348
|
|
|
348
349
|
ws.send(JSON.stringify([{id: message.id, type: 'result', success: true, result: null}, {
|
|
349
350
|
id: message.id, type: 'event', event: {
|
|
@@ -399,6 +400,8 @@ class BrowserModModule {
|
|
|
399
400
|
this.adapter.log.debug('Updated browser_mod settings: ' + message.key + ' to ' + message.value);
|
|
400
401
|
}
|
|
401
402
|
//think about making this permanent somehow? -> but only if we find a way to allow browser_mod_settings panel.
|
|
403
|
+
} else if (method === 'recall_id') {
|
|
404
|
+
ws.send(JSON.stringify({id: message.id, type: 'result', success: true, result: ws.browserID}));
|
|
402
405
|
} else {
|
|
403
406
|
this.adapter.log.warn('Unknown browser_mod method: ' + JSON.stringify(message));
|
|
404
407
|
}
|
package/lib/modules/history.js
CHANGED
|
@@ -67,7 +67,7 @@ async function getHistory(adapter, entities, start, end, noAttributes, user) {
|
|
|
67
67
|
}
|
|
68
68
|
let found = false;
|
|
69
69
|
let best = null;
|
|
70
|
-
let bestDiff =
|
|
70
|
+
let bestDiff = 10000;
|
|
71
71
|
const results = attributesResult[attribute.attribute].result;
|
|
72
72
|
for (const result of results) {
|
|
73
73
|
if (result.val !== null) {
|
|
@@ -81,10 +81,10 @@ async function getHistory(adapter, entities, start, end, noAttributes, user) {
|
|
|
81
81
|
}
|
|
82
82
|
if (found) {
|
|
83
83
|
attributeValues[attribute.attribute] = typeof attribute.historyParser === 'function' ? attribute.historyParser(id, best.val) : best.val;
|
|
84
|
-
best.used = true;
|
|
85
|
-
} else {
|
|
84
|
+
best.used = true; //will be checked later, all "unused" attributes will be added without state later.
|
|
85
|
+
} /*else { //let's try to leave attribute empty, for now, if no value in history that is closer than 10 seconds.
|
|
86
86
|
attributeValues[attribute.attribute] = entity.attributes[attribute.attribute]; //use current value as default if none found.
|
|
87
|
-
}
|
|
87
|
+
}*/
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
for (const key of Object.keys(entity.attributes)) {
|
|
@@ -103,7 +103,7 @@ async function getHistory(adapter, entities, start, end, noAttributes, user) {
|
|
|
103
103
|
s: typeof entity.context.STATE.historyParser === 'function' ?
|
|
104
104
|
entity.context.STATE.historyParser(id, e.val).toString() :
|
|
105
105
|
iobState2EntityState(entity, e.val),
|
|
106
|
-
a: noAttributes ?
|
|
106
|
+
a: noAttributes ? {} : getAttributeValues(e, attributesResult, attributesUsed),
|
|
107
107
|
lc: 1,
|
|
108
108
|
lu: 1
|
|
109
109
|
};
|
|
@@ -113,6 +113,8 @@ async function getHistory(adapter, entities, start, end, noAttributes, user) {
|
|
|
113
113
|
//add unused attribute values:
|
|
114
114
|
if (!noAttributes && entity.context.ATTRIBUTES) {
|
|
115
115
|
for (const attribute of entity.context.ATTRIBUTES) {
|
|
116
|
+
//find other attributes for this type. Will use this attribute as "state" for the getAttributeValues function.
|
|
117
|
+
// so we don't want to find this attribute again. Will add all matching attributes anyway. So in later runs this attribute will be "empty", i.e. all used.
|
|
116
118
|
attributesUsed.push(attribute.attribute);
|
|
117
119
|
const results = attributesResult[attribute.attribute].result;
|
|
118
120
|
for (const result of results) {
|
|
@@ -125,7 +127,7 @@ async function getHistory(adapter, entities, start, end, noAttributes, user) {
|
|
|
125
127
|
//state: null,
|
|
126
128
|
lc: 1,
|
|
127
129
|
lu: 1,
|
|
128
|
-
a: noAttributes ?
|
|
130
|
+
a: noAttributes ? {} : getAttributeValues(result, attributesResult, attributesUsed, attributeValues)
|
|
129
131
|
};
|
|
130
132
|
updateTimestamps(data, result, true);
|
|
131
133
|
historyPerEntity.push(data);
|
|
@@ -137,6 +139,7 @@ async function getHistory(adapter, entities, start, end, noAttributes, user) {
|
|
|
137
139
|
if (historyPerEntity.length === 0) {
|
|
138
140
|
historyPerEntity.push({
|
|
139
141
|
s: entity.state,
|
|
142
|
+
a: {},
|
|
140
143
|
lu: start / 1000
|
|
141
144
|
});
|
|
142
145
|
}
|
|
@@ -177,12 +180,12 @@ function sendHistoryResponse(ws, id, historyData, parameters) {
|
|
|
177
180
|
state.s = 'unknown';
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
if (parameters.noAttributes) {
|
|
183
|
+
/*if (parameters.noAttributes) {
|
|
181
184
|
delete state.a;
|
|
182
185
|
}
|
|
183
186
|
if (parameters.minimalResponse) {
|
|
184
187
|
delete state.lc;
|
|
185
|
-
}
|
|
188
|
+
}*/
|
|
186
189
|
}
|
|
187
190
|
}
|
|
188
191
|
|
|
@@ -275,7 +278,7 @@ class HistoryModule {
|
|
|
275
278
|
id: Number(message.id)
|
|
276
279
|
};
|
|
277
280
|
|
|
278
|
-
// add
|
|
281
|
+
// add subscription here.
|
|
279
282
|
ws._subscribes.history = ws._subscribes.history || [];
|
|
280
283
|
ws._subscribes.history.push(parameters);
|
|
281
284
|
} else if (message.type === 'history/history_during_period') {
|
package/lib/modules/person.js
CHANGED
|
@@ -22,6 +22,27 @@ class PersonModule {
|
|
|
22
22
|
return result;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Get the user id from the username
|
|
27
|
+
* @param {string} name
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
getUserIDFromName(name) {
|
|
31
|
+
for (const userObj of Object.values(this.usersCache)) {
|
|
32
|
+
if (userObj.name === name) {
|
|
33
|
+
return userObj.iobId;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//fallback, default user "admin" is misleading, if user renamed admin.. hm. :-/
|
|
38
|
+
if (this.adapter.config.auth && name === 'admin') {
|
|
39
|
+
return 'system.user.admin';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.adapter.log.warn(`Could not get user id for ${name} - Trying with username` + JSON.stringify(this.usersCache['system.user.' + name.toLowerCase()]));
|
|
43
|
+
return 'system.user.' + name.toLowerCase(); //hack and not correct since js-controller 3.2
|
|
44
|
+
}
|
|
45
|
+
|
|
25
46
|
onObjectChange(id, obj) {
|
|
26
47
|
if (id.startsWith('system.user.')) {
|
|
27
48
|
if (obj && obj.common && obj.common.enabled()) {
|
|
@@ -41,6 +62,7 @@ class PersonModule {
|
|
|
41
62
|
|
|
42
63
|
async init() {
|
|
43
64
|
const userObjects = await this.adapter.getForeignObjectsAsync('system.user.*', 'user');
|
|
65
|
+
let defaultUserObject = null;
|
|
44
66
|
for (const [id, obj] of Object.entries(userObjects)) {
|
|
45
67
|
if (obj.common && obj.common.enabled) { //only show enabled persons?
|
|
46
68
|
this.usersCache[id] = {
|
|
@@ -50,8 +72,24 @@ class PersonModule {
|
|
|
50
72
|
picture: obj.common.icon,
|
|
51
73
|
description: obj.common.desc
|
|
52
74
|
};
|
|
75
|
+
if (obj.common.name === this.adapter.config.defaultUser) {
|
|
76
|
+
defaultUserObject = obj;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//default user only relevant for !auth.
|
|
82
|
+
if (!defaultUserObject) {
|
|
83
|
+
//Ok, we did not find defaultUser object. Might be renamed admin?
|
|
84
|
+
defaultUserObject = userObjects['system.user.admin'];
|
|
85
|
+
this.adapter.config.defaultUser = defaultUserObject.common.name;
|
|
86
|
+
if (this.adapter.config.defaultUser !== 'admin' && !this.adapter.config.auth) {
|
|
87
|
+
//Activating auth will hide this setting. Not sure what to do then... In general: all users will be able to read everything in this case.
|
|
88
|
+
this.adapter.log.warn(`Could not find default user ${this.adapter.config.defaultUser} - Using admin - Please update your configuration.`);
|
|
53
89
|
}
|
|
54
90
|
}
|
|
91
|
+
|
|
92
|
+
|
|
55
93
|
await this.adapter.subscribeObjectsAsync('system.user.*');
|
|
56
94
|
}
|
|
57
95
|
}
|
package/lib/modules/todo.js
CHANGED
|
@@ -181,6 +181,9 @@ class TodoModule {
|
|
|
181
181
|
if (state && state.val) {
|
|
182
182
|
try {
|
|
183
183
|
todoList.items = JSON.parse(state.val);
|
|
184
|
+
if (!todoList.items || !Array.isArray(todoList.items)) {
|
|
185
|
+
todoList.items = [];
|
|
186
|
+
}
|
|
184
187
|
//convert legacy items:
|
|
185
188
|
for (const item of todoList.items) {
|
|
186
189
|
if (item.name && !item.summary) {
|
package/lib/server.js
CHANGED
|
@@ -518,7 +518,7 @@ class WebServer {
|
|
|
518
518
|
}
|
|
519
519
|
|
|
520
520
|
async _processSingleCall(ws, data, entity_id) {
|
|
521
|
-
const user =
|
|
521
|
+
const user = this._modules.person.getUserIDFromName(ws.__auth.username || this.config.defaultUser);
|
|
522
522
|
|
|
523
523
|
const entity = entityData.entityId2Entity[entity_id];
|
|
524
524
|
const id = entity.context.STATE.setId;
|
|
@@ -681,7 +681,7 @@ class WebServer {
|
|
|
681
681
|
entity.state = 'unknown';
|
|
682
682
|
if (entity.context.STATE && entity.context.STATE.getId) {
|
|
683
683
|
try {
|
|
684
|
-
const user =
|
|
684
|
+
const user = this._modules.person.getUserIDFromName(this.config.defaultUser); //TODO: why is this always defaultUser?
|
|
685
685
|
const state = await this.adapter.getForeignStateAsync(entity.context.STATE.getId, {user});
|
|
686
686
|
if (state) {
|
|
687
687
|
await this.onStateChange(entity.context.STATE.getId, state);
|
|
@@ -1477,7 +1477,7 @@ class WebServer {
|
|
|
1477
1477
|
file = file.substring(0, pos);
|
|
1478
1478
|
}
|
|
1479
1479
|
try {
|
|
1480
|
-
const user =
|
|
1480
|
+
const user = this._modules.person.getUserIDFromName(this.config.defaultUser); //TODO: why is this always default user?
|
|
1481
1481
|
let data;
|
|
1482
1482
|
if (file.startsWith('/lovelace/')) {
|
|
1483
1483
|
file = file.replace('/lovelace/', '');
|
|
@@ -1501,7 +1501,7 @@ class WebServer {
|
|
|
1501
1501
|
file = file.substring(0, pos);
|
|
1502
1502
|
}
|
|
1503
1503
|
try {
|
|
1504
|
-
const user =
|
|
1504
|
+
const user = this._modules.person.getUserIDFromName(this.config.defaultUser); //TODO: why is this always default user?
|
|
1505
1505
|
const data = await this.adapter.readFileAsync(this.adapter.namespace, file.replace('/local/custom_ui/', '/cards/'), {user});
|
|
1506
1506
|
const pos = req.url.lastIndexOf('.');
|
|
1507
1507
|
res.setHeader('content-type', (mime.getType || mime.lookup).call(data.mimeType, file.substring(pos + 1).toLowerCase()));
|
|
@@ -1655,7 +1655,7 @@ class WebServer {
|
|
|
1655
1655
|
});
|
|
1656
1656
|
|
|
1657
1657
|
this._app.use('/auth/providers', (req, res) => {
|
|
1658
|
-
res.json([{id: null, name: 'ioBroker Local', type: 'iobroker', users: this._modules.person.getShorList()}]);
|
|
1658
|
+
res.json([{id: null, name: 'ioBroker Local', type: 'iobroker', users: this.config.showUsersOnLoginScreen ? this._modules.person.getShorList() : undefined}]);
|
|
1659
1659
|
});
|
|
1660
1660
|
|
|
1661
1661
|
this._app.post('/auth/login_flow', (req, res) => {
|
|
@@ -1844,7 +1844,7 @@ class WebServer {
|
|
|
1844
1844
|
}
|
|
1845
1845
|
|
|
1846
1846
|
try {
|
|
1847
|
-
const user =
|
|
1847
|
+
const user = this._modules.person.getUserIDFromName(this.config.defaultUser); //TODO: why is this always default user?
|
|
1848
1848
|
const image = await this.adapter.readFileAsync(id, url, {user});
|
|
1849
1849
|
if (image.file === null || image.file === undefined) {
|
|
1850
1850
|
throw new Error('File empty');
|
|
@@ -1868,7 +1868,7 @@ class WebServer {
|
|
|
1868
1868
|
if (obj && obj.common.type === 'file') {
|
|
1869
1869
|
contentType = (mime.getType || mime.lookup).call(mime, fileName[0]);
|
|
1870
1870
|
}
|
|
1871
|
-
const user =
|
|
1871
|
+
const user = this._modules.person.getUserIDFromName(this.config.defaultUser);
|
|
1872
1872
|
const data = await this.adapter.getBinaryStateAsync(fileName[0], {user});
|
|
1873
1873
|
if (data !== null && obj !== undefined) {
|
|
1874
1874
|
if (data && typeof data === 'object' && data.val !== undefined && data.ack !== undefined) {
|
|
@@ -1937,7 +1937,7 @@ class WebServer {
|
|
|
1937
1937
|
return res.status(404).json({error: 'Start or end misformated'});
|
|
1938
1938
|
}
|
|
1939
1939
|
|
|
1940
|
-
const user =
|
|
1940
|
+
const user = this._modules.person.getUserIDFromName(this.config.defaultUser);
|
|
1941
1941
|
try {
|
|
1942
1942
|
const state = await this.adapter.getForeignStateAsync(entity.context.STATE.getId, {user});
|
|
1943
1943
|
if (state && state.val) {
|
|
@@ -2018,29 +2018,8 @@ class WebServer {
|
|
|
2018
2018
|
});
|
|
2019
2019
|
}
|
|
2020
2020
|
|
|
2021
|
-
async _getUserId(user) {
|
|
2022
|
-
let userId = this._userNamesToIds[user];
|
|
2023
|
-
if (userId) {
|
|
2024
|
-
return userId;
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
if (typeof this.adapter.getUserID === 'function') {
|
|
2028
|
-
try {
|
|
2029
|
-
userId = await this.adapter.getUserID(user);
|
|
2030
|
-
this._userNamesToIds[user] = userId;
|
|
2031
|
-
} catch (err) {
|
|
2032
|
-
this.log.warn(`Could not get user id for ${user} - ${err}`);
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
if (!userId) {
|
|
2036
|
-
this.log.warn(`Could not get user id for ${user} - Trying with username`);
|
|
2037
|
-
userId = 'system.user.' + user.toLowerCase(); //hack and not correct since js-controller 3.2
|
|
2038
|
-
}
|
|
2039
|
-
return userId;
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
2021
|
async _getCurrentUser(ws) {
|
|
2043
|
-
const user =
|
|
2022
|
+
const user = this._modules.person.getUserIDFromName(ws.__auth.username || this.config.defaultUser);
|
|
2044
2023
|
const userObj = {
|
|
2045
2024
|
id: user,
|
|
2046
2025
|
name: ws.__auth.username || this.config.defaultUser,
|
|
@@ -2098,6 +2077,7 @@ class WebServer {
|
|
|
2098
2077
|
type: 'event',
|
|
2099
2078
|
event: {
|
|
2100
2079
|
event_type: eventType,
|
|
2080
|
+
data: {},
|
|
2101
2081
|
origin: 'LOCAL',
|
|
2102
2082
|
time_fired: Date.now() / 1000
|
|
2103
2083
|
}
|
|
@@ -2158,7 +2138,7 @@ class WebServer {
|
|
|
2158
2138
|
}
|
|
2159
2139
|
if (id) {
|
|
2160
2140
|
try {
|
|
2161
|
-
const user =
|
|
2141
|
+
const user = this._modules.person.getUserIDFromName(userName);
|
|
2162
2142
|
const state = await this.adapter.getForeignStateAsync(id, {user});
|
|
2163
2143
|
if (state && state.val && typeof state.val === 'string') {
|
|
2164
2144
|
const val = state.val.split('?')[0] || '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.lovelace",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "With this adapter you can build visualization for ioBroker with Home Assistant Lovelace UI",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "bluefox",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@iobroker/adapter-core": "^3.0.4",
|
|
23
|
-
"axios": "^1.6.
|
|
23
|
+
"axios": "^1.6.3",
|
|
24
24
|
"body-parser": "^1.20.2",
|
|
25
25
|
"express": "^4.18.2",
|
|
26
26
|
"iobroker.type-detector": "^3.0.5",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"nyc": "^15.1.0",
|
|
32
32
|
"pinyin": "^3.1.0",
|
|
33
33
|
"translit-rus-eng": "^1.0.8",
|
|
34
|
-
"ws": "^8.
|
|
34
|
+
"ws": "^8.16.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@alcalzone/release-script": "^3.7.0",
|
|
@@ -44,13 +44,13 @@
|
|
|
44
44
|
"@types/chai-as-promised": "^7.1.8",
|
|
45
45
|
"@types/gulp": "^4.0.17",
|
|
46
46
|
"@types/mocha": "^10.0.6",
|
|
47
|
-
"@types/node": "^16.18.
|
|
47
|
+
"@types/node": "^16.18.69 < 17",
|
|
48
48
|
"@types/proxyquire": "^1.3.31",
|
|
49
49
|
"@types/sinon": "^17.0.2",
|
|
50
50
|
"@types/sinon-chai": "^3.2.12",
|
|
51
51
|
"chai": "^4.3.10",
|
|
52
52
|
"chai-as-promised": "^7.1.1",
|
|
53
|
-
"eslint": "^8.
|
|
53
|
+
"eslint": "^8.56.0",
|
|
54
54
|
"gulp": "^4.0.2",
|
|
55
55
|
"mocha": "^10.2.0",
|
|
56
56
|
"proxyquire": "^2.1.3",
|