Commit a583f9fa8e9cd123bc9852d3a5c0138928afea6f

Authored by ShvaykaD
2 parents 6b3448d1 b4ae7e37

Merge branch 'master' of github.com:thingsboard/thingsboard

Showing 100 changed files with 435 additions and 1587 deletions

Too many changes to show.

To preserve performance only 100 of 130 files are displayed.

@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>application</artifactId> 26 <artifactId>application</artifactId>
@@ -955,7 +955,7 @@ @@ -955,7 +955,7 @@
955 }, 955 },
956 "methodName": "gateway_restart", 956 "methodName": "gateway_restart",
957 "methodParams": "{}", 957 "methodParams": "{}",
958 - "buttonText": "gateway restart" 958 + "buttonText": "GATEWAY RESTART"
959 }, 959 },
960 "title": "New RPC Button", 960 "title": "New RPC Button",
961 "dropShadow": true, 961 "dropShadow": true,
1 -{  
2 - "title": "Raspberry PI GPIO Demo Dashboard",  
3 - "configuration": {  
4 - "description": "Demo dashboard for Raspberry PI GPIO Demo",  
5 - "widgets": {  
6 - "602177f6-267b-cb87-4e8f-e23d7fb2f61c": {  
7 - "isSystemType": true,  
8 - "bundleAlias": "gpio_widgets",  
9 - "typeAlias": "raspberry_pi_gpio_control",  
10 - "type": "rpc",  
11 - "title": "New widget",  
12 - "sizeX": 6,  
13 - "sizeY": 10,  
14 - "config": {  
15 - "targetDeviceAliases": [],  
16 - "showTitle": true,  
17 - "backgroundColor": "#fff",  
18 - "color": "rgba(0, 0, 0, 0.87)",  
19 - "padding": "0px",  
20 - "settings": {  
21 - "parseGpioStatusFunction": "return body[pin] === true;",  
22 - "gpioStatusChangeRequest": {  
23 - "method": "setGpioStatus",  
24 - "paramsBody": "{\n \"pin\": \"{$pin}\",\n \"enabled\": \"{$enabled}\"\n}"  
25 - },  
26 - "requestTimeout": 500,  
27 - "switchPanelBackgroundColor": "#008a00",  
28 - "gpioStatusRequest": {  
29 - "method": "getGpioStatus",  
30 - "paramsBody": "{}"  
31 - },  
32 - "gpioList": [  
33 - {  
34 - "pin": 7,  
35 - "label": "GPIO 4 (GPCLK0)",  
36 - "row": 3,  
37 - "col": 0,  
38 - "_uniqueKey": 0  
39 - },  
40 - {  
41 - "pin": 11,  
42 - "label": "GPIO 17",  
43 - "row": 5,  
44 - "col": 0,  
45 - "_uniqueKey": 1  
46 - },  
47 - {  
48 - "pin": 12,  
49 - "label": "GPIO 18",  
50 - "row": 5,  
51 - "col": 1,  
52 - "_uniqueKey": 2  
53 - },  
54 - {  
55 - "_uniqueKey": 3,  
56 - "pin": 13,  
57 - "label": "GPIO 27",  
58 - "row": 6,  
59 - "col": 0  
60 - },  
61 - {  
62 - "_uniqueKey": 4,  
63 - "pin": 15,  
64 - "label": "GPIO 22",  
65 - "row": 7,  
66 - "col": 0  
67 - },  
68 - {  
69 - "_uniqueKey": 5,  
70 - "pin": 16,  
71 - "label": "GPIO 23",  
72 - "row": 7,  
73 - "col": 1  
74 - },  
75 - {  
76 - "_uniqueKey": 6,  
77 - "pin": 18,  
78 - "label": "GPIO 24",  
79 - "row": 8,  
80 - "col": 1  
81 - },  
82 - {  
83 - "_uniqueKey": 7,  
84 - "pin": 22,  
85 - "label": "GPIO 25",  
86 - "row": 10,  
87 - "col": 1  
88 - },  
89 - {  
90 - "_uniqueKey": 8,  
91 - "pin": 29,  
92 - "label": "GPIO 5",  
93 - "row": 14,  
94 - "col": 0  
95 - },  
96 - {  
97 - "_uniqueKey": 9,  
98 - "pin": 31,  
99 - "label": "GPIO 6",  
100 - "row": 15,  
101 - "col": 0  
102 - },  
103 - {  
104 - "_uniqueKey": 10,  
105 - "pin": 32,  
106 - "label": "GPIO 12",  
107 - "row": 15,  
108 - "col": 1  
109 - },  
110 - {  
111 - "_uniqueKey": 11,  
112 - "pin": 33,  
113 - "label": "GPIO 13",  
114 - "row": 16,  
115 - "col": 0  
116 - },  
117 - {  
118 - "_uniqueKey": 12,  
119 - "pin": 35,  
120 - "label": "GPIO 19",  
121 - "row": 17,  
122 - "col": 0  
123 - },  
124 - {  
125 - "_uniqueKey": 13,  
126 - "pin": 36,  
127 - "label": "GPIO 16",  
128 - "row": 17,  
129 - "col": 1  
130 - },  
131 - {  
132 - "_uniqueKey": 14,  
133 - "pin": 37,  
134 - "label": "GPIO 26",  
135 - "row": 18,  
136 - "col": 0  
137 - },  
138 - {  
139 - "_uniqueKey": 15,  
140 - "pin": 38,  
141 - "label": "GPIO 20",  
142 - "row": 18,  
143 - "col": 1  
144 - },  
145 - {  
146 - "_uniqueKey": 16,  
147 - "pin": 40,  
148 - "label": "GPIO 21",  
149 - "row": 19,  
150 - "col": 1  
151 - }  
152 - ]  
153 - },  
154 - "title": "Raspberry Pi GPIO Control Panel",  
155 - "datasources": [],  
156 - "targetDeviceAliasIds": [  
157 - "f26b12b6-6938-e1a0-85ec-d88a1f23e382"  
158 - ]  
159 - },  
160 - "row": 0,  
161 - "col": 0,  
162 - "id": "602177f6-267b-cb87-4e8f-e23d7fb2f61c"  
163 - },  
164 - "3cca52a5-e874-eb43-b444-8efa01e663c8": {  
165 - "isSystemType": true,  
166 - "bundleAlias": "gpio_widgets",  
167 - "typeAlias": "raspberry_pi_gpio_panel",  
168 - "type": "latest",  
169 - "title": "New widget",  
170 - "sizeX": 7,  
171 - "sizeY": 10,  
172 - "config": {  
173 - "showTitle": true,  
174 - "backgroundColor": "#fff",  
175 - "color": "rgba(0, 0, 0, 0.87)",  
176 - "padding": "0px",  
177 - "settings": {  
178 - "gpioList": [  
179 - {  
180 - "pin": 1,  
181 - "label": "3.3V",  
182 - "row": 0,  
183 - "col": 0,  
184 - "color": "#fc9700",  
185 - "_uniqueKey": 0  
186 - },  
187 - {  
188 - "pin": 2,  
189 - "label": "5V",  
190 - "row": 0,  
191 - "col": 1,  
192 - "color": "#fb0000",  
193 - "_uniqueKey": 1  
194 - },  
195 - {  
196 - "pin": 3,  
197 - "label": "GPIO 2 (I2C1_SDA)",  
198 - "row": 1,  
199 - "col": 0,  
200 - "color": "#02fefb",  
201 - "_uniqueKey": 2  
202 - },  
203 - {  
204 - "color": "#fb0000",  
205 - "pin": 4,  
206 - "label": "5V",  
207 - "row": 1,  
208 - "col": 1  
209 - },  
210 - {  
211 - "color": "#02fefb",  
212 - "pin": 5,  
213 - "label": "GPIO 3 (I2C1_SCL)",  
214 - "row": 2,  
215 - "col": 0  
216 - },  
217 - {  
218 - "color": "#000000",  
219 - "pin": 6,  
220 - "label": "GND",  
221 - "row": 2,  
222 - "col": 1  
223 - },  
224 - {  
225 - "color": "#00fd00",  
226 - "pin": 7,  
227 - "label": "GPIO 4 (GPCLK0)",  
228 - "row": 3,  
229 - "col": 0  
230 - },  
231 - {  
232 - "color": "#fdfb00",  
233 - "pin": 8,  
234 - "label": "GPIO 14 (UART_TXD)",  
235 - "row": 3,  
236 - "col": 1  
237 - },  
238 - {  
239 - "color": "#000000",  
240 - "pin": 9,  
241 - "label": "GND",  
242 - "row": 4,  
243 - "col": 0  
244 - },  
245 - {  
246 - "color": "#fdfb00",  
247 - "pin": 10,  
248 - "label": "GPIO 15 (UART_RXD)",  
249 - "row": 4,  
250 - "col": 1  
251 - },  
252 - {  
253 - "color": "#00fd00",  
254 - "pin": 11,  
255 - "label": "GPIO 17",  
256 - "row": 5,  
257 - "col": 0  
258 - },  
259 - {  
260 - "color": "#00fd00",  
261 - "pin": 12,  
262 - "label": "GPIO 18",  
263 - "row": 5,  
264 - "col": 1  
265 - },  
266 - {  
267 - "color": "#00fd00",  
268 - "pin": 13,  
269 - "label": "GPIO 27",  
270 - "row": 6,  
271 - "col": 0  
272 - },  
273 - {  
274 - "color": "#000000",  
275 - "pin": 14,  
276 - "label": "GND",  
277 - "row": 6,  
278 - "col": 1  
279 - },  
280 - {  
281 - "color": "#00fd00",  
282 - "pin": 15,  
283 - "label": "GPIO 22",  
284 - "row": 7,  
285 - "col": 0  
286 - },  
287 - {  
288 - "color": "#00fd00",  
289 - "pin": 16,  
290 - "label": "GPIO 23",  
291 - "row": 7,  
292 - "col": 1  
293 - },  
294 - {  
295 - "color": "#fc9700",  
296 - "pin": 17,  
297 - "label": "3.3V",  
298 - "row": 8,  
299 - "col": 0  
300 - },  
301 - {  
302 - "color": "#00fd00",  
303 - "pin": 18,  
304 - "label": "GPIO 24",  
305 - "row": 8,  
306 - "col": 1  
307 - },  
308 - {  
309 - "color": "#fd01fd",  
310 - "pin": 19,  
311 - "label": "GPIO 10 (SPI_MOSI)",  
312 - "row": 9,  
313 - "col": 0  
314 - },  
315 - {  
316 - "color": "#000000",  
317 - "pin": 20,  
318 - "label": "GND",  
319 - "row": 9,  
320 - "col": 1  
321 - },  
322 - {  
323 - "color": "#fd01fd",  
324 - "pin": 21,  
325 - "label": "GPIO 9 (SPI_MISO)",  
326 - "row": 10,  
327 - "col": 0  
328 - },  
329 - {  
330 - "color": "#00fd00",  
331 - "pin": 22,  
332 - "label": "GPIO 25",  
333 - "row": 10,  
334 - "col": 1  
335 - },  
336 - {  
337 - "color": "#fd01fd",  
338 - "pin": 23,  
339 - "label": "GPIO 11 (SPI_SCLK)",  
340 - "row": 11,  
341 - "col": 0  
342 - },  
343 - {  
344 - "color": "#fd01fd",  
345 - "pin": 24,  
346 - "label": "GPIO 8 (SPI_CE0)",  
347 - "row": 11,  
348 - "col": 1  
349 - },  
350 - {  
351 - "color": "#000000",  
352 - "pin": 25,  
353 - "label": "GND",  
354 - "row": 12,  
355 - "col": 0  
356 - },  
357 - {  
358 - "color": "#fd01fd",  
359 - "pin": 26,  
360 - "label": "GPIO 7 (SPI_CE1)",  
361 - "row": 12,  
362 - "col": 1  
363 - },  
364 - {  
365 - "color": "#ffffff",  
366 - "pin": 27,  
367 - "label": "ID_SD",  
368 - "row": 13,  
369 - "col": 0  
370 - },  
371 - {  
372 - "color": "#ffffff",  
373 - "pin": 28,  
374 - "label": "ID_SC",  
375 - "row": 13,  
376 - "col": 1  
377 - },  
378 - {  
379 - "color": "#00fd00",  
380 - "pin": 29,  
381 - "label": "GPIO 5",  
382 - "row": 14,  
383 - "col": 0  
384 - },  
385 - {  
386 - "color": "#000000",  
387 - "pin": 30,  
388 - "label": "GND",  
389 - "row": 14,  
390 - "col": 1  
391 - },  
392 - {  
393 - "color": "#00fd00",  
394 - "pin": 31,  
395 - "label": "GPIO 6",  
396 - "row": 15,  
397 - "col": 0  
398 - },  
399 - {  
400 - "color": "#00fd00",  
401 - "pin": 32,  
402 - "label": "GPIO 12",  
403 - "row": 15,  
404 - "col": 1  
405 - },  
406 - {  
407 - "color": "#00fd00",  
408 - "pin": 33,  
409 - "label": "GPIO 13",  
410 - "row": 16,  
411 - "col": 0  
412 - },  
413 - {  
414 - "color": "#000000",  
415 - "pin": 34,  
416 - "label": "GND",  
417 - "row": 16,  
418 - "col": 1  
419 - },  
420 - {  
421 - "color": "#00fd00",  
422 - "pin": 35,  
423 - "label": "GPIO 19",  
424 - "row": 17,  
425 - "col": 0  
426 - },  
427 - {  
428 - "color": "#00fd00",  
429 - "pin": 36,  
430 - "label": "GPIO 16",  
431 - "row": 17,  
432 - "col": 1  
433 - },  
434 - {  
435 - "color": "#00fd00",  
436 - "pin": 37,  
437 - "label": "GPIO 26",  
438 - "row": 18,  
439 - "col": 0  
440 - },  
441 - {  
442 - "color": "#00fd00",  
443 - "pin": 38,  
444 - "label": "GPIO 20",  
445 - "row": 18,  
446 - "col": 1  
447 - },  
448 - {  
449 - "color": "#000000",  
450 - "pin": 39,  
451 - "label": "GND",  
452 - "row": 19,  
453 - "col": 0  
454 - },  
455 - {  
456 - "color": "#00fd00",  
457 - "pin": 40,  
458 - "label": "GPIO 21",  
459 - "row": 19,  
460 - "col": 1  
461 - }  
462 - ],  
463 - "ledPanelBackgroundColor": "#008a00"  
464 - },  
465 - "title": "Raspberry Pi GPIO Status Panel",  
466 - "datasources": [  
467 - {  
468 - "type": "entity",  
469 - "dataKeys": [  
470 - {  
471 - "name": "7",  
472 - "type": "attribute",  
473 - "label": "7",  
474 - "color": "#2196f3",  
475 - "settings": {},  
476 - "_hash": 0.20925966435886978  
477 - },  
478 - {  
479 - "name": "11",  
480 - "type": "attribute",  
481 - "label": "11",  
482 - "color": "#4caf50",  
483 - "settings": {},  
484 - "_hash": 0.330267349594344  
485 - },  
486 - {  
487 - "name": "12",  
488 - "type": "attribute",  
489 - "label": "12",  
490 - "color": "#f44336",  
491 - "settings": {},  
492 - "_hash": 0.5040578704481748  
493 - },  
494 - {  
495 - "name": "13",  
496 - "type": "attribute",  
497 - "label": "13",  
498 - "color": "#ffc107",  
499 - "settings": {},  
500 - "_hash": 0.588956328191639  
501 - },  
502 - {  
503 - "name": "15",  
504 - "type": "attribute",  
505 - "label": "15",  
506 - "color": "#607d8b",  
507 - "settings": {},  
508 - "_hash": 0.9229040530336119  
509 - },  
510 - {  
511 - "name": "16",  
512 - "type": "attribute",  
513 - "label": "16",  
514 - "color": "#9c27b0",  
515 - "settings": {},  
516 - "_hash": 0.8692315253041654  
517 - },  
518 - {  
519 - "name": "18",  
520 - "type": "attribute",  
521 - "label": "18",  
522 - "color": "#8bc34a",  
523 - "settings": {},  
524 - "_hash": 0.41465562857521543  
525 - },  
526 - {  
527 - "name": "22",  
528 - "type": "attribute",  
529 - "label": "22",  
530 - "color": "#3f51b5",  
531 - "settings": {},  
532 - "_hash": 0.36135260043112827  
533 - },  
534 - {  
535 - "name": "29",  
536 - "type": "attribute",  
537 - "label": "29",  
538 - "color": "#e91e63",  
539 - "settings": {},  
540 - "_hash": 0.9904592276182183  
541 - },  
542 - {  
543 - "name": "31",  
544 - "type": "attribute",  
545 - "label": "31",  
546 - "color": "#ffeb3b",  
547 - "settings": {},  
548 - "_hash": 0.038330985429919195  
549 - },  
550 - {  
551 - "name": "32",  
552 - "type": "attribute",  
553 - "label": "32",  
554 - "color": "#03a9f4",  
555 - "settings": {},  
556 - "_hash": 0.4334683890135089  
557 - },  
558 - {  
559 - "name": "33",  
560 - "type": "attribute",  
561 - "label": "33",  
562 - "color": "#ff9800",  
563 - "settings": {},  
564 - "_hash": 0.6487255992492305  
565 - },  
566 - {  
567 - "name": "35",  
568 - "type": "attribute",  
569 - "label": "35",  
570 - "color": "#673ab7",  
571 - "settings": {},  
572 - "_hash": 0.971555321150732  
573 - },  
574 - {  
575 - "name": "36",  
576 - "type": "attribute",  
577 - "label": "36",  
578 - "color": "#cddc39",  
579 - "settings": {},  
580 - "_hash": 0.7826129728424382  
581 - },  
582 - {  
583 - "name": "37",  
584 - "type": "attribute",  
585 - "label": "37",  
586 - "color": "#009688",  
587 - "settings": {},  
588 - "_hash": 0.44925676517537627  
589 - },  
590 - {  
591 - "name": "38",  
592 - "type": "attribute",  
593 - "label": "38",  
594 - "color": "#795548",  
595 - "settings": {},  
596 - "_hash": 0.051518155759787465  
597 - },  
598 - {  
599 - "name": "40",  
600 - "type": "attribute",  
601 - "label": "40",  
602 - "color": "#00bcd4",  
603 - "settings": {},  
604 - "_hash": 0.8733296686871144  
605 - }  
606 - ],  
607 - "name": "RPi",  
608 - "entityAliasId": "f26b12b6-6938-e1a0-85ec-d88a1f23e382"  
609 - }  
610 - ],  
611 - "timewindow": {  
612 - "realtime": {  
613 - "timewindowMs": 60000  
614 - }  
615 - }  
616 - },  
617 - "row": 0,  
618 - "col": 6,  
619 - "id": "3cca52a5-e874-eb43-b444-8efa01e663c8"  
620 - }  
621 - },  
622 - "states": {  
623 - "default": {  
624 - "name": "Default",  
625 - "root": true,  
626 - "layouts": {  
627 - "main": {  
628 - "widgets": {  
629 - "602177f6-267b-cb87-4e8f-e23d7fb2f61c": {  
630 - "sizeX": 6,  
631 - "sizeY": 10,  
632 - "row": 0,  
633 - "col": 0  
634 - },  
635 - "3cca52a5-e874-eb43-b444-8efa01e663c8": {  
636 - "sizeX": 7,  
637 - "sizeY": 10,  
638 - "row": 0,  
639 - "col": 6  
640 - }  
641 - },  
642 - "gridSettings": {  
643 - "backgroundColor": "#eeeeee",  
644 - "color": "rgba(0,0,0,0.870588)",  
645 - "columns": 24,  
646 - "margins": [  
647 - 10,  
648 - 10  
649 - ],  
650 - "backgroundSizeMode": "100%"  
651 - }  
652 - }  
653 - }  
654 - }  
655 - },  
656 - "entityAliases": {  
657 - "f26b12b6-6938-e1a0-85ec-d88a1f23e382": {  
658 - "id": "f26b12b6-6938-e1a0-85ec-d88a1f23e382",  
659 - "alias": "RPi",  
660 - "filter": {  
661 - "type": "entityName",  
662 - "resolveMultiple": false,  
663 - "entityType": "DEVICE",  
664 - "entityNameFilter": "Raspberry Pi Demo Device"  
665 - }  
666 - }  
667 - },  
668 - "timewindow": {  
669 - "displayValue": "",  
670 - "selectedTab": 0,  
671 - "realtime": {  
672 - "interval": 1000,  
673 - "timewindowMs": 60000  
674 - },  
675 - "history": {  
676 - "historyType": 0,  
677 - "interval": 1000,  
678 - "timewindowMs": 60000,  
679 - "fixedTimewindow": {  
680 - "startTimeMs": 1498653734150,  
681 - "endTimeMs": 1498740134150  
682 - }  
683 - },  
684 - "aggregation": {  
685 - "type": "AVG",  
686 - "limit": 200  
687 - }  
688 - },  
689 - "settings": {  
690 - "stateControllerId": "default",  
691 - "showTitle": true,  
692 - "showDashboardsSelect": true,  
693 - "showEntitiesSelect": true,  
694 - "showDashboardTimewindow": true,  
695 - "showDashboardExport": true,  
696 - "toolbarAlwaysOpen": false  
697 - }  
698 - },  
699 - "name": "Raspberry PI GPIO Demo Dashboard"  
700 -}  
1 -{  
2 - "title": "Temperature & Humidity Demo Dashboard",  
3 - "configuration": {  
4 - "description": "Demo dashboard for sample applications that upload temperature and humidity received from DHT11 or DHT22 sensors",  
5 - "widgets": {  
6 - "03e06986-1c50-e9e4-267c-2bae930ad9a2": {  
7 - "isSystemType": true,  
8 - "bundleAlias": "digital_gauges",  
9 - "typeAlias": "digital_thermometer",  
10 - "type": "latest",  
11 - "title": "New widget",  
12 - "sizeX": 5,  
13 - "sizeY": 5,  
14 - "config": {  
15 - "datasources": [  
16 - {  
17 - "type": "entity",  
18 - "dataKeys": [  
19 - {  
20 - "name": "temperature",  
21 - "type": "timeseries",  
22 - "label": "temperature",  
23 - "color": "#2196f3",  
24 - "settings": {},  
25 - "_hash": 0.3720839051412099  
26 - }  
27 - ],  
28 - "name": "DHT11",  
29 - "entityAliasId": "63a93238-c13f-4403-4bcc-9ccc86bd6a62"  
30 - }  
31 - ],  
32 - "timewindow": {  
33 - "realtime": {  
34 - "timewindowMs": 60000  
35 - }  
36 - },  
37 - "showTitle": false,  
38 - "backgroundColor": "#000000",  
39 - "color": "rgba(0, 0, 0, 0.87)",  
40 - "padding": "0px",  
41 - "settings": {  
42 - "maxValue": 50,  
43 - "donutStartAngle": 90,  
44 - "showValue": true,  
45 - "showMinMax": true,  
46 - "gaugeWidthScale": 1,  
47 - "levelColors": [  
48 - "#304ffe",  
49 - "#7e57c2",  
50 - "#ff4081",  
51 - "#d32f2f"  
52 - ],  
53 - "refreshAnimationType": "<>",  
54 - "refreshAnimationTime": 700,  
55 - "startAnimationType": "<>",  
56 - "startAnimationTime": 700,  
57 - "titleFont": {  
58 - "family": "RobotoDraft",  
59 - "size": 12,  
60 - "style": "normal",  
61 - "weight": "500"  
62 - },  
63 - "labelFont": {  
64 - "family": "RobotoDraft",  
65 - "size": 8,  
66 - "style": "normal",  
67 - "weight": "500"  
68 - },  
69 - "valueFont": {  
70 - "family": "Segment7Standard",  
71 - "style": "normal",  
72 - "weight": "500",  
73 - "size": 18  
74 - },  
75 - "minMaxFont": {  
76 - "family": "Segment7Standard",  
77 - "size": 12,  
78 - "style": "normal",  
79 - "weight": "500"  
80 - },  
81 - "dashThickness": 1.5,  
82 - "decimals": 0,  
83 - "minValue": 0,  
84 - "units": "°C",  
85 - "gaugeColor": "#333333",  
86 - "neonGlowBrightness": 35,  
87 - "gaugeType": "donut",  
88 - "showTitle": false  
89 - },  
90 - "title": "Temperature"  
91 - },  
92 - "row": 0,  
93 - "col": 0,  
94 - "id": "03e06986-1c50-e9e4-267c-2bae930ad9a2"  
95 - },  
96 - "88808eb1-d381-9970-c852-e3499df68bd8": {  
97 - "isSystemType": true,  
98 - "bundleAlias": "digital_gauges",  
99 - "typeAlias": "digital_vertical_bar",  
100 - "type": "latest",  
101 - "title": "New widget",  
102 - "sizeX": 3,  
103 - "sizeY": 5,  
104 - "config": {  
105 - "datasources": [  
106 - {  
107 - "type": "entity",  
108 - "dataKeys": [  
109 - {  
110 - "name": "humidity",  
111 - "type": "timeseries",  
112 - "label": "humidity",  
113 - "color": "#2196f3",  
114 - "settings": {},  
115 - "_hash": 0.9492802776509441  
116 - }  
117 - ],  
118 - "name": "DHT11",  
119 - "entityAliasId": "63a93238-c13f-4403-4bcc-9ccc86bd6a62"  
120 - }  
121 - ],  
122 - "timewindow": {  
123 - "realtime": {  
124 - "timewindowMs": 60000  
125 - }  
126 - },  
127 - "showTitle": false,  
128 - "backgroundColor": "#000000",  
129 - "color": "rgba(0, 0, 0, 0.87)",  
130 - "padding": "0px",  
131 - "settings": {  
132 - "maxValue": 100,  
133 - "donutStartAngle": 90,  
134 - "showValue": true,  
135 - "showMinMax": true,  
136 - "gaugeWidthScale": 0.75,  
137 - "levelColors": [  
138 - "#3d5afe",  
139 - "#f44336"  
140 - ],  
141 - "refreshAnimationType": "<>",  
142 - "refreshAnimationTime": 700,  
143 - "startAnimationType": "<>",  
144 - "startAnimationTime": 700,  
145 - "titleFont": {  
146 - "family": "RobotoDraft",  
147 - "size": 12,  
148 - "style": "normal",  
149 - "weight": "500"  
150 - },  
151 - "labelFont": {  
152 - "family": "RobotoDraft",  
153 - "size": 8,  
154 - "style": "normal",  
155 - "weight": "500"  
156 - },  
157 - "valueFont": {  
158 - "family": "Segment7Standard",  
159 - "style": "normal",  
160 - "weight": "500",  
161 - "size": 14  
162 - },  
163 - "minMaxFont": {  
164 - "family": "Segment7Standard",  
165 - "size": 8,  
166 - "style": "normal",  
167 - "weight": "normal",  
168 - "color": "#cccccc"  
169 - },  
170 - "neonGlowBrightness": 20,  
171 - "decimals": 0,  
172 - "showUnitTitle": true,  
173 - "gaugeColor": "#171a1c",  
174 - "gaugeType": "verticalBar",  
175 - "showTitle": false,  
176 - "minValue": 0,  
177 - "dashThickness": 1.2  
178 - },  
179 - "title": "Humidity"  
180 - },  
181 - "row": 0,  
182 - "col": 5,  
183 - "id": "88808eb1-d381-9970-c852-e3499df68bd8"  
184 - }  
185 - },  
186 - "states": {  
187 - "default": {  
188 - "name": "Default",  
189 - "root": true,  
190 - "layouts": {  
191 - "main": {  
192 - "widgets": {  
193 - "03e06986-1c50-e9e4-267c-2bae930ad9a2": {  
194 - "sizeX": 5,  
195 - "sizeY": 5,  
196 - "row": 0,  
197 - "col": 0  
198 - },  
199 - "88808eb1-d381-9970-c852-e3499df68bd8": {  
200 - "sizeX": 3,  
201 - "sizeY": 5,  
202 - "row": 0,  
203 - "col": 5  
204 - }  
205 - },  
206 - "gridSettings": {  
207 - "backgroundColor": "#eeeeee",  
208 - "color": "rgba(0,0,0,0.870588)",  
209 - "columns": 24,  
210 - "margins": [  
211 - 10,  
212 - 10  
213 - ],  
214 - "backgroundSizeMode": "100%"  
215 - }  
216 - }  
217 - }  
218 - }  
219 - },  
220 - "entityAliases": {  
221 - "63a93238-c13f-4403-4bcc-9ccc86bd6a62": {  
222 - "id": "63a93238-c13f-4403-4bcc-9ccc86bd6a62",  
223 - "alias": "DHT11",  
224 - "filter": {  
225 - "type": "entityName",  
226 - "resolveMultiple": false,  
227 - "entityType": "DEVICE",  
228 - "entityNameFilter": "DHT11 Demo Device"  
229 - }  
230 - }  
231 - },  
232 - "timewindow": {  
233 - "displayValue": "",  
234 - "selectedTab": 0,  
235 - "realtime": {  
236 - "interval": 1000,  
237 - "timewindowMs": 60000  
238 - },  
239 - "history": {  
240 - "historyType": 0,  
241 - "interval": 1000,  
242 - "timewindowMs": 60000,  
243 - "fixedTimewindow": {  
244 - "startTimeMs": 1498653790019,  
245 - "endTimeMs": 1498740190019  
246 - }  
247 - },  
248 - "aggregation": {  
249 - "type": "AVG",  
250 - "limit": 200  
251 - }  
252 - },  
253 - "settings": {  
254 - "stateControllerId": "default",  
255 - "showTitle": true,  
256 - "showDashboardsSelect": true,  
257 - "showEntitiesSelect": true,  
258 - "showDashboardTimewindow": true,  
259 - "showDashboardExport": true,  
260 - "toolbarAlwaysOpen": false  
261 - }  
262 - },  
263 - "name": "Temperature & Humidity Demo Dashboard"  
264 -}  
@@ -147,9 +147,9 @@ @@ -147,9 +147,9 @@
147 "name": "Add", 147 "name": "Add",
148 "icon": "add", 148 "icon": "add",
149 "type": "customPretty", 149 "type": "customPretty",
150 - "customHtml": "<form #addEntityForm=\"ngForm\" [formGroup]=\"addEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"add-entity-form\">\n <mat-toolbar color=\"primary\">\n <h2>Add thermostat</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Thermostat name</mat-label>\n <input matInput formControlName=\"entityName\" required>\n <mat-error *ngIf=\"addEntityFormGroup.get('entityName').hasError('required')\">\n Thermostat name is required.\n </mat-error>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxLayout=\"column\">\n <mat-slide-toggle formControlName=\"alarmTemperature\">\n High temperature alarm\n </mat-slide-toggle>\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>High temperature threshold, °C</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"addEntityFormGroup.get('attributes').get('alarmTemperature').value\"\n formControlName=\"thresholdTemperature\">\n <mat-error *ngIf=\"addEntityFormGroup.get('attributes').get('thresholdTemperature').hasError('required')\">\n High temperature threshold is required.\n </mat-error>\n </mat-form-field>\n \n <mat-slide-toggle formControlName=\"alarmHumidity\">\n Low humidity alarm\n </mat-slide-toggle>\n \n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Low humidity threshold, %</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"addEntityFormGroup.get('attributes').get('alarmHumidity').value\"\n formControlName=\"thresholdHumidity\">\n <mat-error *ngIf=\"addEntityFormGroup.get('attributes').get('thresholdHumidity').hasError('required')\">\n Low humidity threshold is required.\n </mat-error>\n </mat-form-field>\n </div>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty\">\n Create\n </button>\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>", 150 + "customHtml": "<form #addEntityForm=\"ngForm\" [formGroup]=\"addEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"add-entity-form\">\n <mat-toolbar color=\"primary\">\n <h2>Add thermostat</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Thermostat name</mat-label>\n <input matInput formControlName=\"entityName\" required>\n <mat-error *ngIf=\"addEntityFormGroup.get('entityName').hasError('required')\">\n Thermostat name is required.\n </mat-error>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxLayout=\"column\">\n <mat-slide-toggle formControlName=\"temperatureAlarmFlag\">\n High temperature alarm\n </mat-slide-toggle>\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>High temperature threshold, °C</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"addEntityFormGroup.get('attributes').get('temperatureAlarmFlag').value\"\n formControlName=\"temperatureAlarmThreshold\">\n <mat-error *ngIf=\"addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').hasError('required')\">\n High temperature threshold is required.\n </mat-error>\n </mat-form-field>\n \n <mat-slide-toggle formControlName=\"humidityAlarmFlag\">\n Low humidity alarm\n </mat-slide-toggle>\n \n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Low humidity threshold, %</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"addEntityFormGroup.get('attributes').get('humidityAlarmFlag').value\"\n formControlName=\"humidityAlarmThreshold\">\n <mat-error *ngIf=\"addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').hasError('required')\">\n Low humidity threshold is required.\n </mat-error>\n </mat-form-field>\n </div>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty\">\n Create\n </button>\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>",
151 "customCss": ".add-entity-form{\n width: 300px;\n}\n", 151 "customCss": ".add-entity-form{\n width: 300px;\n}\n",
152 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.addEntityFormGroup = vm.fb.group({\n entityName: ['', [vm.validators.required]],\n attributes: vm.fb.group({\n alarmTemperature: [false],\n thresholdTemperature: [{value: null, disabled: true}],\n alarmHumidity: [false],\n thresholdHumidity: [{value: null, disabled: true}]\n })\n });\n \n vm.addEntityFormGroup.get('attributes').get('alarmTemperature').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('thresholdTemperature').disable();\n }\n });\n \n vm.addEntityFormGroup.get('attributes').get('alarmHumidity').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('thresholdHumidity').disable();\n }\n });\n\n vm.save = function() {\n vm.addEntityFormGroup.markAsPristine();\n saveEntityObservable().subscribe(\n function (entity) {\n saveAttributes(entity.id).subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function saveEntityObservable() {\n const formValues = vm.addEntityFormGroup.value;\n let entity = {\n name: formValues.entityName,\n type: \"thermostat\"\n };\n return deviceService.saveDevice(entity);\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.addEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if(attributes[key] !== null) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", 152 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.addEntityFormGroup = vm.fb.group({\n entityName: ['', [vm.validators.required]],\n attributes: vm.fb.group({\n temperatureAlarmFlag: [false],\n temperatureAlarmThreshold: [{value: null, disabled: true}],\n humidityAlarmFlag: [false],\n humidityAlarmThreshold: [{value: null, disabled: true}]\n })\n });\n \n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').disable();\n }\n });\n \n vm.addEntityFormGroup.get('attributes').get('humidityAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').disable();\n }\n });\n\n vm.save = function() {\n vm.addEntityFormGroup.markAsPristine();\n saveEntityObservable().subscribe(\n function (entity) {\n saveAttributes(entity.id).subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function saveEntityObservable() {\n const formValues = vm.addEntityFormGroup.value;\n let entity = {\n name: formValues.entityName,\n type: \"thermostat\"\n };\n return deviceService.saveDevice(entity);\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.addEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if(attributes[key] !== null) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}",
153 "customResources": [], 153 "customResources": [],
154 "id": "8ab5a518-67d2-b6a2-956d-81fd512294b2" 154 "id": "8ab5a518-67d2-b6a2-956d-81fd512294b2"
155 } 155 }
@@ -167,9 +167,9 @@ @@ -167,9 +167,9 @@
167 "name": "Edit", 167 "name": "Edit",
168 "icon": "edit", 168 "icon": "edit",
169 "type": "customPretty", 169 "type": "customPretty",
170 - "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar color=\"primary\">\n <h2>Edit thermostat {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Thermostat name</mat-label>\n <input matInput formControlName=\"entityName\" readonly>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxLayout=\"column\">\n <mat-slide-toggle formControlName=\"alarmTemperature\">\n High temperature alarm\n </mat-slide-toggle>\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>High temperature threshold, °C</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"editEntityFormGroup.get('attributes').get('alarmTemperature').value\"\n formControlName=\"thresholdTemperature\">\n <mat-error *ngIf=\"editEntityFormGroup.get('attributes').get('thresholdTemperature').hasError('required')\">\n High temperature threshold is required.\n </mat-error>\n </mat-form-field>\n\n <mat-slide-toggle formControlName=\"alarmHumidity\">\n Low humidity alarm\n </mat-slide-toggle>\n\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Low humidity threshold, %</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"editEntityFormGroup.get('attributes').get('alarmHumidity').value\"\n formControlName=\"thresholdHumidity\">\n <mat-error *ngIf=\"editEntityFormGroup.get('attributes').get('thresholdHumidity').hasError('required')\">\n Low humidity threshold is required.\n </mat-error>\n </mat-form-field>\n </div>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>", 170 + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar color=\"primary\">\n <h2>Edit thermostat {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Thermostat name</mat-label>\n <input matInput formControlName=\"entityName\" readonly>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxLayout=\"column\">\n <mat-slide-toggle formControlName=\"temperatureAlarmFlag\">\n High temperature alarm\n </mat-slide-toggle>\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>High temperature threshold, °C</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"editEntityFormGroup.get('attributes').get('temperatureAlarmFlag').value\"\n formControlName=\"temperatureAlarmThreshold\">\n <mat-error *ngIf=\"editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').hasError('required')\">\n High temperature threshold is required.\n </mat-error>\n </mat-form-field>\n\n <mat-slide-toggle formControlName=\"humidityAlarmFlag\">\n Low humidity alarm\n </mat-slide-toggle>\n\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Low humidity threshold, %</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"editEntityFormGroup.get('attributes').get('humidityAlarmFlag').value\"\n formControlName=\"humidityAlarmThreshold\">\n <mat-error *ngIf=\"editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').hasError('required')\">\n Low humidity threshold is required.\n </mat-error>\n </mat-form-field>\n </div>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>",
171 "customCss": ".edit-entity-form{\n width: 300px;\n}", 171 "customCss": ".edit-entity-form{\n width: 300px;\n}",
172 - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId;\n vm.entityName = entityName;\n vm.attributes = {};\n \n vm.editEntityFormGroup = vm.fb.group({\n entityName: [''],\n attributes: vm.fb.group({\n alarmTemperature: [false],\n thresholdTemperature: [{value: null, disabled: true}],\n alarmHumidity: [false],\n thresholdHumidity: [{value: null, disabled: true}]\n })\n });\n \n vm.editEntityFormGroup.get('attributes').get('alarmTemperature').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').disable();\n }\n });\n \n vm.editEntityFormGroup.get('attributes').get('alarmHumidity').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').disable();\n }\n });\n \n \n getEntityInfo();\n \n \n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveAttributes(entityId).subscribe(\n function () {\n vm.dialogRef.close(null);\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function getEntityAttributes(attributes) {\n for (var i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value;\n }\n }\n \n function getEntityInfo() {\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe(\n function (attributes) {\n getEntityAttributes(attributes);\n vm.editEntityFormGroup.patchValue({\n entityName: vm.entityName,\n attributes: vm.attributes\n });\n // if(vm.attributes.alarmTemperature) {\n // vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n // }\n // if(vm.attributes.alarmHumidity) {\n // vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n // }\n }\n );\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.editEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if (attributes[key] !== vm.attributes[key]) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", 172 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId;\n vm.entityName = entityName;\n vm.attributes = {};\n \n vm.editEntityFormGroup = vm.fb.group({\n entityName: [''],\n attributes: vm.fb.group({\n temperatureAlarmFlag: [false],\n temperatureAlarmThreshold: [{value: null, disabled: true}],\n humidityAlarmFlag: [false],\n humidityAlarmThreshold: [{value: null, disabled: true}]\n })\n });\n \n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').disable();\n }\n });\n \n vm.editEntityFormGroup.get('attributes').get('humidityAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').disable();\n }\n });\n \n \n getEntityInfo();\n \n \n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveAttributes(entityId).subscribe(\n function () {\n vm.dialogRef.close(null);\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function getEntityAttributes(attributes) {\n for (var i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value;\n }\n }\n \n function getEntityInfo() {\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe(\n function (attributes) {\n getEntityAttributes(attributes);\n vm.editEntityFormGroup.patchValue({\n entityName: vm.entityName,\n attributes: vm.attributes\n });\n // if(vm.attributes.temperatureAlarmFlag) {\n // vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n // }\n // if(vm.attributes.humidityAlarmFlag) {\n // vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n // }\n }\n );\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.editEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if (attributes[key] !== vm.attributes[key]) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}",
173 "customResources": [], 173 "customResources": [],
174 "id": "7506576f-87ba-d3a0-88fb-e304d451776d" 174 "id": "7506576f-87ba-d3a0-88fb-e304d451776d"
175 }, 175 },
@@ -550,7 +550,7 @@ @@ -550,7 +550,7 @@
550 "type": "entity", 550 "type": "entity",
551 "dataKeys": [ 551 "dataKeys": [
552 { 552 {
553 - "name": "alarmTemperature", 553 + "name": "temperatureAlarmFlag",
554 "type": "attribute", 554 "type": "attribute",
555 "label": "High temperature alarm", 555 "label": "High temperature alarm",
556 "color": "#4caf50", 556 "color": "#4caf50",
@@ -565,7 +565,7 @@ @@ -565,7 +565,7 @@
565 "_hash": 0.8725278440159361 565 "_hash": 0.8725278440159361
566 }, 566 },
567 { 567 {
568 - "name": "thresholdTemperature", 568 + "name": "temperatureAlarmThreshold",
569 "type": "attribute", 569 "type": "attribute",
570 "label": "High temperature threshold, °C", 570 "label": "High temperature threshold, °C",
571 "color": "#f44336", 571 "color": "#f44336",
@@ -576,12 +576,12 @@ @@ -576,12 +576,12 @@
576 "isEditable": "editable", 576 "isEditable": "editable",
577 "dataKeyHidden": false, 577 "dataKeyHidden": false,
578 "step": 1, 578 "step": 1,
579 - "disabledOnDataKey": "alarmTemperature" 579 + "disabledOnDataKey": "temperatureAlarmFlag"
580 }, 580 },
581 "_hash": 0.7316078472857874 581 "_hash": 0.7316078472857874
582 }, 582 },
583 { 583 {
584 - "name": "alarmHumidity", 584 + "name": "humidityAlarmFlag",
585 "type": "attribute", 585 "type": "attribute",
586 "label": "Low humidity alarm", 586 "label": "Low humidity alarm",
587 "color": "#ffc107", 587 "color": "#ffc107",
@@ -596,7 +596,7 @@ @@ -596,7 +596,7 @@
596 "_hash": 0.5339673667431057 596 "_hash": 0.5339673667431057
597 }, 597 },
598 { 598 {
599 - "name": "thresholdHumidity", 599 + "name": "humidityAlarmThreshold",
600 "type": "attribute", 600 "type": "attribute",
601 "label": "Low humidity threshold, %", 601 "label": "Low humidity threshold, %",
602 "color": "#607d8b", 602 "color": "#607d8b",
@@ -607,7 +607,7 @@ @@ -607,7 +607,7 @@
607 "isEditable": "editable", 607 "isEditable": "editable",
608 "dataKeyHidden": false, 608 "dataKeyHidden": false,
609 "step": 1, 609 "step": 1,
610 - "disabledOnDataKey": "alarmHumidity" 610 + "disabledOnDataKey": "humidityAlarmFlag"
611 }, 611 },
612 "_hash": 0.2687091190358901 612 "_hash": 0.2687091190358901
613 } 613 }
1 -{  
2 - "ruleChain": {  
3 - "additionalInfo": null,  
4 - "name": "Root Rule Chain",  
5 - "firstRuleNodeId": null,  
6 - "root": true,  
7 - "debugMode": false,  
8 - "configuration": null  
9 - },  
10 - "metadata": {  
11 - "firstNodeIndex": 3,  
12 - "nodes": [  
13 - {  
14 - "additionalInfo": {  
15 - "layoutX": 1069,  
16 - "layoutY": 267  
17 - },  
18 - "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",  
19 - "name": "Is Thermostat?",  
20 - "debugMode": false,  
21 - "configuration": {  
22 - "jsScript": "return msg.id.entityType === \"DEVICE\" && msg.type === \"thermostat\";"  
23 - }  
24 - },  
25 - {  
26 - "additionalInfo": {  
27 - "layoutX": 824,  
28 - "layoutY": 156  
29 - },  
30 - "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",  
31 - "name": "Save Timeseries",  
32 - "debugMode": false,  
33 - "configuration": {  
34 - "defaultTTL": 0  
35 - }  
36 - },  
37 - {  
38 - "additionalInfo": {  
39 - "layoutX": 825,  
40 - "layoutY": 52  
41 - },  
42 - "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",  
43 - "name": "Save Client Attributes",  
44 - "debugMode": false,  
45 - "configuration": {  
46 - "scope": "CLIENT_SCOPE",  
47 - "notifyDevice": "false"  
48 - }  
49 - },  
50 - {  
51 - "additionalInfo": {  
52 - "layoutX": 347,  
53 - "layoutY": 149  
54 - },  
55 - "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",  
56 - "name": "Message Type Switch",  
57 - "debugMode": false,  
58 - "configuration": {  
59 - "version": 0  
60 - }  
61 - },  
62 - {  
63 - "additionalInfo": {  
64 - "layoutX": 839,  
65 - "layoutY": 345  
66 - },  
67 - "type": "org.thingsboard.rule.engine.action.TbLogNode",  
68 - "name": "Log RPC from Device",  
69 - "debugMode": false,  
70 - "configuration": {  
71 - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"  
72 - }  
73 - },  
74 - {  
75 - "additionalInfo": {  
76 - "layoutX": 832,  
77 - "layoutY": 407  
78 - },  
79 - "type": "org.thingsboard.rule.engine.action.TbLogNode",  
80 - "name": "Log Other",  
81 - "debugMode": false,  
82 - "configuration": {  
83 - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"  
84 - }  
85 - },  
86 - {  
87 - "additionalInfo": {  
88 - "layoutX": 825,  
89 - "layoutY": 468  
90 - },  
91 - "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",  
92 - "name": "RPC Call Request",  
93 - "debugMode": false,  
94 - "configuration": {  
95 - "timeoutInSeconds": 60  
96 - }  
97 - },  
98 - {  
99 - "additionalInfo": {  
100 - "layoutX": 1069,  
101 - "layoutY": 90  
102 - },  
103 - "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",  
104 - "name": "Is Thermostat?",  
105 - "debugMode": false,  
106 - "configuration": {  
107 - "jsScript": "return metadata[\"deviceType\"] === \"thermostat\";"  
108 - }  
109 - },  
110 - {  
111 - "additionalInfo": {  
112 - "layoutX": 1090,  
113 - "layoutY": 360  
114 - },  
115 - "type": "org.thingsboard.rule.engine.action.TbCreateRelationNode",  
116 - "name": "Relate to Asset",  
117 - "debugMode": false,  
118 - "configuration": {  
119 - "direction": "FROM",  
120 - "relationType": "ToAlarmPropagationAsset",  
121 - "entityType": "ASSET",  
122 - "entityNamePattern": "Thermostat Alarms",  
123 - "entityTypePattern": "AlarmPropagationAsset",  
124 - "entityCacheExpiration": 300,  
125 - "createEntityIfNotExists": true,  
126 - "changeOriginatorToRelatedEntity": false,  
127 - "removeCurrentRelations": false  
128 - }  
129 - }  
130 - ],  
131 - "connections": [  
132 - {  
133 - "fromIndex": 0,  
134 - "toIndex": 8,  
135 - "type": "True"  
136 - },  
137 - {  
138 - "fromIndex": 1,  
139 - "toIndex": 7,  
140 - "type": "Success"  
141 - },  
142 - {  
143 - "fromIndex": 3,  
144 - "toIndex": 5,  
145 - "type": "Other"  
146 - },  
147 - {  
148 - "fromIndex": 3,  
149 - "toIndex": 2,  
150 - "type": "Post attributes"  
151 - },  
152 - {  
153 - "fromIndex": 3,  
154 - "toIndex": 1,  
155 - "type": "Post telemetry"  
156 - },  
157 - {  
158 - "fromIndex": 3,  
159 - "toIndex": 4,  
160 - "type": "RPC Request from Device"  
161 - },  
162 - {  
163 - "fromIndex": 3,  
164 - "toIndex": 6,  
165 - "type": "RPC Request to Device"  
166 - },  
167 - {  
168 - "fromIndex": 3,  
169 - "toIndex": 0,  
170 - "type": "Entity Created"  
171 - }  
172 - ],  
173 - "ruleChainConnections": [  
174 - {  
175 - "fromIndex": 7,  
176 - "targetRuleChainId": {  
177 - "entityType": "RULE_CHAIN",  
178 - "id": "25e26570-89ed-11ea-a650-cd6e14e633bd"  
179 - },  
180 - "additionalInfo": {  
181 - "layoutX": 1109,  
182 - "layoutY": 182,  
183 - "ruleChainNodeId": "rule-chain-node-10"  
184 - },  
185 - "type": "True"  
186 - }  
187 - ]  
188 - }  
189 -}  
1 -{  
2 - "ruleChain": {  
3 - "additionalInfo": {  
4 - "description": ""  
5 - },  
6 - "name": "Thermostat Alarms",  
7 - "firstRuleNodeId": null,  
8 - "root": false,  
9 - "debugMode": false,  
10 - "configuration": null  
11 - },  
12 - "metadata": {  
13 - "firstNodeIndex": 6,  
14 - "nodes": [  
15 - {  
16 - "additionalInfo": {  
17 - "layoutX": 822,  
18 - "layoutY": 294  
19 - },  
20 - "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",  
21 - "name": "Save Timeseries",  
22 - "debugMode": false,  
23 - "configuration": {  
24 - "defaultTTL": 0  
25 - }  
26 - },  
27 - {  
28 - "additionalInfo": {  
29 - "description": null,  
30 - "layoutX": 824,  
31 - "layoutY": 221  
32 - },  
33 - "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",  
34 - "name": "Save Client Attributes",  
35 - "debugMode": false,  
36 - "configuration": {  
37 - "scope": "SERVER_SCOPE",  
38 - "notifyDevice": null  
39 - }  
40 - },  
41 - {  
42 - "additionalInfo": {  
43 - "layoutX": 494,  
44 - "layoutY": 309  
45 - },  
46 - "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",  
47 - "name": "Message Type Switch",  
48 - "debugMode": false,  
49 - "configuration": {  
50 - "version": 0  
51 - }  
52 - },  
53 - {  
54 - "additionalInfo": {  
55 - "layoutX": 824,  
56 - "layoutY": 383  
57 - },  
58 - "type": "org.thingsboard.rule.engine.action.TbLogNode",  
59 - "name": "Log RPC from Device",  
60 - "debugMode": false,  
61 - "configuration": {  
62 - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"  
63 - }  
64 - },  
65 - {  
66 - "additionalInfo": {  
67 - "layoutX": 823,  
68 - "layoutY": 444  
69 - },  
70 - "type": "org.thingsboard.rule.engine.action.TbLogNode",  
71 - "name": "Log Other",  
72 - "debugMode": false,  
73 - "configuration": {  
74 - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"  
75 - }  
76 - },  
77 - {  
78 - "additionalInfo": {  
79 - "layoutX": 822,  
80 - "layoutY": 507  
81 - },  
82 - "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",  
83 - "name": "RPC Call Request",  
84 - "debugMode": false,  
85 - "configuration": {  
86 - "timeoutInSeconds": 60  
87 - }  
88 - },  
89 - {  
90 - "additionalInfo": {  
91 - "description": "",  
92 - "layoutX": 209,  
93 - "layoutY": 307  
94 - },  
95 - "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",  
96 - "name": "Device Profile Node",  
97 - "debugMode": false,  
98 - "configuration": {  
99 - "persistAlarmRulesState": false,  
100 - "fetchAlarmRulesStateOnStart": false  
101 - }  
102 - }  
103 - ],  
104 - "connections": [  
105 - {  
106 - "fromIndex": 2,  
107 - "toIndex": 4,  
108 - "type": "Other"  
109 - },  
110 - {  
111 - "fromIndex": 2,  
112 - "toIndex": 1,  
113 - "type": "Post attributes"  
114 - },  
115 - {  
116 - "fromIndex": 2,  
117 - "toIndex": 0,  
118 - "type": "Post telemetry"  
119 - },  
120 - {  
121 - "fromIndex": 2,  
122 - "toIndex": 3,  
123 - "type": "RPC Request from Device"  
124 - },  
125 - {  
126 - "fromIndex": 2,  
127 - "toIndex": 5,  
128 - "type": "RPC Request to Device"  
129 - },  
130 - {  
131 - "fromIndex": 6,  
132 - "toIndex": 2,  
133 - "type": "Success"  
134 - }  
135 - ],  
136 - "ruleChainConnections": null  
137 - }  
138 -}  
@@ -33,7 +33,7 @@ @@ -33,7 +33,7 @@
33 "templateCss": ".tb-toast {\n min-width: 0;\n font-size: 14px !important;\n}", 33 "templateCss": ".tb-toast {\n min-width: 0;\n font-size: 14px !important;\n}",
34 "controllerScript": "self.onInit = function() {\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.multipleInputWidget.onDataUpdated();\r\n}\r\n", 34 "controllerScript": "self.onInit = function() {\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.multipleInputWidget.onDataUpdated();\r\n}\r\n",
35 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"updateAllValues\": {\n \"title\":\"Update all values, not only modified\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"saveButtonLabel\": {\n \"title\": \"'SAVE' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"resetButtonLabel\": {\n \"title\": \"'UNDO' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showGroupTitle\": {\n \"title\":\"Show title for group of fields, related to different entities\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"groupTitle\": {\n \"title\": \"Group title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n {\n \"key\": \"updateAllValues\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"saveButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"resetButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n \"showResultMessage\",\n \"showGroupTitle\",\n \"groupTitle\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n {\n \"key\": \"fieldsInRow\",\n \"condition\": \"model.fieldsAlignment === 'row'\"\n }\n ]\n}", 35 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"updateAllValues\": {\n \"title\":\"Update all values, not only modified\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"saveButtonLabel\": {\n \"title\": \"'SAVE' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"resetButtonLabel\": {\n \"title\": \"'UNDO' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showGroupTitle\": {\n \"title\":\"Show title for group of fields, related to different entities\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"groupTitle\": {\n \"title\": \"Group title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n {\n \"key\": \"updateAllValues\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"saveButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"resetButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n \"showResultMessage\",\n \"showGroupTitle\",\n \"groupTitle\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n {\n \"key\": \"fieldsInRow\",\n \"condition\": \"model.fieldsAlignment === 'row'\"\n }\n ]\n}",
36 - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"step\": {\n \"title\": \"Step interval between values\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\"\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"minValueErrorMessage\": {\n \"title\": \"'Min Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValueErrorMessage\": {\n \"title\": \"'Max Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"invalidDateErrorMessage\": {\n \"title\": \"'Invalid Date' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"selectOptionsType\": {\n \"title\": \"Select options type\",\n \"type\": \"string\",\n \"default\": \"valueWithLabel\"\n },\n \"selectOptionsList\": {\n \"title\": \"Select options list\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"value\": {\n \"title\": \"Value\",\n \"type\": \"string\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n }\n },\n \"required\": [\"value\", \"label\"]\n }\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n },\n {\n \"value\": \"selectOption\",\n \"label\": \"Selectable option\"\n }\n ]\n },\n {\n \"key\": \"selectOptionsType\",\n \"condition\": \"model.dataKeyValueType === 'selectOption'\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"valueWithLabel\",\n \"label\": \"Values with labels\"\n },\n {\n \"value\": \"rawValue\",\n \"label\": \"Raw values\"\n }\n ]\n },\n {\n \"key\": \"selectOptionsList\",\n \"type\": \"array\",\n \"condition\": \"model.dataKeyValueType === 'selectOption'\",\n \"items\": [\n \"selectOptionsList[].value\",\n {\n \"key\": \"selectOptionsList[].label\",\n \"condition\": \"model.selectOptionsType === 'valueWithLabel'\"\n }\n ]\n },\n {\n \"key\": \"step\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"minValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n \"required\",\n {\n \"key\": \"requiredErrorMessage\",\n \"condition\": \"model.required === true\"\n },\n {\n \"key\": \"invalidDateErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'dateTime' || model.dataKeyValueType === 'date' || model.dataKeyValueType === 'time'\"\n },\n {\n \"key\": \"minValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", 36 + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"step\": {\n \"title\": \"Step interval between values\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\"\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"minValueErrorMessage\": {\n \"title\": \"'Min Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValueErrorMessage\": {\n \"title\": \"'Max Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"invalidDateErrorMessage\": {\n \"title\": \"'Invalid Date' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n }\n ]\n },\n {\n \"key\": \"step\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"minValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n \"required\",\n {\n \"key\": \"requiredErrorMessage\",\n \"condition\": \"model.required === true\"\n },\n {\n \"key\": \"invalidDateErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'dateTime' || model.dataKeyValueType === 'date' || model.dataKeyValueType === 'time'\"\n },\n {\n \"key\": \"minValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n",
37 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 37 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
38 } 38 }
39 }, 39 },
@@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.Customer; @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.Customer;
39 import org.thingsboard.server.common.data.DataConstants; 39 import org.thingsboard.server.common.data.DataConstants;
40 import org.thingsboard.server.common.data.Device; 40 import org.thingsboard.server.common.data.Device;
41 import org.thingsboard.server.common.data.DeviceProfile; 41 import org.thingsboard.server.common.data.DeviceProfile;
  42 +import org.thingsboard.server.common.data.TenantProfile;
