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 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>application</artifactId>
... ...
... ... @@ -955,7 +955,7 @@
955 955 },
956 956 "methodName": "gateway_restart",
957 957 "methodParams": "{}",
958   - "buttonText": "gateway restart"
  958 + "buttonText": "GATEWAY RESTART"
959 959 },
960 960 "title": "New RPC Button",
961 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   -}
\ No newline at end of file
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   -}
\ No newline at end of file
... ... @@ -147,9 +147,9 @@
147 147 "name": "Add",
148 148 "icon": "add",
149 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 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 153 "customResources": [],
154 154 "id": "8ab5a518-67d2-b6a2-956d-81fd512294b2"
155 155 }
... ... @@ -167,9 +167,9 @@
167 167 "name": "Edit",
168 168 "icon": "edit",
169 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 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 173 "customResources": [],
174 174 "id": "7506576f-87ba-d3a0-88fb-e304d451776d"
175 175 },
... ... @@ -550,7 +550,7 @@
550 550 "type": "entity",
551 551 "dataKeys": [
552 552 {
553   - "name": "alarmTemperature",
  553 + "name": "temperatureAlarmFlag",
554 554 "type": "attribute",
555 555 "label": "High temperature alarm",
556 556 "color": "#4caf50",
... ... @@ -565,7 +565,7 @@
565 565 "_hash": 0.8725278440159361
566 566 },
567 567 {
568   - "name": "thresholdTemperature",
  568 + "name": "temperatureAlarmThreshold",
569 569 "type": "attribute",
570 570 "label": "High temperature threshold, °C",
571 571 "color": "#f44336",
... ... @@ -576,12 +576,12 @@
576 576 "isEditable": "editable",
577 577 "dataKeyHidden": false,
578 578 "step": 1,
579   - "disabledOnDataKey": "alarmTemperature"
  579 + "disabledOnDataKey": "temperatureAlarmFlag"
580 580 },
581 581 "_hash": 0.7316078472857874
582 582 },
583 583 {
584   - "name": "alarmHumidity",
  584 + "name": "humidityAlarmFlag",
585 585 "type": "attribute",
586 586 "label": "Low humidity alarm",
587 587 "color": "#ffc107",
... ... @@ -596,7 +596,7 @@
596 596 "_hash": 0.5339673667431057
597 597 },
598 598 {
599   - "name": "thresholdHumidity",
  599 + "name": "humidityAlarmThreshold",
600 600 "type": "attribute",
601 601 "label": "Low humidity threshold, %",
602 602 "color": "#607d8b",
... ... @@ -607,7 +607,7 @@
607 607 "isEditable": "editable",
608 608 "dataKeyHidden": false,
609 609 "step": 1,
610   - "disabledOnDataKey": "alarmHumidity"
  610 + "disabledOnDataKey": "humidityAlarmFlag"
611 611 },
612 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   -}
\ No newline at end of file
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   -}
\ No newline at end of file
... ... @@ -33,7 +33,7 @@
33 33 "templateCss": ".tb-toast {\n min-width: 0;\n font-size: 14px !important;\n}",
34 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 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 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 39 import org.thingsboard.server.common.data.DataConstants;
40 40 import org.thingsboard.server.common.data.Device;
41 41 import org.thingsboard.server.common.data.DeviceProfile;
  42 +import org.thingsboard.server.common.data.TenantProfile;
42 43 import org.thingsboard.server.common.data.alarm.Alarm;
43 44 import org.thingsboard.server.common.data.asset.Asset;
44 45 import org.thingsboard.server.common.data.id.DeviceId;
... ... @@ -511,13 +512,24 @@ class DefaultTbContext implements TbContext {
511 512 }
512 513
513 514 @Override
  515 + public void addTenantProfileListener(Consumer<TenantProfile> listener) {
  516 + mainCtx.getTenantProfileCache().addListener(getTenantId(), getSelfId(), listener);
  517 + }
  518 +
  519 + @Override
