Commit a583f9fa8e9cd123bc9852d3a5c0138928afea6f
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> | ... | ... |
application/src/main/data/json/demo/dashboards/raspberry_pi_gpio_demo_dashboard.json
deleted
100644 → 0
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 |
application/src/main/data/json/demo/dashboards/temperature___humidity_demo_dashboard.json
deleted
100644 → 0
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 | } | ... | ... |
application/src/main/data/json/demo/rule_chains/root_rule_chain.json
deleted
100644 → 0
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 |
application/src/main/data/json/demo/rule_chains/thermostat_alarms.json
deleted
100644 → 0
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)); | ... | ... |
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
... | ... | @@ -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()); | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
... | ... | @@ -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">— 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">— 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 <a | |
154 | - style="box-sizing: border-box; color: #999999; margin: 0px;" | |
155 | - href="mailto:${targetEmail}">${targetEmail}</a> 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 <a style="box-sizing: border-box; color: #999999; margin: 0px;" href="mailto:${targetEmail}">${targetEmail}</a> 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">— 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">— 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 <a | |
148 | - style="box-sizing: border-box; color: #999999; margin: 0px;" | |
149 | - href="mailto:${targetEmail}">${targetEmail}</a> 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 <a style="box-sizing: border-box; color: #999999; margin: 0px;" href="mailto:${targetEmail}">${targetEmail}</a> 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 ${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 ${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">— 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">— 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 <a | |
155 | - style="box-sizing: border-box; color: #999999; margin: 0px;" | |
156 | - href="mailto:${targetEmail}">${targetEmail}</a> 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 <a style="box-sizing: border-box; color: #999999; margin: 0px;" href="mailto:${targetEmail}">${targetEmail}</a> 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 | ... | ... |
... | ... | @@ -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 | } | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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}`; | ... | ... |
... | ... | @@ -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 | } | ... | ... |