42 import org.thingsboard.server.common.data.alarm.Alarm; 43 import org.thingsboard.server.common.data.alarm.Alarm;
43 import org.thingsboard.server.common.data.asset.Asset; 44 import org.thingsboard.server.common.data.asset.Asset;
44 import org.thingsboard.server.common.data.id.DeviceId; 45 import org.thingsboard.server.common.data.id.DeviceId;
@@ -511,13 +512,24 @@ class DefaultTbContext implements TbContext { @@ -511,13 +512,24 @@ class DefaultTbContext implements TbContext {
511 } 512 }
512 513
513 @Override 514 @Override
  515 + public void addTenantProfileListener(Consumer<TenantProfile> listener) {
  516 + mainCtx.getTenantProfileCache().addListener(getTenantId(), getSelfId(), listener);
  517 + }
  518 +
  519 + @Override
514 public void addDeviceProfileListeners(Consumer<DeviceProfile> profileListener, BiConsumer<DeviceId, DeviceProfile> deviceListener) { 520 public void addDeviceProfileListeners(Consumer<DeviceProfile> profileListener, BiConsumer<DeviceId, DeviceProfile> deviceListener) {
515 mainCtx.getDeviceProfileCache().addListener(getTenantId(), getSelfId(), profileListener, deviceListener); 521 mainCtx.getDeviceProfileCache().addListener(getTenantId(), getSelfId(), profileListener, deviceListener);
516 } 522 }
517 523
518 @Override 524 @Override
519 - public void removeProfileListener() { 525 + public void removeListeners() {
520 mainCtx.getDeviceProfileCache().removeListener(getTenantId(), getSelfId()); 526 mainCtx.getDeviceProfileCache().removeListener(getTenantId(), getSelfId());
  527 + mainCtx.getTenantProfileCache().removeListener(getTenantId(), getSelfId());
  528 + }
  529 +
  530 + @Override
  531 + public TenantProfile getTenantProfile() {
  532 + return mainCtx.getTenantProfileCache().get(getTenantId());
521 } 533 }
522 534
523 private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { 535 private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
@@ -26,7 +26,7 @@ import org.springframework.web.bind.annotation.ResponseBody; @@ -26,7 +26,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
26 import org.springframework.web.bind.annotation.RestController; 26 import org.springframework.web.bind.annotation.RestController;
27 import org.thingsboard.rule.engine.api.MailService; 27 import org.thingsboard.rule.engine.api.MailService;
28 import org.thingsboard.rule.engine.api.SmsService; 28 import org.thingsboard.rule.engine.api.SmsService;
29 -import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; 29 +import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
30 import org.thingsboard.server.common.data.AdminSettings; 30 import org.thingsboard.server.common.data.AdminSettings;
31 import org.thingsboard.server.common.data.UpdateMessage; 31 import org.thingsboard.server.common.data.UpdateMessage;
32 import org.thingsboard.server.common.data.exception.ThingsboardException; 32 import org.thingsboard.server.common.data.exception.ThingsboardException;
@@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.asset.Asset; @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.asset.Asset;
33 import org.thingsboard.server.common.data.asset.AssetInfo; 33 import org.thingsboard.server.common.data.asset.AssetInfo;
34 import org.thingsboard.server.common.data.asset.AssetSearchQuery; 34 import org.thingsboard.server.common.data.asset.AssetSearchQuery;
35 import org.thingsboard.server.common.data.audit.ActionType; 35 import org.thingsboard.server.common.data.audit.ActionType;
  36 +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
36 import org.thingsboard.server.common.data.exception.ThingsboardException; 37 import org.thingsboard.server.common.data.exception.ThingsboardException;
37 import org.thingsboard.server.common.data.id.AssetId; 38 import org.thingsboard.server.common.data.id.AssetId;
38 import org.thingsboard.server.common.data.id.CustomerId; 39 import org.thingsboard.server.common.data.id.CustomerId;
@@ -51,6 +52,8 @@ import java.util.ArrayList; @@ -51,6 +52,8 @@ import java.util.ArrayList;
51 import java.util.List; 52 import java.util.List;
52 import java.util.stream.Collectors; 53 import java.util.stream.Collectors;
53 54
  55 +import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
  56 +
54 @RestController 57 @RestController
55 @TbCoreComponent 58 @TbCoreComponent
56 @RequestMapping("/api") 59 @RequestMapping("/api")
@@ -89,6 +92,10 @@ public class AssetController extends BaseController { @@ -89,6 +92,10 @@ public class AssetController extends BaseController {
89 @ResponseBody 92 @ResponseBody
90 public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException { 93 public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException {
91 try { 94 try {
  95 + if (TB_SERVICE_QUEUE.equals(asset.getType())) {
  96 + throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
  97 + }
  98 +
92 asset.setTenantId(getCurrentUser().getTenantId()); 99 asset.setTenantId(getCurrentUser().getTenantId());
93 100
94 checkEntity(asset.getId(), asset, Resource.ASSET); 101 checkEntity(asset.getId(), asset, Resource.ASSET);
@@ -45,6 +45,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; @@ -45,6 +45,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
45 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; 45 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
46 import org.thingsboard.server.common.data.DataConstants; 46 import org.thingsboard.server.common.data.DataConstants;
47 import org.thingsboard.server.common.data.EntityType; 47 import org.thingsboard.server.common.data.EntityType;
  48 +import org.thingsboard.server.common.data.TenantProfile;
48 import org.thingsboard.server.common.data.audit.ActionType; 49 import org.thingsboard.server.common.data.audit.ActionType;
49 import org.thingsboard.server.common.data.exception.ThingsboardException; 50 import org.thingsboard.server.common.data.exception.ThingsboardException;
50 import org.thingsboard.server.common.data.id.DeviceId; 51 import org.thingsboard.server.common.data.id.DeviceId;
@@ -69,6 +70,7 @@ import org.thingsboard.server.common.data.kv.LongDataEntry; @@ -69,6 +70,7 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
69 import org.thingsboard.server.common.data.kv.ReadTsKvQuery; 70 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
70 import org.thingsboard.server.common.data.kv.StringDataEntry; 71 import org.thingsboard.server.common.data.kv.StringDataEntry;
71 import org.thingsboard.server.common.data.kv.TsKvEntry; 72 import org.thingsboard.server.common.data.kv.TsKvEntry;
  73 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
72 import org.thingsboard.server.common.transport.adaptor.JsonConverter; 74 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
73 import org.thingsboard.server.dao.timeseries.TimeseriesService; 75 import org.thingsboard.server.dao.timeseries.TimeseriesService;
74 import org.thingsboard.server.queue.util.TbCoreComponent; 76 import org.thingsboard.server.queue.util.TbCoreComponent;
@@ -93,6 +95,7 @@ import java.util.Map; @@ -93,6 +95,7 @@ import java.util.Map;
93 import java.util.Set; 95 import java.util.Set;
94 import java.util.concurrent.ExecutorService; 96 import java.util.concurrent.ExecutorService;
95 import java.util.concurrent.Executors; 97 import java.util.concurrent.Executors;
  98 +import java.util.concurrent.TimeUnit;
96 import java.util.stream.Collectors; 99 import java.util.stream.Collectors;
97 100
98 /** 101 /**
@@ -205,7 +208,7 @@ public class TelemetryController extends BaseController { @@ -205,7 +208,7 @@ public class TelemetryController extends BaseController {
205 @RequestParam(name = "interval", defaultValue = "0") Long interval, 208 @RequestParam(name = "interval", defaultValue = "0") Long interval,
206 @RequestParam(name = "limit", defaultValue = "100") Integer limit, 209 @RequestParam(name = "limit", defaultValue = "100") Integer limit,
207 @RequestParam(name = "agg", defaultValue = "NONE") String aggStr, 210 @RequestParam(name = "agg", defaultValue = "NONE") String aggStr,
208 - @RequestParam(name= "orderBy", defaultValue = "DESC") String orderBy, 211 + @RequestParam(name = "orderBy", defaultValue = "DESC") String orderBy,
209 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { 212 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
210 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, 213 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
211 (result, tenantId, entityId) -> { 214 (result, tenantId, entityId) -> {
@@ -392,7 +395,7 @@ public class TelemetryController extends BaseController { @@ -392,7 +395,7 @@ public class TelemetryController extends BaseController {
392 if (attributes.isEmpty()) { 395 if (attributes.isEmpty()) {
393 return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST); 396 return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST);
394 } 397 }
395 - for (AttributeKvEntry attributeKvEntry: attributes) { 398 + for (AttributeKvEntry attributeKvEntry : attributes) {
396 if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) { 399 if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) {
397 return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST); 400 return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST);
398 } 401 }
@@ -440,9 +443,13 @@ public class TelemetryController extends BaseController { @@ -440,9 +443,13 @@ public class TelemetryController extends BaseController {
440 if (entries.isEmpty()) { 443 if (entries.isEmpty()) {
441 return getImmediateDeferredResult("No timeseries data found in request body!", HttpStatus.BAD_REQUEST); 444 return getImmediateDeferredResult("No timeseries data found in request body!", HttpStatus.BAD_REQUEST);
442 } 445 }
443 - SecurityUser user = getCurrentUser();  
444 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_TELEMETRY, entityIdSrc, (result, tenantId, entityId) -> { 446 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_TELEMETRY, entityIdSrc, (result, tenantId, entityId) -> {
445 - tsSubService.saveAndNotify(tenantId, entityId, entries, ttl, new FutureCallback<Void>() { 447 + long tenantTtl = ttl;
  448 + if (!TenantId.SYS_TENANT_ID.equals(tenantId) && tenantTtl == 0) {
  449 + TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
  450 + tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays());
  451 + }
  452 + tsSubService.saveAndNotify(tenantId, entityId, entries, tenantTtl, new FutureCallback<Void>() {
446 @Override 453 @Override
447 public void onSuccess(@Nullable Void tmp) { 454 public void onSuccess(@Nullable Void tmp) {
448 result.setResult(new ResponseEntity(HttpStatus.OK)); 455 result.setResult(new ResponseEntity(HttpStatus.OK));
@@ -147,6 +147,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @@ -147,6 +147,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
147 public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) { 147 public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) {
148 ToUsageStatsServiceMsg statsMsg = msg.getValue(); 148 ToUsageStatsServiceMsg statsMsg = msg.getValue();
149 TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); 149 TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB()));
  150 +
  151 + if (tenantProfileCache.get(tenantId) == null) {
  152 + return;
  153 + }
  154 +
150 TenantApiUsageState tenantState; 155 TenantApiUsageState tenantState;
151 List<TsKvEntry> updatedEntries; 156 List<TsKvEntry> updatedEntries;
152 Map<ApiFeature, ApiUsageStateValue> result; 157 Map<ApiFeature, ApiUsageStateValue> result;
@@ -166,7 +171,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @@ -166,7 +171,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
166 long newValue = tenantState.add(recordKey, kvProto.getValue()); 171 long newValue = tenantState.add(recordKey, kvProto.getValue());
167 updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue))); 172 updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue)));
168 long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); 173 long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue());
169 - updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); 174 + updatedEntries.add(new BasicTsKvEntry(newHourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue)));
170 apiFeatures.add(recordKey.getApiFeature()); 175 apiFeatures.add(recordKey.getApiFeature());
171 } 176 }
172 result = tenantState.checkStateUpdatedDueToThreshold(apiFeatures); 177 result = tenantState.checkStateUpdatedDueToThreshold(apiFeatures);
@@ -343,7 +348,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @@ -343,7 +348,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
343 long now = System.currentTimeMillis(); 348 long now = System.currentTimeMillis();
344 myTenantStates.values().forEach(state -> { 349 myTenantStates.values().forEach(state -> {
345 if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) { 350 if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) {
  351 + TenantId tenantId = state.getTenantId();
346 state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); 352 state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth());
  353 + ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder();
  354 + msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
  355 + msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
  356 + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
  357 + msg.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(0).build());
  358 + }
  359 + process(new TbProtoQueueMsg<>(UUID.randomUUID(), msg.build()), TbCallback.EMPTY);
347 } 360 }
348 }); 361 });
349 } finally { 362 } finally {
@@ -18,9 +18,7 @@ package org.thingsboard.server.service.device; @@ -18,9 +18,7 @@ package org.thingsboard.server.service.device;
18 import com.fasterxml.jackson.core.JsonProcessingException; 18 import com.fasterxml.jackson.core.JsonProcessingException;
19 import com.fasterxml.jackson.databind.JsonNode; 19 import com.fasterxml.jackson.databind.JsonNode;
20 import com.fasterxml.jackson.databind.node.ObjectNode; 20 import com.fasterxml.jackson.databind.node.ObjectNode;
21 -import com.google.common.util.concurrent.Futures;  
22 import com.google.common.util.concurrent.ListenableFuture; 21 import com.google.common.util.concurrent.ListenableFuture;
23 -import com.google.common.util.concurrent.MoreExecutors;  
24 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
25 import org.apache.commons.lang.RandomStringUtils; 23 import org.apache.commons.lang.RandomStringUtils;
26 import org.springframework.beans.factory.annotation.Autowired; 24 import org.springframework.beans.factory.annotation.Autowired;
@@ -114,6 +112,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { @@ -114,6 +112,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
114 public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { 112 public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) {
115 String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); 113 String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey();
116 String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); 114 String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret();
  115 + if (!StringUtils.isEmpty(provisionRequest.getDeviceName())) {
  116 + provisionRequest.setDeviceName(provisionRequest.getDeviceName().trim());
  117 + if (StringUtils.isEmpty(provisionRequest.getDeviceName())) {
  118 + log.warn("Provision request contains empty device name!");
  119 + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
  120 + }
  121 + }
117 122
118 if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) { 123 if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) {
119 throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); 124 throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name());
@@ -82,7 +82,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -82,7 +82,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
82 82
83 import java.util.Arrays; 83 import java.util.Arrays;
84 import java.util.Collections; 84 import java.util.Collections;
85 -import java.util.LinkedHashMap; 85 +import java.util.TreeMap;
86 86
87 @Service 87 @Service
88 @Profile("install") 88 @Profile("install")
@@ -272,7 +272,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -272,7 +272,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
272 thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); 272 thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
273 thermostatDeviceProfile.setDescription("Thermostat device profile"); 273 thermostatDeviceProfile.setDescription("Thermostat device profile");
274 thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChains( 274 thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChains(
275 - demoTenant.getId(), new PageLink(1, 0, "Thermostat Alarms")).getData().get(0).getId()); 275 + demoTenant.getId(), new PageLink(1, 0, "Thermostat")).getData().get(0).getId());
276 276
277 DeviceProfileData deviceProfileData = new DeviceProfileData(); 277 DeviceProfileData deviceProfileData = new DeviceProfileData();
278 DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); 278 DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
@@ -290,13 +290,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -290,13 +290,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
290 AlarmCondition temperatureCondition = new AlarmCondition(); 290 AlarmCondition temperatureCondition = new AlarmCondition();
291 temperatureCondition.setSpec(new SimpleAlarmConditionSpec()); 291 temperatureCondition.setSpec(new SimpleAlarmConditionSpec());
292 292
293 - KeyFilter alarmTemperatureAttributeFilter = new KeyFilter();  
294 - alarmTemperatureAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmTemperature"));  
295 - alarmTemperatureAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);  
296 - BooleanFilterPredicate alarmTemperatureAttributePredicate = new BooleanFilterPredicate();  
297 - alarmTemperatureAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);  
298 - alarmTemperatureAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));  
299 - alarmTemperatureAttributeFilter.setPredicate(alarmTemperatureAttributePredicate); 293 + KeyFilter temperatureAlarmFlagAttributeFilter = new KeyFilter();
  294 + temperatureAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperatureAlarmFlag"));
  295 + temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
  296 + BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate();
  297 + temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
  298 + temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
  299 + temperatureAlarmFlagAttributeFilter.setPredicate(temperatureAlarmFlagAttributePredicate);
300 300
301 KeyFilter temperatureTimeseriesFilter = new KeyFilter(); 301 KeyFilter temperatureTimeseriesFilter = new KeyFilter();
302 temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); 302 temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
@@ -305,13 +305,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -305,13 +305,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
305 temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); 305 temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
306 FilterPredicateValue<Double> temperatureTimeseriesPredicateValue = 306 FilterPredicateValue<Double> temperatureTimeseriesPredicateValue =
307 new FilterPredicateValue<>(25.0, null, 307 new FilterPredicateValue<>(25.0, null,
308 - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature")); 308 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold"));
309 temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue); 309 temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue);
310 temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate); 310 temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate);
311 - temperatureCondition.setCondition(Arrays.asList(alarmTemperatureAttributeFilter, temperatureTimeseriesFilter)); 311 + temperatureCondition.setCondition(Arrays.asList(temperatureAlarmFlagAttributeFilter, temperatureTimeseriesFilter));
312 temperatureRule.setAlarmDetails("Current temperature = ${temperature}"); 312 temperatureRule.setAlarmDetails("Current temperature = ${temperature}");
313 temperatureRule.setCondition(temperatureCondition); 313 temperatureRule.setCondition(temperatureCondition);
314 - highTemperature.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule))); 314 + highTemperature.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule)));
315 315
316 AlarmRule clearTemperatureRule = new AlarmRule(); 316 AlarmRule clearTemperatureRule = new AlarmRule();
317 AlarmCondition clearTemperatureCondition = new AlarmCondition(); 317 AlarmCondition clearTemperatureCondition = new AlarmCondition();
@@ -324,7 +324,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -324,7 +324,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
324 clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL); 324 clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL);
325 FilterPredicateValue<Double> clearTemperatureTimeseriesPredicateValue = 325 FilterPredicateValue<Double> clearTemperatureTimeseriesPredicateValue =
326 new FilterPredicateValue<>(25.0, null, 326 new FilterPredicateValue<>(25.0, null,
327 - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature")); 327 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold"));
328 328
329 clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue); 329 clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue);
330 clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate); 330 clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate);
@@ -340,13 +340,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -340,13 +340,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
340 AlarmCondition humidityCondition = new AlarmCondition(); 340 AlarmCondition humidityCondition = new AlarmCondition();
341 humidityCondition.setSpec(new SimpleAlarmConditionSpec()); 341 humidityCondition.setSpec(new SimpleAlarmConditionSpec());
342 342
343 - KeyFilter alarmHumidityAttributeFilter = new KeyFilter();  
344 - alarmHumidityAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmHumidity"));  
345 - alarmHumidityAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);  
346 - BooleanFilterPredicate alarmHumidityAttributePredicate = new BooleanFilterPredicate();  
347 - alarmHumidityAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);  
348 - alarmHumidityAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));  
349 - alarmHumidityAttributeFilter.setPredicate(alarmHumidityAttributePredicate); 343 + KeyFilter humidityAlarmFlagAttributeFilter = new KeyFilter();
  344 + humidityAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "humidityAlarmFlag"));
  345 + humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
  346 + BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate();
  347 + humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
  348 + humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
  349 + humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagAttributePredicate);
350 350
351 KeyFilter humidityTimeseriesFilter = new KeyFilter(); 351 KeyFilter humidityTimeseriesFilter = new KeyFilter();
352 humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); 352 humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity"));
@@ -355,14 +355,14 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -355,14 +355,14 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
355 humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); 355 humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
356 FilterPredicateValue<Double> humidityTimeseriesPredicateValue = 356 FilterPredicateValue<Double> humidityTimeseriesPredicateValue =
357 new FilterPredicateValue<>(60.0, null, 357 new FilterPredicateValue<>(60.0, null,
358 - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity")); 358 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold"));
359 humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue); 359 humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue);
360 humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate); 360 humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate);
361 - humidityCondition.setCondition(Arrays.asList(alarmHumidityAttributeFilter, humidityTimeseriesFilter)); 361 + humidityCondition.setCondition(Arrays.asList(humidityAlarmFlagAttributeFilter, humidityTimeseriesFilter));
362 362
363 humidityRule.setCondition(humidityCondition); 363 humidityRule.setCondition(humidityCondition);
364 humidityRule.setAlarmDetails("Current humidity = ${humidity}"); 364 humidityRule.setAlarmDetails("Current humidity = ${humidity}");
365 - lowHumidity.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule))); 365 + lowHumidity.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule)));
366 366
367 AlarmRule clearHumidityRule = new AlarmRule(); 367 AlarmRule clearHumidityRule = new AlarmRule();
368 AlarmCondition clearHumidityCondition = new AlarmCondition(); 368 AlarmCondition clearHumidityCondition = new AlarmCondition();
@@ -375,7 +375,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -375,7 +375,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
375 clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL); 375 clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL);
376 FilterPredicateValue<Double> clearHumidityTimeseriesPredicateValue = 376 FilterPredicateValue<Double> clearHumidityTimeseriesPredicateValue =
377 new FilterPredicateValue<>(60.0, null, 377 new FilterPredicateValue<>(60.0, null,
378 - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity")); 378 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold"));
379 379
380 clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue); 380 clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue);
381 clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate); 381 clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate);
@@ -394,18 +394,18 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -394,18 +394,18 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
394 attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE, 394 attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE,
395 Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)), 395 Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)),
396 new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -122.1503)), 396 new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -122.1503)),
397 - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)),  
398 - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)),  
399 - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 20)),  
400 - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 50)))); 397 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)),
  398 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("humidityAlarmFlag", true)),
  399 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperatureAlarmThreshold", (long) 20)),
  400 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 50))));
401 401
402 attributesService.save(demoTenant.getId(), t2Id, DataConstants.SERVER_SCOPE, 402 attributesService.save(demoTenant.getId(), t2Id, DataConstants.SERVER_SCOPE,
403 Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.493801)), 403 Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.493801)),
404 new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -121.948769)), 404 new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -121.948769)),
405 - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)),  
406 - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)),  
407 - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 25)),  
408 - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 30)))); 405 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)),
  406 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("humidityAlarmFlag", true)),
  407 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperatureAlarmThreshold", (long) 25)),
  408 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 30))));
409 409
410 installScripts.loadDashboards(demoTenant.getId(), null); 410 installScripts.loadDashboards(demoTenant.getId(), null);
411 } 411 }
@@ -210,26 +210,9 @@ public class InstallScripts { @@ -210,26 +210,9 @@ public class InstallScripts {
210 210
211 211
212 public void loadDemoRuleChains(TenantId tenantId) throws Exception { 212 public void loadDemoRuleChains(TenantId tenantId) throws Exception {
213 - Path ruleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, RULE_CHAINS_DIR);  
214 try { 213 try {
215 - JsonNode ruleChainJson = objectMapper.readTree(ruleChainsDir.resolve("thermostat_alarms.json").toFile());  
216 - RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);  
217 - RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);  
218 - ruleChain.setTenantId(tenantId);  
219 - ruleChain = ruleChainService.saveRuleChain(ruleChain);  
220 - ruleChainMetaData.setRuleChainId(ruleChain.getId());  
221 - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);  
222 -  
223 - JsonNode rootChainJson = objectMapper.readTree(ruleChainsDir.resolve("root_rule_chain.json").toFile());  
224 - RuleChain rootChain = objectMapper.treeToValue(rootChainJson.get("ruleChain"), RuleChain.class);  
225 - RuleChainMetaData rootChainMetaData = objectMapper.treeToValue(rootChainJson.get("metadata"), RuleChainMetaData.class);  
226 -  
227 - RuleChainId thermostatsRuleChainId = ruleChain.getId();  
228 - rootChainMetaData.getRuleChainConnections().forEach(connection -> connection.setTargetRuleChainId(thermostatsRuleChainId));  
229 - rootChain.setTenantId(tenantId);  
230 - rootChain = ruleChainService.saveRuleChain(rootChain);  
231 - rootChainMetaData.setRuleChainId(rootChain.getId());  
232 - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), rootChainMetaData); 214 + createDefaultRuleChains(tenantId);
  215 + createDefaultRuleChain(tenantId, "Thermostat");
233 } catch (Exception e) { 216 } catch (Exception e) {
234 log.error("Unable to load dashboard from json", e); 217 log.error("Unable to load dashboard from json", e);
235 throw new RuntimeException("Unable to load dashboard from json", e); 218 throw new RuntimeException("Unable to load dashboard from json", e);
@@ -250,6 +250,8 @@ public class DefaultMailService implements MailService { @@ -250,6 +250,8 @@ public class DefaultMailService implements MailService {
250 helper.setText(body); 250 helper.setText(body);
251 mailSender.send(helper.getMimeMessage()); 251 mailSender.send(helper.getMimeMessage());
252 apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); 252 apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1);
  253 + } else {
  254 + throw new RuntimeException("Email sending is disabled due to API limits!");
253 } 255 }
254 } 256 }
255 257
@@ -297,7 +297,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -297,7 +297,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
297 try { 297 try {
298 handleUsageStats(msg, callback); 298 handleUsageStats(msg, callback);
299 } catch (Throwable e) { 299 } catch (Throwable e) {
300 - log.warn("[{}] Failed to process usge stats: {}", id, msg, e); 300 + log.warn("[{}] Failed to process usage stats: {}", id, msg, e);
301 callback.onFailure(e); 301 callback.onFailure(e);
302 } 302 }
303 }); 303 });
@@ -18,9 +18,9 @@ package org.thingsboard.server.service.sms; @@ -18,9 +18,9 @@ package org.thingsboard.server.service.sms;
18 import org.springframework.stereotype.Component; 18 import org.springframework.stereotype.Component;
19 import org.thingsboard.rule.engine.api.sms.SmsSender; 19 import org.thingsboard.rule.engine.api.sms.SmsSender;
20 import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; 20 import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
21 -import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration;  
22 -import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration;  
23 -import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; 21 +import org.thingsboard.server.common.data.sms.config.AwsSnsSmsProviderConfiguration;
  22 +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration;
  23 +import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration;
24 import org.thingsboard.server.service.sms.aws.AwsSmsSender; 24 import org.thingsboard.server.service.sms.aws.AwsSmsSender;
25 import org.thingsboard.server.service.sms.twilio.TwilioSmsSender; 25 import org.thingsboard.server.service.sms.twilio.TwilioSmsSender;
26 26
@@ -22,8 +22,8 @@ import org.springframework.stereotype.Service; @@ -22,8 +22,8 @@ import org.springframework.stereotype.Service;
22 import org.thingsboard.rule.engine.api.SmsService; 22 import org.thingsboard.rule.engine.api.SmsService;
23 import org.thingsboard.rule.engine.api.sms.SmsSender; 23 import org.thingsboard.rule.engine.api.sms.SmsSender;
24 import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; 24 import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
25 -import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration;  
26 -import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; 25 +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration;
  26 +import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
27 import org.thingsboard.server.common.data.AdminSettings; 27 import org.thingsboard.server.common.data.AdminSettings;
28 import org.thingsboard.server.common.data.ApiUsageRecordKey; 28 import org.thingsboard.server.common.data.ApiUsageRecordKey;
29 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; 29 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@@ -106,6 +106,8 @@ public class DefaultSmsService implements SmsService { @@ -106,6 +106,8 @@ public class DefaultSmsService implements SmsService {
106 apiUsageClient.report(tenantId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount); 106 apiUsageClient.report(tenantId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount);
107 } 107 }
108 } 108 }
  109 + } else {
  110 + throw new RuntimeException("SMS sending is disabled due to API limits!");
109 } 111 }
110 } 112 }
111 113
@@ -23,7 +23,7 @@ import com.amazonaws.services.sns.AmazonSNSClient; @@ -23,7 +23,7 @@ import com.amazonaws.services.sns.AmazonSNSClient;
23 import com.amazonaws.services.sns.model.PublishRequest; 23 import com.amazonaws.services.sns.model.PublishRequest;
24 import lombok.extern.slf4j.Slf4j; 24 import lombok.extern.slf4j.Slf4j;
25 import org.apache.commons.lang3.StringUtils; 25 import org.apache.commons.lang3.StringUtils;
26 -import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; 26 +import org.thingsboard.server.common.data.sms.config.AwsSnsSmsProviderConfiguration;
27 import org.thingsboard.rule.engine.api.sms.exception.SmsException; 27 import org.thingsboard.rule.engine.api.sms.exception.SmsException;
28 import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; 28 import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
29 import org.thingsboard.server.service.sms.AbstractSmsSender; 29 import org.thingsboard.server.service.sms.AbstractSmsSender;
@@ -19,7 +19,7 @@ import com.twilio.http.TwilioRestClient; @@ -19,7 +19,7 @@ import com.twilio.http.TwilioRestClient;
19 import com.twilio.rest.api.v2010.account.Message; 19 import com.twilio.rest.api.v2010.account.Message;
20 import com.twilio.type.PhoneNumber; 20 import com.twilio.type.PhoneNumber;
21 import org.apache.commons.lang3.StringUtils; 21 import org.apache.commons.lang3.StringUtils;
22 -import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; 22 +import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration;
23 import org.thingsboard.rule.engine.api.sms.exception.SmsException; 23 import org.thingsboard.rule.engine.api.sms.exception.SmsException;
24 import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; 24 import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
25 import org.thingsboard.server.service.sms.AbstractSmsSender; 25 import org.thingsboard.server.service.sms.AbstractSmsSender;
@@ -521,27 +521,27 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -521,27 +521,27 @@ public class DefaultDeviceStateService implements DeviceStateService {
521 521
522 private void save(DeviceId deviceId, String key, long value) { 522 private void save(DeviceId deviceId, String key, long value) {
523 if (persistToTelemetry) { 523 if (persistToTelemetry) {
524 - tsSubService.saveAndNotify( 524 + tsSubService.saveAndNotifyInternal(
525 TenantId.SYS_TENANT_ID, deviceId, 525 TenantId.SYS_TENANT_ID, deviceId,
526 Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))), 526 Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))),
527 - new AttributeSaveCallback(deviceId, key, value)); 527 + new AttributeSaveCallback<>(deviceId, key, value));
528 } else { 528 } else {
529 - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); 529 + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback<>(deviceId, key, value));
530 } 530 }
531 } 531 }
532 532
533 private void save(DeviceId deviceId, String key, boolean value) { 533 private void save(DeviceId deviceId, String key, boolean value) {
534 if (persistToTelemetry) { 534 if (persistToTelemetry) {
535 - tsSubService.saveAndNotify( 535 + tsSubService.saveAndNotifyInternal(
536 TenantId.SYS_TENANT_ID, deviceId, 536 TenantId.SYS_TENANT_ID, deviceId,
537 Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), 537 Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))),
538 - new AttributeSaveCallback(deviceId, key, value)); 538 + new AttributeSaveCallback<>(deviceId, key, value));
539 } else { 539 } else {
540 - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); 540 + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback<>(deviceId, key, value));
541 } 541 }
542 } 542 }
543 543
544 - private static class AttributeSaveCallback implements FutureCallback<Void> { 544 + private static class AttributeSaveCallback<T> implements FutureCallback<T> {
545 private final DeviceId deviceId; 545 private final DeviceId deviceId;
546 private final String key; 546 private final String key;
547 private final Object value; 547 private final Object value;
@@ -553,7 +553,7 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -553,7 +553,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
553 } 553 }
554 554
555 @Override 555 @Override
556 - public void onSuccess(@Nullable Void result) { 556 + public void onSuccess(@Nullable T result) {
557 log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value); 557 log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value);
558 } 558 }
559 559
@@ -25,6 +25,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; @@ -25,6 +25,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
25 import org.thingsboard.server.common.data.ApiUsageRecordKey; 25 import org.thingsboard.server.common.data.ApiUsageRecordKey;
26 import org.thingsboard.server.common.data.EntityType; 26 import org.thingsboard.server.common.data.EntityType;
27 import org.thingsboard.server.common.data.EntityView; 27 import org.thingsboard.server.common.data.EntityView;
  28 +import org.thingsboard.server.common.data.TenantProfile;
28 import org.thingsboard.server.common.data.id.EntityId; 29 import org.thingsboard.server.common.data.id.EntityId;
29 import org.thingsboard.server.common.data.id.TenantId; 30 import org.thingsboard.server.common.data.id.TenantId;
30 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 31 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@@ -34,13 +35,14 @@ import org.thingsboard.server.common.data.kv.DoubleDataEntry; @@ -34,13 +35,14 @@ import org.thingsboard.server.common.data.kv.DoubleDataEntry;
34 import org.thingsboard.server.common.data.kv.LongDataEntry; 35 import org.thingsboard.server.common.data.kv.LongDataEntry;
35 import org.thingsboard.server.common.data.kv.StringDataEntry; 36 import org.thingsboard.server.common.data.kv.StringDataEntry;
36 import org.thingsboard.server.common.data.kv.TsKvEntry; 37 import org.thingsboard.server.common.data.kv.TsKvEntry;
  38 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
37 import org.thingsboard.server.common.msg.queue.ServiceType; 39 import org.thingsboard.server.common.msg.queue.ServiceType;
38 import org.thingsboard.server.common.msg.queue.TbCallback; 40 import org.thingsboard.server.common.msg.queue.TbCallback;
39 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 41 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
40 import org.thingsboard.server.dao.attributes.AttributesService; 42 import org.thingsboard.server.dao.attributes.AttributesService;
41 import org.thingsboard.server.dao.entityview.EntityViewService; 43 import org.thingsboard.server.dao.entityview.EntityViewService;
  44 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
42 import org.thingsboard.server.dao.timeseries.TimeseriesService; 45 import org.thingsboard.server.dao.timeseries.TimeseriesService;
43 -import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;  
44 import org.thingsboard.server.gen.transport.TransportProtos; 46 import org.thingsboard.server.gen.transport.TransportProtos;
45 import org.thingsboard.server.queue.discovery.PartitionService; 47 import org.thingsboard.server.queue.discovery.PartitionService;
46 import org.thingsboard.server.queue.usagestats.TbApiUsageClient; 48 import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
@@ -119,11 +121,12 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @@ -119,11 +121,12 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
119 @Override 121 @Override
120 public void saveAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) { 122 public void saveAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) {
121 checkInternalEntity(entityId); 123 checkInternalEntity(entityId);
122 - if (apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { 124 + boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null;
  125 + if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) {
123 saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback<Integer>() { 126 saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback<Integer>() {
124 @Override 127 @Override
125 public void onSuccess(Integer result) { 128 public void onSuccess(Integer result) {
126 - if (result != null && result > 0) { 129 + if (!sysTenant && result != null && result > 0) {
127 apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); 130 apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
128 } 131 }
129 callback.onSuccess(null); 132 callback.onSuccess(null);
@@ -134,7 +137,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @@ -134,7 +137,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
134 callback.onFailure(t); 137 callback.onFailure(t);
135 } 138 }
136 }); 139 });
137 - } else{ 140 + } else {
138 callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!")); 141 callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!"));
139 } 142 }
140 } 143 }
@@ -4,4 +4,4 @@ account.activated.subject=Thingsboard - your account has been activated @@ -4,4 +4,4 @@ account.activated.subject=Thingsboard - your account has been activated
4 reset.password.subject=Thingsboard - Password reset has been requested 4 reset.password.subject=Thingsboard - Password reset has been requested
5 password.was.reset.subject=Thingsboard - your account password has been reset 5 password.was.reset.subject=Thingsboard - your account password has been reset
6 account.lockout.subject=Thingsboard - User account has been lockout 6 account.lockout.subject=Thingsboard - User account has been lockout
7 -api.usage.state=Thingsboard - Api Usage State for tenant has been updated  
  7 +api.usage.state=Thingsboard - Account limits
@@ -103,40 +103,30 @@ @@ -103,40 +103,30 @@
103 style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" 103 style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
104 bgcolor="#f6f6f6"> 104 bgcolor="#f6f6f6">
105 105
106 -<table class="main"  
107 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;"  
108 - cellspacing="0" cellpadding="0" bgcolor="#f6f6f6"> 106 +<table class="main" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;" cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
109 <tbody> 107 <tbody>
110 <tr style="box-sizing: border-box; margin: 0px;"> 108 <tr style="box-sizing: border-box; margin: 0px;">
111 - <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;"  
112 - align="center" valign="top">  
113 - <table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;"  
114 - width="600" cellspacing="0" cellpadding="0"> 109 + <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" align="center" valign="top">
  110 + <table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 367px; background-color: #ffffff; width: 600px; max-width: 600px !important;" width="600" cellspacing="0" cellpadding="0">
115 <tbody> 111 <tbody>
116 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> 112 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
117 - <td class="content-block"  
118 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;"  
119 - valign="top">  
120 - <h2>Your ThingsBoard account feature was disabled</h2>  
121 - </td> 113 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px; height: 110px;" valign="top"><img src="https://media.thingsboard.io/email/head.png" alt="" width="598" height="91" /></td>
122 </tr> 114 </tr>
123 - <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">  
124 - <td class="content-block"  
125 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"  
126 - valign="top">We have disabled the ${apiFeature} for your account because ThingsBoard has already ${apiLimitValueLabel}. 115 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; margin: 0;">
  116 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #000000; box-sizing: border-box; font-size: 16px; margin: 0px; padding: 0px 32px; height: 66px; vertical-align: middle;" valign="middle">Your ThingsBoard account feature was <strong>disabled</strong></td>
  117 + </tr>
  118 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; line-height: 24px; margin: 0;">
  119 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0px; padding: 0px 32px; height: 93px; vertical-align: top;" valign="top">
  120 + <div style="padding: 16px; margin-bottom: 24px; border: solid 2px #EB5757; border-radius: 6px; background: rgba(235, 87, 87, 0.05);"><img style="vertical-align: middle; padding-right: 6px;" src="https://media.thingsboard.io/email/alarm.png" alt="" width="20" height="20" />
  121 + <div style="display: inline; vertical-align: middle;">We have <strong>disabled</strong> the ${apiFeature} for your account because ThingsBoard has already <strong>${apiLimitValueLabel}.</strong></div>
  122 + </div>
127 </td> 123 </td>
128 </tr> 124 </tr>
129 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> 125 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
130 - <td class="content-block"  
131 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 59px;"  
132 - valign="top">Please contact your system administrator to resolve the issue.  
133 - </td> 126 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 32px; height: 59px;" valign="top">Please contact your system administrator to resolve the issue.</td>
134 </tr> 127 </tr>
135 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> 128 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
136 - <td class="content-block"  
137 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"  
138 - valign="top">&mdash; The ThingsBoard  
139 - </td> 129 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 32px; height: 40px;" valign="top">&mdash; The ThingsBoard</td>
140 </tr> 130 </tr>
141 </tbody> 131 </tbody>
142 </table> 132 </table>
@@ -144,16 +134,10 @@ @@ -144,16 +134,10 @@
144 </tr> 134 </tr>
145 </tbody> 135 </tbody>
146 </table> 136 </table>
147 -<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;"  
148 - cellpadding="0px 0px 20px"> 137 +<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" cellpadding="0px 0px 20px">
149 <tbody> 138 <tbody>
150 <tr style="box-sizing: border-box; margin: 0px;"> 139 <tr style="box-sizing: border-box; margin: 0px;">
151 - <td class="aligncenter content-block"  
152 - style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"  
153 - align="center" valign="top">This email was sent to&nbsp;<a  
154 - style="box-sizing: border-box; color: #999999; margin: 0px;"  
155 - href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.  
156 - </td> 140 + <td class="aligncenter content-block" style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;" align="center" valign="top">This email was sent to&nbsp;<a style="box-sizing: border-box; color: #999999; margin: 0px;" href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.</td>
157 </tr> 141 </tr>
158 </tbody> 142 </tbody>
159 </table> 143 </table>
@@ -103,34 +103,27 @@ @@ -103,34 +103,27 @@
103 style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" 103 style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
104 bgcolor="#f6f6f6"> 104 bgcolor="#f6f6f6">
105 105
106 -<table class="main"  
107 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;"  
108 - cellspacing="0" cellpadding="0" bgcolor="#f6f6f6"> 106 +<table class="main" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;" cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
109 <tbody> 107 <tbody>
110 <tr style="box-sizing: border-box; margin: 0px;"> 108 <tr style="box-sizing: border-box; margin: 0px;">
111 - <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;"  
112 - align="center" valign="top">  
113 - <table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;"  
114 - width="600" cellspacing="0" cellpadding="0"> 109 + <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" align="center" valign="top">
  110 + <table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 310px; background-color: #ffffff; width: 600px; max-width: 600px !important;" width="600" cellspacing="0" cellpadding="0">
115 <tbody> 111 <tbody>
116 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> 112 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
117 - <td class="content-block"  
118 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;"  
119 - valign="top">  
120 - <h2>Your ThingsBoard account feature was enabled</h2>  
121 - </td> 113 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px; height: 110px;" valign="top"><img src="https://media.thingsboard.io/email/head.png" alt="" width="598" height="91" /></td>
122 </tr> 114 </tr>
123 - <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">  
124 - <td class="content-block"  
125 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"  
126 - valign="top">We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages. 115 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; margin: 0;">
  116 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #000000; box-sizing: border-box; font-size: 16px; margin: 0px; padding: 0px 32px; height: 66px; vertical-align: middle;" valign="middle">Your ThingsBoard account feature was <strong>enabled</strong></td>
  117 + </tr>
  118 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; line-height: 24px; margin: 0;">
  119 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0px; padding: 0px 32px; height: 93px; vertical-align: top;" valign="top">
  120 + <div style="padding: 16px; margin-bottom: 24px; border: solid 2px #27AE60; border-radius: 6px; background: rgba(39, 174, 96, 0.05);"><img style="vertical-align: middle; padding-right: 6px;" src="https://media.thingsboard.io/email/confirm.png" alt="" width="20" height="20" />
  121 + <div style="display: inline; vertical-align: middle;">We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages.</div>
  122 + </div>
127 </td> 123 </td>
128 </tr> 124 </tr>
129 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> 125 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
130 - <td class="content-block"  
131 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"  
132 - valign="top">&mdash; The ThingsBoard  
133 - </td> 126 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 32px; height: 40px;" valign="top">&mdash; The ThingsBoard</td>
134 </tr> 127 </tr>
135 </tbody> 128 </tbody>
136 </table> 129 </table>
@@ -138,16 +131,10 @@ @@ -138,16 +131,10 @@
138 </tr> 131 </tr>
139 </tbody> 132 </tbody>
140 </table> 133 </table>
141 -<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;"  
142 - cellpadding="0px 0px 20px"> 134 +<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" cellpadding="0px 0px 20px">
143 <tbody> 135 <tbody>
144 <tr style="box-sizing: border-box; margin: 0px;"> 136 <tr style="box-sizing: border-box; margin: 0px;">
145 - <td class="aligncenter content-block"  
146 - style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"  
147 - align="center" valign="top">This email was sent to&nbsp;<a  
148 - style="box-sizing: border-box; color: #999999; margin: 0px;"  
149 - href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.  
150 - </td> 137 + <td class="aligncenter content-block" style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;" align="center" valign="top">This email was sent to&nbsp;<a style="box-sizing: border-box; color: #999999; margin: 0px;" href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.</td>
151 </tr> 138 </tr>
152 </tbody> 139 </tbody>
153 </table> 140 </table>
@@ -103,41 +103,30 @@ @@ -103,41 +103,30 @@
103 style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" 103 style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
104 bgcolor="#f6f6f6"> 104 bgcolor="#f6f6f6">
105 105
106 -<table class="main"  
107 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;"  
108 - cellspacing="0" cellpadding="0" bgcolor="#f6f6f6"> 106 +<table class="main" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;" cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
109 <tbody> 107 <tbody>
110 <tr style="box-sizing: border-box; margin: 0px;"> 108 <tr style="box-sizing: border-box; margin: 0px;">
111 - <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;"  
112 - align="center" valign="top">  
113 - <table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;"  
114 - width="600" cellspacing="0" cellpadding="0"> 109 + <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" align="center" valign="top">
  110 + <table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 367px; background-color: #ffffff; width: 600px; max-width: 600px !important;" width="600" cellspacing="0" cellpadding="0">
115 <tbody> 111 <tbody>
116 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> 112 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
117 - <td class="content-block"  
118 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;"  
119 - valign="top">  
120 - <h2>Your ThingsBoard account feature may be disabled soon</h2>  
121 - </td> 113 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px; height: 110px;" valign="top"><img src="https://media.thingsboard.io/email/head.png" alt="" width="598" height="91" /></td>
122 </tr> 114 </tr>
123 - <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">  
124 - <td class="content-block"  
125 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"  
126 - valign="top">  
127 - ThingsBoard has already&nbsp;${apiValueLabel}.<br> ${apiFeature} will be disabled for your account once the limit will be reached. 115 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; margin: 0;">
  116 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #000000; box-sizing: border-box; font-size: 16px; margin: 0px; padding: 0px 32px; height: 66px; vertical-align: middle;" valign="middle"><strong>Warning:</strong> your ThingsBoard account feature may be disabled soon</td>
  117 + </tr>
  118 + <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; line-height: 24px; margin: 0;">
  119 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0px; padding: 0px 32px; height: 93px; vertical-align: top;" valign="top">
  120 + <div style="padding: 16px; margin-bottom: 24px; border: solid 2px #F2994A; border-radius: 6px; background: rgba(242, 153, 74, 0.05);"><img style="vertical-align: middle; padding-right: 6px;" src="https://media.thingsboard.io/email/warning.png" alt="" width="20" height="20" />
  121 + <div style="display: inline; vertical-align: middle;">ThingsBoard has already&nbsp;${apiValueLabel}.<br />${apiFeature} will be <strong>disabled</strong> for your account once the limit will be reached.</div>
  122 + </div>
128 </td> 123 </td>
129 </tr> 124 </tr>
130 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> 125 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
131 - <td class="content-block"  
132 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 59px;"  
133 - valign="top">Please contact your system administrator to resolve the issue.  
134 - </td> 126 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 32px; height: 59px;" valign="top">Please contact your system administrator to resolve the issue.</td>
135 </tr> 127 </tr>
136 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> 128 <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
137 - <td class="content-block"  
138 - style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"  
139 - valign="top">&mdash; The ThingsBoard  
140 - </td> 129 + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 32px; height: 40px;" valign="top">&mdash; The ThingsBoard</td>
141 </tr> 130 </tr>
142 </tbody> 131 </tbody>
143 </table> 132 </table>
@@ -145,16 +134,10 @@ @@ -145,16 +134,10 @@
145 </tr> 134 </tr>
146 </tbody> 135 </tbody>
147 </table> 136 </table>
148 -<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;"  
149 - cellpadding="0px 0px 20px"> 137 +<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" cellpadding="0px 0px 20px">
150 <tbody> 138 <tbody>
151 <tr style="box-sizing: border-box; margin: 0px;"> 139 <tr style="box-sizing: border-box; margin: 0px;">
152 - <td class="aligncenter content-block"  
153 - style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"  
154 - align="center" valign="top">This email was sent to&nbsp;<a  
155 - style="box-sizing: border-box; color: #999999; margin: 0px;"  
156 - href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.  
157 - </td> 140 + <td class="aligncenter content-block" style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;" align="center" valign="top">This email was sent to&nbsp;<a style="box-sizing: border-box; color: #999999; margin: 0px;" href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.</td>
158 </tr> 141 </tr>
159 </tbody> 142 </tbody>
160 </table> 143 </table>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -15,9 +15,6 @@ @@ -15,9 +15,6 @@
15 */ 15 */
16 package org.thingsboard.server.dao.device; 16 package org.thingsboard.server.dao.device;
17 17
18 -import com.google.common.util.concurrent.ListenableFuture;  
19 -import org.thingsboard.server.common.data.Device;  
20 -import org.thingsboard.server.common.data.DeviceProfile;  
21 import org.thingsboard.server.dao.device.provision.ProvisionFailedException; 18 import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
22 import org.thingsboard.server.dao.device.provision.ProvisionRequest; 19 import org.thingsboard.server.dao.device.provision.ProvisionRequest;
23 import org.thingsboard.server.dao.device.provision.ProvisionResponse; 20 import org.thingsboard.server.dao.device.provision.ProvisionResponse;
@@ -16,9 +16,12 @@ @@ -16,9 +16,12 @@
16 package org.thingsboard.server.dao.tenant; 16 package org.thingsboard.server.dao.tenant;
17 17
18 import org.thingsboard.server.common.data.TenantProfile; 18 import org.thingsboard.server.common.data.TenantProfile;
  19 +import org.thingsboard.server.common.data.id.EntityId;
19 import org.thingsboard.server.common.data.id.TenantId; 20 import org.thingsboard.server.common.data.id.TenantId;
20 import org.thingsboard.server.common.data.id.TenantProfileId; 21 import org.thingsboard.server.common.data.id.TenantProfileId;
21 22
  23 +import java.util.function.Consumer;
  24 +
22 public interface TbTenantProfileCache { 25 public interface TbTenantProfileCache {
23 26
24 TenantProfile get(TenantId tenantId); 27 TenantProfile get(TenantId tenantId);
@@ -31,4 +34,8 @@ public interface TbTenantProfileCache { @@ -31,4 +34,8 @@ public interface TbTenantProfileCache {
31 34
32 void evict(TenantId id); 35 void evict(TenantId id);
33 36
  37 + void addListener(TenantId tenantId, EntityId listenerId, Consumer<TenantProfile> profileListener);
  38 +
  39 + void removeListener(TenantId tenantId, EntityId listenerId);
  40 +
34 } 41 }
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -18,9 +18,8 @@ package org.thingsboard.server.common.data.device.profile; @@ -18,9 +18,8 @@ package org.thingsboard.server.common.data.device.profile;
18 import lombok.Data; 18 import lombok.Data;
19 import org.thingsboard.server.common.data.alarm.AlarmSeverity; 19 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
20 20
21 -import java.util.LinkedHashMap;  
22 import java.util.List; 21 import java.util.List;
23 -import java.util.Map; 22 +import java.util.TreeMap;
24 23
25 @Data 24 @Data
26 public class DeviceProfileAlarm { 25 public class DeviceProfileAlarm {
@@ -28,7 +27,7 @@ public class DeviceProfileAlarm { @@ -28,7 +27,7 @@ public class DeviceProfileAlarm {
28 private String id; 27 private String id;
29 private String alarmType; 28 private String alarmType;
30 29
31 - private LinkedHashMap<AlarmSeverity, AlarmRule> createRules; 30 + private TreeMap<AlarmSeverity, AlarmRule> createRules;
32 private AlarmRule clearRule; 31 private AlarmRule clearRule;
33 32
34 // Hidden in advanced settings 33 // Hidden in advanced settings
common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java renamed from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.rule.engine.api.sms.config; 16 +package org.thingsboard.server.common.data.sms.config;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 19
common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java renamed from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.rule.engine.api.sms.config; 16 +package org.thingsboard.server.common.data.sms.config;
17 17
18 import com.fasterxml.jackson.annotation.JsonIgnore; 18 import com.fasterxml.jackson.annotation.JsonIgnore;
19 import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 19 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java renamed from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.rule.engine.api.sms.config; 16 +package org.thingsboard.server.common.data.sms.config;
17 17
18 public enum SmsProviderType { 18 public enum SmsProviderType {
19 AWS_SNS, 19 AWS_SNS,
common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java renamed from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.rule.engine.api.sms.config; 16 +package org.thingsboard.server.common.data.sms.config;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 19
common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java renamed from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.rule.engine.api.sms.config; 16 +package org.thingsboard.server.common.data.sms.config;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 19
@@ -45,6 +45,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura @@ -45,6 +45,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
45 private long maxEmails; 45 private long maxEmails;
46 private long maxSms; 46 private long maxSms;
47 47
  48 + private int defaultStorageTtlDays;
  49 +
48 private double warnThreshold; 50 private double warnThreshold;
49 51
50 @Override 52 @Override
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>common</artifactId> 26 <artifactId>common</artifactId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -195,9 +195,11 @@ public class HashPartitionService implements PartitionService { @@ -195,9 +195,11 @@ public class HashPartitionService implements PartitionService {
195 if (current.getServiceTypesList().contains(serviceType.name())) { 195 if (current.getServiceTypesList().contains(serviceType.name())) {
196 result.add(current.getServiceId()); 196 result.add(current.getServiceId());
197 } 197 }
198 - for (ServiceInfo serviceInfo : currentOtherServices) {  
199 - if (serviceInfo.getServiceTypesList().contains(serviceType.name())) {  
200 - result.add(serviceInfo.getServiceId()); 198 + if (currentOtherServices != null) {
  199 + for (ServiceInfo serviceInfo : currentOtherServices) {
  200 + if (serviceInfo.getServiceTypesList().contains(serviceType.name())) {
  201 + result.add(serviceInfo.getServiceId());
  202 + }
201 } 203 }
202 } 204 }
203 return result; 205 return result;
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 <modelVersion>4.0.0</modelVersion> 22 <modelVersion>4.0.0</modelVersion>
23 <parent> 23 <parent>
24 <groupId>org.thingsboard</groupId> 24 <groupId>org.thingsboard</groupId>
25 - <version>3.2.0-SNAPSHOT</version> 25 + <version>3.2.1-SNAPSHOT</version>
26 <artifactId>common</artifactId> 26 <artifactId>common</artifactId>
27 </parent> 27 </parent>
28 <groupId>org.thingsboard.common</groupId> 28 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
19 import org.eclipse.californium.core.CoapResource; 19 import org.eclipse.californium.core.CoapResource;
20 import org.eclipse.californium.core.CoapServer; 20 import org.eclipse.californium.core.CoapServer;
21 import org.eclipse.californium.core.network.CoapEndpoint; 21 import org.eclipse.californium.core.network.CoapEndpoint;
  22 +import org.eclipse.californium.core.network.config.NetworkConfig;
22 import org.springframework.beans.factory.annotation.Autowired; 23 import org.springframework.beans.factory.annotation.Autowired;
23 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 24 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
24 import org.springframework.stereotype.Service; 25 import org.springframework.stereotype.Service;
@@ -46,7 +47,7 @@ public class CoapTransportService { @@ -46,7 +47,7 @@ public class CoapTransportService {
46 public void init() throws UnknownHostException { 47 public void init() throws UnknownHostException {
47 log.info("Starting CoAP transport..."); 48 log.info("Starting CoAP transport...");
48 log.info("Starting CoAP transport server"); 49 log.info("Starting CoAP transport server");
49 - this.server = new CoapServer(); 50 + this.server = new CoapServer(NetworkConfig.createStandardWithoutFile());
50 createResources(); 51 createResources();
51 InetAddress addr = InetAddress.getByName(coapTransportContext.getHost()); 52 InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());
52 InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort()); 53 InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -179,6 +179,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -179,6 +179,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
179 } 179 }
180 } 180 }
181 } else { 181 } else {
  182 + ctx.close();
182 throw new RuntimeException("Unsupported topic for provisioning requests!"); 183 throw new RuntimeException("Unsupported topic for provisioning requests!");
183 } 184 }
184 } catch (RuntimeException | AdaptorException e) { 185 } catch (RuntimeException | AdaptorException e) {
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>dao</artifactId> 26 <artifactId>dao</artifactId>
@@ -78,6 +78,8 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ @@ -78,6 +78,8 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
78 public static final String INCORRECT_PAGE_LINK = "Incorrect page link "; 78 public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
79 public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; 79 public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
80 public static final String INCORRECT_ASSET_ID = "Incorrect assetId "; 80 public static final String INCORRECT_ASSET_ID = "Incorrect assetId ";
  81 + public static final String TB_SERVICE_QUEUE = "TbServiceQueue";
  82 +
81 @Autowired 83 @Autowired
82 private AssetDao assetDao; 84 private AssetDao assetDao;
83 85
@@ -329,8 +331,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ @@ -329,8 +331,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
329 protected void validateCreate(TenantId tenantId, Asset asset) { 331 protected void validateCreate(TenantId tenantId, Asset asset) {
330 DefaultTenantProfileConfiguration profileConfiguration = 332 DefaultTenantProfileConfiguration profileConfiguration =
331 (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); 333 (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
332 - long maxAssets = profileConfiguration.getMaxAssets();  
333 - validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET); 334 + if (!TB_SERVICE_QUEUE.equals(asset.getType())) {
  335 + long maxAssets = profileConfiguration.getMaxAssets();
  336 + validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
  337 + }
334 } 338 }
335 339
336 @Override 340 @Override
@@ -122,5 +122,5 @@ public interface AssetRepository extends PagingAndSortingRepository<AssetEntity, @@ -122,5 +122,5 @@ public interface AssetRepository extends PagingAndSortingRepository<AssetEntity,
122 @Query("SELECT DISTINCT a.type FROM AssetEntity a WHERE a.tenantId = :tenantId") 122 @Query("SELECT DISTINCT a.type FROM AssetEntity a WHERE a.tenantId = :tenantId")
123 List<String> findTenantAssetTypes(@Param("tenantId") UUID tenantId); 123 List<String> findTenantAssetTypes(@Param("tenantId") UUID tenantId);
124 124
125 - Long countByTenantId(UUID tenantId); 125 + Long countByTenantIdAndTypeIsNot(UUID tenantId, String type);
126 } 126 }
@@ -39,6 +39,8 @@ import java.util.Objects; @@ -39,6 +39,8 @@ import java.util.Objects;
39 import java.util.Optional; 39 import java.util.Optional;
40 import java.util.UUID; 40 import java.util.UUID;
41 41
  42 +import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
  43 +
42 /** 44 /**
43 * Created by Valerii Sosliuk on 5/19/2017. 45 * Created by Valerii Sosliuk on 5/19/2017.
44 */ 46 */
@@ -179,6 +181,6 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im @@ -179,6 +181,6 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
179 181
180 @Override 182 @Override
181 public Long countByTenantId(TenantId tenantId) { 183 public Long countByTenantId(TenantId tenantId) {
182 - return assetRepository.countByTenantId(tenantId.getId()); 184 + return assetRepository.countByTenantIdAndTypeIsNot(tenantId.getId(), TB_SERVICE_QUEUE);
183 } 185 }
184 } 186 }
@@ -19,16 +19,15 @@ import lombok.extern.slf4j.Slf4j; @@ -19,16 +19,15 @@ import lombok.extern.slf4j.Slf4j;
19 import org.springframework.stereotype.Service; 19 import org.springframework.stereotype.Service;
20 import org.thingsboard.server.common.data.Tenant; 20 import org.thingsboard.server.common.data.Tenant;
21 import org.thingsboard.server.common.data.TenantProfile; 21 import org.thingsboard.server.common.data.TenantProfile;
  22 +import org.thingsboard.server.common.data.id.EntityId;
22 import org.thingsboard.server.common.data.id.TenantId; 23 import org.thingsboard.server.common.data.id.TenantId;
23 import org.thingsboard.server.common.data.id.TenantProfileId; 24 import org.thingsboard.server.common.data.id.TenantProfileId;
24 -import org.thingsboard.server.dao.tenant.TbTenantProfileCache;  
25 -import org.thingsboard.server.dao.tenant.TenantProfileService;  
26 -import org.thingsboard.server.dao.tenant.TenantService;  
27 25
28 import java.util.concurrent.ConcurrentHashMap; 26 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.ConcurrentMap; 27 import java.util.concurrent.ConcurrentMap;
30 import java.util.concurrent.locks.Lock; 28 import java.util.concurrent.locks.Lock;
31 import java.util.concurrent.locks.ReentrantLock; 29 import java.util.concurrent.locks.ReentrantLock;
  30 +import java.util.function.Consumer;
32 31
33 @Service 32 @Service
34 @Slf4j 33 @Slf4j
@@ -40,6 +39,7 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache { @@ -40,6 +39,7 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache {
40 39
41 private final ConcurrentMap<TenantProfileId, TenantProfile> tenantProfilesMap = new ConcurrentHashMap<>(); 40 private final ConcurrentMap<TenantProfileId, TenantProfile> tenantProfilesMap = new ConcurrentHashMap<>();
42 private final ConcurrentMap<TenantId, TenantProfileId> tenantsMap = new ConcurrentHashMap<>(); 41 private final ConcurrentMap<TenantId, TenantProfileId> tenantsMap = new ConcurrentHashMap<>();
  42 + private final ConcurrentMap<TenantId, ConcurrentMap<EntityId, Consumer<TenantProfile>>> profileListeners = new ConcurrentHashMap<>();
43 43
44 public DefaultTbTenantProfileCache(TenantProfileService tenantProfileService, TenantService tenantService) { 44 public DefaultTbTenantProfileCache(TenantProfileService tenantProfileService, TenantService tenantService) {
45 this.tenantProfileService = tenantProfileService; 45 this.tenantProfileService = tenantProfileService;
@@ -85,17 +85,56 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache { @@ -85,17 +85,56 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache {
85 public void put(TenantProfile profile) { 85 public void put(TenantProfile profile) {
86 if (profile.getId() != null) { 86 if (profile.getId() != null) {
87 tenantProfilesMap.put(profile.getId(), profile); 87 tenantProfilesMap.put(profile.getId(), profile);
  88 + notifyTenantListeners(profile);
88 } 89 }
89 } 90 }
90 91
91 @Override 92 @Override
92 public void evict(TenantProfileId profileId) { 93 public void evict(TenantProfileId profileId) {
93 tenantProfilesMap.remove(profileId); 94 tenantProfilesMap.remove(profileId);
  95 + notifyTenantListeners(get(profileId));
  96 + }
  97 +
  98 + public void notifyTenantListeners(TenantProfile tenantProfile) {
  99 + if (tenantProfile != null) {
  100 + tenantsMap.forEach(((tenantId, tenantProfileId) -> {
  101 + if (tenantProfileId.equals(tenantProfile.getId())) {
  102 + ConcurrentMap<EntityId, Consumer<TenantProfile>> tenantListeners = profileListeners.get(tenantId);
  103 + if (tenantListeners != null) {
  104 + tenantListeners.forEach((id, listener) -> listener.accept(tenantProfile));
  105 + }
  106 + }
  107 + }));
  108 + }
94 } 109 }
95 110
96 @Override 111 @Override
97 public void evict(TenantId tenantId) { 112 public void evict(TenantId tenantId) {
98 tenantsMap.remove(tenantId); 113 tenantsMap.remove(tenantId);
  114 + TenantProfile tenantProfile = get(tenantId);
  115 + if (tenantProfile != null) {
  116 + ConcurrentMap<EntityId, Consumer<TenantProfile>> tenantListeners = profileListeners.get(tenantId);
  117 + if (tenantListeners != null) {
  118 + tenantListeners.forEach((id, listener) -> listener.accept(tenantProfile));
  119 + }
  120 + }
  121 + }
  122 +
  123 + @Override
  124 + public void addListener(TenantId tenantId, EntityId listenerId, Consumer<TenantProfile> profileListener) {
  125 + //Force cache of the tenant id.
  126 + get(tenantId);
  127 + if (profileListener != null) {
  128 + profileListeners.computeIfAbsent(tenantId, id -> new ConcurrentHashMap<>()).put(listenerId, profileListener);
  129 + }
  130 + }
  131 +
  132 + @Override
  133 + public void removeListener(TenantId tenantId, EntityId listenerId) {
  134 + ConcurrentMap<EntityId, Consumer<TenantProfile>> tenantListeners = profileListeners.get(tenantId);
  135 + if (tenantListeners != null) {
  136 + tenantListeners.remove(listenerId);
  137 + }
99 } 138 }
100 139
101 } 140 }
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 21
22 <parent> 22 <parent>
23 <groupId>org.thingsboard</groupId> 23 <groupId>org.thingsboard</groupId>
24 - <version>3.2.0-SNAPSHOT</version> 24 + <version>3.2.1-SNAPSHOT</version>
25 <artifactId>msa</artifactId> 25 <artifactId>msa</artifactId>
26 </parent> 26 </parent>
27 <groupId>org.thingsboard.msa</groupId> 27 <groupId>org.thingsboard.msa</groupId>
1 { 1 {
2 "name": "thingsboard-js-executor", 2 "name": "thingsboard-js-executor",
3 "private": true, 3 "private": true,
4 - "version": "3.2.0", 4 + "version": "3.2.1",
5 "description": "ThingsBoard JavaScript Executor Microservice", 5 "description": "ThingsBoard JavaScript Executor Microservice",
6 "main": "server.js", 6 "main": "server.js",
7 "bin": "server.js", 7 "bin": "server.js",
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>msa</artifactId> 24 <artifactId>msa</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa</groupId> 26 <groupId>org.thingsboard.msa</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>msa</artifactId> 26 <artifactId>msa</artifactId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>msa</artifactId> 24 <artifactId>msa</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa</groupId> 26 <groupId>org.thingsboard.msa</groupId>
@@ -27,7 +27,11 @@ fi @@ -27,7 +27,11 @@ fi
27 exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 & 27 exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 &
28 28
29 if [ ! -f ${firstlaunch} ]; then 29 if [ ! -f ${firstlaunch} ]; then
30 - psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" 30 + sleep 2
  31 + while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard"
  32 + do
  33 + sleep 1
  34 + done
31 fi 35 fi
32 36
33 cassandra_data_dir=${CASSANDRA_DATA} 37 cassandra_data_dir=${CASSANDRA_DATA}
@@ -27,5 +27,9 @@ fi @@ -27,5 +27,9 @@ fi
27 exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 & 27 exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 &
28 28
29 if [ ! -f ${firstlaunch} ]; then 29 if [ ! -f ${firstlaunch} ]; then
30 - psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard"  
31 -fi 30 + sleep 2
  31 + while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard"
  32 + do
  33 + sleep 1
  34 + done
  35 +fi
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>msa</artifactId> 24 <artifactId>msa</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa</groupId> 26 <groupId>org.thingsboard.msa</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.msa</groupId> 22 <groupId>org.thingsboard.msa</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa.transport</groupId> 26 <groupId>org.thingsboard.msa.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.msa</groupId> 22 <groupId>org.thingsboard.msa</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa.transport</groupId> 26 <groupId>org.thingsboard.msa.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.msa</groupId> 22 <groupId>org.thingsboard.msa</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa.transport</groupId> 26 <groupId>org.thingsboard.msa.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>msa</artifactId> 24 <artifactId>msa</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa</groupId> 26 <groupId>org.thingsboard.msa</groupId>
1 { 1 {
2 "name": "thingsboard-web-ui", 2 "name": "thingsboard-web-ui",
3 "private": true, 3 "private": true,
4 - "version": "3.2.0", 4 + "version": "3.2.1",
5 "description": "ThingsBoard Web UI Microservice", 5 "description": "ThingsBoard Web UI Microservice",
6 "main": "server.js", 6 "main": "server.js",
7 "bin": "server.js", 7 "bin": "server.js",
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>msa</artifactId> 24 <artifactId>msa</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa</groupId> 26 <groupId>org.thingsboard.msa</groupId>
@@ -19,11 +19,11 @@ @@ -19,11 +19,11 @@
19 <modelVersion>4.0.0</modelVersion> 19 <modelVersion>4.0.0</modelVersion>
20 <parent> 20 <parent>
21 <groupId>org.thingsboard</groupId> 21 <groupId>org.thingsboard</groupId>
22 - <version>3.2.0-SNAPSHOT</version> 22 + <version>3.2.1-SNAPSHOT</version>
23 <artifactId>thingsboard</artifactId> 23 <artifactId>thingsboard</artifactId>
24 </parent> 24 </parent>
25 <artifactId>netty-mqtt</artifactId> 25 <artifactId>netty-mqtt</artifactId>
26 - <version>3.2.0-SNAPSHOT</version> 26 + <version>3.2.1-SNAPSHOT</version>
27 <packaging>jar</packaging> 27 <packaging>jar</packaging>
28 28
29 <name>Netty MQTT Client</name> 29 <name>Netty MQTT Client</name>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <groupId>org.thingsboard</groupId> 21 <groupId>org.thingsboard</groupId>
22 <artifactId>thingsboard</artifactId> 22 <artifactId>thingsboard</artifactId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <packaging>pom</packaging> 24 <packaging>pom</packaging>
25 25
26 <name>Thingsboard</name> 26 <name>Thingsboard</name>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>rest-client</artifactId> 26 <artifactId>rest-client</artifactId>
@@ -109,6 +109,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -109,6 +109,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
109 import org.thingsboard.server.common.data.security.DeviceCredentialsType; 109 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
110 import org.thingsboard.server.common.data.security.model.SecuritySettings; 110 import org.thingsboard.server.common.data.security.model.SecuritySettings;
111 import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; 111 import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
  112 +import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
112 import org.thingsboard.server.common.data.widget.WidgetType; 113 import org.thingsboard.server.common.data.widget.WidgetType;
113 import org.thingsboard.server.common.data.widget.WidgetsBundle; 114 import org.thingsboard.server.common.data.widget.WidgetsBundle;
114 115
@@ -218,7 +219,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { @@ -218,7 +219,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
218 } 219 }
219 220
220 public void sendTestMail(AdminSettings adminSettings) { 221 public void sendTestMail(AdminSettings adminSettings) {
221 - restTemplate.postForEntity(baseURL + "/api/admin/settings/testMail", adminSettings, AdminSettings.class); 222 + restTemplate.postForLocation(baseURL + "/api/admin/settings/testMail", adminSettings);
  223 + }
  224 +
  225 + public void sendTestSms(TestSmsRequest testSmsRequest) {
  226 + restTemplate.postForLocation(baseURL + "/api/admin/settings/testSms", testSmsRequest);
222 } 227 }
223 228
224 public Optional<SecuritySettings> getSecuritySettings() { 229 public Optional<SecuritySettings> getSecuritySettings() {
@@ -1714,6 +1719,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { @@ -1714,6 +1719,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
1714 return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Params, OAuth2ClientsParams.class).getBody(); 1719 return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Params, OAuth2ClientsParams.class).getBody();
1715 } 1720 }
1716 1721
  1722 + public String getLoginProcessingUrl() {
  1723 + return restTemplate.getForEntity(baseURL + "/api/oauth2/loginProcessingUrl", String.class).getBody();
  1724 + }
  1725 +
1717 public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { 1726 public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) {
1718 restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId()); 1727 restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId());
1719 } 1728 }
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>rule-engine</artifactId> 26 <artifactId>rule-engine</artifactId>
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 <modelVersion>4.0.0</modelVersion> 22 <modelVersion>4.0.0</modelVersion>
23 <parent> 23 <parent>
24 <groupId>org.thingsboard</groupId> 24 <groupId>org.thingsboard</groupId>
25 - <version>3.2.0-SNAPSHOT</version> 25 + <version>3.2.1-SNAPSHOT</version>
26 <artifactId>rule-engine</artifactId> 26 <artifactId>rule-engine</artifactId>
27 </parent> 27 </parent>
28 <groupId>org.thingsboard.rule-engine</groupId> 28 <groupId>org.thingsboard.rule-engine</groupId>
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 */ 15 */
16 package org.thingsboard.rule.engine.api; 16 package org.thingsboard.rule.engine.api;
17 17
18 -import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; 18 +import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
19 import org.thingsboard.server.common.data.exception.ThingsboardException; 19 import org.thingsboard.server.common.data.exception.ThingsboardException;
20 import org.thingsboard.server.common.data.id.TenantId; 20 import org.thingsboard.server.common.data.id.TenantId;
21 21
@@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.ApiUsageRecordKey; @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.ApiUsageRecordKey;
23 import org.thingsboard.server.common.data.Customer; 23 import org.thingsboard.server.common.data.Customer;
24 import org.thingsboard.server.common.data.Device; 24 import org.thingsboard.server.common.data.Device;
25 import org.thingsboard.server.common.data.DeviceProfile; 25 import org.thingsboard.server.common.data.DeviceProfile;
  26 +import org.thingsboard.server.common.data.TenantProfile;
26 import org.thingsboard.server.common.data.alarm.Alarm; 27 import org.thingsboard.server.common.data.alarm.Alarm;
27 import org.thingsboard.server.common.data.asset.Asset; 28 import org.thingsboard.server.common.data.asset.Asset;
28 import org.thingsboard.server.common.data.id.DeviceId; 29 import org.thingsboard.server.common.data.id.DeviceId;
@@ -237,7 +238,11 @@ public interface TbContext { @@ -237,7 +238,11 @@ public interface TbContext {
237 238
238 void clearRuleNodeStates(); 239 void clearRuleNodeStates();
239 240
  241 + void addTenantProfileListener(Consumer<TenantProfile> listener);
  242 +
240 void addDeviceProfileListeners(Consumer<DeviceProfile> listener, BiConsumer<DeviceId, DeviceProfile> deviceListener); 243 void addDeviceProfileListeners(Consumer<DeviceProfile> listener, BiConsumer<DeviceId, DeviceProfile> deviceListener);
241 244
242 - void removeProfileListener(); 245 + void removeListeners();
  246 +
  247 + TenantProfile getTenantProfile();
243 } 248 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 */ 15 */
16 package org.thingsboard.rule.engine.api.sms; 16 package org.thingsboard.rule.engine.api.sms;
17 17
18 -import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; 18 +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration;
19 19
20 public interface SmsSenderFactory { 20 public interface SmsSenderFactory {
21 21
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 <modelVersion>4.0.0</modelVersion> 22 <modelVersion>4.0.0</modelVersion>
23 <parent> 23 <parent>
24 <groupId>org.thingsboard</groupId> 24 <groupId>org.thingsboard</groupId>
25 - <version>3.2.0-SNAPSHOT</version> 25 + <version>3.2.1-SNAPSHOT</version>
26 <artifactId>rule-engine</artifactId> 26 <artifactId>rule-engine</artifactId>
27 </parent> 27 </parent>
28 <groupId>org.thingsboard.rule-engine</groupId> 28 <groupId>org.thingsboard.rule-engine</groupId>
@@ -54,7 +54,8 @@ import java.util.concurrent.TimeUnit; @@ -54,7 +54,8 @@ import java.util.concurrent.TimeUnit;
54 relationTypes = {"Alarm Created", "Alarm Updated", "Alarm Severity Updated", "Alarm Cleared", "Success", "Failure"}, 54 relationTypes = {"Alarm Created", "Alarm Updated", "Alarm Severity Updated", "Alarm Cleared", "Success", "Failure"},
55 configClazz = TbDeviceProfileNodeConfiguration.class, 55 configClazz = TbDeviceProfileNodeConfiguration.class,
56 nodeDescription = "Process device messages based on device profile settings", 56 nodeDescription = "Process device messages based on device profile settings",
57 - nodeDetails = "Create and clear alarms based on alarm rules defined in device profile. Generates ", 57 + nodeDetails = "Create and clear alarms based on alarm rules defined in device profile. The output relation type is either " +
  58 + "'Alarm Created', 'Alarm Updated', 'Alarm Severity Updated' and 'Alarm Cleared' or simply 'Success' if no alarms were affected.",
58 uiResources = {"static/rulenode/rulenode-core-config.js"}, 59 uiResources = {"static/rulenode/rulenode-core-config.js"},
59 configDirective = "tbDeviceProfileConfig" 60 configDirective = "tbDeviceProfileConfig"
60 ) 61 )
@@ -119,7 +120,6 @@ public class TbDeviceProfileNode implements TbNode { @@ -119,7 +120,6 @@ public class TbDeviceProfileNode implements TbNode {
119 } else { 120 } else {
120 removeDeviceState(deviceId); 121 removeDeviceState(deviceId);
121 } 122 }
122 -  
123 } else { 123 } else {
124 if (EntityType.DEVICE.equals(originatorType)) { 124 if (EntityType.DEVICE.equals(originatorType)) {
125 DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); 125 DeviceId deviceId = new DeviceId(msg.getOriginator().getId());
@@ -149,7 +149,7 @@ public class TbDeviceProfileNode implements TbNode { @@ -149,7 +149,7 @@ public class TbDeviceProfileNode implements TbNode {
149 149
150 @Override 150 @Override
151 public void destroy() { 151 public void destroy() {
152 - ctx.removeProfileListener(); 152 + ctx.removeListeners();
153 deviceStates.clear(); 153 deviceStates.clear();
154 } 154 }
155 155
@@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.sms; @@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.sms;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 import org.thingsboard.rule.engine.api.NodeConfiguration; 19 import org.thingsboard.rule.engine.api.NodeConfiguration;
20 -import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; 20 +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration;
21 21
22 @Data 22 @Data
23 public class TbSendSmsNodeConfiguration implements NodeConfiguration { 23 public class TbSendSmsNodeConfiguration implements NodeConfiguration {
@@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.TbContext; @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.TbContext;
24 import org.thingsboard.rule.engine.api.TbNode; 24 import org.thingsboard.rule.engine.api.TbNode;
25 import org.thingsboard.rule.engine.api.TbNodeConfiguration; 25 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
26 import org.thingsboard.rule.engine.api.TbNodeException; 26 import org.thingsboard.rule.engine.api.TbNodeException;
  27 +import org.thingsboard.server.common.data.TenantProfile;
27 import org.thingsboard.server.common.data.kv.BasicTsKvEntry; 28 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
28 import org.thingsboard.server.common.data.kv.KvEntry; 29 import org.thingsboard.server.common.data.kv.KvEntry;
29 import org.thingsboard.server.common.data.kv.TsKvEntry; 30 import org.thingsboard.server.common.data.kv.TsKvEntry;
@@ -31,10 +32,12 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @@ -31,10 +32,12 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
31 import org.thingsboard.server.common.msg.TbMsg; 32 import org.thingsboard.server.common.msg.TbMsg;
32 import org.thingsboard.server.common.msg.session.SessionMsgType; 33 import org.thingsboard.server.common.msg.session.SessionMsgType;
33 import org.thingsboard.server.common.transport.adaptor.JsonConverter; 34 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  35 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
34 36
35 import java.util.ArrayList; 37 import java.util.ArrayList;
36 import java.util.List; 38 import java.util.List;
37 import java.util.Map; 39 import java.util.Map;
  40 +import java.util.concurrent.TimeUnit;
38 41
39 @Slf4j 42 @Slf4j
40 @RuleNode( 43 @RuleNode(
@@ -50,10 +53,20 @@ import java.util.Map; @@ -50,10 +53,20 @@ import java.util.Map;
50 public class TbMsgTimeseriesNode implements TbNode { 53 public class TbMsgTimeseriesNode implements TbNode {
51 54
52 private TbMsgTimeseriesNodeConfiguration config; 55 private TbMsgTimeseriesNodeConfiguration config;
  56 + private TbContext ctx;
  57 + private long tenantProfileDefaultStorageTtl;
53 58
54 @Override 59 @Override
55 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { 60 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
56 this.config = TbNodeUtils.convert(configuration, TbMsgTimeseriesNodeConfiguration.class); 61 this.config = TbNodeUtils.convert(configuration, TbMsgTimeseriesNodeConfiguration.class);
  62 + this.ctx = ctx;
  63 + ctx.addTenantProfileListener(this::onTenantProfileUpdate);
  64 + onTenantProfileUpdate(ctx.getTenantProfile());
  65 + }
  66 +
  67 + void onTenantProfileUpdate(TenantProfile tenantProfile) {
  68 + DefaultTenantProfileConfiguration configuration = (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration();
  69 + tenantProfileDefaultStorageTtl = TimeUnit.DAYS.toSeconds(configuration.getDefaultStorageTtlDays());
57 } 70 }
58 71
59 @Override 72 @Override
@@ -77,6 +90,9 @@ public class TbMsgTimeseriesNode implements TbNode { @@ -77,6 +90,9 @@ public class TbMsgTimeseriesNode implements TbNode {
77 } 90 }
78 String ttlValue = msg.getMetaData().getValue("TTL"); 91 String ttlValue = msg.getMetaData().getValue("TTL");
79 long ttl = !StringUtils.isEmpty(ttlValue) ? Long.parseLong(ttlValue) : config.getDefaultTTL(); 92 long ttl = !StringUtils.isEmpty(ttlValue) ? Long.parseLong(ttlValue) : config.getDefaultTTL();
  93 + if (ttl == 0L) {
  94 + ttl = tenantProfileDefaultStorageTtl;
  95 + }
80 ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getOriginator(), tsKvEntryList, ttl, new TelemetryNodeCallback(ctx, msg)); 96 ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getOriginator(), tsKvEntryList, ttl, new TelemetryNodeCallback(ctx, msg));
81 } 97 }
82 98
@@ -96,6 +112,7 @@ public class TbMsgTimeseriesNode implements TbNode { @@ -96,6 +112,7 @@ public class TbMsgTimeseriesNode implements TbNode {
96 112
97 @Override 113 @Override
98 public void destroy() { 114 public void destroy() {
  115 + ctx.removeListeners();
99 } 116 }
100 117
101 } 118 }
@@ -23,17 +23,13 @@ import org.junit.runner.RunWith; @@ -23,17 +23,13 @@ import org.junit.runner.RunWith;
23 import org.mockito.AdditionalAnswers; 23 import org.mockito.AdditionalAnswers;
24 import org.mockito.Mock; 24 import org.mockito.Mock;
25 import org.mockito.Mockito; 25 import org.mockito.Mockito;
26 -import org.mockito.invocation.InvocationOnMock;  
27 import org.mockito.runners.MockitoJUnitRunner; 26 import org.mockito.runners.MockitoJUnitRunner;
28 -import org.mockito.stubbing.Answer;  
29 -import org.springframework.util.StringUtils;  
30 import org.thingsboard.rule.engine.api.RuleEngineAlarmService; 27 import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
31 import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; 28 import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
32 import org.thingsboard.rule.engine.api.TbContext; 29 import org.thingsboard.rule.engine.api.TbContext;
33 import org.thingsboard.rule.engine.api.TbNodeConfiguration; 30 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
34 import org.thingsboard.rule.engine.api.TbNodeException; 31 import org.thingsboard.rule.engine.api.TbNodeException;
35 import org.thingsboard.server.common.data.DeviceProfile; 32 import org.thingsboard.server.common.data.DeviceProfile;
36 -import org.thingsboard.server.common.data.alarm.Alarm;  
37 import org.thingsboard.server.common.data.alarm.AlarmSeverity; 33 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
38 import org.thingsboard.server.common.data.device.profile.AlarmCondition; 34 import org.thingsboard.server.common.data.device.profile.AlarmCondition;
39 import org.thingsboard.server.common.data.device.profile.AlarmRule; 35 import org.thingsboard.server.common.data.device.profile.AlarmRule;
@@ -52,11 +48,10 @@ import org.thingsboard.server.common.msg.TbMsg; @@ -52,11 +48,10 @@ import org.thingsboard.server.common.msg.TbMsg;
52 import org.thingsboard.server.common.msg.TbMsgDataType; 48 import org.thingsboard.server.common.msg.TbMsgDataType;
53 import org.thingsboard.server.common.msg.TbMsgMetaData; 49 import org.thingsboard.server.common.msg.TbMsgMetaData;
54 import org.thingsboard.server.common.msg.session.SessionMsgType; 50 import org.thingsboard.server.common.msg.session.SessionMsgType;
55 -import org.thingsboard.server.dao.alarm.AlarmService;  
56 import org.thingsboard.server.dao.timeseries.TimeseriesService; 51 import org.thingsboard.server.dao.timeseries.TimeseriesService;
57 52
58 import java.util.Collections; 53 import java.util.Collections;
59 -import java.util.LinkedHashMap; 54 +import java.util.TreeMap;
60 import java.util.UUID; 55 import java.util.UUID;
61 56
62 import static org.mockito.Mockito.verify; 57 import static org.mockito.Mockito.verify;
@@ -140,7 +135,7 @@ public class TbDeviceProfileNodeTest { @@ -140,7 +135,7 @@ public class TbDeviceProfileNodeTest {
140 DeviceProfileAlarm dpa = new DeviceProfileAlarm(); 135 DeviceProfileAlarm dpa = new DeviceProfileAlarm();
141 dpa.setId("highTemperatureAlarmID"); 136 dpa.setId("highTemperatureAlarmID");
142 dpa.setAlarmType("highTemperatureAlarm"); 137 dpa.setAlarmType("highTemperatureAlarm");
143 - dpa.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); 138 + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
144 139
145 KeyFilter lowTempFilter = new KeyFilter(); 140 KeyFilter lowTempFilter = new KeyFilter();
146 lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); 141 lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>tools</artifactId> 26 <artifactId>tools</artifactId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.transport</groupId> 26 <groupId>org.thingsboard.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.transport</groupId> 26 <groupId>org.thingsboard.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.transport</groupId> 26 <groupId>org.thingsboard.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>transport</artifactId> 26 <artifactId>transport</artifactId>
1 { 1 {
2 "name": "thingsboard", 2 "name": "thingsboard",
3 - "version": "3.2.0", 3 + "version": "3.2.1",
4 "scripts": { 4 "scripts": {
5 "ng": "ng", 5 "ng": "ng",
6 "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --host 0.0.0.0 --open", 6 "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --host 0.0.0.0 --open",
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.2.0-SNAPSHOT</version> 23 + <version>3.2.1-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -282,8 +282,7 @@ export class AuthService { @@ -282,8 +282,7 @@ export class AuthService {
282 if (publicId) { 282 if (publicId) {
283 return this.publicLogin(publicId).pipe( 283 return this.publicLogin(publicId).pipe(
284 mergeMap((response) => { 284 mergeMap((response) => {
285 - this.updateAndValidateToken(response.token, 'jwt_token', false);  
286 - this.updateAndValidateToken(response.refreshToken, 'refresh_token', false); 285 + this.updateAndValidateTokens(response.token, response.refreshToken, false);
287 return this.procceedJwtTokenValidate(); 286 return this.procceedJwtTokenValidate();
288 }), 287 }),
289 catchError((err) => { 288 catchError((err) => {
@@ -317,8 +316,7 @@ export class AuthService { @@ -317,8 +316,7 @@ export class AuthService {
317 }; 316 };
318 return this.http.post<LoginResponse>('/api/auth/login', loginRequest, defaultHttpOptions()).pipe( 317 return this.http.post<LoginResponse>('/api/auth/login', loginRequest, defaultHttpOptions()).pipe(
319 mergeMap((loginResponse: LoginResponse) => { 318 mergeMap((loginResponse: LoginResponse) => {
320 - this.updateAndValidateToken(loginResponse.token, 'jwt_token', false);  
321 - this.updateAndValidateToken(loginResponse.refreshToken, 'refresh_token', false); 319 + this.updateAndValidateTokens(loginResponse.token, loginResponse.refreshToken, false);
322 return this.procceedJwtTokenValidate(); 320 return this.procceedJwtTokenValidate();
323 } 321 }
324 ) 322 )
@@ -439,7 +437,7 @@ export class AuthService { @@ -439,7 +437,7 @@ export class AuthService {
439 })); 437 }));
440 } 438 }
441 439
442 - public refreshJwtToken(): Observable<LoginResponse> { 440 + public refreshJwtToken(loadUserElseStoreJwtToken = true): Observable<LoginResponse> {
443 let response: Observable<LoginResponse> = this.refreshTokenSubject; 441 let response: Observable<LoginResponse> = this.refreshTokenSubject;
444 if (this.refreshTokenSubject === null) { 442 if (this.refreshTokenSubject === null) {
445 this.refreshTokenSubject = new ReplaySubject<LoginResponse>(1); 443 this.refreshTokenSubject = new ReplaySubject<LoginResponse>(1);
@@ -456,7 +454,11 @@ export class AuthService { @@ -456,7 +454,11 @@ export class AuthService {
456 }; 454 };
457 const refreshObservable = this.http.post<LoginResponse>('/api/auth/token', refreshTokenRequest, defaultHttpOptions()); 455 const refreshObservable = this.http.post<LoginResponse>('/api/auth/token', refreshTokenRequest, defaultHttpOptions());
458 refreshObservable.subscribe((loginResponse: LoginResponse) => { 456 refreshObservable.subscribe((loginResponse: LoginResponse) => {
459 - this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false); 457 + if (loadUserElseStoreJwtToken) {
  458 + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false);
  459 + } else {
  460 + this.updateAndValidateTokens(loginResponse.token, loginResponse.refreshToken, true);
  461 + }
460 this.refreshTokenSubject.next(loginResponse); 462 this.refreshTokenSubject.next(loginResponse);
461 this.refreshTokenSubject.complete(); 463 this.refreshTokenSubject.complete();
462 this.refreshTokenSubject = null; 464 this.refreshTokenSubject = null;
@@ -474,7 +476,7 @@ export class AuthService { @@ -474,7 +476,7 @@ export class AuthService {
474 const subject = new ReplaySubject<void>(); 476 const subject = new ReplaySubject<void>();
475 if (!AuthService.isTokenValid('jwt_token')) { 477 if (!AuthService.isTokenValid('jwt_token')) {
476 if (doRefresh) { 478 if (doRefresh) {
477 - this.refreshJwtToken().subscribe( 479 + this.refreshJwtToken(!doRefresh).subscribe(
478 () => { 480 () => {
479 subject.next(); 481 subject.next();
480 subject.complete(); 482 subject.complete();
@@ -505,8 +507,7 @@ export class AuthService { @@ -505,8 +507,7 @@ export class AuthService {
505 this.notifyUnauthenticated(); 507 this.notifyUnauthenticated();
506 } 508 }
507 } else { 509 } else {
508 - this.updateAndValidateToken(jwtToken, 'jwt_token', true);  
509 - this.updateAndValidateToken(refreshToken, 'refresh_token', true); 510 + this.updateAndValidateTokens(jwtToken, refreshToken, true);
510 if (notify) { 511 if (notify) {
511 this.notifyUserLoaded(false); 512 this.notifyUserLoaded(false);
512 this.loadUser(false).subscribe( 513 this.loadUser(false).subscribe(
@@ -525,6 +526,11 @@ export class AuthService { @@ -525,6 +526,11 @@ export class AuthService {
525 } 526 }
526 } 527 }
527 528
  529 + private updateAndValidateTokens(jwtToken, refreshToken, notify: boolean) {
  530 + this.updateAndValidateToken(jwtToken, 'jwt_token', notify);
  531 + this.updateAndValidateToken(refreshToken, 'refresh_token', notify);
  532 + }
  533 +
528 public parsePublicId(): string { 534 public parsePublicId(): string {
529 const token = AuthService.getJwtToken(); 535 const token = AuthService.getJwtToken();
530 if (token) { 536 if (token) {
@@ -130,7 +130,7 @@ export class DashboardService { @@ -130,7 +130,7 @@ export class DashboardService {
130 const publicCustomerId = publicCustomers[0].customerId.id; 130 const publicCustomerId = publicCustomers[0].customerId.id;
131 let url = this.window.location.protocol + '//' + this.window.location.hostname; 131 let url = this.window.location.protocol + '//' + this.window.location.hostname;
132 const port = this.window.location.port; 132 const port = this.window.location.port;
133 - if (port !== '80' && port !== '443') { 133 + if (port && port.length > 0 && port !== '80' && port !== '443') {
134 url += ':' + port; 134 url += ':' + port;
135 } 135 }
136 url += `/dashboard/${dashboard.id.id}?publicId=${publicCustomerId}`; 136 url += `/dashboard/${dashboard.id.id}?publicId=${publicCustomerId}`;
@@ -23,6 +23,7 @@ export interface MenuSection extends HasUUID{ @@ -23,6 +23,7 @@ export interface MenuSection extends HasUUID{
23 type: MenuSectionType; 23 type: MenuSectionType;
24 path: string; 24 path: string;
25 icon: string; 25 icon: string;
  26 + notExact?: boolean;
26 isMdiIcon?: boolean; 27 isMdiIcon?: boolean;
27 height?: string; 28 height?: string;
28 pages?: Array<MenuSection>; 29 pages?: Array<MenuSection>;
@@ -288,6 +288,14 @@ export class MenuService { @@ -288,6 +288,14 @@ export class MenuService {
288 type: 'link', 288 type: 'link',
289 path: '/auditLogs', 289 path: '/auditLogs',
290 icon: 'track_changes' 290 icon: 'track_changes'
  291 + },
  292 + {
  293 + id: guid(),
  294 + name: 'api-usage.api-usage',
  295 + type: 'link',
  296 + path: '/usage',
  297 + icon: 'insert_chart',
  298 + notExact: true
291 } 299 }
292 ); 300 );
293 return sections; 301 return sections;
@@ -374,6 +382,11 @@ export class MenuService { @@ -374,6 +382,11 @@ export class MenuService {
374 name: 'audit-log.audit-logs', 382 name: 'audit-log.audit-logs',
375 icon: 'track_changes', 383 icon: 'track_changes',
376 path: '/auditLogs' 384 path: '/auditLogs'
  385 + },
  386 + {
  387 + name: 'api-usage.api-usage',
  388 + icon: 'insert_chart',
  389 + path: '/usage'
377 } 390 }
378 ] 391 ]
379 } 392 }
@@ -21,17 +21,18 @@ import { Inject, Injectable, NgZone } from '@angular/core'; @@ -21,17 +21,18 @@ import { Inject, Injectable, NgZone } from '@angular/core';
21 import { WINDOW } from '@core/services/window.service'; 21 import { WINDOW } from '@core/services/window.service';
22 import { ExceptionData } from '@app/shared/models/error.models'; 22 import { ExceptionData } from '@app/shared/models/error.models';
23 import { 23 import {
  24 + baseUrl,
24 createLabelFromDatasource, 25 createLabelFromDatasource,
25 deepClone, 26 deepClone,
26 deleteNullProperties, 27 deleteNullProperties,
27 guid, 28 guid,
28 isDefined, 29 isDefined,
29 - isDefinedAndNotNull, 30 + isDefinedAndNotNull, isString,
30 isUndefined 31 isUndefined
31 } from '@core/utils'; 32 } from '@core/utils';
32 import { WindowMessage } from '@shared/models/window-message.model'; 33 import { WindowMessage } from '@shared/models/window-message.model';
33 import { TranslateService } from '@ngx-translate/core'; 34 import { TranslateService } from '@ngx-translate/core';
34 -import { customTranslationsPrefix } from '@app/shared/models/constants'; 35 +import { customTranslationsPrefix, i18nPrefix } from '@app/shared/models/constants';
35 import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models'; 36 import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models';
36 import { EntityType } from '@shared/models/entity-type.models'; 37 import { EntityType } from '@shared/models/entity-type.models';
37 import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models'; 38 import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models';
@@ -42,6 +43,8 @@ import jsonSchemaDefaults from 'json-schema-defaults'; @@ -42,6 +43,8 @@ import jsonSchemaDefaults from 'json-schema-defaults';
42 import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints'; 43 import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints';
43 import { Observable, of, ReplaySubject } from 'rxjs'; 44 import { Observable, of, ReplaySubject } from 'rxjs';
44 45
  46 +const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g');
  47 +
45 const predefinedFunctions: { [func: string]: string } = { 48 const predefinedFunctions: { [func: string]: string } = {
46 Sin: 'return Math.round(1000*Math.sin(time/5000));', 49 Sin: 'return Math.round(1000*Math.sin(time/5000));',
47 Cos: 'return Math.round(1000*Math.cos(time/5000));', 50 Cos: 'return Math.round(1000*Math.cos(time/5000));',
@@ -107,8 +110,8 @@ export class UtilsService { @@ -107,8 +110,8 @@ export class UtilsService {
107 materialIcons: Array<string> = []; 110 materialIcons: Array<string> = [];
108 111
109 constructor(@Inject(WINDOW) private window: Window, 112 constructor(@Inject(WINDOW) private window: Window,
110 - private zone: NgZone,  
111 - private translate: TranslateService) { 113 + private zone: NgZone,
  114 + private translate: TranslateService) {
112 let frame: Element = null; 115 let frame: Element = null;
113 try { 116 try {
114 frame = window.frameElement; 117 frame = window.frameElement;
@@ -221,8 +224,31 @@ export class UtilsService { @@ -221,8 +224,31 @@ export class UtilsService {
221 } 224 }
222 225
223 public customTranslation(translationValue: string, defaultValue: string): string { 226 public customTranslation(translationValue: string, defaultValue: string): string {
  227 + if (translationValue && isString(translationValue)) {
  228 + if (translationValue.includes(`{${i18nPrefix}`)) {
  229 + const matches = translationValue.match(i18nRegExp);
  230 + let result = translationValue;
  231 + for (const match of matches) {
  232 + const translationId = match.substring(6, match.length - 1);
  233 + result = result.replace(match, this.doTranslate(translationId, match));
  234 + }
  235 + return result;
  236 + } else {
  237 + return this.doTranslate(translationValue, defaultValue, customTranslationsPrefix);
  238 + }
  239 + } else {
  240 + return translationValue;
  241 + }
  242 + }
  243 +
  244 + private doTranslate(translationValue: string, defaultValue: string, prefix?: string): string {
224 let result: string; 245 let result: string;
225 - const translationId = customTranslationsPrefix + translationValue; 246 + let translationId;
  247 + if (prefix) {
  248 + translationId = prefix + translationValue;
  249 + } else {
  250 + translationId = translationValue;
  251 + }
226 const translation = this.translate.instant(translationId); 252 const translation = this.translate.instant(translationId);
227 if (translation !== translationId) { 253 if (translation !== translationId) {
228 result = translation + ''; 254 result = translation + '';
@@ -384,7 +410,7 @@ export class UtilsService { @@ -384,7 +410,7 @@ export class UtilsService {
384 } 410 }
385 411
386 public updateQueryParam(name: string, value: string | null) { 412 public updateQueryParam(name: string, value: string | null) {
387 - const baseUrl = [this.window.location.protocol, '//', this.window.location.host, this.window.location.pathname].join(''); 413 + const baseUrlPart = [baseUrl(), this.window.location.pathname].join('');
388 const urlQueryString = this.window.location.search; 414 const urlQueryString = this.window.location.search;
389 let newParam = ''; 415 let newParam = '';
390 let params = ''; 416 let params = '';
@@ -404,7 +430,11 @@ export class UtilsService { @@ -404,7 +430,11 @@ export class UtilsService {
404 } else if (newParam) { 430 } else if (newParam) {
405 params = '?' + newParam; 431 params = '?' + newParam;
406 } 432 }
407 - this.window.history.replaceState({}, '', baseUrl + params); 433 + this.window.history.replaceState({}, '', baseUrlPart + params);
  434 + }
  435 +
  436 + public baseUrl(): string {
  437 + return baseUrl();
408 } 438 }
409 439
410 public deepClone<T>(target: T, ignoreFields?: string[]): T { 440 public deepClone<T>(target: T, ignoreFields?: string[]): T {
@@ -385,6 +385,15 @@ export function padValue(val: any, dec: number): string { @@ -385,6 +385,15 @@ export function padValue(val: any, dec: number): string {
385 return strVal; 385 return strVal;
386 } 386 }
387 387
  388 +export function baseUrl(): string {
  389 + let url = window.location.protocol + '//' + window.location.hostname;
  390 + const port = window.location.port;
  391 + if (port && port.length > 0 && port !== '80' && port !== '443') {
  392 + url += ':' + port;
  393 + }
  394 + return url;
  395 +}
  396 +
388 export function sortObjectKeys<T>(obj: T): T { 397 export function sortObjectKeys<T>(obj: T): T {
389 return Object.keys(obj).sort().reduce((acc, key) => { 398 return Object.keys(obj).sort().reduce((acc, key) => {
390 acc[key] = obj[key]; 399 acc[key] = obj[key];
@@ -14,12 +14,12 @@ @@ -14,12 +14,12 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; 17 +import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
18 import { EntityId } from '@shared/models/id/entity-id'; 18 import { EntityId } from '@shared/models/id/entity-id';
19 import { DeviceService } from '@core/http/device.service'; 19 import { DeviceService } from '@core/http/device.service';
20 import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; 20 import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models';
21 import { isDefinedAndNotNull, isEqual } from '@core/utils'; 21 import { isDefinedAndNotNull, isEqual } from '@core/utils';
22 -import { BehaviorSubject, Subject } from 'rxjs'; 22 +import { BehaviorSubject, Subject, Subscription } from 'rxjs';
23 import { distinctUntilChanged, filter, mergeMap, tap } from 'rxjs/operators'; 23 import { distinctUntilChanged, filter, mergeMap, tap } from 'rxjs/operators';
24 import { EntityType } from '@shared/models/entity-type.models'; 24 import { EntityType } from '@shared/models/entity-type.models';
25 import { ActionNotificationShow } from '@core/notification/notification.actions'; 25 import { ActionNotificationShow } from '@core/notification/notification.actions';
@@ -32,12 +32,10 @@ import { TranslateService } from '@ngx-translate/core'; @@ -32,12 +32,10 @@ import { TranslateService } from '@ngx-translate/core';
32 templateUrl: './copy-device-credentials.component.html', 32 templateUrl: './copy-device-credentials.component.html',
33 styleUrls: [] 33 styleUrls: []
34 }) 34 })
35 -export class CopyDeviceCredentialsComponent implements OnDestroy { 35 +export class CopyDeviceCredentialsComponent implements OnInit, OnDestroy {
36 36
37 private deviceId$ = new BehaviorSubject<EntityId>(null); 37 private deviceId$ = new BehaviorSubject<EntityId>(null);
38 38
39 - private credentials$ = new Subject<DeviceCredentials>();  
40 -  
41 private tooltipMessage: string; 39 private tooltipMessage: string;
42 40
43 public hideButton = true; 41 public hideButton = true;
@@ -56,13 +54,14 @@ export class CopyDeviceCredentialsComponent implements OnDestroy { @@ -56,13 +54,14 @@ export class CopyDeviceCredentialsComponent implements OnDestroy {
56 @Input() disabled: boolean; 54 @Input() disabled: boolean;
57 55
58 @Input() 56 @Input()
59 - set credentials(credential: DeviceCredentials) {  
60 - this.credentials$.next(credential);  
61 - } 57 + credentials$: Subject<DeviceCredentials>;
  58 +
  59 + private credentialsSubscription: Subscription = null;
62 60
63 constructor(private store: Store<AppState>, 61 constructor(private store: Store<AppState>,
64 private translate: TranslateService, 62 private translate: TranslateService,
65 - private deviceService: DeviceService 63 + private deviceService: DeviceService,
  64 + private cd: ChangeDetectorRef
66 ) { 65 ) {
67 this.deviceId$.pipe( 66 this.deviceId$.pipe(
68 filter(device => isDefinedAndNotNull(device) && device.entityType === EntityType.DEVICE), 67 filter(device => isDefinedAndNotNull(device) && device.entityType === EntityType.DEVICE),
@@ -73,19 +72,23 @@ export class CopyDeviceCredentialsComponent implements OnDestroy { @@ -73,19 +72,23 @@ export class CopyDeviceCredentialsComponent implements OnDestroy {
73 this.processingValue(deviceCredentials); 72 this.processingValue(deviceCredentials);
74 this.loading = false; 73 this.loading = false;
75 }); 74 });
  75 + }
76 76
77 - this.credentials$.pipe( 77 + ngOnInit(): void {
  78 + this.credentialsSubscription = this.credentials$.pipe(
78 filter(credential => isDefinedAndNotNull(credential)), 79 filter(credential => isDefinedAndNotNull(credential)),
79 distinctUntilChanged((prev, curr) => isEqual(prev, curr)) 80 distinctUntilChanged((prev, curr) => isEqual(prev, curr))
80 ).subscribe(deviceCredentials => { 81 ).subscribe(deviceCredentials => {
81 - console.warn(deviceCredentials);  
82 this.processingValue(deviceCredentials); 82 this.processingValue(deviceCredentials);
  83 + this.cd.detectChanges();
83 }); 84 });
84 } 85 }
85 86
86 ngOnDestroy(): void { 87 ngOnDestroy(): void {
87 this.deviceId$.unsubscribe(); 88 this.deviceId$.unsubscribe();
88 - this.credentials$.unsubscribe(); 89 + if (this.credentialsSubscription !== null) {
  90 + this.credentialsSubscription.unsubscribe();
  91 + }
89 } 92 }
90 93
91 private processingValue(credential: DeviceCredentials): void { 94 private processingValue(credential: DeviceCredentials): void {
@@ -125,4 +128,5 @@ export class CopyDeviceCredentialsComponent implements OnDestroy { @@ -125,4 +128,5 @@ export class CopyDeviceCredentialsComponent implements OnDestroy {
125 horizontalPosition: 'right' 128 horizontalPosition: 'right'
126 })); 129 }));
127 } 130 }
  131 +
128 } 132 }