514 520 public void addDeviceProfileListeners(Consumer<DeviceProfile> profileListener, BiConsumer<DeviceId, DeviceProfile> deviceListener) {
515 521 mainCtx.getDeviceProfileCache().addListener(getTenantId(), getSelfId(), profileListener, deviceListener);
516 522 }
517 523
518 524 @Override
519   - public void removeProfileListener() {
  525 + public void removeListeners() {
520 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 535 private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
... ...
... ... @@ -26,7 +26,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
26 26 import org.springframework.web.bind.annotation.RestController;
27 27 import org.thingsboard.rule.engine.api.MailService;
28 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 30 import org.thingsboard.server.common.data.AdminSettings;
31 31 import org.thingsboard.server.common.data.UpdateMessage;
32 32 import org.thingsboard.server.common.data.exception.ThingsboardException;
... ...
... ... @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.asset.Asset;
33 33 import org.thingsboard.server.common.data.asset.AssetInfo;
34 34 import org.thingsboard.server.common.data.asset.AssetSearchQuery;
35 35 import org.thingsboard.server.common.data.audit.ActionType;
  36 +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
36 37 import org.thingsboard.server.common.data.exception.ThingsboardException;
37 38 import org.thingsboard.server.common.data.id.AssetId;
38 39 import org.thingsboard.server.common.data.id.CustomerId;
... ... @@ -51,6 +52,8 @@ import java.util.ArrayList;
51 52 import java.util.List;
52 53 import java.util.stream.Collectors;
53 54
  55 +import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
  56 +
54 57 @RestController
55 58 @TbCoreComponent
56 59 @RequestMapping("/api")
... ... @@ -89,6 +92,10 @@ public class AssetController extends BaseController {
89 92 @ResponseBody
90 93 public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException {
91 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 99 asset.setTenantId(getCurrentUser().getTenantId());
93 100
94 101 checkEntity(asset.getId(), asset, Resource.ASSET);
... ...
... ... @@ -45,6 +45,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
45 45 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
46 46 import org.thingsboard.server.common.data.DataConstants;
47 47 import org.thingsboard.server.common.data.EntityType;
  48 +import org.thingsboard.server.common.data.TenantProfile;
48 49 import org.thingsboard.server.common.data.audit.ActionType;
49 50 import org.thingsboard.server.common.data.exception.ThingsboardException;
50 51 import org.thingsboard.server.common.data.id.DeviceId;
... ... @@ -69,6 +70,7 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
69 70 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
70 71 import org.thingsboard.server.common.data.kv.StringDataEntry;
71 72 import org.thingsboard.server.common.data.kv.TsKvEntry;
  73 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
72 74 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
73 75 import org.thingsboard.server.dao.timeseries.TimeseriesService;
74 76 import org.thingsboard.server.queue.util.TbCoreComponent;
... ... @@ -93,6 +95,7 @@ import java.util.Map;
93 95 import java.util.Set;
94 96 import java.util.concurrent.ExecutorService;
95 97 import java.util.concurrent.Executors;
  98 +import java.util.concurrent.TimeUnit;
96 99 import java.util.stream.Collectors;
97 100
98 101 /**
... ... @@ -205,7 +208,7 @@ public class TelemetryController extends BaseController {
205 208 @RequestParam(name = "interval", defaultValue = "0") Long interval,
206 209 @RequestParam(name = "limit", defaultValue = "100") Integer limit,
207 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 212 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
210 213 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
211 214 (result, tenantId, entityId) -> {
... ... @@ -392,7 +395,7 @@ public class TelemetryController extends BaseController {
392 395 if (attributes.isEmpty()) {
393 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 399 if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) {
397 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 443 if (entries.isEmpty()) {
441 444 return getImmediateDeferredResult("No timeseries data found in request body!", HttpStatus.BAD_REQUEST);
442 445 }
443   - SecurityUser user = getCurrentUser();
444 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 453 @Override
447 454 public void onSuccess(@Nullable Void tmp) {
448 455 result.setResult(new ResponseEntity(HttpStatus.OK));
... ...
... ... @@ -147,6 +147,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
147 147 public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) {
148 148 ToUsageStatsServiceMsg statsMsg = msg.getValue();
149 149 TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB()));
  150 +
  151 + if (tenantProfileCache.get(tenantId) == null) {
  152 + return;
  153 + }
  154 +
150 155 TenantApiUsageState tenantState;
151 156 List<TsKvEntry> updatedEntries;
152 157 Map<ApiFeature, ApiUsageStateValue> result;
... ... @@ -166,7 +171,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
166 171 long newValue = tenantState.add(recordKey, kvProto.getValue());
167 172 updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue)));
168 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 175 apiFeatures.add(recordKey.getApiFeature());
171 176 }
172 177 result = tenantState.checkStateUpdatedDueToThreshold(apiFeatures);
... ... @@ -343,7 +348,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
343 348 long now = System.currentTimeMillis();
344 349 myTenantStates.values().forEach(state -> {
345 350 if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) {
  351 + TenantId tenantId = state.getTenantId();
346 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 362 } finally {
... ...
... ... @@ -18,9 +18,7 @@ package org.thingsboard.server.service.device;
18 18 import com.fasterxml.jackson.core.JsonProcessingException;
19 19 import com.fasterxml.jackson.databind.JsonNode;
20 20 import com.fasterxml.jackson.databind.node.ObjectNode;
21   -import com.google.common.util.concurrent.Futures;
22 21 import com.google.common.util.concurrent.ListenableFuture;
23   -import com.google.common.util.concurrent.MoreExecutors;
24 22 import lombok.extern.slf4j.Slf4j;
25 23 import org.apache.commons.lang.RandomStringUtils;
26 24 import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -114,6 +112,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
114 112 public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) {
115 113 String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey();
116 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 123 if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) {
119 124 throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name());
... ...
... ... @@ -82,7 +82,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
82 82
83 83 import java.util.Arrays;
84 84 import java.util.Collections;
85   -import java.util.LinkedHashMap;
  85 +import java.util.TreeMap;
86 86
87 87 @Service
88 88 @Profile("install")
... ... @@ -272,7 +272,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
272 272 thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
273 273 thermostatDeviceProfile.setDescription("Thermostat device profile");
274 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 277 DeviceProfileData deviceProfileData = new DeviceProfileData();
278 278 DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
... ... @@ -290,13 +290,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
290 290 AlarmCondition temperatureCondition = new AlarmCondition();
291 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 301 KeyFilter temperatureTimeseriesFilter = new KeyFilter();
302 302 temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
... ... @@ -305,13 +305,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
305 305 temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
306 306 FilterPredicateValue<Double> temperatureTimeseriesPredicateValue =
307 307 new FilterPredicateValue<>(25.0, null,
308   - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature"));
  308 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold"));
309 309 temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue);
310 310 temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate);
311   - temperatureCondition.setCondition(Arrays.asList(alarmTemperatureAttributeFilter, temperatureTimeseriesFilter));
  311 + temperatureCondition.setCondition(Arrays.asList(temperatureAlarmFlagAttributeFilter, temperatureTimeseriesFilter));
312 312 temperatureRule.setAlarmDetails("Current temperature = ${temperature}");
313 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 316 AlarmRule clearTemperatureRule = new AlarmRule();
317 317 AlarmCondition clearTemperatureCondition = new AlarmCondition();
... ... @@ -324,7 +324,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
324 324 clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL);
325 325 FilterPredicateValue<Double> clearTemperatureTimeseriesPredicateValue =
326 326 new FilterPredicateValue<>(25.0, null,
327   - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature"));
  327 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold"));
328 328
329 329 clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue);
330 330 clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate);
... ... @@ -340,13 +340,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
340 340 AlarmCondition humidityCondition = new AlarmCondition();
341 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 351 KeyFilter humidityTimeseriesFilter = new KeyFilter();
352 352 humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity"));
... ... @@ -355,14 +355,14 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
355 355 humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
356 356 FilterPredicateValue<Double> humidityTimeseriesPredicateValue =
357 357 new FilterPredicateValue<>(60.0, null,
358   - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity"));
  358 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold"));
359 359 humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue);
360 360 humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate);
361   - humidityCondition.setCondition(Arrays.asList(alarmHumidityAttributeFilter, humidityTimeseriesFilter));
  361 + humidityCondition.setCondition(Arrays.asList(humidityAlarmFlagAttributeFilter, humidityTimeseriesFilter));
362 362
363 363 humidityRule.setCondition(humidityCondition);
364 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 367 AlarmRule clearHumidityRule = new AlarmRule();
368 368 AlarmCondition clearHumidityCondition = new AlarmCondition();
... ... @@ -375,7 +375,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
375 375 clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL);
376 376 FilterPredicateValue<Double> clearHumidityTimeseriesPredicateValue =
377 377 new FilterPredicateValue<>(60.0, null,
378   - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity"));
  378 + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold"));
379 379
380 380 clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue);
381 381 clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate);
... ... @@ -394,18 +394,18 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
394 394 attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE,
395 395 Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)),
396 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 402 attributesService.save(demoTenant.getId(), t2Id, DataConstants.SERVER_SCOPE,
403 403 Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.493801)),
404 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 410 installScripts.loadDashboards(demoTenant.getId(), null);
411 411 }
... ...
... ... @@ -210,26 +210,9 @@ public class InstallScripts {
210 210
211 211
212 212 public void loadDemoRuleChains(TenantId tenantId) throws Exception {
213   - Path ruleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, RULE_CHAINS_DIR);
214 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 216 } catch (Exception e) {
234 217 log.error("Unable to load dashboard from json", e);
235 218 throw new RuntimeException("Unable to load dashboard from json", e);
... ...
... ... @@ -250,6 +250,8 @@ public class DefaultMailService implements MailService {
250 250 helper.setText(body);
251 251 mailSender.send(helper.getMimeMessage());
252 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 297 try {
298 298 handleUsageStats(msg, callback);
299 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 301 callback.onFailure(e);
302 302 }
303 303 });
... ...
... ... @@ -18,9 +18,9 @@ package org.thingsboard.server.service.sms;
18 18 import org.springframework.stereotype.Component;
19 19 import org.thingsboard.rule.engine.api.sms.SmsSender;
20 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 24 import org.thingsboard.server.service.sms.aws.AwsSmsSender;
25 25 import org.thingsboard.server.service.sms.twilio.TwilioSmsSender;
26 26
... ...
... ... @@ -22,8 +22,8 @@ import org.springframework.stereotype.Service;
22 22 import org.thingsboard.rule.engine.api.SmsService;
23 23 import org.thingsboard.rule.engine.api.sms.SmsSender;
24 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 27 import org.thingsboard.server.common.data.AdminSettings;
28 28 import org.thingsboard.server.common.data.ApiUsageRecordKey;
29 29 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
... ... @@ -106,6 +106,8 @@ public class DefaultSmsService implements SmsService {
106 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 23 import com.amazonaws.services.sns.model.PublishRequest;
24 24 import lombok.extern.slf4j.Slf4j;
25 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 27 import org.thingsboard.rule.engine.api.sms.exception.SmsException;
28 28 import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
29 29 import org.thingsboard.server.service.sms.AbstractSmsSender;
... ...
... ... @@ -19,7 +19,7 @@ import com.twilio.http.TwilioRestClient;
19 19 import com.twilio.rest.api.v2010.account.Message;
20 20 import com.twilio.type.PhoneNumber;
21 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 23 import org.thingsboard.rule.engine.api.sms.exception.SmsException;
24 24 import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
25 25 import org.thingsboard.server.service.sms.AbstractSmsSender;
... ...
... ... @@ -521,27 +521,27 @@ public class DefaultDeviceStateService implements DeviceStateService {
521 521
522 522 private void save(DeviceId deviceId, String key, long value) {
523 523 if (persistToTelemetry) {
524   - tsSubService.saveAndNotify(
  524 + tsSubService.saveAndNotifyInternal(
525 525 TenantId.SYS_TENANT_ID, deviceId,
526 526 Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))),
527   - new AttributeSaveCallback(deviceId, key, value));
  527 + new AttributeSaveCallback<>(deviceId, key, value));
528 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 533 private void save(DeviceId deviceId, String key, boolean value) {
534 534 if (persistToTelemetry) {
535   - tsSubService.saveAndNotify(
  535 + tsSubService.saveAndNotifyInternal(
536 536 TenantId.SYS_TENANT_ID, deviceId,
537 537 Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))),
538   - new AttributeSaveCallback(deviceId, key, value));
  538 + new AttributeSaveCallback<>(deviceId, key, value));
539 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 545 private final DeviceId deviceId;
546 546 private final String key;
547 547 private final Object value;
... ... @@ -553,7 +553,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
553 553 }
554 554
555 555 @Override
556   - public void onSuccess(@Nullable Void result) {
  556 + public void onSuccess(@Nullable T result) {
557 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 25 import org.thingsboard.server.common.data.ApiUsageRecordKey;
26 26 import org.thingsboard.server.common.data.EntityType;
27 27 import org.thingsboard.server.common.data.EntityView;
  28 +import org.thingsboard.server.common.data.TenantProfile;
28 29 import org.thingsboard.server.common.data.id.EntityId;
29 30 import org.thingsboard.server.common.data.id.TenantId;
30 31 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
... ... @@ -34,13 +35,14 @@ import org.thingsboard.server.common.data.kv.DoubleDataEntry;
34 35 import org.thingsboard.server.common.data.kv.LongDataEntry;
35 36 import org.thingsboard.server.common.data.kv.StringDataEntry;
36 37 import org.thingsboard.server.common.data.kv.TsKvEntry;
  38 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
37 39 import org.thingsboard.server.common.msg.queue.ServiceType;
38 40 import org.thingsboard.server.common.msg.queue.TbCallback;
39 41 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
40 42 import org.thingsboard.server.dao.attributes.AttributesService;
41 43 import org.thingsboard.server.dao.entityview.EntityViewService;
  44 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
42 45 import org.thingsboard.server.dao.timeseries.TimeseriesService;
43   -import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
44 46 import org.thingsboard.server.gen.transport.TransportProtos;
45 47 import org.thingsboard.server.queue.discovery.PartitionService;
46 48 import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
... ... @@ -119,11 +121,12 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
119 121 @Override
120 122 public void saveAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) {
121 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 126 saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback<Integer>() {
124 127 @Override
125 128 public void onSuccess(Integer result) {
126   - if (result != null && result > 0) {
  129 + if (!sysTenant && result != null && result > 0) {
127 130 apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
128 131 }
129 132 callback.onSuccess(null);
... ... @@ -134,7 +137,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
134 137 callback.onFailure(t);
135 138 }
136 139 });
137   - } else{
  140 + } else {
138 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 reset.password.subject=Thingsboard - Password reset has been requested
5 5 password.was.reset.subject=Thingsboard - your account password has been reset
6 6 account.lockout.subject=Thingsboard - User account has been lockout
7   -api.usage.state=Thingsboard - Api Usage State for tenant has been updated
\ No newline at end of file
  7 +api.usage.state=Thingsboard - Account limits
\ No newline at end of file
... ...
... ... @@ -103,40 +103,30 @@
103 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 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 107 <tbody>
110 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 111 <tbody>
116 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 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 123 </td>
128 124 </tr>
129 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 127 </tr>
135 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 130 </tr>
141 131 </tbody>
142 132 </table>
... ... @@ -144,16 +134,10 @@
144 134 </tr>
145 135 </tbody>
146 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 138 <tbody>
150 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 141 </tr>
158 142 </tbody>
159 143 </table>
... ...
... ... @@ -103,34 +103,27 @@
103 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 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 107 <tbody>
110 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 111 <tbody>
116 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 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 123 </td>
128 124 </tr>
129 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 127 </tr>
135 128 </tbody>
136 129 </table>
... ... @@ -138,16 +131,10 @@
138 131 </tr>
139 132 </tbody>
140 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 135 <tbody>
144 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 138 </tr>
152 139 </tbody>
153 140 </table>
... ...
... ... @@ -103,41 +103,30 @@
103 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 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 107 <tbody>
110 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 111 <tbody>
116 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 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 123 </td>
129 124 </tr>
130 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 127 </tr>
136 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 130 </tr>
142 131 </tbody>
143 132 </table>
... ... @@ -145,16 +134,10 @@
145 134 </tr>
146 135 </tbody>
147 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 138 <tbody>
151 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 141 </tr>
159 142 </tbody>
160 143 </table>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -15,9 +15,6 @@
15 15 */
16 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 18 import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
22 19 import org.thingsboard.server.dao.device.provision.ProvisionRequest;
23 20 import org.thingsboard.server.dao.device.provision.ProvisionResponse;
... ...
... ... @@ -16,9 +16,12 @@
16 16 package org.thingsboard.server.dao.tenant;
17 17
18 18 import org.thingsboard.server.common.data.TenantProfile;
  19 +import org.thingsboard.server.common.data.id.EntityId;
19 20 import org.thingsboard.server.common.data.id.TenantId;
20 21 import org.thingsboard.server.common.data.id.TenantProfileId;
21 22
  23 +import java.util.function.Consumer;
  24 +
22 25 public interface TbTenantProfileCache {
23 26
24 27 TenantProfile get(TenantId tenantId);
... ... @@ -31,4 +34,8 @@ public interface TbTenantProfileCache {
31 34
32 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 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -18,9 +18,8 @@ package org.thingsboard.server.common.data.device.profile;
18 18 import lombok.Data;
19 19 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
20 20
21   -import java.util.LinkedHashMap;
22 21 import java.util.List;
23   -import java.util.Map;
  22 +import java.util.TreeMap;
24 23
25 24 @Data
26 25 public class DeviceProfileAlarm {
... ... @@ -28,7 +27,7 @@ public class DeviceProfileAlarm {
28 27 private String id;
29 28 private String alarmType;
30 29
31   - private LinkedHashMap<AlarmSeverity, AlarmRule> createRules;
  30 + private TreeMap<AlarmSeverity, AlarmRule> createRules;
32 31 private AlarmRule clearRule;
33 32
34 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 13 * See the License for the specific language governing permissions and
14 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 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 13 * See the License for the specific language governing permissions and
14 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 18 import com.fasterxml.jackson.annotation.JsonIgnore;
19 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 13 * See the License for the specific language governing permissions and
14 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 18 public enum SmsProviderType {
19 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 13 * See the License for the specific language governing permissions and
14 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 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 13 * See the License for the specific language governing permissions and
14 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 18 import lombok.Data;
19 19
... ...
... ... @@ -45,6 +45,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
45 45 private long maxEmails;
46 46 private long maxSms;
47 47
  48 + private int defaultStorageTtlDays;
  49 +
48 50 private double warnThreshold;
49 51
50 52 @Override
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>common</artifactId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -195,9 +195,11 @@ public class HashPartitionService implements PartitionService {
195 195 if (current.getServiceTypesList().contains(serviceType.name())) {
196 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 205 return result;
... ...
... ... @@ -22,7 +22,7 @@
22 22 <modelVersion>4.0.0</modelVersion>
23 23 <parent>
24 24 <groupId>org.thingsboard</groupId>
25   - <version>3.2.0-SNAPSHOT</version>
  25 + <version>3.2.1-SNAPSHOT</version>
26 26 <artifactId>common</artifactId>
27 27 </parent>
28 28 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
19 19 import org.eclipse.californium.core.CoapResource;
20 20 import org.eclipse.californium.core.CoapServer;
21 21 import org.eclipse.californium.core.network.CoapEndpoint;
  22 +import org.eclipse.californium.core.network.config.NetworkConfig;
22 23 import org.springframework.beans.factory.annotation.Autowired;
23 24 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
24 25 import org.springframework.stereotype.Service;
... ... @@ -46,7 +47,7 @@ public class CoapTransportService {
46 47 public void init() throws UnknownHostException {
47 48 log.info("Starting CoAP transport...");
48 49 log.info("Starting CoAP transport server");
49   - this.server = new CoapServer();
  50 + this.server = new CoapServer(NetworkConfig.createStandardWithoutFile());
50 51 createResources();
51 52 InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());
52 53 InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -179,6 +179,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
179 179 }
180 180 }
181 181 } else {
  182 + ctx.close();
182 183 throw new RuntimeException("Unsupported topic for provisioning requests!");
183 184 }
184 185 } catch (RuntimeException | AdaptorException e) {
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>dao</artifactId>
... ...
... ... @@ -78,6 +78,8 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
78 78 public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
79 79 public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
80 80 public static final String INCORRECT_ASSET_ID = "Incorrect assetId ";
  81 + public static final String TB_SERVICE_QUEUE = "TbServiceQueue";
  82 +
81 83 @Autowired
82 84 private AssetDao assetDao;
83 85
... ... @@ -329,8 +331,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
329 331 protected void validateCreate(TenantId tenantId, Asset asset) {
330 332 DefaultTenantProfileConfiguration profileConfiguration =
331 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 340 @Override
... ...
... ... @@ -122,5 +122,5 @@ public interface AssetRepository extends PagingAndSortingRepository<AssetEntity,
122 122 @Query("SELECT DISTINCT a.type FROM AssetEntity a WHERE a.tenantId = :tenantId")
123 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 39 import java.util.Optional;
40 40 import java.util.UUID;
41 41
  42 +import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
  43 +
42 44 /**
43 45 * Created by Valerii Sosliuk on 5/19/2017.
44 46 */
... ... @@ -179,6 +181,6 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
179 181
180 182 @Override
181 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 19 import org.springframework.stereotype.Service;
20 20 import org.thingsboard.server.common.data.Tenant;
21 21 import org.thingsboard.server.common.data.TenantProfile;
  22 +import org.thingsboard.server.common.data.id.EntityId;
22 23 import org.thingsboard.server.common.data.id.TenantId;
23 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 26 import java.util.concurrent.ConcurrentHashMap;
29 27 import java.util.concurrent.ConcurrentMap;
30 28 import java.util.concurrent.locks.Lock;
31 29 import java.util.concurrent.locks.ReentrantLock;
  30 +import java.util.function.Consumer;
32 31
33 32 @Service
34 33 @Slf4j
... ... @@ -40,6 +39,7 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache {
40 39
41 40 private final ConcurrentMap<TenantProfileId, TenantProfile> tenantProfilesMap = new ConcurrentHashMap<>();
42 41 private final ConcurrentMap<TenantId, TenantProfileId> tenantsMap = new ConcurrentHashMap<>();
  42 + private final ConcurrentMap<TenantId, ConcurrentMap<EntityId, Consumer<TenantProfile>>> profileListeners = new ConcurrentHashMap<>();
43 43
44 44 public DefaultTbTenantProfileCache(TenantProfileService tenantProfileService, TenantService tenantService) {
45 45 this.tenantProfileService = tenantProfileService;
... ... @@ -85,17 +85,56 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache {
85 85 public void put(TenantProfile profile) {
86 86 if (profile.getId() != null) {
87 87 tenantProfilesMap.put(profile.getId(), profile);
  88 + notifyTenantListeners(profile);
88 89 }
89 90 }
90 91
91 92 @Override
92 93 public void evict(TenantProfileId profileId) {
93 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 111 @Override
97 112 public void evict(TenantId tenantId) {
98 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 21
22 22 <parent>
23 23 <groupId>org.thingsboard</groupId>
24   - <version>3.2.0-SNAPSHOT</version>
  24 + <version>3.2.1-SNAPSHOT</version>
25 25 <artifactId>msa</artifactId>
26 26 </parent>
27 27 <groupId>org.thingsboard.msa</groupId>
... ...
1 1 {
2 2 "name": "thingsboard-js-executor",
3 3 "private": true,
4   - "version": "3.2.0",
  4 + "version": "3.2.1",
5 5 "description": "ThingsBoard JavaScript Executor Microservice",
6 6 "main": "server.js",
7 7 "bin": "server.js",
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>msa</artifactId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -27,7 +27,11 @@ fi
27 27 exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 &
28 28
29 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 35 fi
32 36
33 37 cassandra_data_dir=${CASSANDRA_DATA}
... ...
... ... @@ -27,5 +27,9 @@ fi
27 27 exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 &
28 28
29 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
\ No newline at end of file
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.msa</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.msa</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.msa</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
1 1 {
2 2 "name": "thingsboard-web-ui",
3 3 "private": true,
4   - "version": "3.2.0",
  4 + "version": "3.2.1",
5 5 "description": "ThingsBoard Web UI Microservice",
6 6 "main": "server.js",
7 7 "bin": "server.js",
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -19,11 +19,11 @@
19 19 <modelVersion>4.0.0</modelVersion>
20 20 <parent>
21 21 <groupId>org.thingsboard</groupId>
22   - <version>3.2.0-SNAPSHOT</version>
  22 + <version>3.2.1-SNAPSHOT</version>
23 23 <artifactId>thingsboard</artifactId>
24 24 </parent>
25 25 <artifactId>netty-mqtt</artifactId>
26   - <version>3.2.0-SNAPSHOT</version>
  26 + <version>3.2.1-SNAPSHOT</version>
27 27 <packaging>jar</packaging>
28 28
29 29 <name>Netty MQTT Client</name>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <groupId>org.thingsboard</groupId>
22 22 <artifactId>thingsboard</artifactId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <packaging>pom</packaging>
25 25
26 26 <name>Thingsboard</name>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>rest-client</artifactId>
... ...
... ... @@ -109,6 +109,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
109 109 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
110 110 import org.thingsboard.server.common.data.security.model.SecuritySettings;
111 111 import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
  112 +import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
112 113 import org.thingsboard.server.common.data.widget.WidgetType;
113 114 import org.thingsboard.server.common.data.widget.WidgetsBundle;
114 115
... ... @@ -218,7 +219,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
218 219 }
219 220
220 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 229 public Optional<SecuritySettings> getSecuritySettings() {
... ... @@ -1714,6 +1719,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
1714 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 1726 public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) {
1718 1727 restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId());
1719 1728 }
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>rule-engine</artifactId>
... ...
... ... @@ -22,7 +22,7 @@
22 22 <modelVersion>4.0.0</modelVersion>
23 23 <parent>
24 24 <groupId>org.thingsboard</groupId>
25   - <version>3.2.0-SNAPSHOT</version>
  25 + <version>3.2.1-SNAPSHOT</version>
26 26 <artifactId>rule-engine</artifactId>
27 27 </parent>
28 28 <groupId>org.thingsboard.rule-engine</groupId>
... ...
... ... @@ -15,7 +15,7 @@
15 15 */
16 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 19 import org.thingsboard.server.common.data.exception.ThingsboardException;
20 20 import org.thingsboard.server.common.data.id.TenantId;
21 21
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.ApiUsageRecordKey;
23 23 import org.thingsboard.server.common.data.Customer;
24 24 import org.thingsboard.server.common.data.Device;
25 25 import org.thingsboard.server.common.data.DeviceProfile;
  26 +import org.thingsboard.server.common.data.TenantProfile;
26 27 import org.thingsboard.server.common.data.alarm.Alarm;
27 28 import org.thingsboard.server.common.data.asset.Asset;
28 29 import org.thingsboard.server.common.data.id.DeviceId;
... ... @@ -237,7 +238,11 @@ public interface TbContext {
237 238
238 239 void clearRuleNodeStates();
239 240
  241 + void addTenantProfileListener(Consumer<TenantProfile> listener);
  242 +
240 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 15 */
16 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 20 public interface SmsSenderFactory {
21 21
... ...
... ... @@ -22,7 +22,7 @@
22 22 <modelVersion>4.0.0</modelVersion>
23 23 <parent>
24 24 <groupId>org.thingsboard</groupId>
25   - <version>3.2.0-SNAPSHOT</version>
  25 + <version>3.2.1-SNAPSHOT</version>
26 26 <artifactId>rule-engine</artifactId>
27 27 </parent>
28 28 <groupId>org.thingsboard.rule-engine</groupId>
... ...
... ... @@ -54,7 +54,8 @@ import java.util.concurrent.TimeUnit;
54 54 relationTypes = {"Alarm Created", "Alarm Updated", "Alarm Severity Updated", "Alarm Cleared", "Success", "Failure"},
55 55 configClazz = TbDeviceProfileNodeConfiguration.class,
56 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 59 uiResources = {"static/rulenode/rulenode-core-config.js"},
59 60 configDirective = "tbDeviceProfileConfig"
60 61 )
... ... @@ -119,7 +120,6 @@ public class TbDeviceProfileNode implements TbNode {
119 120 } else {
120 121 removeDeviceState(deviceId);
121 122 }
122   -
123 123 } else {
124 124 if (EntityType.DEVICE.equals(originatorType)) {
125 125 DeviceId deviceId = new DeviceId(msg.getOriginator().getId());
... ... @@ -149,7 +149,7 @@ public class TbDeviceProfileNode implements TbNode {
149 149
150 150 @Override
151 151 public void destroy() {
152   - ctx.removeProfileListener();
  152 + ctx.removeListeners();
153 153 deviceStates.clear();
154 154 }
155 155
... ...
... ... @@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.sms;
17 17
18 18 import lombok.Data;
19 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 22 @Data
23 23 public class TbSendSmsNodeConfiguration implements NodeConfiguration {
... ...
... ... @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.TbContext;
24 24 import org.thingsboard.rule.engine.api.TbNode;
25 25 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
26 26 import org.thingsboard.rule.engine.api.TbNodeException;
  27 +import org.thingsboard.server.common.data.TenantProfile;
27 28 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
28 29 import org.thingsboard.server.common.data.kv.KvEntry;
29 30 import org.thingsboard.server.common.data.kv.TsKvEntry;
... ... @@ -31,10 +32,12 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
31 32 import org.thingsboard.server.common.msg.TbMsg;
32 33 import org.thingsboard.server.common.msg.session.SessionMsgType;
33 34 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  35 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
34 36
35 37 import java.util.ArrayList;
36 38 import java.util.List;
37 39 import java.util.Map;
  40 +import java.util.concurrent.TimeUnit;
38 41
39 42 @Slf4j
40 43 @RuleNode(
... ... @@ -50,10 +53,20 @@ import java.util.Map;
50 53 public class TbMsgTimeseriesNode implements TbNode {
51 54
52 55 private TbMsgTimeseriesNodeConfiguration config;
  56 + private TbContext ctx;
  57 + private long tenantProfileDefaultStorageTtl;
53 58
54 59 @Override
55 60 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
56 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 72 @Override
... ... @@ -77,6 +90,9 @@ public class TbMsgTimeseriesNode implements TbNode {
77 90 }
78 91 String ttlValue = msg.getMetaData().getValue("TTL");
79 92 long ttl = !StringUtils.isEmpty(ttlValue) ? Long.parseLong(ttlValue) : config.getDefaultTTL();
  93 + if (ttl == 0L) {
  94 + ttl = tenantProfileDefaultStorageTtl;
  95 + }
80 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 112
97 113 @Override
98 114 public void destroy() {
  115 + ctx.removeListeners();
99 116 }
100 117
101 118 }
... ...
... ... @@ -23,17 +23,13 @@ import org.junit.runner.RunWith;
23 23 import org.mockito.AdditionalAnswers;
24 24 import org.mockito.Mock;
25 25 import org.mockito.Mockito;
26   -import org.mockito.invocation.InvocationOnMock;
27 26 import org.mockito.runners.MockitoJUnitRunner;
28   -import org.mockito.stubbing.Answer;
29   -import org.springframework.util.StringUtils;
30 27 import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
31 28 import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
32 29 import org.thingsboard.rule.engine.api.TbContext;
33 30 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
34 31 import org.thingsboard.rule.engine.api.TbNodeException;
35 32 import org.thingsboard.server.common.data.DeviceProfile;
36   -import org.thingsboard.server.common.data.alarm.Alarm;
37 33 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
38 34 import org.thingsboard.server.common.data.device.profile.AlarmCondition;
39 35 import org.thingsboard.server.common.data.device.profile.AlarmRule;
... ... @@ -52,11 +48,10 @@ import org.thingsboard.server.common.msg.TbMsg;
52 48 import org.thingsboard.server.common.msg.TbMsgDataType;
53 49 import org.thingsboard.server.common.msg.TbMsgMetaData;
54 50 import org.thingsboard.server.common.msg.session.SessionMsgType;
55   -import org.thingsboard.server.dao.alarm.AlarmService;
56 51 import org.thingsboard.server.dao.timeseries.TimeseriesService;
57 52
58 53 import java.util.Collections;
59   -import java.util.LinkedHashMap;
  54 +import java.util.TreeMap;
60 55 import java.util.UUID;
61 56
62 57 import static org.mockito.Mockito.verify;
... ... @@ -140,7 +135,7 @@ public class TbDeviceProfileNodeTest {
140 135 DeviceProfileAlarm dpa = new DeviceProfileAlarm();
141 136 dpa.setId("highTemperatureAlarmID");
142 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 140 KeyFilter lowTempFilter = new KeyFilter();
146 141 lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>tools</artifactId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>transport</artifactId>
... ...
1 1 {
2 2 "name": "thingsboard",
3   - "version": "3.2.0",
  3 + "version": "3.2.1",
4 4 "scripts": {
5 5 "ng": "ng",
6 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 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.2.0-SNAPSHOT</version>
  23 + <version>3.2.1-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard</groupId>
... ...
... ... @@ -282,8 +282,7 @@ export class AuthService {
282 282 if (publicId) {
283 283 return this.publicLogin(publicId).pipe(
284 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 286 return this.procceedJwtTokenValidate();
288 287 }),
289 288 catchError((err) => {
... ... @@ -317,8 +316,7 @@ export class AuthService {
317 316 };
318 317 return this.http.post<LoginResponse>('/api/auth/login', loginRequest, defaultHttpOptions()).pipe(
319 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 320 return this.procceedJwtTokenValidate();
323 321 }
324 322 )
... ... @@ -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 441 let response: Observable<LoginResponse> = this.refreshTokenSubject;
444 442 if (this.refreshTokenSubject === null) {
445 443 this.refreshTokenSubject = new ReplaySubject<LoginResponse>(1);
... ... @@ -456,7 +454,11 @@ export class AuthService {
456 454 };
457 455 const refreshObservable = this.http.post<LoginResponse>('/api/auth/token', refreshTokenRequest, defaultHttpOptions());
458 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 462 this.refreshTokenSubject.next(loginResponse);
461 463 this.refreshTokenSubject.complete();
462 464 this.refreshTokenSubject = null;
... ... @@ -474,7 +476,7 @@ export class AuthService {
474 476 const subject = new ReplaySubject<void>();
475 477 if (!AuthService.isTokenValid('jwt_token')) {
476 478 if (doRefresh) {
477   - this.refreshJwtToken().subscribe(
  479 + this.refreshJwtToken(!doRefresh).subscribe(
478 480 () => {
479 481 subject.next();
480 482 subject.complete();
... ... @@ -505,8 +507,7 @@ export class AuthService {
505 507 this.notifyUnauthenticated();
506 508 }
507 509 } else {
508   - this.updateAndValidateToken(jwtToken, 'jwt_token', true);
509   - this.updateAndValidateToken(refreshToken, 'refresh_token', true);
  510 + this.updateAndValidateTokens(jwtToken, refreshToken, true);
510 511 if (notify) {
511 512 this.notifyUserLoaded(false);
512 513 this.loadUser(false).subscribe(
... ... @@ -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 534 public parsePublicId(): string {
529 535 const token = AuthService.getJwtToken();
530 536 if (token) {
... ...
... ... @@ -130,7 +130,7 @@ export class DashboardService {
130 130 const publicCustomerId = publicCustomers[0].customerId.id;
131 131 let url = this.window.location.protocol + '//' + this.window.location.hostname;
132 132 const port = this.window.location.port;
133   - if (port !== '80' && port !== '443') {
  133 + if (port && port.length > 0 && port !== '80' && port !== '443') {
134 134 url += ':' + port;
135 135 }
136 136 url += `/dashboard/${dashboard.id.id}?publicId=${publicCustomerId}`;
... ...
... ... @@ -23,6 +23,7 @@ export interface MenuSection extends HasUUID{
23 23 type: MenuSectionType;
24 24 path: string;
25 25 icon: string;
  26 + notExact?: boolean;
26 27 isMdiIcon?: boolean;
27 28 height?: string;
28 29 pages?: Array<MenuSection>;
... ...
... ... @@ -288,6 +288,14 @@ export class MenuService {
288 288 type: 'link',
289 289 path: '/auditLogs',
290 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 301 return sections;
... ... @@ -374,6 +382,11 @@ export class MenuService {
374 382 name: 'audit-log.audit-logs',
375 383 icon: 'track_changes',
376 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 21 import { WINDOW } from '@core/services/window.service';
22 22 import { ExceptionData } from '@app/shared/models/error.models';
23 23 import {
  24 + baseUrl,
24 25 createLabelFromDatasource,
25 26 deepClone,
26 27 deleteNullProperties,
27 28 guid,
28 29 isDefined,
29   - isDefinedAndNotNull,
  30 + isDefinedAndNotNull, isString,
30 31 isUndefined
31 32 } from '@core/utils';
32 33 import { WindowMessage } from '@shared/models/window-message.model';
33 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 36 import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models';
36 37 import { EntityType } from '@shared/models/entity-type.models';
37 38 import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models';
... ... @@ -42,6 +43,8 @@ import jsonSchemaDefaults from 'json-schema-defaults';
42 43 import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints';
43 44 import { Observable, of, ReplaySubject } from 'rxjs';
44 45
  46 +const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g');
  47 +
45 48 const predefinedFunctions: { [func: string]: string } = {
46 49 Sin: 'return Math.round(1000*Math.sin(time/5000));',
47 50 Cos: 'return Math.round(1000*Math.cos(time/5000));',
... ... @@ -107,8 +110,8 @@ export class UtilsService {
107 110 materialIcons: Array<string> = [];
108 111
109 112 constructor(@Inject(WINDOW) private window: Window,
110   - private zone: NgZone,
111   - private translate: TranslateService) {
  113 + private zone: NgZone,
  114 + private translate: TranslateService) {
112 115 let frame: Element = null;
113 116 try {
114 117 frame = window.frameElement;
... ... @@ -221,8 +224,31 @@ export class UtilsService {
221 224 }
222 225
223 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 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 252 const translation = this.translate.instant(translationId);
227 253 if (translation !== translationId) {
228 254 result = translation + '';
... ... @@ -384,7 +410,7 @@ export class UtilsService {
384 410 }
385 411
386 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 414 const urlQueryString = this.window.location.search;
389 415 let newParam = '';
390 416 let params = '';
... ... @@ -404,7 +430,11 @@ export class UtilsService {
404 430 } else if (newParam) {
405 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 440 public deepClone<T>(target: T, ignoreFields?: string[]): T {
... ...
... ... @@ -385,6 +385,15 @@ export function padValue(val: any, dec: number): string {
385 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 397 export function sortObjectKeys<T>(obj: T): T {
389 398 return Object.keys(obj).sort().reduce((acc, key) => {
390 399 acc[key] = obj[key];
... ...
... ... @@ -14,12 +14,12 @@
14 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 18 import { EntityId } from '@shared/models/id/entity-id';
19 19 import { DeviceService } from '@core/http/device.service';
20 20 import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models';
21 21 import { isDefinedAndNotNull, isEqual } from '@core/utils';
22   -import { BehaviorSubject, Subject } from 'rxjs';
  22 +import { BehaviorSubject, Subject, Subscription } from 'rxjs';
23 23 import { distinctUntilChanged, filter, mergeMap, tap } from 'rxjs/operators';
24 24 import { EntityType } from '@shared/models/entity-type.models';
25 25 import { ActionNotificationShow } from '@core/notification/notification.actions';
... ... @@ -32,12 +32,10 @@ import { TranslateService } from '@ngx-translate/core';
32 32 templateUrl: './copy-device-credentials.component.html',
33 33 styleUrls: []
34 34 })
35   -export class CopyDeviceCredentialsComponent implements OnDestroy {
  35 +export class CopyDeviceCredentialsComponent implements OnInit, OnDestroy {
36 36
37 37 private deviceId$ = new BehaviorSubject<EntityId>(null);
38 38
39   - private credentials$ = new Subject<DeviceCredentials>();
40   -
41 39 private tooltipMessage: string;
42 40
43 41 public hideButton = true;
... ... @@ -56,13 +54,14 @@ export class CopyDeviceCredentialsComponent implements OnDestroy {
56 54 @Input() disabled: boolean;
57 55
58 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 61 constructor(private store: Store<AppState>,
64 62 private translate: TranslateService,
65   - private deviceService: DeviceService
  63 + private deviceService: DeviceService,
  64 + private cd: ChangeDetectorRef
66 65 ) {
67 66 this.deviceId$.pipe(
68 67 filter(device => isDefinedAndNotNull(device) && device.entityType === EntityType.DEVICE),
... ... @@ -73,19 +72,23 @@ export class CopyDeviceCredentialsComponent implements OnDestroy {
73 72 this.processingValue(deviceCredentials);
74 73 this.loading = false;
75 74 });
  75 + }
76 76
77   - this.credentials$.pipe(
  77 + ngOnInit(): void {
  78 + this.credentialsSubscription = this.credentials$.pipe(
78 79 filter(credential => isDefinedAndNotNull(credential)),
79 80 distinctUntilChanged((prev, curr) => isEqual(prev, curr))
80 81 ).subscribe(deviceCredentials => {
81   - console.warn(deviceCredentials);
82 82 this.processingValue(deviceCredentials);
  83 + this.cd.detectChanges();
83 84 });
84 85 }
85 86
86 87 ngOnDestroy(): void {
87 88 this.deviceId$.unsubscribe();
88   - this.credentials$.unsubscribe();
  89 + if (this.credentialsSubscription !== null) {
  90 + this.credentialsSubscription.unsubscribe();
  91 + }
89 92 }
90 93
91 94 private processingValue(credential: DeviceCredentials): void {
... ... @@ -125,4 +128,5 @@ export class CopyDeviceCredentialsComponent implements OnDestroy {
125 128 horizontalPosition: 'right'
126 129 }));
127 130 }
  131 +
128 132 }
... ...