Commit d79d00aa87a676a907e108cf7990095f126a21c5
Merge remote-tracking branch 'upstream/master' into develop/3.3-edge
Showing
48 changed files
with
2157 additions
and
1574 deletions
Too many changes to show.
To preserve performance only 48 of 683 files are displayed.
... | ... | @@ -150,10 +150,6 @@ |
150 | 150 | <artifactId>spring-boot-starter-websocket</artifactId> |
151 | 151 | </dependency> |
152 | 152 | <dependency> |
153 | - <groupId>org.springframework.cloud</groupId> | |
154 | - <artifactId>spring-cloud-starter-oauth2</artifactId> | |
155 | - </dependency> | |
156 | - <dependency> | |
157 | 153 | <groupId>org.springframework.security</groupId> |
158 | 154 | <artifactId>spring-security-oauth2-client</artifactId> |
159 | 155 | </dependency> |
... | ... | @@ -202,6 +198,14 @@ |
202 | 198 | <artifactId>javax.mail</artifactId> |
203 | 199 | </dependency> |
204 | 200 | <dependency> |
201 | + <groupId>com.twilio.sdk</groupId> | |
202 | + <artifactId>twilio</artifactId> | |
203 | + </dependency> | |
204 | + <dependency> | |
205 | + <groupId>com.amazonaws</groupId> | |
206 | + <artifactId>aws-java-sdk-sns</artifactId> | |
207 | + </dependency> | |
208 | + <dependency> | |
205 | 209 | <groupId>org.apache.curator</groupId> |
206 | 210 | <artifactId>curator-recipes</artifactId> |
207 | 211 | </dependency> | ... | ... |
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 | }, |
... | ... | @@ -219,8 +219,8 @@ |
219 | 219 | "defaultPageSize": 10, |
220 | 220 | "defaultSortOrder": "-createdTime", |
221 | 221 | "enableSelectColumnDisplay": false, |
222 | - "enableStatusFilter": true, | |
223 | - "alarmsTitle": "Alarms" | |
222 | + "alarmsTitle": "Alarms", | |
223 | + "enableFilter": true | |
224 | 224 | }, |
225 | 225 | "title": "New Alarms table", |
226 | 226 | "dropShadow": true, |
... | ... | @@ -234,6 +234,9 @@ |
234 | 234 | "showLegend": false, |
235 | 235 | "alarmSource": { |
236 | 236 | "type": "entity", |
237 | + "name": "alarms", | |
238 | + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e", | |
239 | + "filterId": null, | |
237 | 240 | "dataKeys": [ |
238 | 241 | { |
239 | 242 | "name": "createdTime", |
... | ... | @@ -275,9 +278,7 @@ |
275 | 278 | "settings": {}, |
276 | 279 | "_hash": 0.7977920750136249 |
277 | 280 | } |
278 | - ], | |
279 | - "entityAliasId": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813", | |
280 | - "name": "alarms" | |
281 | + ] | |
281 | 282 | }, |
282 | 283 | "alarmSearchStatus": "ANY", |
283 | 284 | "alarmsPollingInterval": 5, |
... | ... | @@ -549,7 +550,7 @@ |
549 | 550 | "type": "entity", |
550 | 551 | "dataKeys": [ |
551 | 552 | { |
552 | - "name": "alarmTemperature", | |
553 | + "name": "temperatureAlarmFlag", | |
553 | 554 | "type": "attribute", |
554 | 555 | "label": "High temperature alarm", |
555 | 556 | "color": "#4caf50", |
... | ... | @@ -564,7 +565,7 @@ |
564 | 565 | "_hash": 0.8725278440159361 |
565 | 566 | }, |
566 | 567 | { |
567 | - "name": "thresholdTemperature", | |
568 | + "name": "temperatureAlarmThreshold", | |
568 | 569 | "type": "attribute", |
569 | 570 | "label": "High temperature threshold, °C", |
570 | 571 | "color": "#f44336", |
... | ... | @@ -575,12 +576,12 @@ |
575 | 576 | "isEditable": "editable", |
576 | 577 | "dataKeyHidden": false, |
577 | 578 | "step": 1, |
578 | - "disabledOnDataKey": "alarmTemperature" | |
579 | + "disabledOnDataKey": "temperatureAlarmFlag" | |
579 | 580 | }, |
580 | 581 | "_hash": 0.7316078472857874 |
581 | 582 | }, |
582 | 583 | { |
583 | - "name": "alarmHumidity", | |
584 | + "name": "humidityAlarmFlag", | |
584 | 585 | "type": "attribute", |
585 | 586 | "label": "Low humidity alarm", |
586 | 587 | "color": "#ffc107", |
... | ... | @@ -595,7 +596,7 @@ |
595 | 596 | "_hash": 0.5339673667431057 |
596 | 597 | }, |
597 | 598 | { |
598 | - "name": "thresholdHumidity", | |
599 | + "name": "humidityAlarmThreshold", | |
599 | 600 | "type": "attribute", |
600 | 601 | "label": "Low humidity threshold, %", |
601 | 602 | "color": "#607d8b", |
... | ... | @@ -606,7 +607,7 @@ |
606 | 607 | "isEditable": "editable", |
607 | 608 | "dataKeyHidden": false, |
608 | 609 | "step": 1, |
609 | - "disabledOnDataKey": "alarmHumidity" | |
610 | + "disabledOnDataKey": "humidityAlarmFlag" | |
610 | 611 | }, |
611 | 612 | "_hash": 0.2687091190358901 |
612 | 613 | } |
... | ... | @@ -1031,7 +1032,8 @@ |
1031 | 1032 | "markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;", |
1032 | 1033 | "useLabelFunction": true, |
1033 | 1034 | "provider": "openstreet-map", |
1034 | - "draggableMarker": true | |
1035 | + "draggableMarker": true, | |
1036 | + "editablePolygon": true | |
1035 | 1037 | }, |
1036 | 1038 | "title": "New Markers Placement - OpenStreetMap", |
1037 | 1039 | "dropShadow": true, |
... | ... | @@ -1062,61 +1064,6 @@ |
1062 | 1064 | "displayTimewindow": true |
1063 | 1065 | }, |
1064 | 1066 | "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf" |
1065 | - }, | |
1066 | - "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": { | |
1067 | - "isSystemType": true, | |
1068 | - "bundleAlias": "input_widgets", | |
1069 | - "typeAlias": "update_double_timeseries", | |
1070 | - "type": "latest", | |
1071 | - "title": "New widget", | |
1072 | - "sizeX": 7.5, | |
1073 | - "sizeY": 3, | |
1074 | - "config": { | |
1075 | - "datasources": [ | |
1076 | - { | |
1077 | - "type": "entity", | |
1078 | - "name": null, | |
1079 | - "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547", | |
1080 | - "dataKeys": [ | |
1081 | - { | |
1082 | - "name": "temperature", | |
1083 | - "type": "timeseries", | |
1084 | - "label": "temperature", | |
1085 | - "color": "#2196f3", | |
1086 | - "settings": {}, | |
1087 | - "_hash": 0.4164505192982848 | |
1088 | - } | |
1089 | - ] | |
1090 | - } | |
1091 | - ], | |
1092 | - "timewindow": { | |
1093 | - "realtime": { | |
1094 | - "timewindowMs": 60000 | |
1095 | - } | |
1096 | - }, | |
1097 | - "showTitle": true, | |
1098 | - "backgroundColor": "#fff", | |
1099 | - "color": "rgba(0, 0, 0, 0.87)", | |
1100 | - "padding": "8px", | |
1101 | - "settings": { | |
1102 | - "showResultMessage": true, | |
1103 | - "showLabel": true | |
1104 | - }, | |
1105 | - "title": "New Update double timeseries", | |
1106 | - "dropShadow": true, | |
1107 | - "enableFullscreen": false, | |
1108 | - "widgetStyle": {}, | |
1109 | - "titleStyle": { | |
1110 | - "fontSize": "16px", | |
1111 | - "fontWeight": 400 | |
1112 | - }, | |
1113 | - "useDashboardTimewindow": true, | |
1114 | - "showLegend": false, | |
1115 | - "actions": {} | |
1116 | - }, | |
1117 | - "row": 0, | |
1118 | - "col": 0, | |
1119 | - "id": "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3" | |
1120 | 1067 | } |
1121 | 1068 | }, |
1122 | 1069 | "states": { |
... | ... | @@ -1215,12 +1162,6 @@ |
1215 | 1162 | "sizeY": 6, |
1216 | 1163 | "row": 6, |
1217 | 1164 | "col": 0 |
1218 | - }, | |
1219 | - "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": { | |
1220 | - "sizeX": 7.5, | |
1221 | - "sizeY": 3, | |
1222 | - "row": 12, | |
1223 | - "col": 0 | |
1224 | 1165 | } |
1225 | 1166 | }, |
1226 | 1167 | "gridSettings": { |
... | ... | @@ -1257,16 +1198,6 @@ |
1257 | 1198 | "stateEntityParamName": null, |
1258 | 1199 | "defaultStateEntity": null |
1259 | 1200 | } |
1260 | - }, | |
1261 | - "ce27a9d0-93bf-b7a4-054d-d0369a8cf813": { | |
1262 | - "id": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813", | |
1263 | - "alias": "Thermostat-alarm", | |
1264 | - "filter": { | |
1265 | - "type": "entityName", | |
1266 | - "resolveMultiple": false, | |
1267 | - "entityType": "ASSET", | |
1268 | - "entityNameFilter": "Thermostat Alarms" | |
1269 | - } | |
1270 | 1201 | } |
1271 | 1202 | }, |
1272 | 1203 | "timewindow": { |
... | ... | @@ -1301,7 +1232,8 @@ |
1301 | 1232 | "showDashboardTimewindow": true, |
1302 | 1233 | "showDashboardExport": true, |
1303 | 1234 | "toolbarAlwaysOpen": true |
1304 | - } | |
1235 | + }, | |
1236 | + "filters": {} | |
1305 | 1237 | }, |
1306 | 1238 | "name": "Thermostats" |
1307 | 1239 | } |
\ No newline at end of file | ... | ... |
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 | - "type": "CORE", | |
6 | - "firstRuleNodeId": null, | |
7 | - "root": true, | |
8 | - "debugMode": false, | |
9 | - "configuration": null | |
10 | - }, | |
11 | - "metadata": { | |
12 | - "firstNodeIndex": 3, | |
13 | - "nodes": [ | |
14 | - { | |
15 | - "additionalInfo": { | |
16 | - "layoutX": 1069, | |
17 | - "layoutY": 267 | |
18 | - }, | |
19 | - "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode", | |
20 | - "name": "Is Thermostat?", | |
21 | - "debugMode": false, | |
22 | - "configuration": { | |
23 | - "jsScript": "return msg.id.entityType === \"DEVICE\" && msg.type === \"thermostat\";" | |
24 | - } | |
25 | - }, | |
26 | - { | |
27 | - "additionalInfo": { | |
28 | - "layoutX": 824, | |
29 | - "layoutY": 156 | |
30 | - }, | |
31 | - "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", | |
32 | - "name": "Save Timeseries", | |
33 | - "debugMode": false, | |
34 | - "configuration": { | |
35 | - "defaultTTL": 0 | |
36 | - } | |
37 | - }, | |
38 | - { | |
39 | - "additionalInfo": { | |
40 | - "layoutX": 825, | |
41 | - "layoutY": 52 | |
42 | - }, | |
43 | - "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", | |
44 | - "name": "Save Client Attributes", | |
45 | - "debugMode": false, | |
46 | - "configuration": { | |
47 | - "scope": "CLIENT_SCOPE", | |
48 | - "notifyDevice": "false" | |
49 | - } | |
50 | - }, | |
51 | - { | |
52 | - "additionalInfo": { | |
53 | - "layoutX": 347, | |
54 | - "layoutY": 149 | |
55 | - }, | |
56 | - "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", | |
57 | - "name": "Message Type Switch", | |
58 | - "debugMode": false, | |
59 | - "configuration": { | |
60 | - "version": 0 | |
61 | - } | |
62 | - }, | |
63 | - { | |
64 | - "additionalInfo": { | |
65 | - "layoutX": 839, | |
66 | - "layoutY": 345 | |
67 | - }, | |
68 | - "type": "org.thingsboard.rule.engine.action.TbLogNode", | |
69 | - "name": "Log RPC from Device", | |
70 | - "debugMode": false, | |
71 | - "configuration": { | |
72 | - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" | |
73 | - } | |
74 | - }, | |
75 | - { | |
76 | - "additionalInfo": { | |
77 | - "layoutX": 832, | |
78 | - "layoutY": 407 | |
79 | - }, | |
80 | - "type": "org.thingsboard.rule.engine.action.TbLogNode", | |
81 | - "name": "Log Other", | |
82 | - "debugMode": false, | |
83 | - "configuration": { | |
84 | - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" | |
85 | - } | |
86 | - }, | |
87 | - { | |
88 | - "additionalInfo": { | |
89 | - "layoutX": 825, | |
90 | - "layoutY": 468 | |
91 | - }, | |
92 | - "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", | |
93 | - "name": "RPC Call Request", | |
94 | - "debugMode": false, | |
95 | - "configuration": { | |
96 | - "timeoutInSeconds": 60 | |
97 | - } | |
98 | - }, | |
99 | - { | |
100 | - "additionalInfo": { | |
101 | - "layoutX": 1069, | |
102 | - "layoutY": 90 | |
103 | - }, | |
104 | - "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode", | |
105 | - "name": "Is Thermostat?", | |
106 | - "debugMode": false, | |
107 | - "configuration": { | |
108 | - "jsScript": "return metadata[\"deviceType\"] === \"thermostat\";" | |
109 | - } | |
110 | - }, | |
111 | - { | |
112 | - "additionalInfo": { | |
113 | - "layoutX": 1090, | |
114 | - "layoutY": 360 | |
115 | - }, | |
116 | - "type": "org.thingsboard.rule.engine.action.TbCreateRelationNode", | |
117 | - "name": "Relate to Asset", | |
118 | - "debugMode": false, | |
119 | - "configuration": { | |
120 | - "direction": "FROM", | |
121 | - "relationType": "ToAlarmPropagationAsset", | |
122 | - "entityType": "ASSET", | |
123 | - "entityNamePattern": "Thermostat Alarms", | |
124 | - "entityTypePattern": "AlarmPropagationAsset", | |
125 | - "entityCacheExpiration": 300, | |
126 | - "createEntityIfNotExists": true, | |
127 | - "changeOriginatorToRelatedEntity": false, | |
128 | - "removeCurrentRelations": false | |
129 | - } | |
130 | - } | |
131 | - ], | |
132 | - "connections": [ | |
133 | - { | |
134 | - "fromIndex": 0, | |
135 | - "toIndex": 8, | |
136 | - "type": "True" | |
137 | - }, | |
138 | - { | |
139 | - "fromIndex": 1, | |
140 | - "toIndex": 7, | |
141 | - "type": "Success" | |
142 | - }, | |
143 | - { | |
144 | - "fromIndex": 3, | |
145 | - "toIndex": 5, | |
146 | - "type": "Other" | |
147 | - }, | |
148 | - { | |
149 | - "fromIndex": 3, | |
150 | - "toIndex": 2, | |
151 | - "type": "Post attributes" | |
152 | - }, | |
153 | - { | |
154 | - "fromIndex": 3, | |
155 | - "toIndex": 1, | |
156 | - "type": "Post telemetry" | |
157 | - }, | |
158 | - { | |
159 | - "fromIndex": 3, | |
160 | - "toIndex": 4, | |
161 | - "type": "RPC Request from Device" | |
162 | - }, | |
163 | - { | |
164 | - "fromIndex": 3, | |
165 | - "toIndex": 6, | |
166 | - "type": "RPC Request to Device" | |
167 | - }, | |
168 | - { | |
169 | - "fromIndex": 3, | |
170 | - "toIndex": 0, | |
171 | - "type": "Entity Created" | |
172 | - } | |
173 | - ], | |
174 | - "ruleChainConnections": [ | |
175 | - { | |
176 | - "fromIndex": 7, | |
177 | - "targetRuleChainId": { | |
178 | - "entityType": "RULE_CHAIN", | |
179 | - "id": "25e26570-89ed-11ea-a650-cd6e14e633bd" | |
180 | - }, | |
181 | - "additionalInfo": { | |
182 | - "layoutX": 1109, | |
183 | - "layoutY": 182, | |
184 | - "ruleChainNodeId": "rule-chain-node-10" | |
185 | - }, | |
186 | - "type": "True" | |
187 | - } | |
188 | - ] | |
189 | - } | |
190 | -} | |
\ 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": null, | |
4 | - "name": "Thermostat Alarms", | |
5 | - "type": "CORE", | |
6 | - "firstRuleNodeId": null, | |
7 | - "root": false, | |
8 | - "debugMode": false, | |
9 | - "configuration": null | |
10 | - }, | |
11 | - "metadata": { | |
12 | - "firstNodeIndex": 5, | |
13 | - "nodes": [ | |
14 | - { | |
15 | - "additionalInfo": { | |
16 | - "layoutX": 929, | |
17 | - "layoutY": 67 | |
18 | - }, | |
19 | - "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode", | |
20 | - "name": "Create Temp Alarm", | |
21 | - "debugMode": false, | |
22 | - "configuration": { | |
23 | - "alarmType": "High Temperature", | |
24 | - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.temperature;\nreturn details;", | |
25 | - "severity": "MAJOR", | |
26 | - "propagate": true, | |
27 | - "useMessageAlarmData": false, | |
28 | - "relationTypes": [ | |
29 | - "ToAlarmPropagationAsset" | |
30 | - ] | |
31 | - } | |
32 | - }, | |
33 | - { | |
34 | - "additionalInfo": { | |
35 | - "layoutX": 930, | |
36 | - "layoutY": 201 | |
37 | - }, | |
38 | - "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode", | |
39 | - "name": "Clear Temp Alarm", | |
40 | - "debugMode": false, | |
41 | - "configuration": { | |
42 | - "alarmType": "High Temperature", | |
43 | - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;" | |
44 | - } | |
45 | - }, | |
46 | - { | |
47 | - "additionalInfo": { | |
48 | - "layoutX": 930, | |
49 | - "layoutY": 131 | |
50 | - }, | |
51 | - "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode", | |
52 | - "name": "Create Humidity Alarm", | |
53 | - "debugMode": false, | |
54 | - "configuration": { | |
55 | - "alarmType": "Low Humidity", | |
56 | - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.humidity;\nreturn details;", | |
57 | - "severity": "MINOR", | |
58 | - "propagate": true, | |
59 | - "useMessageAlarmData": false, | |
60 | - "relationTypes": [ | |
61 | - "ToAlarmPropagationAsset" | |
62 | - ] | |
63 | - } | |
64 | - }, | |
65 | - { | |
66 | - "additionalInfo": { | |
67 | - "layoutX": 929, | |
68 | - "layoutY": 275 | |
69 | - }, | |
70 | - "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode", | |
71 | - "name": "Clear Humidity Alarm", | |
72 | - "debugMode": false, | |
73 | - "configuration": { | |
74 | - "alarmType": "Low Humidity", | |
75 | - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;" | |
76 | - } | |
77 | - }, | |
78 | - { | |
79 | - "additionalInfo": { | |
80 | - "layoutX": 586, | |
81 | - "layoutY": 148 | |
82 | - }, | |
83 | - "type": "org.thingsboard.rule.engine.filter.TbJsSwitchNode", | |
84 | - "name": "Check Alarms", | |
85 | - "debugMode": false, | |
86 | - "configuration": { | |
87 | - "jsScript": "var relations = [];\nif(metadata[\"ss_alarmTemperature\"] === \"true\"){\n if(msg.temperature > metadata[\"ss_thresholdTemperature\"]){\n relations.push(\"NewTempAlarm\");\n } else {\n relations.push(\"ClearTempAlarm\");\n }\n}\nif(metadata[\"ss_alarmHumidity\"] === \"true\"){\n if(msg.humidity < metadata[\"ss_thresholdHumidity\"]){\n relations.push(\"NewHumidityAlarm\");\n } else {\n relations.push(\"ClearHumidityAlarm\");\n }\n}\n\nreturn relations;" | |
88 | - } | |
89 | - }, | |
90 | - { | |
91 | - "additionalInfo": { | |
92 | - "layoutX": 321, | |
93 | - "layoutY": 149 | |
94 | - }, | |
95 | - "type": "org.thingsboard.rule.engine.metadata.TbGetAttributesNode", | |
96 | - "name": "Fetch Configuration", | |
97 | - "debugMode": false, | |
98 | - "configuration": { | |
99 | - "clientAttributeNames": [], | |
100 | - "sharedAttributeNames": [], | |
101 | - "serverAttributeNames": [ | |
102 | - "alarmTemperature", | |
103 | - "thresholdTemperature", | |
104 | - "alarmHumidity", | |
105 | - "thresholdHumidity" | |
106 | - ], | |
107 | - "latestTsKeyNames": [], | |
108 | - "tellFailureIfAbsent": false, | |
109 | - "getLatestValueWithTs": false | |
110 | - } | |
111 | - } | |
112 | - ], | |
113 | - "connections": [ | |
114 | - { | |
115 | - "fromIndex": 4, | |
116 | - "toIndex": 0, | |
117 | - "type": "NewTempAlarm" | |
118 | - }, | |
119 | - { | |
120 | - "fromIndex": 4, | |
121 | - "toIndex": 1, | |
122 | - "type": "ClearTempAlarm" | |
123 | - }, | |
124 | - { | |
125 | - "fromIndex": 4, | |
126 | - "toIndex": 2, | |
127 | - "type": "NewHumidityAlarm" | |
128 | - }, | |
129 | - { | |
130 | - "fromIndex": 4, | |
131 | - "toIndex": 3, | |
132 | - "type": "ClearHumidityAlarm" | |
133 | - }, | |
134 | - { | |
135 | - "fromIndex": 5, | |
136 | - "toIndex": 4, | |
137 | - "type": "Success" | |
138 | - } | |
139 | - ], | |
140 | - "ruleChainConnections": null | |
141 | - } | |
142 | -} | |
\ No newline at end of file |
1 | +{ | |
2 | + "providerId": "Facebook", | |
3 | + "accessTokenUri": "https://graph.facebook.com/v2.8/oauth/access_token", | |
4 | + "authorizationUri": "https://www.facebook.com/v2.8/dialog/oauth", | |
5 | + "scope": ["email","public_profile"], | |
6 | + "jwkSetUri": null, | |
7 | + "userInfoUri": "https://graph.facebook.com/me?fields=id,name,first_name,last_name,email", | |
8 | + "clientAuthenticationMethod": "BASIC", | |
9 | + "userNameAttributeName": "email", | |
10 | + "mapperConfig": { | |
11 | + "type": "BASIC", | |
12 | + "basic": { | |
13 | + "emailAttributeKey": "email", | |
14 | + "firstNameAttributeKey": "first_name", | |
15 | + "lastNameAttributeKey": "last_name", | |
16 | + "tenantNameStrategy": "DOMAIN" | |
17 | + } | |
18 | + }, | |
19 | + "comment": null, | |
20 | + "loginButtonIcon": "facebook-logo", | |
21 | + "loginButtonLabel": "Facebook", | |
22 | + "helpLink": "https://developers.facebook.com/docs/facebook-login/web#logindialog" | |
23 | +} | ... | ... |
1 | +{ | |
2 | + "providerId": "Github", | |
3 | + "accessTokenUri": "https://github.com/login/oauth/access_token", | |
4 | + "authorizationUri": "https://github.com/login/oauth/authorize", | |
5 | + "scope": ["read:user","user:email"], | |
6 | + "jwkSetUri": null, | |
7 | + "userInfoUri": "https://api.github.com/user", | |
8 | + "clientAuthenticationMethod": "BASIC", | |
9 | + "userNameAttributeName": "login", | |
10 | + "mapperConfig": { | |
11 | + "type": "GITHUB", | |
12 | + "basic": { | |
13 | + "firstNameAttributeKey": "name", | |
14 | + "tenantNameStrategy": "DOMAIN" | |
15 | + } | |
16 | + }, | |
17 | + "comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to <a href=\"https://docs.github.com/en/rest/reference/users#list-email-addresses-for-the-authenticated-user\">Github Documentation</a>", | |
18 | + "loginButtonIcon": "github-logo", | |
19 | + "loginButtonLabel": "Github", | |
20 | + "helpLink": "https://docs.github.com/en/developers/apps/creating-an-oauth-app" | |
21 | +} | ... | ... |
1 | +{ | |
2 | + "providerId": "Google", | |
3 | + "additionalInfo": null, | |
4 | + "accessTokenUri": "https://oauth2.googleapis.com/token", | |
5 | + "authorizationUri": "https://accounts.google.com/o/oauth2/v2/auth", | |
6 | + "scope": ["email","openid","profile"], | |
7 | + "jwkSetUri": "https://www.googleapis.com/oauth2/v3/certs", | |
8 | + "userInfoUri": "https://openidconnect.googleapis.com/v1/userinfo", | |
9 | + "clientAuthenticationMethod": "BASIC", | |
10 | + "userNameAttributeName": "email", | |
11 | + "mapperConfig": { | |
12 | + "type": "BASIC", | |
13 | + "basic": { | |
14 | + "emailAttributeKey": "email", | |
15 | + "firstNameAttributeKey": "given_name", | |
16 | + "lastNameAttributeKey": "family_name", | |
17 | + "tenantNameStrategy": "DOMAIN" | |
18 | + } | |
19 | + }, | |
20 | + "comment": null, | |
21 | + "loginButtonIcon": "google-logo", | |
22 | + "loginButtonLabel": "Google", | |
23 | + "helpLink": "https://developers.google.com/adwords/api/docs/guides/authentication" | |
24 | +} | ... | ... |
... | ... | @@ -19,7 +19,7 @@ |
19 | 19 | ], |
20 | 20 | "templateHtml": "<canvas id=\"barChart\"></canvas>\n", |
21 | 21 | "templateCss": "", |
22 | - "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataset = {\n label: datasource.dataKeys[d].label,\n data: [0],\n backgroundColor: [datasource.dataKeys[d].color],\n borderColor: [datasource.dataKeys[d].color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", | |
22 | + "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataKey = datasource.dataKeys[d];\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n var dataset = {\n label: dataKey.label + units,\n data: [0],\n backgroundColor: [dataKey.color],\n borderColor: [dataKey.color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", | |
23 | 23 | "settingsSchema": "{}", |
24 | 24 | "dataKeySettingsSchema": "{}\n", |
25 | 25 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars - Chart.js\"}" |
... | ... | @@ -55,7 +55,7 @@ |
55 | 55 | ], |
56 | 56 | "templateHtml": "<canvas id=\"pieChart\"></canvas>\n", |
57 | 57 | "templateCss": "", |
58 | - "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = typeof self.ctx.settings.borderWidth !== 'undefined' ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", | |
58 | + "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = typeof self.ctx.settings.borderWidth !== 'undefined' ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", | |
59 | 59 | "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"borderWidth\": {\n \"title\": \"Border width\",\n \"type\": \"number\",\n \"default\": 5\n },\n \"borderColor\": {\n \"title\": \"Border color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"legend\": {\n \"title\": \"Legend settings\",\n \"type\": \"object\",\n \"properties\": {\n \"display\": {\n \"title\": \"Display legend\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelsFontColor\": {\n \"title\": \"Labels font color\",\n \"type\": \"string\",\n \"default\": \"#666\"\n }\n }\n }\n },\n \"required\": []\n },\n \"form\": [\n \"borderWidth\", \n {\n \"key\": \"borderColor\",\n \"type\": \"color\"\n }, \n {\n \"key\": \"legend\",\n \"items\": [\n \"legend.display\",\n {\n \"key\": \"legend.labelsFontColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n}", |
60 | 60 | "dataKeySettingsSchema": "{}\n", |
61 | 61 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#26a69a\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#afb42b\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#673ab7\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"borderWidth\":5,\"borderColor\":\"#fff\",\"legend\":{\"display\":true,\"labelsFontColor\":\"#666666\"}},\"title\":\"Doughnut - Chart.js\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" |
... | ... | @@ -91,7 +91,7 @@ |
91 | 91 | ], |
92 | 92 | "templateHtml": "<canvas id=\"pieChart\"></canvas>\n", |
93 | 93 | "templateCss": "", |
94 | - "controllerScript": "self.onInit = function() {\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'pie',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n }); \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n", | |
94 | + "controllerScript": "self.onInit = function() {\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'pie',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n }); \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n", | |
95 | 95 | "settingsSchema": "{}", |
96 | 96 | "dataKeySettingsSchema": "{}\n", |
97 | 97 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Pie - Chart.js\"}" |
... | ... | @@ -111,7 +111,7 @@ |
111 | 111 | ], |
112 | 112 | "templateHtml": "<canvas id=\"pieChart\"></canvas>\n", |
113 | 113 | "templateCss": "", |
114 | - "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'polarArea',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n try {\n self.ctx.chart.resize();\n } catch (e) {}\n }\n}\n", | |
114 | + "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'polarArea',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n try {\n self.ctx.chart.resize();\n } catch (e) {}\n }\n}\n", | |
115 | 115 | "settingsSchema": "{}", |
116 | 116 | "dataKeySettingsSchema": "{}\n", |
117 | 117 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fifth\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.2074391823443591,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Polar Area - Chart.js\"}" |
... | ... | @@ -131,7 +131,7 @@ |
131 | 131 | ], |
132 | 132 | "templateHtml": "<canvas id=\"radarChart\"></canvas>\n", |
133 | 133 | "templateCss": "", |
134 | - "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n\n var backgroundColor = tinycolor(self.ctx.data[0].dataKey.color);\n backgroundColor.setAlpha(0.2);\n var borderColor = tinycolor(self.ctx.data[0].dataKey.color);\n borderColor.setAlpha(1);\n var dataset = {\n label: self.ctx.datasources[0].name,\n data: [],\n backgroundColor: backgroundColor.toRgbString(),\n borderColor: borderColor.toRgbString(),\n pointBackgroundColor: borderColor.toRgbString(),\n pointBorderColor: borderColor.darken().toRgbString(),\n borderWidth: 1\n }\n \n barData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n barData.labels.push(dataKey.label);\n dataset.data.push(0);\n }\n\n var ctx = $('#radarChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'radar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n } \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n self.ctx.chart.resize();\n }\n}\n", | |
134 | + "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n\n var backgroundColor = tinycolor(self.ctx.data[0].dataKey.color);\n backgroundColor.setAlpha(0.2);\n var borderColor = tinycolor(self.ctx.data[0].dataKey.color);\n borderColor.setAlpha(1);\n var dataset = {\n label: self.ctx.datasources[0].name,\n data: [],\n backgroundColor: backgroundColor.toRgbString(),\n borderColor: borderColor.toRgbString(),\n pointBackgroundColor: borderColor.toRgbString(),\n pointBorderColor: borderColor.darken().toRgbString(),\n borderWidth: 1\n }\n \n barData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n barData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n }\n\n var ctx = $('#radarChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'radar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n } \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n self.ctx.chart.resize();\n }\n}\n", | |
135 | 135 | "settingsSchema": "{}", |
136 | 136 | "dataKeySettingsSchema": "{}\n", |
137 | 137 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Radar - Chart.js\"}" | ... | ... |
... | ... | @@ -32,8 +32,8 @@ |
32 | 32 | "templateHtml": "<tb-multiple-input-widget \n [ctx]=\"ctx\">\n</tb-multiple-input-widget>", |
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 | - "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 (only if action buttons are visible)\",\n \"type\":\"boolean\",\n \"default\": false\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 \"updateAllValues\",\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 \"fieldsInRow\"\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 \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\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 \"step\": {\n \"title\": \"Step interval between values (only for numbers)\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\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 \"required\",\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 \"step\",\n \"requiredErrorMessage\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", | |
35 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"updateAllValues\": {\n \"title\":\"Update all values, not only modified\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"saveButtonLabel\": {\n \"title\": \"'SAVE' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"resetButtonLabel\": {\n \"title\": \"'UNDO' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showGroupTitle\": {\n \"title\":\"Show title for group of fields, related to different entities\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"groupTitle\": {\n \"title\": \"Group title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n {\n \"key\": \"updateAllValues\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"saveButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"resetButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n \"showResultMessage\",\n \"showGroupTitle\",\n \"groupTitle\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n {\n \"key\": \"fieldsInRow\",\n \"condition\": \"model.fieldsAlignment === 'row'\"\n }\n ]\n}", | |
36 | + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"step\": {\n \"title\": \"Step interval between values\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\"\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"minValueErrorMessage\": {\n \"title\": \"'Min Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValueErrorMessage\": {\n \"title\": \"'Max Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"invalidDateErrorMessage\": {\n \"title\": \"'Invalid Date' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"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 | }, |
... | ... | @@ -391,16 +391,16 @@ |
391 | 391 | }, |
392 | 392 | { |
393 | 393 | "alias": "web_camera_input", |
394 | - "name": "Web Camera Input", | |
394 | + "name": "Photo camera input", | |
395 | 395 | "descriptor": { |
396 | 396 | "type": "latest", |
397 | 397 | "sizeX": 7.5, |
398 | 398 | "sizeY": 3, |
399 | 399 | "resources": [], |
400 | - "templateHtml": "<tb-web-camera-widget \n [ctx]=\"ctx\">\n</tb-web-camera-widget>", | |
400 | + "templateHtml": "<tb-photo-camera-widget \n [ctx]=\"ctx\">\n</tb-photo-camera-widget>", | |
401 | 401 | "templateCss": "", |
402 | - "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.webCameraInputWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n}\n", | |
403 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Web Camera\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"imageFormat\": {\n \"title\": \"Image Format\",\n \"type\": \"string\",\n \"default\": \"image/png\"\n },\n \"imageQuality\":{\n \"title\":\"Image quality that use lossy compression such as jpeg and webp\",\n \"type\":\"number\",\n \"default\": 0.92,\n \"min\": 0,\n \"max\": 1\n },\n \"maxWidth\": {\n \"title\": \"The maximal image width\",\n \"type\": \"number\",\n \"default\": 640\n }, \n \"maxHeight\": {\n \"title\": \"The maximal image heigth\",\n \"type\": \"number\",\n \"default\": 480\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n {\n \"key\": \"imageFormat\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"image/jpeg\",\n \"label\": \"JPEG\"\n },\n {\n \"value\": \"image/png\",\n \"label\": \"PNG\"\n },\n {\n \"value\": \"image/webp\",\n \"label\": \"WEBP\"\n }\n ]\n },\n \"imageQuality\",\n \"maxWidth\",\n \"maxHeight\"\n ]\n}", | |
402 | + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.photoCameraInputWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n}\n", | |
403 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Photo Camera\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"imageFormat\": {\n \"title\": \"Image Format\",\n \"type\": \"string\",\n \"default\": \"image/png\"\n },\n \"imageQuality\":{\n \"title\":\"Image quality that use lossy compression such as jpeg and webp\",\n \"type\":\"number\",\n \"default\": 0.92,\n \"min\": 0,\n \"max\": 1\n },\n \"maxWidth\": {\n \"title\": \"The maximal image width\",\n \"type\": \"number\",\n \"default\": 640\n }, \n \"maxHeight\": {\n \"title\": \"The maximal image heigth\",\n \"type\": \"number\",\n \"default\": 480\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n {\n \"key\": \"imageFormat\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"image/jpeg\",\n \"label\": \"JPEG\"\n },\n {\n \"value\": \"image/png\",\n \"label\": \"PNG\"\n },\n {\n \"value\": \"image/webp\",\n \"label\": \"WEBP\"\n }\n ]\n },\n \"imageQuality\",\n \"maxWidth\",\n \"maxHeight\"\n ]\n}", | |
404 | 404 | "dataKeySettingsSchema": "{}\n", |
405 | 405 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Web Camera Input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" |
406 | 406 | } | ... | ... |
... | ... | @@ -14,19 +14,92 @@ |
14 | 14 | -- limitations under the License. |
15 | 15 | -- |
16 | 16 | |
17 | +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( | |
18 | + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, | |
19 | + enabled boolean, | |
20 | + created_time bigint NOT NULL, | |
21 | + additional_info varchar, | |
22 | + client_id varchar(255), | |
23 | + client_secret varchar(255), | |
24 | + authorization_uri varchar(255), | |
25 | + token_uri varchar(255), | |
26 | + scope varchar(255), | |
27 | + user_info_uri varchar(255), | |
28 | + user_name_attribute_name varchar(255), | |
29 | + jwk_set_uri varchar(255), | |
30 | + client_authentication_method varchar(255), | |
31 | + login_button_label varchar(255), | |
32 | + login_button_icon varchar(255), | |
33 | + allow_user_creation boolean, | |
34 | + activate_user boolean, | |
35 | + type varchar(31), | |
36 | + basic_email_attribute_key varchar(31), | |
37 | + basic_first_name_attribute_key varchar(31), | |
38 | + basic_last_name_attribute_key varchar(31), | |
39 | + basic_tenant_name_strategy varchar(31), | |
40 | + basic_tenant_name_pattern varchar(255), | |
41 | + basic_customer_name_pattern varchar(255), | |
42 | + basic_default_dashboard_name varchar(255), | |
43 | + basic_always_full_screen boolean, | |
44 | + custom_url varchar(255), | |
45 | + custom_username varchar(255), | |
46 | + custom_password varchar(255), | |
47 | + custom_send_token boolean | |
48 | +); | |
49 | + | |
50 | +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( | |
51 | + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, | |
52 | + created_time bigint NOT NULL, | |
53 | + domain_name varchar(255), | |
54 | + domain_scheme varchar(31), | |
55 | + client_registration_info_id uuid | |
56 | +); | |
57 | + | |
58 | +CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( | |
59 | + id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, | |
60 | + created_time bigint NOT NULL, | |
61 | + additional_info varchar, | |
62 | + provider_id varchar(255), | |
63 | + authorization_uri varchar(255), | |
64 | + token_uri varchar(255), | |
65 | + scope varchar(255), | |
66 | + user_info_uri varchar(255), | |
67 | + user_name_attribute_name varchar(255), | |
68 | + jwk_set_uri varchar(255), | |
69 | + client_authentication_method varchar(255), | |
70 | + type varchar(31), | |
71 | + basic_email_attribute_key varchar(31), | |
72 | + basic_first_name_attribute_key varchar(31), | |
73 | + basic_last_name_attribute_key varchar(31), | |
74 | + basic_tenant_name_strategy varchar(31), | |
75 | + basic_tenant_name_pattern varchar(255), | |
76 | + basic_customer_name_pattern varchar(255), | |
77 | + basic_default_dashboard_name varchar(255), | |
78 | + basic_always_full_screen boolean, | |
79 | + comment varchar, | |
80 | + login_button_icon varchar(255), | |
81 | + login_button_label varchar(255), | |
82 | + help_link varchar(255), | |
83 | + CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) | |
84 | +); | |
85 | + | |
17 | 86 | CREATE TABLE IF NOT EXISTS device_profile ( |
18 | 87 | id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY, |
19 | 88 | created_time bigint NOT NULL, |
20 | 89 | name varchar(255), |
21 | 90 | type varchar(255), |
22 | 91 | transport_type varchar(255), |
92 | + provision_type varchar(255), | |
23 | 93 | profile_data jsonb, |
24 | 94 | description varchar, |
25 | 95 | search_text varchar(255), |
26 | 96 | is_default boolean, |
27 | 97 | tenant_id uuid, |
28 | 98 | default_rule_chain_id uuid, |
99 | + default_queue_name varchar(255), | |
100 | + provision_device_key varchar, | |
29 | 101 | CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), |
102 | + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), | |
30 | 103 | CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) |
31 | 104 | ); |
32 | 105 | ... | ... |
... | ... | @@ -32,6 +32,8 @@ import org.springframework.data.redis.core.RedisTemplate; |
32 | 32 | import org.springframework.scheduling.annotation.Scheduled; |
33 | 33 | import org.springframework.stereotype.Component; |
34 | 34 | import org.thingsboard.rule.engine.api.MailService; |
35 | +import org.thingsboard.rule.engine.api.SmsService; | |
36 | +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; | |
35 | 37 | import org.thingsboard.server.actors.service.ActorService; |
36 | 38 | import org.thingsboard.server.actors.tenant.DebugTbRateLimits; |
37 | 39 | import org.thingsboard.server.common.data.DataConstants; |
... | ... | @@ -67,6 +69,8 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; |
67 | 69 | import org.thingsboard.server.dao.user.UserService; |
68 | 70 | import org.thingsboard.server.queue.discovery.PartitionService; |
69 | 71 | import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
72 | +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; | |
73 | +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; | |
70 | 74 | import org.thingsboard.server.service.component.ComponentDiscoveryService; |
71 | 75 | import org.thingsboard.server.service.edge.rpc.EdgeRpcService; |
72 | 76 | import org.thingsboard.server.service.executors.DbCallbackExecutorService; |
... | ... | @@ -74,12 +78,14 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService; |
74 | 78 | import org.thingsboard.server.service.executors.SharedEventLoopGroupService; |
75 | 79 | import org.thingsboard.server.service.mail.MailExecutorService; |
76 | 80 | import org.thingsboard.server.service.profile.TbDeviceProfileCache; |
81 | +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; | |
77 | 82 | import org.thingsboard.server.service.queue.TbClusterService; |
78 | 83 | import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; |
79 | 84 | import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; |
80 | 85 | import org.thingsboard.server.service.script.JsExecutorService; |
81 | 86 | import org.thingsboard.server.service.script.JsInvokeService; |
82 | 87 | import org.thingsboard.server.service.session.DeviceSessionCacheService; |
88 | +import org.thingsboard.server.service.sms.SmsExecutorService; | |
83 | 89 | import org.thingsboard.server.service.state.DeviceStateService; |
84 | 90 | import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; |
85 | 91 | import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
... | ... | @@ -109,6 +115,14 @@ public class ActorSystemContext { |
109 | 115 | |
110 | 116 | @Autowired |
111 | 117 | @Getter |
118 | + private TbApiUsageStateService apiUsageStateService; | |
119 | + | |
120 | + @Autowired | |
121 | + @Getter | |
122 | + private TbApiUsageClient apiUsageClient; | |
123 | + | |
124 | + @Autowired | |
125 | + @Getter | |
112 | 126 | @Setter |
113 | 127 | private TbServiceInfoProvider serviceInfoProvider; |
114 | 128 | |
... | ... | @@ -131,6 +145,10 @@ public class ActorSystemContext { |
131 | 145 | |
132 | 146 | @Autowired |
133 | 147 | @Getter |
148 | + private TbTenantProfileCache tenantProfileCache; | |
149 | + | |
150 | + @Autowired | |
151 | + @Getter | |
134 | 152 | private TbDeviceProfileCache deviceProfileCache; |
135 | 153 | |
136 | 154 | @Autowired |
... | ... | @@ -218,6 +236,10 @@ public class ActorSystemContext { |
218 | 236 | |
219 | 237 | @Autowired |
220 | 238 | @Getter |
239 | + private SmsExecutorService smsExecutor; | |
240 | + | |
241 | + @Autowired | |
242 | + @Getter | |
221 | 243 | private DbCallbackExecutorService dbCallbackExecutor; |
222 | 244 | |
223 | 245 | @Autowired |
... | ... | @@ -234,6 +256,14 @@ public class ActorSystemContext { |
234 | 256 | |
235 | 257 | @Autowired |
236 | 258 | @Getter |
259 | + private SmsService smsService; | |
260 | + | |
261 | + @Autowired | |
262 | + @Getter | |
263 | + private SmsSenderFactory smsSenderFactory; | |
264 | + | |
265 | + @Autowired | |
266 | + @Getter | |
237 | 267 | private ClaimDevicesService claimDevicesService; |
238 | 268 | |
239 | 269 | @Autowired |
... | ... | @@ -325,6 +355,10 @@ public class ActorSystemContext { |
325 | 355 | @Getter |
326 | 356 | private boolean allowSystemMailService; |
327 | 357 | |
358 | + @Value("${actors.rule.allow_system_sms_service}") | |
359 | + @Getter | |
360 | + private boolean allowSystemSmsService; | |
361 | + | |
328 | 362 | @Value("${transport.sessions.inactivity_timeout}") |
329 | 363 | @Getter |
330 | 364 | private long sessionInactivityTimeout; |
... | ... | @@ -553,7 +587,11 @@ public class ActorSystemContext { |
553 | 587 | |
554 | 588 | public void scheduleMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs) { |
555 | 589 | log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs); |
556 | - getScheduler().schedule(() -> ctx.tell(msg), delayInMs, TimeUnit.MILLISECONDS); | |
590 | + if (delayInMs > 0) { | |
591 | + getScheduler().schedule(() -> ctx.tell(msg), delayInMs, TimeUnit.MILLISECONDS); | |
592 | + } else { | |
593 | + ctx.tell(msg); | |
594 | + } | |
557 | 595 | } |
558 | 596 | |
559 | 597 | } | ... | ... |
... | ... | @@ -39,8 +39,8 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
39 | 39 | import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; |
40 | 40 | import org.thingsboard.server.common.msg.queue.RuleEngineException; |
41 | 41 | import org.thingsboard.server.common.msg.queue.ServiceType; |
42 | -import org.thingsboard.server.dao.model.ModelConstants; | |
43 | 42 | import org.thingsboard.server.dao.tenant.TenantService; |
43 | +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; | |
44 | 44 | import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; |
45 | 45 | |
46 | 46 | import java.util.HashSet; |
... | ... | @@ -50,13 +50,14 @@ import java.util.Set; |
50 | 50 | @Slf4j |
51 | 51 | public class AppActor extends ContextAwareActor { |
52 | 52 | |
53 | - private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); | |
53 | + private final TbTenantProfileCache tenantProfileCache; | |
54 | 54 | private final TenantService tenantService; |
55 | 55 | private final Set<TenantId> deletedTenants; |
56 | 56 | private boolean ruleChainsInitialized; |
57 | 57 | |
58 | 58 | private AppActor(ActorSystemContext systemContext) { |
59 | 59 | super(systemContext); |
60 | + this.tenantProfileCache = systemContext.getTenantProfileCache(); | |
60 | 61 | this.tenantService = systemContext.getTenantService(); |
61 | 62 | this.deletedTenants = new HashSet<>(); |
62 | 63 | } |
... | ... | @@ -117,8 +118,7 @@ public class AppActor extends ContextAwareActor { |
117 | 118 | boolean isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); |
118 | 119 | boolean isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); |
119 | 120 | for (Tenant tenant : tenantIterator) { |
120 | - // TODO: Tenant Profile from cache | |
121 | - TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(TenantId.SYS_TENANT_ID, tenant.getTenantProfileId()); | |
121 | + TenantProfile tenantProfile = tenantProfileCache.get(tenant.getTenantProfileId()); | |
122 | 122 | if (isCore || (isRuleEngine && !tenantProfile.isIsolatedTbRuleEngine())) { |
123 | 123 | log.debug("[{}] Creating tenant actor", tenant.getId()); |
124 | 124 | getOrCreateTenantActor(tenant.getId()); |
... | ... | @@ -133,7 +133,7 @@ public class AppActor extends ContextAwareActor { |
133 | 133 | } |
134 | 134 | |
135 | 135 | private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) { |
136 | - if (SYSTEM_TENANT.equals(msg.getTenantId())) { | |
136 | + if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) { | |
137 | 137 | msg.getTbMsg().getCallback().onFailure(new RuleEngineException("Message has system tenant id!")); |
138 | 138 | } else { |
139 | 139 | if (!deletedTenants.contains(msg.getTenantId())) { |
... | ... | @@ -146,15 +146,20 @@ public class AppActor extends ContextAwareActor { |
146 | 146 | |
147 | 147 | private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { |
148 | 148 | TbActorRef target = null; |
149 | - if (SYSTEM_TENANT.equals(msg.getTenantId())) { | |
150 | - log.warn("Message has system tenant id: {}", msg); | |
149 | + if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) { | |
150 | + if (!EntityType.TENANT_PROFILE.equals(msg.getEntityId().getEntityType())) { | |
151 | + log.warn("Message has system tenant id: {}", msg); | |
152 | + } | |
151 | 153 | } else { |
152 | - if (msg.getEntityId().getEntityType() == EntityType.TENANT | |
153 | - && msg.getEvent() == ComponentLifecycleEvent.DELETED) { | |
154 | - log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg); | |
154 | + if (EntityType.TENANT.equals(msg.getEntityId().getEntityType())) { | |
155 | 155 | TenantId tenantId = new TenantId(msg.getEntityId().getId()); |
156 | - deletedTenants.add(tenantId); | |
157 | - ctx.stop(new TbEntityActorId(tenantId)); | |
156 | + if (msg.getEvent() == ComponentLifecycleEvent.DELETED) { | |
157 | + log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg); | |
158 | + deletedTenants.add(tenantId); | |
159 | + ctx.stop(new TbEntityActorId(tenantId)); | |
160 | + } else { | |
161 | + target = getOrCreateTenantActor(msg.getTenantId()); | |
162 | + } | |
158 | 163 | } else { |
159 | 164 | target = getOrCreateTenantActor(msg.getTenantId()); |
160 | 165 | } | ... | ... |
... | ... | @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; |
20 | 20 | import io.netty.channel.EventLoopGroup; |
21 | 21 | import lombok.extern.slf4j.Slf4j; |
22 | 22 | import org.springframework.data.redis.core.RedisTemplate; |
23 | +import org.springframework.util.StringUtils; | |
23 | 24 | import org.thingsboard.common.util.ListeningExecutor; |
24 | 25 | import org.thingsboard.rule.engine.api.MailService; |
25 | 26 | import org.thingsboard.rule.engine.api.RuleEngineAlarmService; |
... | ... | @@ -27,15 +28,21 @@ import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; |
27 | 28 | import org.thingsboard.rule.engine.api.RuleEngineRpcService; |
28 | 29 | import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; |
29 | 30 | import org.thingsboard.rule.engine.api.ScriptEngine; |
31 | +import org.thingsboard.rule.engine.api.SmsService; | |
30 | 32 | import org.thingsboard.rule.engine.api.TbContext; |
31 | 33 | import org.thingsboard.rule.engine.api.TbRelationTypes; |
34 | +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; | |
32 | 35 | import org.thingsboard.server.actors.ActorSystemContext; |
33 | 36 | import org.thingsboard.server.actors.TbActorRef; |
37 | +import org.thingsboard.server.common.data.ApiUsageRecordKey; | |
34 | 38 | import org.thingsboard.server.common.data.Customer; |
35 | 39 | import org.thingsboard.server.common.data.DataConstants; |
36 | 40 | import org.thingsboard.server.common.data.Device; |
41 | +import org.thingsboard.server.common.data.DeviceProfile; | |
42 | +import org.thingsboard.server.common.data.TenantProfile; | |
37 | 43 | import org.thingsboard.server.common.data.alarm.Alarm; |
38 | 44 | import org.thingsboard.server.common.data.asset.Asset; |
45 | +import org.thingsboard.server.common.data.id.DeviceId; | |
39 | 46 | import org.thingsboard.server.common.data.id.EntityId; |
40 | 47 | import org.thingsboard.server.common.data.id.RuleChainId; |
41 | 48 | import org.thingsboard.server.common.data.id.RuleNodeId; |
... | ... | @@ -47,6 +54,7 @@ import org.thingsboard.server.common.data.rule.RuleNodeState; |
47 | 54 | import org.thingsboard.server.common.msg.TbActorMsg; |
48 | 55 | import org.thingsboard.server.common.msg.TbMsg; |
49 | 56 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
57 | +import org.thingsboard.server.common.msg.queue.ServiceQueue; | |
50 | 58 | import org.thingsboard.server.common.msg.queue.ServiceType; |
51 | 59 | import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
52 | 60 | import org.thingsboard.server.dao.asset.AssetService; |
... | ... | @@ -72,6 +80,7 @@ import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; |
72 | 80 | |
73 | 81 | import java.util.Collections; |
74 | 82 | import java.util.Set; |
83 | +import java.util.function.BiConsumer; | |
75 | 84 | import java.util.function.Consumer; |
76 | 85 | |
77 | 86 | /** |
... | ... | @@ -185,6 +194,9 @@ class DefaultTbContext implements TbContext { |
185 | 194 | } |
186 | 195 | |
187 | 196 | private TopicPartitionInfo resolvePartition(TbMsg tbMsg, String queueName) { |
197 | + if (StringUtils.isEmpty(queueName)) { | |
198 | + queueName = ServiceQueue.MAIN; | |
199 | + } | |
188 | 200 | return mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator()); |
189 | 201 | } |
190 | 202 | |
... | ... | @@ -253,26 +265,26 @@ class DefaultTbContext implements TbContext { |
253 | 265 | } |
254 | 266 | |
255 | 267 | public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) { |
256 | - return entityCreatedMsg(customer, customer.getId(), ruleNodeId); | |
268 | + return entityActionMsg(customer, customer.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); | |
257 | 269 | } |
258 | 270 | |
259 | 271 | public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { |
260 | - return entityCreatedMsg(device, device.getId(), ruleNodeId); | |
272 | + return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); | |
261 | 273 | } |
262 | 274 | |
263 | 275 | public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { |
264 | - return entityCreatedMsg(asset, asset.getId(), ruleNodeId); | |
276 | + return entityActionMsg(asset, asset.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); | |
265 | 277 | } |
266 | 278 | |
267 | - public TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId) { | |
268 | - return entityCreatedMsg(alarm, alarm.getId(), ruleNodeId); | |
279 | + public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { | |
280 | + return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action); | |
269 | 281 | } |
270 | 282 | |
271 | - public <E, I extends EntityId> TbMsg entityCreatedMsg(E entity, I id, RuleNodeId ruleNodeId) { | |
283 | + public <E, I extends EntityId> TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) { | |
272 | 284 | try { |
273 | - return TbMsg.newMsg(DataConstants.ENTITY_CREATED, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity))); | |
285 | + return TbMsg.newMsg(action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity))); | |
274 | 286 | } catch (JsonProcessingException | IllegalArgumentException e) { |
275 | - throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " created msg: " + e); | |
287 | + throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e); | |
276 | 288 | } |
277 | 289 | } |
278 | 290 | |
... | ... | @@ -297,6 +309,11 @@ class DefaultTbContext implements TbContext { |
297 | 309 | } |
298 | 310 | |
299 | 311 | @Override |
312 | + public ListeningExecutor getSmsExecutor() { | |
313 | + return mainCtx.getSmsExecutor(); | |
314 | + } | |
315 | + | |
316 | + @Override | |
300 | 317 | public ListeningExecutor getDbCallbackExecutor() { |
301 | 318 | return mainCtx.getDbCallbackExecutor(); |
302 | 319 | } |
... | ... | @@ -308,7 +325,7 @@ class DefaultTbContext implements TbContext { |
308 | 325 | |
309 | 326 | @Override |
310 | 327 | public ScriptEngine createJsScriptEngine(String script, String... argNames) { |
311 | - return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames); | |
328 | + return new RuleNodeJsScriptEngine(getTenantId(), mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames); | |
312 | 329 | } |
313 | 330 | |
314 | 331 | @Override |
... | ... | @@ -432,6 +449,20 @@ class DefaultTbContext implements TbContext { |
432 | 449 | } |
433 | 450 | |
434 | 451 | @Override |
452 | + public SmsService getSmsService() { | |
453 | + if (mainCtx.isAllowSystemSmsService()) { | |
454 | + return mainCtx.getSmsService(); | |
455 | + } else { | |
456 | + throw new RuntimeException("Access to System SMS Service is forbidden!"); | |
457 | + } | |
458 | + } | |
459 | + | |
460 | + @Override | |
461 | + public SmsSenderFactory getSmsSenderFactory() { | |
462 | + return mainCtx.getSmsSenderFactory(); | |
463 | + } | |
464 | + | |
465 | + @Override | |
435 | 466 | public RuleEngineRpcService getRpcService() { |
436 | 467 | return mainCtx.getTbRuleEngineDeviceRpcService(); |
437 | 468 | } |
... | ... | @@ -476,6 +507,43 @@ class DefaultTbContext implements TbContext { |
476 | 507 | return mainCtx.getRuleNodeStateService().save(getTenantId(), state); |
477 | 508 | } |
478 | 509 | |
510 | + @Override | |
511 | + public void clearRuleNodeStates() { | |
512 | + if (log.isDebugEnabled()) { | |
513 | + log.debug("[{}][{}] Going to clear rule node states", getTenantId(), getSelfId()); | |
514 | + } | |
515 | + mainCtx.getRuleNodeStateService().removeByRuleNodeId(getTenantId(), getSelfId()); | |
516 | + } | |
517 | + | |
518 | + @Override | |
519 | + public void removeRuleNodeStateForEntity(EntityId entityId) { | |
520 | + if (log.isDebugEnabled()) { | |
521 | + log.debug("[{}][{}][{}] Remove Rule Node State for entity.", getTenantId(), getSelfId(), entityId); | |
522 | + } | |
523 | + mainCtx.getRuleNodeStateService().removeByRuleNodeIdAndEntityId(getTenantId(), getSelfId(), entityId); | |
524 | + } | |
525 | + | |
526 | + @Override | |
527 | + public void addTenantProfileListener(Consumer<TenantProfile> listener) { | |
528 | + mainCtx.getTenantProfileCache().addListener(getTenantId(), getSelfId(), listener); | |
529 | + } | |
530 | + | |
531 | + @Override | |
532 | + public void addDeviceProfileListeners(Consumer<DeviceProfile> profileListener, BiConsumer<DeviceId, DeviceProfile> deviceListener) { | |
533 | + mainCtx.getDeviceProfileCache().addListener(getTenantId(), getSelfId(), profileListener, deviceListener); | |
534 | + } | |
535 | + | |
536 | + @Override | |
537 | + public void removeListeners() { | |
538 | + mainCtx.getDeviceProfileCache().removeListener(getTenantId(), getSelfId()); | |
539 | + mainCtx.getTenantProfileCache().removeListener(getTenantId(), getSelfId()); | |
540 | + } | |
541 | + | |
542 | + @Override | |
543 | + public TenantProfile getTenantProfile() { | |
544 | + return mainCtx.getTenantProfileCache().get(getTenantId()); | |
545 | + } | |
546 | + | |
479 | 547 | private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { |
480 | 548 | TbMsgMetaData metaData = new TbMsgMetaData(); |
481 | 549 | metaData.putValue("ruleNodeId", ruleNodeId.toString()); | ... | ... |
... | ... | @@ -23,6 +23,7 @@ import org.thingsboard.server.actors.TbActorRef; |
23 | 23 | import org.thingsboard.server.actors.TbEntityActorId; |
24 | 24 | import org.thingsboard.server.actors.service.DefaultActorService; |
25 | 25 | import org.thingsboard.server.actors.shared.ComponentMsgProcessor; |
26 | +import org.thingsboard.server.common.data.ApiUsageRecordKey; | |
26 | 27 | import org.thingsboard.server.common.data.EntityType; |
27 | 28 | import org.thingsboard.server.common.data.id.EntityId; |
28 | 29 | import org.thingsboard.server.common.data.id.RuleChainId; |
... | ... | @@ -47,6 +48,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; |
47 | 48 | import org.thingsboard.server.queue.TbQueueCallback; |
48 | 49 | import org.thingsboard.server.queue.common.MultipleTbQueueTbMsgCallbackWrapper; |
49 | 50 | import org.thingsboard.server.queue.common.TbQueueTbMsgCallbackWrapper; |
51 | +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; | |
50 | 52 | import org.thingsboard.server.service.queue.TbClusterService; |
51 | 53 | |
52 | 54 | import java.util.ArrayList; |
... | ... | @@ -69,15 +71,16 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh |
69 | 71 | private final Map<RuleNodeId, List<RuleNodeRelation>> nodeRoutes; |
70 | 72 | private final RuleChainService service; |
71 | 73 | private final TbClusterService clusterService; |
74 | + private final TbApiUsageClient apiUsageClient; | |
72 | 75 | private String ruleChainName; |
73 | 76 | |
74 | 77 | private RuleNodeId firstId; |
75 | 78 | private RuleNodeCtx firstNode; |
76 | 79 | private boolean started; |
77 | 80 | |
78 | - RuleChainActorMessageProcessor(TenantId tenantId, RuleChain ruleChain, ActorSystemContext systemContext | |
79 | - , TbActorRef parent, TbActorRef self) { | |
81 | + RuleChainActorMessageProcessor(TenantId tenantId, RuleChain ruleChain, ActorSystemContext systemContext, TbActorRef parent, TbActorRef self) { | |
80 | 82 | super(systemContext, tenantId, ruleChain.getId()); |
83 | + this.apiUsageClient = systemContext.getApiUsageClient(); | |
81 | 84 | this.ruleChainName = ruleChain.getName(); |
82 | 85 | this.parent = parent; |
83 | 86 | this.self = self; | ... | ... |
... | ... | @@ -64,6 +64,12 @@ public abstract class RuleChainManagerActor extends ContextAwareActor { |
64 | 64 | } |
65 | 65 | } |
66 | 66 | |
67 | + protected void destroyRuleChains() { | |
68 | + for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.CORE, link), ContextAwareActor.ENTITY_PACK_LIMIT)) { | |
69 | + ctx.stop(new TbEntityActorId(ruleChain.getId())); | |
70 | + } | |
71 | + } | |
72 | + | |
67 | 73 | protected void visit(RuleChain entity, TbActorRef actorRef) { |
68 | 74 | if (entity != null && entity.isRoot() && entity.getType().equals(RuleChainType.CORE)) { |
69 | 75 | rootChain = entity; | ... | ... |
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
... | ... | @@ -21,13 +21,18 @@ import org.thingsboard.server.actors.ActorSystemContext; |
21 | 21 | import org.thingsboard.server.actors.TbActorCtx; |
22 | 22 | import org.thingsboard.server.actors.TbActorRef; |
23 | 23 | import org.thingsboard.server.actors.shared.ComponentMsgProcessor; |
24 | +import org.thingsboard.server.common.data.ApiUsageRecordKey; | |
25 | +import org.thingsboard.server.common.data.TenantProfile; | |
24 | 26 | import org.thingsboard.server.common.data.id.RuleNodeId; |
25 | 27 | import org.thingsboard.server.common.data.id.TenantId; |
26 | 28 | import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; |
27 | 29 | import org.thingsboard.server.common.data.rule.RuleNode; |
30 | +import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; | |
31 | +import org.thingsboard.server.common.msg.TbMsg; | |
28 | 32 | import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; |
29 | 33 | import org.thingsboard.server.common.msg.queue.RuleNodeException; |
30 | 34 | import org.thingsboard.server.common.msg.queue.RuleNodeInfo; |
35 | +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; | |
31 | 36 | |
32 | 37 | /** |
33 | 38 | * @author Andrew Shvayka |
... | ... | @@ -36,6 +41,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod |
36 | 41 | |
37 | 42 | private final String ruleChainName; |
38 | 43 | private final TbActorRef self; |
44 | + private final TbApiUsageClient apiUsageClient; | |
39 | 45 | private RuleNode ruleNode; |
40 | 46 | private TbNode tbNode; |
41 | 47 | private DefaultTbContext defaultCtx; |
... | ... | @@ -44,6 +50,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod |
44 | 50 | RuleNodeActorMessageProcessor(TenantId tenantId, String ruleChainName, RuleNodeId ruleNodeId, ActorSystemContext systemContext |
45 | 51 | , TbActorRef parent, TbActorRef self) { |
46 | 52 | super(systemContext, tenantId, ruleNodeId); |
53 | + this.apiUsageClient = systemContext.getApiUsageClient(); | |
47 | 54 | this.ruleChainName = ruleChainName; |
48 | 55 | this.self = self; |
49 | 56 | this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId); |
... | ... | @@ -92,26 +99,42 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod |
92 | 99 | |
93 | 100 | public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception { |
94 | 101 | checkActive(msg.getMsg()); |
95 | - if (ruleNode.isDebugMode()) { | |
96 | - systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), "Self"); | |
97 | - } | |
98 | - try { | |
99 | - tbNode.onMsg(defaultCtx, msg.getMsg()); | |
100 | - } catch (Exception e) { | |
101 | - defaultCtx.tellFailure(msg.getMsg(), e); | |
102 | + TbMsg tbMsg = msg.getMsg(); | |
103 | + int ruleNodeCount = tbMsg.getAndIncrementRuleNodeCounter(); | |
104 | + int maxRuleNodeExecutionsPerMessage = getTenantProfileConfiguration().getMaxRuleNodeExecsPerMessage(); | |
105 | + if (maxRuleNodeExecutionsPerMessage == 0 || ruleNodeCount < maxRuleNodeExecutionsPerMessage) { | |
106 | + apiUsageClient.report(tenantId, ApiUsageRecordKey.RE_EXEC_COUNT); | |
107 | + if (ruleNode.isDebugMode()) { | |
108 | + systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), "Self"); | |
109 | + } | |
110 | + try { | |
111 | + tbNode.onMsg(defaultCtx, msg.getMsg()); | |
112 | + } catch (Exception e) { | |
113 | + defaultCtx.tellFailure(msg.getMsg(), e); | |
114 | + } | |
115 | + } else { | |
116 | + tbMsg.getCallback().onFailure(new RuleNodeException("Message is processed by more then " + maxRuleNodeExecutionsPerMessage + " rule nodes!", ruleChainName, ruleNode)); | |
102 | 117 | } |
103 | 118 | } |
104 | 119 | |
105 | 120 | void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception { |
106 | 121 | msg.getMsg().getCallback().onProcessingStart(info); |
107 | 122 | checkActive(msg.getMsg()); |
108 | - if (ruleNode.isDebugMode()) { | |
109 | - systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType()); | |
110 | - } | |
111 | - try { | |
112 | - tbNode.onMsg(msg.getCtx(), msg.getMsg()); | |
113 | - } catch (Exception e) { | |
114 | - msg.getCtx().tellFailure(msg.getMsg(), e); | |
123 | + TbMsg tbMsg = msg.getMsg(); | |
124 | + int ruleNodeCount = tbMsg.getAndIncrementRuleNodeCounter(); | |
125 | + int maxRuleNodeExecutionsPerMessage = getTenantProfileConfiguration().getMaxRuleNodeExecsPerMessage(); | |
126 | + if (maxRuleNodeExecutionsPerMessage == 0 || ruleNodeCount < maxRuleNodeExecutionsPerMessage) { | |
127 | + apiUsageClient.report(tenantId, ApiUsageRecordKey.RE_EXEC_COUNT); | |
128 | + if (ruleNode.isDebugMode()) { | |
129 | + systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType()); | |
130 | + } | |
131 | + try { | |
132 | + tbNode.onMsg(msg.getCtx(), msg.getMsg()); | |
133 | + } catch (Exception e) { | |
134 | + msg.getCtx().tellFailure(msg.getMsg(), e); | |
135 | + } | |
136 | + } else { | |
137 | + tbMsg.getCallback().onFailure(new RuleNodeException("Message is processed by more then " + maxRuleNodeExecutionsPerMessage + " rule nodes!", ruleChainName, ruleNode)); | |
115 | 138 | } |
116 | 139 | } |
117 | 140 | ... | ... |
... | ... | @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; |
20 | 20 | import org.springframework.beans.factory.annotation.Value; |
21 | 21 | import org.springframework.boot.context.event.ApplicationReadyEvent; |
22 | 22 | import org.springframework.context.event.EventListener; |
23 | +import org.springframework.core.annotation.Order; | |
23 | 24 | import org.springframework.stereotype.Service; |
24 | 25 | import org.thingsboard.common.util.ThingsBoardThreadFactory; |
25 | 26 | import org.thingsboard.server.actors.ActorSystemContext; |
... | ... | @@ -113,6 +114,7 @@ public class DefaultActorService implements ActorService { |
113 | 114 | } |
114 | 115 | |
115 | 116 | @EventListener(ApplicationReadyEvent.class) |
117 | + @Order(value = 2) | |
116 | 118 | public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { |
117 | 119 | log.info("Received application ready event. Sending application init message to actor system"); |
118 | 120 | appActor.tellWithHighPriority(new AppInitMsg()); | ... | ... |
... | ... | @@ -19,9 +19,11 @@ import lombok.extern.slf4j.Slf4j; |
19 | 19 | import org.thingsboard.server.actors.ActorSystemContext; |
20 | 20 | import org.thingsboard.server.actors.TbActorCtx; |
21 | 21 | import org.thingsboard.server.actors.stats.StatsPersistTick; |
22 | +import org.thingsboard.server.common.data.TenantProfile; | |
22 | 23 | import org.thingsboard.server.common.data.id.EntityId; |
23 | 24 | import org.thingsboard.server.common.data.id.TenantId; |
24 | 25 | import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; |
26 | +import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; | |
25 | 27 | import org.thingsboard.server.common.msg.TbMsg; |
26 | 28 | import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; |
27 | 29 | import org.thingsboard.server.common.msg.queue.RuleNodeException; |
... | ... | @@ -39,6 +41,10 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract |
39 | 41 | this.entityId = id; |
40 | 42 | } |
41 | 43 | |
44 | + protected TenantProfileConfiguration getTenantProfileConfiguration() { | |
45 | + return systemContext.getTenantProfileCache().get(tenantId).getProfileData().getConfiguration(); | |
46 | + } | |
47 | + | |
42 | 48 | public abstract String getComponentName(); |
43 | 49 | |
44 | 50 | public abstract void start(TbActorCtx context) throws Exception; | ... | ... |
... | ... | @@ -29,6 +29,7 @@ import org.thingsboard.server.actors.device.DeviceActorCreator; |
29 | 29 | import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; |
30 | 30 | import org.thingsboard.server.actors.service.ContextBasedCreator; |
31 | 31 | import org.thingsboard.server.actors.service.DefaultActorService; |
32 | +import org.thingsboard.server.common.data.ApiUsageState; | |
32 | 33 | import org.thingsboard.server.common.data.EntityType; |
33 | 34 | import org.thingsboard.server.common.data.Tenant; |
34 | 35 | import org.thingsboard.server.common.data.TenantProfile; |
... | ... | @@ -62,6 +63,7 @@ public class TenantActor extends RuleChainManagerActor { |
62 | 63 | |
63 | 64 | private boolean isRuleEngineForCurrentTenant; |
64 | 65 | private boolean isCore; |
66 | + private ApiUsageState apiUsageState; | |
65 | 67 | |
66 | 68 | private TenantActor(ActorSystemContext systemContext, TenantId tenantId) { |
67 | 69 | super(systemContext, tenantId); |
... | ... | @@ -79,21 +81,24 @@ public class TenantActor extends RuleChainManagerActor { |
79 | 81 | cantFindTenant = true; |
80 | 82 | log.info("[{}] Started tenant actor for missing tenant.", tenantId); |
81 | 83 | } else { |
84 | + apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenant.getId())); | |
85 | + | |
82 | 86 | // This Service may be started for specific tenant only. |
83 | 87 | Optional<TenantId> isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant(); |
84 | 88 | |
85 | - // TODO: Tenant Profile from cache | |
86 | - | |
87 | - TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(tenantId, tenant.getTenantProfileId()); | |
89 | + TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId()); | |
88 | 90 | |
89 | - isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); | |
90 | 91 | isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); |
91 | - | |
92 | + isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); | |
92 | 93 | if (isRuleEngineForCurrentTenant) { |
93 | 94 | try { |
94 | 95 | if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) { |
95 | - log.info("[{}] Going to init rule chains", tenantId); | |
96 | - initRuleChains(); | |
96 | + if (apiUsageState.isReExecEnabled()) { | |
97 | + log.info("[{}] Going to init rule chains", tenantId); | |
98 | + initRuleChains(); | |
99 | + } else { | |
100 | + log.info("[{}] Skip init of the rule chains due to API limits", tenantId); | |
101 | + } | |
97 | 102 | } else { |
98 | 103 | isRuleEngineForCurrentTenant = false; |
99 | 104 | } |
... | ... | @@ -105,8 +110,6 @@ public class TenantActor extends RuleChainManagerActor { |
105 | 110 | } |
106 | 111 | } catch (Exception e) { |
107 | 112 | log.warn("[{}] Unknown failure", tenantId, e); |
108 | -// TODO: throw this in 3.1? | |
109 | -// throw new TbActorException("Failed to init actor", e); | |
110 | 113 | } |
111 | 114 | } |
112 | 115 | |
... | ... | @@ -122,7 +125,7 @@ public class TenantActor extends RuleChainManagerActor { |
122 | 125 | if (msg.getMsgType().equals(MsgType.QUEUE_TO_RULE_ENGINE_MSG)) { |
123 | 126 | QueueToRuleEngineMsg queueMsg = (QueueToRuleEngineMsg) msg; |
124 | 127 | queueMsg.getTbMsg().getCallback().onSuccess(); |
125 | - } else if (msg.getMsgType().equals(MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG)){ | |
128 | + } else if (msg.getMsgType().equals(MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG)) { | |
126 | 129 | TransportToDeviceActorMsgWrapper transportMsg = (TransportToDeviceActorMsgWrapper) msg; |
127 | 130 | transportMsg.getCallback().onSuccess(); |
128 | 131 | } |
... | ... | @@ -180,26 +183,33 @@ public class TenantActor extends RuleChainManagerActor { |
180 | 183 | return; |
181 | 184 | } |
182 | 185 | TbMsg tbMsg = msg.getTbMsg(); |
183 | - if (tbMsg.getRuleChainId() == null) { | |
184 | - if (getRootChainActor() != null) { | |
185 | - getRootChainActor().tell(msg); | |
186 | + if (apiUsageState.isReExecEnabled()) { | |
187 | + if (tbMsg.getRuleChainId() == null) { | |
188 | + if (getRootChainActor() != null) { | |
189 | + getRootChainActor().tell(msg); | |
190 | + } else { | |
191 | + tbMsg.getCallback().onFailure(new RuleEngineException("No Root Rule Chain available!")); | |
192 | + log.info("[{}] No Root Chain: {}", tenantId, msg); | |
193 | + } | |
186 | 194 | } else { |
187 | - tbMsg.getCallback().onFailure(new RuleEngineException("No Root Rule Chain available!")); | |
188 | - log.info("[{}] No Root Chain: {}", tenantId, msg); | |
195 | + try { | |
196 | + ctx.tell(new TbEntityActorId(tbMsg.getRuleChainId()), msg); | |
197 | + } catch (TbActorNotRegisteredException ex) { | |
198 | + log.trace("Received message for non-existing rule chain: [{}]", tbMsg.getRuleChainId()); | |
199 | + //TODO: 3.1 Log it to dead letters queue; | |
200 | + tbMsg.getCallback().onSuccess(); | |
201 | + } | |
189 | 202 | } |
190 | 203 | } else { |
191 | - try { | |
192 | - ctx.tell(new TbEntityActorId(tbMsg.getRuleChainId()), msg); | |
193 | - } catch (TbActorNotRegisteredException ex) { | |
194 | - log.trace("Received message for non-existing rule chain: [{}]", tbMsg.getRuleChainId()); | |
195 | - //TODO: 3.1 Log it to dead letters queue; | |
196 | - tbMsg.getCallback().onSuccess(); | |
197 | - } | |
204 | + log.trace("[{}] Ack message because Rule Engine is disabled", tenantId); | |
205 | + tbMsg.getCallback().onSuccess(); | |
198 | 206 | } |
199 | 207 | } |
200 | 208 | |
201 | 209 | private void onRuleChainMsg(RuleChainAwareMsg msg) { |
202 | - getOrCreateActor(msg.getRuleChainId()).tell(msg); | |
210 | + if (apiUsageState.isReExecEnabled()) { | |
211 | + getOrCreateActor(msg.getRuleChainId()).tell(msg); | |
212 | + } | |
203 | 213 | } |
204 | 214 | |
205 | 215 | private void onToDeviceActorMsg(DeviceAwareMsg msg, boolean priority) { |
... | ... | @@ -215,7 +225,17 @@ public class TenantActor extends RuleChainManagerActor { |
215 | 225 | } |
216 | 226 | |
217 | 227 | private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { |
218 | - if (msg.getEntityId().getEntityType() == EntityType.EDGE) { | |
228 | + if (msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { | |
229 | + ApiUsageState old = apiUsageState; | |
230 | + apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId)); | |
231 | + if (old.isReExecEnabled() && !apiUsageState.isReExecEnabled()) { | |
232 | + log.info("[{}] Received API state update. Going to DISABLE Rule Engine execution.", tenantId); | |
233 | + destroyRuleChains(); | |
234 | + } else if (!old.isReExecEnabled() && apiUsageState.isReExecEnabled()) { | |
235 | + log.info("[{}] Received API state update. Going to ENABLE Rule Engine execution.", tenantId); | |
236 | + initRuleChains(); | |
237 | + } | |
238 | + } else if (msg.getEntityId().getEntityType() == EntityType.EDGE) { | |
219 | 239 | EdgeId edgeId = new EdgeId(msg.getEntityId().getId()); |
220 | 240 | EdgeRpcService edgeRpcService = systemContext.getEdgeRpcService(); |
221 | 241 | if (msg.getEvent() == ComponentLifecycleEvent.DELETED) { | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.config; | |
17 | + | |
18 | +import lombok.extern.slf4j.Slf4j; | |
19 | +import org.springframework.beans.factory.annotation.Autowired; | |
20 | +import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; | |
21 | +import org.springframework.security.crypto.keygen.StringKeyGenerator; | |
22 | +import org.springframework.security.oauth2.client.registration.ClientRegistration; | |
23 | +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; | |
24 | +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; | |
25 | +import org.springframework.security.oauth2.core.AuthorizationGrantType; | |
26 | +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; | |
27 | +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; | |
28 | +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; | |
29 | +import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; | |
30 | +import org.springframework.security.oauth2.core.oidc.OidcScopes; | |
31 | +import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; | |
32 | +import org.springframework.security.web.util.UrlUtils; | |
33 | +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; | |
34 | +import org.springframework.stereotype.Service; | |
35 | +import org.springframework.util.CollectionUtils; | |
36 | +import org.springframework.util.StringUtils; | |
37 | +import org.springframework.web.util.UriComponents; | |
38 | +import org.springframework.web.util.UriComponentsBuilder; | |
39 | +import org.thingsboard.server.dao.oauth2.OAuth2Configuration; | |
40 | +import org.thingsboard.server.utils.MiscUtils; | |
41 | + | |
42 | +import javax.servlet.http.HttpServletRequest; | |
43 | +import java.nio.charset.StandardCharsets; | |
44 | +import java.security.MessageDigest; | |
45 | +import java.security.NoSuchAlgorithmException; | |
46 | +import java.util.Base64; | |
47 | +import java.util.HashMap; | |
48 | +import java.util.Map; | |
49 | + | |
50 | +@Service | |
51 | +@Slf4j | |
52 | +public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { | |
53 | + public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization"; | |
54 | + public static final String DEFAULT_LOGIN_PROCESSING_URI = "/login/oauth2/code/"; | |
55 | + private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"; | |
56 | + private static final char PATH_DELIMITER = '/'; | |
57 | + | |
58 | + private final AntPathRequestMatcher authorizationRequestMatcher = new AntPathRequestMatcher( | |
59 | + DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}"); | |
60 | + private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder()); | |
61 | + private final StringKeyGenerator secureKeyGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96); | |
62 | + | |
63 | + @Autowired | |
64 | + private ClientRegistrationRepository clientRegistrationRepository; | |
65 | + | |
66 | + @Autowired(required = false) | |
67 | + private OAuth2Configuration oauth2Configuration; | |
68 | + | |
69 | + | |
70 | + @Override | |
71 | + public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { | |
72 | + String registrationId = this.resolveRegistrationId(request); | |
73 | + String redirectUriAction = getAction(request, "login"); | |
74 | + return resolve(request, registrationId, redirectUriAction); | |
75 | + } | |
76 | + | |
77 | + @Override | |
78 | + public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId) { | |
79 | + if (registrationId == null) { | |
80 | + return null; | |
81 | + } | |
82 | + String redirectUriAction = getAction(request, "authorize"); | |
83 | + return resolve(request, registrationId, redirectUriAction); | |
84 | + } | |
85 | + | |
86 | + private String getAction(HttpServletRequest request, String defaultAction) { | |
87 | + String action = request.getParameter("action"); | |
88 | + if (action == null) { | |
89 | + return defaultAction; | |
90 | + } | |
91 | + return action; | |
92 | + } | |
93 | + | |
94 | + private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) { | |
95 | + if (registrationId == null) { | |
96 | + return null; | |
97 | + } | |
98 | + | |
99 | + ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId); | |
100 | + if (clientRegistration == null) { | |
101 | + throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId); | |
102 | + } | |
103 | + | |
104 | + Map<String, Object> attributes = new HashMap<>(); | |
105 | + attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); | |
106 | + | |
107 | + OAuth2AuthorizationRequest.Builder builder; | |
108 | + if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { | |
109 | + builder = OAuth2AuthorizationRequest.authorizationCode(); | |
110 | + Map<String, Object> additionalParameters = new HashMap<>(); | |
111 | + if (!CollectionUtils.isEmpty(clientRegistration.getScopes()) && | |
112 | + clientRegistration.getScopes().contains(OidcScopes.OPENID)) { | |
113 | + // Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest | |
114 | + // scope | |
115 | + // REQUIRED. OpenID Connect requests MUST contain the "openid" scope value. | |
116 | + addNonceParameters(attributes, additionalParameters); | |
117 | + } | |
118 | + if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) { | |
119 | + addPkceParameters(attributes, additionalParameters); | |
120 | + } | |
121 | + builder.additionalParameters(additionalParameters); | |
122 | + } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) { | |
123 | + builder = OAuth2AuthorizationRequest.implicit(); | |
124 | + } else { | |
125 | + throw new IllegalArgumentException("Invalid Authorization Grant Type (" + | |
126 | + clientRegistration.getAuthorizationGrantType().getValue() + | |
127 | + ") for Client Registration with Id: " + clientRegistration.getRegistrationId()); | |
128 | + } | |
129 | + | |
130 | + String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction); | |
131 | + | |
132 | + return builder | |
133 | + .clientId(clientRegistration.getClientId()) | |
134 | + .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()) | |
135 | + .redirectUri(redirectUriStr) | |
136 | + .scopes(clientRegistration.getScopes()) | |
137 | + .state(this.stateGenerator.generateKey()) | |
138 | + .attributes(attributes) | |
139 | + .build(); | |
140 | + } | |
141 | + | |
142 | + private String resolveRegistrationId(HttpServletRequest request) { | |
143 | + if (this.authorizationRequestMatcher.matches(request)) { | |
144 | + return this.authorizationRequestMatcher | |
145 | + .matcher(request).getVariables().get(REGISTRATION_ID_URI_VARIABLE_NAME); | |
146 | + } | |
147 | + return null; | |
148 | + } | |
149 | + | |
150 | + /** | |
151 | + * Expands the {@link ClientRegistration#getRedirectUriTemplate()} with following provided variables:<br/> | |
152 | + * - baseUrl (e.g. https://localhost/app) <br/> | |
153 | + * - baseScheme (e.g. https) <br/> | |
154 | + * - baseHost (e.g. localhost) <br/> | |
155 | + * - basePort (e.g. :8080) <br/> | |
156 | + * - basePath (e.g. /app) <br/> | |
157 | + * - registrationId (e.g. google) <br/> | |
158 | + * - action (e.g. login) <br/> | |
159 | + * <p/> | |
160 | + * Null variables are provided as empty strings. | |
161 | + * <p/> | |
162 | + * Default redirectUriTemplate is: {@link org.springframework.security.config.oauth2.client}.CommonOAuth2Provider#DEFAULT_REDIRECT_URL | |
163 | + * | |
164 | + * @return expanded URI | |
165 | + */ | |
166 | + private String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration, String action) { | |
167 | + Map<String, String> uriVariables = new HashMap<>(); | |
168 | + uriVariables.put("registrationId", clientRegistration.getRegistrationId()); | |
169 | + | |
170 | + UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) | |
171 | + .replacePath(request.getContextPath()) | |
172 | + .replaceQuery(null) | |
173 | + .fragment(null) | |
174 | + .build(); | |
175 | + String scheme = uriComponents.getScheme(); | |
176 | + uriVariables.put("baseScheme", scheme == null ? "" : scheme); | |
177 | + String host = uriComponents.getHost(); | |
178 | + uriVariables.put("baseHost", host == null ? "" : host); | |
179 | + // following logic is based on HierarchicalUriComponents#toUriString() | |
180 | + int port = uriComponents.getPort(); | |
181 | + uriVariables.put("basePort", port == -1 ? "" : ":" + port); | |
182 | + String path = uriComponents.getPath(); | |
183 | + if (StringUtils.hasLength(path)) { | |
184 | + if (path.charAt(0) != PATH_DELIMITER) { | |
185 | + path = PATH_DELIMITER + path; | |
186 | + } | |
187 | + } | |
188 | + uriVariables.put("basePath", path == null ? "" : path); | |
189 | + uriVariables.put("baseUrl", uriComponents.toUriString()); | |
190 | + | |
191 | + uriVariables.put("action", action == null ? "" : action); | |
192 | + | |
193 | + String redirectUri = getRedirectUri(request); | |
194 | + log.trace("Redirect URI - {}.", redirectUri); | |
195 | + | |
196 | + return UriComponentsBuilder.fromUriString(redirectUri) | |
197 | + .buildAndExpand(uriVariables) | |
198 | + .toUriString(); | |
199 | + } | |
200 | + | |
201 | + private String getRedirectUri(HttpServletRequest request) { | |
202 | + String loginProcessingUri = oauth2Configuration != null ? oauth2Configuration.getLoginProcessingUrl() : DEFAULT_LOGIN_PROCESSING_URI; | |
203 | + | |
204 | + String scheme = MiscUtils.getScheme(request); | |
205 | + String domainName = MiscUtils.getDomainName(request); | |
206 | + int port = MiscUtils.getPort(request); | |
207 | + String baseUrl = scheme + "://" + domainName; | |
208 | + if (needsPort(scheme, port)){ | |
209 | + baseUrl += ":" + port; | |
210 | + } | |
211 | + return baseUrl + loginProcessingUri; | |
212 | + } | |
213 | + | |
214 | + private boolean needsPort(String scheme, int port) { | |
215 | + boolean isHttpDefault = "http".equals(scheme.toLowerCase()) && port == 80; | |
216 | + boolean isHttpsDefault = "https".equals(scheme.toLowerCase()) && port == 443; | |
217 | + return !isHttpDefault && !isHttpsDefault; | |
218 | + } | |
219 | + | |
220 | + /** | |
221 | + * Creates nonce and its hash for use in OpenID Connect 1.0 Authentication Requests. | |
222 | + * | |
223 | + * @param attributes where the {@link OidcParameterNames#NONCE} is stored for the authentication request | |
224 | + * @param additionalParameters where the {@link OidcParameterNames#NONCE} hash is added for the authentication request | |
225 | + * | |
226 | + * @since 5.2 | |
227 | + * @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest">3.1.2.1. Authentication Request</a> | |
228 | + */ | |
229 | + private void addNonceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) { | |
230 | + try { | |
231 | + String nonce = this.secureKeyGenerator.generateKey(); | |
232 | + String nonceHash = createHash(nonce); | |
233 | + attributes.put(OidcParameterNames.NONCE, nonce); | |
234 | + additionalParameters.put(OidcParameterNames.NONCE, nonceHash); | |
235 | + } catch (NoSuchAlgorithmException e) { } | |
236 | + } | |
237 | + | |
238 | + /** | |
239 | + * Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests | |
240 | + * | |
241 | + * @param attributes where {@link PkceParameterNames#CODE_VERIFIER} is stored for the token request | |
242 | + * @param additionalParameters where {@link PkceParameterNames#CODE_CHALLENGE} and, usually, | |
243 | + * {@link PkceParameterNames#CODE_CHALLENGE_METHOD} are added to be used in the authorization request. | |
244 | + * | |
245 | + * @since 5.2 | |
246 | + * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-1.1">1.1. Protocol Flow</a> | |
247 | + * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.1">4.1. Client Creates a Code Verifier</a> | |
248 | + * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">4.2. Client Creates the Code Challenge</a> | |
249 | + */ | |
250 | + private void addPkceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) { | |
251 | + String codeVerifier = this.secureKeyGenerator.generateKey(); | |
252 | + attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier); | |
253 | + try { | |
254 | + String codeChallenge = createHash(codeVerifier); | |
255 | + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge); | |
256 | + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); | |
257 | + } catch (NoSuchAlgorithmException e) { | |
258 | + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeVerifier); | |
259 | + } | |
260 | + } | |
261 | + | |
262 | + private static String createHash(String value) throws NoSuchAlgorithmException { | |
263 | + MessageDigest md = MessageDigest.getInstance("SHA-256"); | |
264 | + byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII)); | |
265 | + return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); | |
266 | + } | |
267 | +} | ... | ... |
... | ... | @@ -32,6 +32,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe |
32 | 32 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; |
33 | 33 | import org.springframework.security.config.http.SessionCreationPolicy; |
34 | 34 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
35 | +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; | |
35 | 36 | import org.springframework.security.web.authentication.AuthenticationFailureHandler; |
36 | 37 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler; |
37 | 38 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; |
... | ... | @@ -175,6 +176,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt |
175 | 176 | web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**"); |
176 | 177 | } |
177 | 178 | |
179 | + @Autowired | |
180 | + private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver; | |
181 | + | |
178 | 182 | @Override |
179 | 183 | protected void configure(HttpSecurity http) throws Exception { |
180 | 184 | http.headers().cacheControl().and().frameOptions().disable() |
... | ... | @@ -207,8 +211,10 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt |
207 | 211 | .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class) |
208 | 212 | .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) |
209 | 213 | .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class); |
210 | - if (oauth2Configuration != null && oauth2Configuration.isEnabled()) { | |
214 | + if (oauth2Configuration != null) { | |
211 | 215 | http.oauth2Login() |
216 | + .authorizationEndpoint().authorizationRequestResolver(oAuth2AuthorizationRequestResolver) | |
217 | + .and() | |
212 | 218 | .loginPage("/oauth2Login") |
213 | 219 | .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl()) |
214 | 220 | .successHandler(oauth2AuthenticationSuccessHandler) | ... | ... |
... | ... | @@ -25,6 +25,8 @@ import org.springframework.web.bind.annotation.RequestMethod; |
25 | 25 | 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 | +import org.thingsboard.rule.engine.api.SmsService; | |
29 | +import org.thingsboard.server.common.data.sms.config.TestSmsRequest; | |
28 | 30 | import org.thingsboard.server.common.data.AdminSettings; |
29 | 31 | import org.thingsboard.server.common.data.UpdateMessage; |
30 | 32 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
... | ... | @@ -46,6 +48,9 @@ public class AdminController extends BaseController { |
46 | 48 | private MailService mailService; |
47 | 49 | |
48 | 50 | @Autowired |
51 | + private SmsService smsService; | |
52 | + | |
53 | + @Autowired | |
49 | 54 | private AdminSettingsService adminSettingsService; |
50 | 55 | |
51 | 56 | @Autowired |
... | ... | @@ -80,6 +85,8 @@ public class AdminController extends BaseController { |
80 | 85 | if (adminSettings.getKey().equals("mail")) { |
81 | 86 | mailService.updateMailConfiguration(); |
82 | 87 | ((ObjectNode) adminSettings.getJsonValue()).put("password", ""); |
88 | + } else if (adminSettings.getKey().equals("sms")) { | |
89 | + smsService.updateSmsConfiguration(); | |
83 | 90 | } |
84 | 91 | return adminSettings; |
85 | 92 | } catch (Exception e) { |
... | ... | @@ -128,6 +135,17 @@ public class AdminController extends BaseController { |
128 | 135 | } |
129 | 136 | |
130 | 137 | @PreAuthorize("hasAuthority('SYS_ADMIN')") |
138 | + @RequestMapping(value = "/settings/testSms", method = RequestMethod.POST) | |
139 | + public void sendTestSms(@RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException { | |
140 | + try { | |
141 | + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); | |
142 | + smsService.sendTestSms(testSmsRequest); | |
143 | + } catch (Exception e) { | |
144 | + throw handleException(e); | |
145 | + } | |
146 | + } | |
147 | + | |
148 | + @PreAuthorize("hasAuthority('SYS_ADMIN')") | |
131 | 149 | @RequestMapping(value = "/updates", method = RequestMethod.GET) |
132 | 150 | @ResponseBody |
133 | 151 | public UpdateMessage checkUpdates() throws ThingsboardException { | ... | ... |
... | ... | @@ -133,6 +133,7 @@ public class AlarmController extends BaseController { |
133 | 133 | long ackTs = System.currentTimeMillis(); |
134 | 134 | alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get(); |
135 | 135 | alarm.setAckTs(ackTs); |
136 | + alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK); | |
136 | 137 | logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null); |
137 | 138 | |
138 | 139 | sendNotificationMsgToEdgeService(getTenantId(), alarmId, ActionType.ALARM_ACK); |
... | ... | @@ -152,6 +153,7 @@ public class AlarmController extends BaseController { |
152 | 153 | long clearTs = System.currentTimeMillis(); |
153 | 154 | alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get(); |
154 | 155 | alarm.setClearTs(clearTs); |
156 | + alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); | |
155 | 157 | logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null); |
156 | 158 | |
157 | 159 | sendNotificationMsgToEdgeService(getTenantId(), alarmId, ActionType.ALARM_CLEAR); | ... | ... |
... | ... | @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.asset.AssetSearchQuery; |
36 | 36 | import org.thingsboard.server.common.data.audit.ActionType; |
37 | 37 | import org.thingsboard.server.common.data.edge.Edge; |
38 | 38 | import org.thingsboard.server.common.data.edge.EdgeEventType; |
39 | +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; | |
39 | 40 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
40 | 41 | import org.thingsboard.server.common.data.id.AssetId; |
41 | 42 | import org.thingsboard.server.common.data.id.CustomerId; |
... | ... | @@ -55,6 +56,8 @@ import java.util.ArrayList; |
55 | 56 | import java.util.List; |
56 | 57 | import java.util.stream.Collectors; |
57 | 58 | |
59 | +import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE; | |
60 | + | |
58 | 61 | import static org.thingsboard.server.controller.EdgeController.EDGE_ID; |
59 | 62 | |
60 | 63 | @RestController |
... | ... | @@ -95,6 +98,10 @@ public class AssetController extends BaseController { |
95 | 98 | @ResponseBody |
96 | 99 | public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException { |
97 | 100 | try { |
101 | + if (TB_SERVICE_QUEUE.equals(asset.getType())) { | |
102 | + throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS); | |
103 | + } | |
104 | + | |
98 | 105 | asset.setTenantId(getCurrentUser().getTenantId()); |
99 | 106 | |
100 | 107 | checkEntity(asset.getId(), asset, Resource.ASSET); | ... | ... |
... | ... | @@ -39,10 +39,8 @@ import org.thingsboard.server.common.data.edge.EdgeEventType; |
39 | 39 | import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
40 | 40 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
41 | 41 | import org.thingsboard.server.common.data.id.TenantId; |
42 | -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; | |
43 | 42 | import org.thingsboard.server.common.data.security.UserCredentials; |
44 | 43 | import org.thingsboard.server.dao.audit.AuditLogService; |
45 | -import org.thingsboard.server.dao.oauth2.OAuth2Service; | |
46 | 44 | import org.thingsboard.server.queue.util.TbCoreComponent; |
47 | 45 | import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; |
48 | 46 | import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; |
... | ... | @@ -85,9 +83,6 @@ public class AuthController extends BaseController { |
85 | 83 | @Autowired |
86 | 84 | private AuditLogService auditLogService; |
87 | 85 | |
88 | - @Autowired | |
89 | - private OAuth2Service oauth2Service; | |
90 | - | |
91 | 86 | @PreAuthorize("isAuthenticated()") |
92 | 87 | @RequestMapping(value = "/auth/user", method = RequestMethod.GET) |
93 | 88 | public @ResponseBody User getUser() throws ThingsboardException { |
... | ... | @@ -175,7 +170,8 @@ public class AuthController extends BaseController { |
175 | 170 | try { |
176 | 171 | String email = resetPasswordByEmailRequest.get("email").asText(); |
177 | 172 | UserCredentials userCredentials = userService.requestPasswordReset(TenantId.SYS_TENANT_ID, email); |
178 | - String baseUrl = MiscUtils.constructBaseUrl(request); | |
173 | + User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId()); | |
174 | + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); | |
179 | 175 | String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, |
180 | 176 | userCredentials.getResetToken()); |
181 | 177 | |
... | ... | @@ -223,7 +219,7 @@ public class AuthController extends BaseController { |
223 | 219 | User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId()); |
224 | 220 | UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); |
225 | 221 | SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal); |
226 | - String baseUrl = MiscUtils.constructBaseUrl(request); | |
222 | + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); | |
227 | 223 | String loginUrl = String.format("%s/login", baseUrl); |
228 | 224 | String email = user.getEmail(); |
229 | 225 | |
... | ... | @@ -272,7 +268,7 @@ public class AuthController extends BaseController { |
272 | 268 | User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId()); |
273 | 269 | UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); |
274 | 270 | SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), principal); |
275 | - String baseUrl = MiscUtils.constructBaseUrl(request); | |
271 | + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); | |
276 | 272 | String loginUrl = String.format("%s/login", baseUrl); |
277 | 273 | String email = user.getEmail(); |
278 | 274 | mailService.sendPasswordWasResetEmail(loginUrl, email); |
... | ... | @@ -342,14 +338,4 @@ public class AuthController extends BaseController { |
342 | 338 | throw handleException(e); |
343 | 339 | } |
344 | 340 | } |
345 | - | |
346 | - @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) | |
347 | - @ResponseBody | |
348 | - public List<OAuth2ClientInfo> getOAuth2Clients() throws ThingsboardException { | |
349 | - try { | |
350 | - return oauth2Service.getOAuth2Clients(); | |
351 | - } catch (Exception e) { | |
352 | - throw handleException(e); | |
353 | - } | |
354 | - } | |
355 | 341 | } | ... | ... |
... | ... | @@ -100,6 +100,8 @@ import org.thingsboard.server.dao.entityview.EntityViewService; |
100 | 100 | import org.thingsboard.server.dao.exception.DataValidationException; |
101 | 101 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
102 | 102 | import org.thingsboard.server.dao.model.ModelConstants; |
103 | +import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; | |
104 | +import org.thingsboard.server.dao.oauth2.OAuth2Service; | |
103 | 105 | import org.thingsboard.server.dao.relation.RelationService; |
104 | 106 | import org.thingsboard.server.dao.rule.RuleChainService; |
105 | 107 | import org.thingsboard.server.dao.tenant.TenantProfileService; |
... | ... | @@ -117,6 +119,7 @@ import org.thingsboard.server.service.edge.EdgeNotificationService; |
117 | 119 | import org.thingsboard.server.service.edge.rpc.EdgeGrpcService; |
118 | 120 | import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService; |
119 | 121 | import org.thingsboard.server.service.profile.TbDeviceProfileCache; |
122 | +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; | |
120 | 123 | import org.thingsboard.server.service.queue.TbClusterService; |
121 | 124 | import org.thingsboard.server.service.security.model.SecurityUser; |
122 | 125 | import org.thingsboard.server.service.security.permission.AccessControlService; |
... | ... | @@ -187,6 +190,12 @@ public abstract class BaseController { |
187 | 190 | protected DashboardService dashboardService; |
188 | 191 | |
189 | 192 | @Autowired |
193 | + protected OAuth2Service oAuth2Service; | |
194 | + | |
195 | + @Autowired | |
196 | + protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService; | |
197 | + | |
198 | + @Autowired | |
190 | 199 | protected ComponentDiscoveryService componentDescriptorService; |
191 | 200 | |
192 | 201 | @Autowired |
... | ... | @@ -223,6 +232,9 @@ public abstract class BaseController { |
223 | 232 | protected TbQueueProducerProvider producerProvider; |
224 | 233 | |
225 | 234 | @Autowired |
235 | + protected TbTenantProfileCache tenantProfileCache; | |
236 | + | |
237 | + @Autowired | |
226 | 238 | protected TbDeviceProfileCache deviceProfileCache; |
227 | 239 | |
228 | 240 | @Autowired(required = false) |
... | ... | @@ -747,6 +759,12 @@ public abstract class BaseController { |
747 | 759 | case ASSIGNED_TO_TENANT: |
748 | 760 | msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; |
749 | 761 | break; |
762 | + case PROVISION_SUCCESS: | |
763 | + msgType = DataConstants.PROVISION_SUCCESS; | |
764 | + break; | |
765 | + case PROVISION_FAILURE: | |
766 | + msgType = DataConstants.PROVISION_FAILURE; | |
767 | + break; | |
750 | 768 | case ASSIGNED_TO_EDGE: |
751 | 769 | msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE; |
752 | 770 | break; | ... | ... |
... | ... | @@ -54,6 +54,7 @@ import org.thingsboard.server.common.data.id.TenantId; |
54 | 54 | import org.thingsboard.server.common.data.page.PageData; |
55 | 55 | import org.thingsboard.server.common.data.page.PageLink; |
56 | 56 | import org.thingsboard.server.common.data.page.TimePageLink; |
57 | +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | |
57 | 58 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
58 | 59 | import org.thingsboard.server.common.msg.TbMsg; |
59 | 60 | import org.thingsboard.server.common.msg.TbMsgDataType; |
... | ... | @@ -122,8 +123,11 @@ public class DeviceController extends BaseController { |
122 | 123 | |
123 | 124 | Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); |
124 | 125 | |
126 | + tbClusterService.onDeviceChange(savedDevice, null); | |
125 | 127 | tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(), |
126 | 128 | savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null); |
129 | + tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), | |
130 | + device.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); | |
127 | 131 | |
128 | 132 | if (device.getId() != null) { |
129 | 133 | sendNotificationMsgToEdgeService(savedDevice.getTenantId(), savedDevice.getId(), ActionType.UPDATED); |
... | ... | @@ -156,6 +160,9 @@ public class DeviceController extends BaseController { |
156 | 160 | Device device = checkDeviceId(deviceId, Operation.DELETE); |
157 | 161 | deviceService.deleteDevice(getCurrentUser().getTenantId(), deviceId); |
158 | 162 | |
163 | + tbClusterService.onDeviceDeleted(device, null); | |
164 | + tbClusterService.onEntityStateChange(device.getTenantId(), deviceId, ComponentLifecycleEvent.DELETED); | |
165 | + | |
159 | 166 | logEntityAction(deviceId, device, |
160 | 167 | device.getCustomerId(), |
161 | 168 | ActionType.DELETED, null, strDeviceId); | ... | ... |
... | ... | @@ -94,7 +94,6 @@ public class DeviceProfileController extends BaseController { |
94 | 94 | |
95 | 95 | DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); |
96 | 96 | |
97 | - deviceProfileCache.put(savedDeviceProfile); | |
98 | 97 | tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); |
99 | 98 | tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), savedDeviceProfile.getId(), |
100 | 99 | created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); |
... | ... | @@ -120,7 +119,6 @@ public class DeviceProfileController extends BaseController { |
120 | 119 | DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); |
121 | 120 | DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE); |
122 | 121 | deviceProfileService.deleteDeviceProfile(getTenantId(), deviceProfileId); |
123 | - deviceProfileCache.evict(deviceProfileId); | |
124 | 122 | |
125 | 123 | tbClusterService.onDeviceProfileDelete(deviceProfile, null); |
126 | 124 | tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED); |
... | ... | @@ -192,10 +190,11 @@ public class DeviceProfileController extends BaseController { |
192 | 190 | @RequestParam int page, |
193 | 191 | @RequestParam(required = false) String textSearch, |
194 | 192 | @RequestParam(required = false) String sortProperty, |
195 | - @RequestParam(required = false) String sortOrder) throws ThingsboardException { | |
193 | + @RequestParam(required = false) String sortOrder, | |
194 | + @RequestParam(required = false) String transportType) throws ThingsboardException { | |
196 | 195 | try { |
197 | 196 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
198 | - return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink)); | |
197 | + return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink, transportType)); | |
199 | 198 | } catch (Exception e) { |
200 | 199 | throw handleException(e); |
201 | 200 | } | ... | ... |
application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.controller; | |
17 | + | |
18 | +import lombok.extern.slf4j.Slf4j; | |
19 | +import org.springframework.http.HttpStatus; | |
20 | +import org.springframework.security.access.prepost.PreAuthorize; | |
21 | +import org.springframework.web.bind.annotation.*; | |
22 | +import org.thingsboard.server.common.data.EntityType; | |
23 | +import org.thingsboard.server.common.data.audit.ActionType; | |
24 | +import org.thingsboard.server.common.data.exception.ThingsboardException; | |
25 | +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; | |
26 | +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; | |
27 | +import org.thingsboard.server.queue.util.TbCoreComponent; | |
28 | +import org.thingsboard.server.service.security.permission.Operation; | |
29 | +import org.thingsboard.server.service.security.permission.Resource; | |
30 | + | |
31 | +import java.util.List; | |
32 | + | |
33 | +@RestController | |
34 | +@TbCoreComponent | |
35 | +@RequestMapping("/api/oauth2/config/template") | |
36 | +@Slf4j | |
37 | +public class OAuth2ConfigTemplateController extends BaseController { | |
38 | + private static final String CLIENT_REGISTRATION_TEMPLATE_ID = "clientRegistrationTemplateId"; | |
39 | + | |
40 | + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") | |
41 | + @RequestMapping(method = RequestMethod.POST) | |
42 | + @ResponseStatus(value = HttpStatus.OK) | |
43 | + public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(@RequestBody OAuth2ClientRegistrationTemplate clientRegistrationTemplate) throws ThingsboardException { | |
44 | + try { | |
45 | + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.WRITE); | |
46 | + return oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); | |
47 | + } catch (Exception e) { | |
48 | + throw handleException(e); | |
49 | + } | |
50 | + } | |
51 | + | |
52 | + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") | |
53 | + @RequestMapping(value = "/{clientRegistrationTemplateId}", method = RequestMethod.DELETE) | |
54 | + @ResponseStatus(value = HttpStatus.OK) | |
55 | + public void deleteClientRegistrationTemplate(@PathVariable(CLIENT_REGISTRATION_TEMPLATE_ID) String strClientRegistrationTemplateId) throws ThingsboardException { | |
56 | + checkParameter(CLIENT_REGISTRATION_TEMPLATE_ID, strClientRegistrationTemplateId); | |
57 | + try { | |
58 | + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.DELETE); | |
59 | + OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId = new OAuth2ClientRegistrationTemplateId(toUUID(strClientRegistrationTemplateId)); | |
60 | + oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplateId); | |
61 | + } catch (Exception e) { | |
62 | + throw handleException(e); | |
63 | + } | |
64 | + } | |
65 | + | |
66 | + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") | |
67 | + @RequestMapping(method = RequestMethod.GET, produces = "application/json") | |
68 | + @ResponseBody | |
69 | + public List<OAuth2ClientRegistrationTemplate> getClientRegistrationTemplates() throws ThingsboardException { | |
70 | + try { | |
71 | + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.READ); | |
72 | + return oAuth2ConfigTemplateService.findAllClientRegistrationTemplates(); | |
73 | + } catch (Exception e) { | |
74 | + throw handleException(e); | |
75 | + } | |
76 | + } | |
77 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.controller; | |
17 | + | |
18 | +import lombok.extern.slf4j.Slf4j; | |
19 | +import org.springframework.beans.factory.annotation.Autowired; | |
20 | +import org.springframework.http.HttpStatus; | |
21 | +import org.springframework.security.access.prepost.PreAuthorize; | |
22 | +import org.springframework.web.bind.annotation.*; | |
23 | +import org.thingsboard.server.common.data.exception.ThingsboardException; | |
24 | +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; | |
25 | +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; | |
26 | +import org.thingsboard.server.common.data.oauth2.SchemeType; | |
27 | +import org.thingsboard.server.dao.oauth2.OAuth2Configuration; | |
28 | +import org.thingsboard.server.queue.util.TbCoreComponent; | |
29 | +import org.thingsboard.server.service.security.permission.Operation; | |
30 | +import org.thingsboard.server.service.security.permission.Resource; | |
31 | +import org.thingsboard.server.utils.MiscUtils; | |
32 | + | |
33 | +import javax.servlet.http.HttpServletRequest; | |
34 | +import java.util.List; | |
35 | + | |
36 | +@RestController | |
37 | +@TbCoreComponent | |
38 | +@RequestMapping("/api") | |
39 | +@Slf4j | |
40 | +public class OAuth2Controller extends BaseController { | |
41 | + | |
42 | + @Autowired | |
43 | + private OAuth2Configuration oAuth2Configuration; | |
44 | + | |
45 | + @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) | |
46 | + @ResponseBody | |
47 | + public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { | |
48 | + try { | |
49 | + return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request)); | |
50 | + } catch (Exception e) { | |
51 | + throw handleException(e); | |
52 | + } | |
53 | + } | |
54 | + | |
55 | + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") | |
56 | + @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json") | |
57 | + @ResponseBody | |
58 | + public OAuth2ClientsParams getCurrentOAuth2Params() throws ThingsboardException { | |
59 | + try { | |
60 | + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); | |
61 | + return oAuth2Service.findOAuth2Params(); | |
62 | + } catch (Exception e) { | |
63 | + throw handleException(e); | |
64 | + } | |
65 | + } | |
66 | + | |
67 | + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") | |
68 | + @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) | |
69 | + @ResponseStatus(value = HttpStatus.OK) | |
70 | + public OAuth2ClientsParams saveOAuth2Params(@RequestBody OAuth2ClientsParams oauth2Params) throws ThingsboardException { | |
71 | + try { | |
72 | + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE); | |
73 | + oAuth2Service.saveOAuth2Params(oauth2Params); | |
74 | + return oAuth2Service.findOAuth2Params(); | |
75 | + } catch (Exception e) { | |
76 | + throw handleException(e); | |
77 | + } | |
78 | + } | |
79 | + | |
80 | + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") | |
81 | + @RequestMapping(value = "/oauth2/loginProcessingUrl", method = RequestMethod.GET) | |
82 | + @ResponseBody | |
83 | + public String getLoginProcessingUrl() throws ThingsboardException { | |
84 | + try { | |
85 | + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); | |
86 | + return "\"" + oAuth2Configuration.getLoginProcessingUrl() + "\""; | |
87 | + } catch (Exception e) { | |
88 | + throw handleException(e); | |
89 | + } | |
90 | + } | |
91 | +} | ... | ... |
... | ... | @@ -173,10 +173,12 @@ public class RuleChainController extends BaseController { |
173 | 173 | public RuleChain saveRuleChain(@RequestBody DefaultRuleChainCreateRequest request) throws ThingsboardException { |
174 | 174 | try { |
175 | 175 | checkNotNull(request); |
176 | - checkNotNull(request.getName()); | |
176 | + checkParameter(request.getName(), "name"); | |
177 | 177 | |
178 | 178 | RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName()); |
179 | 179 | |
180 | + tbClusterService.onEntityStateChange(savedRuleChain.getTenantId(), savedRuleChain.getId(), ComponentLifecycleEvent.CREATED); | |
181 | + | |
180 | 182 | logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null); |
181 | 183 | |
182 | 184 | return savedRuleChain; |
... | ... | @@ -377,7 +379,7 @@ public class RuleChainController extends BaseController { |
377 | 379 | String errorText = ""; |
378 | 380 | ScriptEngine engine = null; |
379 | 381 | try { |
380 | - engine = new RuleNodeJsScriptEngine(jsInvokeService, getCurrentUser().getId(), script, argNames); | |
382 | + engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, getCurrentUser().getId(), script, argNames); | |
381 | 383 | TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); |
382 | 384 | switch (scriptType) { |
383 | 385 | case "update": | ... | ... |
... | ... | @@ -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,6 +395,11 @@ 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 | } |
398 | + for (AttributeKvEntry attributeKvEntry : attributes) { | |
399 | + if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) { | |
400 | + return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST); | |
401 | + } | |
402 | + } | |
395 | 403 | SecurityUser user = getCurrentUser(); |
396 | 404 | return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { |
397 | 405 | tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback<Void>() { |
... | ... | @@ -435,9 +443,13 @@ public class TelemetryController extends BaseController { |
435 | 443 | if (entries.isEmpty()) { |
436 | 444 | return getImmediateDeferredResult("No timeseries data found in request body!", HttpStatus.BAD_REQUEST); |
437 | 445 | } |
438 | - SecurityUser user = getCurrentUser(); | |
439 | 446 | return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_TELEMETRY, entityIdSrc, (result, tenantId, entityId) -> { |
440 | - 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>() { | |
441 | 453 | @Override |
442 | 454 | public void onSuccess(@Nullable Void tmp) { |
443 | 455 | result.setResult(new ResponseEntity(HttpStatus.OK)); | ... | ... |
... | ... | @@ -91,6 +91,10 @@ public class TenantController extends BaseController { |
91 | 91 | if (newTenant) { |
92 | 92 | installScripts.createDefaultRuleChains(tenant.getId()); |
93 | 93 | } |
94 | + tenantProfileCache.evict(tenant.getId()); | |
95 | + tbClusterService.onTenantChange(tenant, null); | |
96 | + tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(), | |
97 | + newTenant ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); | |
94 | 98 | return tenant; |
95 | 99 | } catch (Exception e) { |
96 | 100 | throw handleException(e); |
... | ... | @@ -104,8 +108,10 @@ public class TenantController extends BaseController { |
104 | 108 | checkParameter("tenantId", strTenantId); |
105 | 109 | try { |
106 | 110 | TenantId tenantId = new TenantId(toUUID(strTenantId)); |
107 | - checkTenantId(tenantId, Operation.DELETE); | |
111 | + Tenant tenant = checkTenantId(tenantId, Operation.DELETE); | |
108 | 112 | tenantService.deleteTenant(tenantId); |
113 | + tenantProfileCache.evict(tenantId); | |
114 | + tbClusterService.onTenantDelete(tenant, null); | |
109 | 115 | tbClusterService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED); |
110 | 116 | } catch (Exception e) { |
111 | 117 | throw handleException(e); | ... | ... |
... | ... | @@ -29,9 +29,12 @@ import org.springframework.web.bind.annotation.RestController; |
29 | 29 | import org.thingsboard.server.common.data.EntityInfo; |
30 | 30 | import org.thingsboard.server.common.data.TenantProfile; |
31 | 31 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
32 | +import org.thingsboard.server.common.data.id.TenantId; | |
32 | 33 | import org.thingsboard.server.common.data.id.TenantProfileId; |
33 | 34 | import org.thingsboard.server.common.data.page.PageData; |
34 | 35 | import org.thingsboard.server.common.data.page.PageLink; |
36 | +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | |
37 | +import org.thingsboard.server.dao.exception.DataValidationException; | |
35 | 38 | import org.thingsboard.server.queue.util.TbCoreComponent; |
36 | 39 | import org.thingsboard.server.service.security.permission.Operation; |
37 | 40 | import org.thingsboard.server.service.security.permission.Resource; |
... | ... | @@ -93,8 +96,12 @@ public class TenantProfileController extends BaseController { |
93 | 96 | } |
94 | 97 | |
95 | 98 | tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile)); |
99 | + tenantProfileCache.put(tenantProfile); | |
100 | + tbClusterService.onTenantProfileChange(tenantProfile, null); | |
101 | + tbClusterService.onEntityStateChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), | |
102 | + newTenantProfile ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); | |
96 | 103 | return tenantProfile; |
97 | - } catch (Exception e) { | |
104 | + } catch (Exception e) { | |
98 | 105 | throw handleException(e); |
99 | 106 | } |
100 | 107 | } |
... | ... | @@ -106,8 +113,9 @@ public class TenantProfileController extends BaseController { |
106 | 113 | checkParameter("tenantProfileId", strTenantProfileId); |
107 | 114 | try { |
108 | 115 | TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); |
109 | - checkTenantProfileId(tenantProfileId, Operation.DELETE); | |
116 | + TenantProfile profile = checkTenantProfileId(tenantProfileId, Operation.DELETE); | |
110 | 117 | tenantProfileService.deleteTenantProfile(getTenantId(), tenantProfileId); |
118 | + tbClusterService.onTenantProfileDelete(profile, null); | |
111 | 119 | } catch (Exception e) { |
112 | 120 | throw handleException(e); |
113 | 121 | } | ... | ... |
... | ... | @@ -52,6 +52,7 @@ import org.thingsboard.server.service.security.model.token.JwtToken; |
52 | 52 | import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
53 | 53 | import org.thingsboard.server.service.security.permission.Operation; |
54 | 54 | import org.thingsboard.server.service.security.permission.Resource; |
55 | +import org.thingsboard.server.service.security.system.SystemSecurityService; | |
55 | 56 | import org.thingsboard.server.utils.MiscUtils; |
56 | 57 | |
57 | 58 | import javax.servlet.http.HttpServletRequest; |
... | ... | @@ -78,6 +79,9 @@ public class UserController extends BaseController { |
78 | 79 | @Autowired |
79 | 80 | private RefreshTokenRepository refreshTokenRepository; |
80 | 81 | |
82 | + @Autowired | |
83 | + private SystemSecurityService systemSecurityService; | |
84 | + | |
81 | 85 | |
82 | 86 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
83 | 87 | @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) |
... | ... | @@ -146,7 +150,7 @@ public class UserController extends BaseController { |
146 | 150 | if (sendEmail) { |
147 | 151 | SecurityUser authUser = getCurrentUser(); |
148 | 152 | UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), savedUser.getId()); |
149 | - String baseUrl = MiscUtils.constructBaseUrl(request); | |
153 | + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); | |
150 | 154 | String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, |
151 | 155 | userCredentials.getActivateToken()); |
152 | 156 | String email = savedUser.getEmail(); |
... | ... | @@ -189,7 +193,7 @@ public class UserController extends BaseController { |
189 | 193 | |
190 | 194 | UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId()); |
191 | 195 | if (!userCredentials.isEnabled()) { |
192 | - String baseUrl = MiscUtils.constructBaseUrl(request); | |
196 | + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); | |
193 | 197 | String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, |
194 | 198 | userCredentials.getActivateToken()); |
195 | 199 | mailService.sendActivationEmail(activateUrl, email); |
... | ... | @@ -214,7 +218,7 @@ public class UserController extends BaseController { |
214 | 218 | SecurityUser authUser = getCurrentUser(); |
215 | 219 | UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId()); |
216 | 220 | if (!userCredentials.isEnabled()) { |
217 | - String baseUrl = MiscUtils.constructBaseUrl(request); | |
221 | + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); | |
218 | 222 | String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, |
219 | 223 | userCredentials.getActivateToken()); |
220 | 224 | return activateUrl; | ... | ... |
... | ... | @@ -194,6 +194,7 @@ public class ThingsboardInstallService { |
194 | 194 | dataUpdateService.updateData("3.1.1"); |
195 | 195 | log.info("Updating system data..."); |
196 | 196 | systemDataLoaderService.updateSystemWidgets(); |
197 | + systemDataLoaderService.createOAuth2Templates(); | |
197 | 198 | break; |
198 | 199 | default: |
199 | 200 | throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); |
... | ... | @@ -226,6 +227,7 @@ public class ThingsboardInstallService { |
226 | 227 | systemDataLoaderService.createDefaultTenantProfiles(); |
227 | 228 | systemDataLoaderService.createAdminSettings(); |
228 | 229 | systemDataLoaderService.loadSystemWidgets(); |
230 | + systemDataLoaderService.createOAuth2Templates(); | |
229 | 231 | // systemDataLoaderService.loadSystemPlugins(); |
230 | 232 | // systemDataLoaderService.loadSystemRules(); |
231 | 233 | ... | ... |
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.apiusage; | |
17 | + | |
18 | +import com.google.common.util.concurrent.FutureCallback; | |
19 | +import lombok.extern.slf4j.Slf4j; | |
20 | +import org.apache.commons.lang3.StringUtils; | |
21 | +import org.checkerframework.checker.nullness.qual.Nullable; | |
22 | +import org.springframework.beans.factory.annotation.Autowired; | |
23 | +import org.springframework.beans.factory.annotation.Value; | |
24 | +import org.springframework.context.annotation.Lazy; | |
25 | +import org.springframework.stereotype.Service; | |
26 | +import org.thingsboard.rule.engine.api.MailService; | |
27 | +import org.thingsboard.server.common.data.ApiFeature; | |
28 | +import org.thingsboard.server.common.data.ApiUsageRecordKey; | |
29 | +import org.thingsboard.server.common.data.ApiUsageState; | |
30 | +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; | |
31 | +import org.thingsboard.server.common.data.ApiUsageStateValue; | |
32 | +import org.thingsboard.server.common.data.Tenant; | |
33 | +import org.thingsboard.server.common.data.TenantProfile; | |
34 | +import org.thingsboard.server.common.data.exception.ThingsboardException; | |
35 | +import org.thingsboard.server.common.data.id.ApiUsageStateId; | |
36 | +import org.thingsboard.server.common.data.id.TenantId; | |
37 | +import org.thingsboard.server.common.data.id.TenantProfileId; | |
38 | +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; | |
39 | +import org.thingsboard.server.common.data.kv.LongDataEntry; | |
40 | +import org.thingsboard.server.common.data.kv.StringDataEntry; | |
41 | +import org.thingsboard.server.common.data.kv.TsKvEntry; | |
42 | +import org.thingsboard.server.common.data.page.PageDataIterable; | |
43 | +import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; | |
44 | +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; | |
45 | +import org.thingsboard.server.common.msg.queue.ServiceType; | |
46 | +import org.thingsboard.server.common.msg.queue.TbCallback; | |
47 | +import org.thingsboard.server.common.msg.tools.SchedulerUtils; | |
48 | +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; | |
49 | +import org.thingsboard.server.dao.tenant.TenantService; | |
50 | +import org.thingsboard.server.dao.timeseries.TimeseriesService; | |
51 | +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; | |
52 | +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; | |
53 | +import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; | |
54 | +import org.thingsboard.server.queue.common.TbProtoQueueMsg; | |
55 | +import org.thingsboard.server.queue.discovery.PartitionChangeEvent; | |
56 | +import org.thingsboard.server.queue.discovery.PartitionService; | |
57 | +import org.thingsboard.server.queue.scheduler.SchedulerComponent; | |
58 | +import org.thingsboard.server.service.queue.TbClusterService; | |
59 | +import org.thingsboard.server.service.telemetry.InternalTelemetryService; | |
60 | + | |
61 | +import javax.annotation.PostConstruct; | |
62 | +import javax.annotation.PreDestroy; | |
63 | +import java.util.ArrayList; | |
64 | +import java.util.Arrays; | |
65 | +import java.util.HashSet; | |
66 | +import java.util.List; | |
67 | +import java.util.Map; | |
68 | +import java.util.Set; | |
69 | +import java.util.UUID; | |
70 | +import java.util.concurrent.ConcurrentHashMap; | |
71 | +import java.util.concurrent.ExecutionException; | |
72 | +import java.util.concurrent.ExecutorService; | |
73 | +import java.util.concurrent.Executors; | |
74 | +import java.util.concurrent.TimeUnit; | |
75 | +import java.util.concurrent.locks.Lock; | |
76 | +import java.util.concurrent.locks.ReentrantLock; | |
77 | +import java.util.stream.Collectors; | |
78 | + | |
79 | +@Slf4j | |
80 | +@Service | |
81 | +public class DefaultTbApiUsageStateService implements TbApiUsageStateService { | |
82 | + | |
83 | + public static final String HOURLY = "Hourly"; | |
84 | + public static final FutureCallback<Integer> VOID_CALLBACK = new FutureCallback<Integer>() { | |
85 | + @Override | |
86 | + public void onSuccess(@Nullable Integer result) { | |
87 | + } | |
88 | + | |
89 | + @Override | |
90 | + public void onFailure(Throwable t) { | |
91 | + } | |
92 | + }; | |
93 | + private final TbClusterService clusterService; | |
94 | + private final PartitionService partitionService; | |
95 | + private final TenantService tenantService; | |
96 | + private final TimeseriesService tsService; | |
97 | + private final ApiUsageStateService apiUsageStateService; | |
98 | + private final SchedulerComponent scheduler; | |
99 | + private final TbTenantProfileCache tenantProfileCache; | |
100 | + private final MailService mailService; | |
101 | + | |
102 | + @Lazy | |
103 | + @Autowired | |
104 | + private InternalTelemetryService tsWsService; | |
105 | + | |
106 | + // Tenants that should be processed on this server | |
107 | + private final Map<TenantId, TenantApiUsageState> myTenantStates = new ConcurrentHashMap<>(); | |
108 | + // Tenants that should be processed on other servers | |
109 | + private final Map<TenantId, ApiUsageState> otherTenantStates = new ConcurrentHashMap<>(); | |
110 | + | |
111 | + @Value("${usage.stats.report.enabled:true}") | |
112 | + private boolean enabled; | |
113 | + | |
114 | + @Value("${usage.stats.check.cycle:60000}") | |
115 | + private long nextCycleCheckInterval; | |
116 | + | |
117 | + private final Lock updateLock = new ReentrantLock(); | |
118 | + | |
119 | + private final ExecutorService mailExecutor; | |
120 | + | |
121 | + public DefaultTbApiUsageStateService(TbClusterService clusterService, | |
122 | + PartitionService partitionService, | |
123 | + TenantService tenantService, | |
124 | + TimeseriesService tsService, | |
125 | + ApiUsageStateService apiUsageStateService, | |
126 | + SchedulerComponent scheduler, | |
127 | + TbTenantProfileCache tenantProfileCache, MailService mailService) { | |
128 | + this.clusterService = clusterService; | |
129 | + this.partitionService = partitionService; | |
130 | + this.tenantService = tenantService; | |
131 | + this.tsService = tsService; | |
132 | + this.apiUsageStateService = apiUsageStateService; | |
133 | + this.scheduler = scheduler; | |
134 | + this.tenantProfileCache = tenantProfileCache; | |
135 | + this.mailService = mailService; | |
136 | + this.mailExecutor = Executors.newSingleThreadExecutor(); | |
137 | + } | |
138 | + | |
139 | + @PostConstruct | |
140 | + public void init() { | |
141 | + if (enabled) { | |
142 | + log.info("Starting api usage service."); | |
143 | + scheduler.scheduleAtFixedRate(this::checkStartOfNextCycle, nextCycleCheckInterval, nextCycleCheckInterval, TimeUnit.MILLISECONDS); | |
144 | + log.info("Started api usage service."); | |
145 | + } | |
146 | + } | |
147 | + | |
148 | + @Override | |
149 | + public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) { | |
150 | + ToUsageStatsServiceMsg statsMsg = msg.getValue(); | |
151 | + TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); | |
152 | + | |
153 | + if (tenantProfileCache.get(tenantId) == null) { | |
154 | + return; | |
155 | + } | |
156 | + | |
157 | + TenantApiUsageState tenantState; | |
158 | + List<TsKvEntry> updatedEntries; | |
159 | + Map<ApiFeature, ApiUsageStateValue> result; | |
160 | + updateLock.lock(); | |
161 | + try { | |
162 | + tenantState = getOrFetchState(tenantId); | |
163 | + long ts = tenantState.getCurrentCycleTs(); | |
164 | + long hourTs = tenantState.getCurrentHourTs(); | |
165 | + long newHourTs = SchedulerUtils.getStartOfCurrentHour(); | |
166 | + if (newHourTs != hourTs) { | |
167 | + tenantState.setHour(newHourTs); | |
168 | + } | |
169 | + updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length); | |
170 | + Set<ApiFeature> apiFeatures = new HashSet<>(); | |
171 | + for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { | |
172 | + ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); | |
173 | + long newValue = tenantState.add(recordKey, kvProto.getValue()); | |
174 | + updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue))); | |
175 | + long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); | |
176 | + updatedEntries.add(new BasicTsKvEntry(newHourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); | |
177 | + apiFeatures.add(recordKey.getApiFeature()); | |
178 | + } | |
179 | + result = tenantState.checkStateUpdatedDueToThreshold(apiFeatures); | |
180 | + } finally { | |
181 | + updateLock.unlock(); | |
182 | + } | |
183 | + tsWsService.saveAndNotifyInternal(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, VOID_CALLBACK); | |
184 | + if (!result.isEmpty()) { | |
185 | + persistAndNotify(tenantState, result); | |
186 | + } | |
187 | + callback.onSuccess(); | |
188 | + } | |
189 | + | |
190 | + @Override | |
191 | + public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { | |
192 | + if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) { | |
193 | + myTenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); | |
194 | + otherTenantStates.entrySet().removeIf(entry -> partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); | |
195 | + initStatesFromDataBase(); | |
196 | + } | |
197 | + } | |
198 | + | |
199 | + @Override | |
200 | + public ApiUsageState getApiUsageState(TenantId tenantId) { | |
201 | + TenantApiUsageState tenantState = myTenantStates.get(tenantId); | |
202 | + if (tenantState != null) { | |
203 | + return tenantState.getApiUsageState(); | |
204 | + } else { | |
205 | + ApiUsageState state = otherTenantStates.get(tenantId); | |
206 | + if (state != null) { | |
207 | + return state; | |
208 | + } else { | |
209 | + if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { | |
210 | + return getOrFetchState(tenantId).getApiUsageState(); | |
211 | + } else { | |
212 | + updateLock.lock(); | |
213 | + try { | |
214 | + state = otherTenantStates.get(tenantId); | |
215 | + if (state == null) { | |
216 | + state = apiUsageStateService.findTenantApiUsageState(tenantId); | |
217 | + if (state != null) { | |
218 | + otherTenantStates.put(tenantId, state); | |
219 | + } | |
220 | + } | |
221 | + } finally { | |
222 | + updateLock.unlock(); | |
223 | + } | |
224 | + return state; | |
225 | + } | |
226 | + } | |
227 | + } | |
228 | + } | |
229 | + | |
230 | + @Override | |
231 | + public void onApiUsageStateUpdate(TenantId tenantId) { | |
232 | + otherTenantStates.remove(tenantId); | |
233 | + } | |
234 | + | |
235 | + @Override | |
236 | + public void onTenantProfileUpdate(TenantProfileId tenantProfileId) { | |
237 | + log.info("[{}] On Tenant Profile Update", tenantProfileId); | |
238 | + TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId); | |
239 | + updateLock.lock(); | |
240 | + try { | |
241 | + myTenantStates.values().forEach(state -> { | |
242 | + if (tenantProfile.getId().equals(state.getTenantProfileId())) { | |
243 | + updateTenantState(state, tenantProfile); | |
244 | + } | |
245 | + }); | |
246 | + } finally { | |
247 | + updateLock.unlock(); | |
248 | + } | |
249 | + } | |
250 | + | |
251 | + @Override | |
252 | + public void onTenantUpdate(TenantId tenantId) { | |
253 | + log.info("[{}] On Tenant Update.", tenantId); | |
254 | + TenantProfile tenantProfile = tenantProfileCache.get(tenantId); | |
255 | + updateLock.lock(); | |
256 | + try { | |
257 | + TenantApiUsageState state = myTenantStates.get(tenantId); | |
258 | + if (state != null && !state.getTenantProfileId().equals(tenantProfile.getId())) { | |
259 | + updateTenantState(state, tenantProfile); | |
260 | + } | |
261 | + } finally { | |
262 | + updateLock.unlock(); | |
263 | + } | |
264 | + } | |
265 | + | |
266 | + private void updateTenantState(TenantApiUsageState state, TenantProfile profile) { | |
267 | + TenantProfileData oldProfileData = state.getTenantProfileData(); | |
268 | + state.setTenantProfileId(profile.getId()); | |
269 | + state.setTenantProfileData(profile.getProfileData()); | |
270 | + Map<ApiFeature, ApiUsageStateValue> result = state.checkStateUpdatedDueToThresholds(); | |
271 | + if (!result.isEmpty()) { | |
272 | + persistAndNotify(state, result); | |
273 | + } | |
274 | + updateProfileThresholds(state.getTenantId(), state.getApiUsageState().getId(), | |
275 | + oldProfileData.getConfiguration(), profile.getProfileData().getConfiguration()); | |
276 | + } | |
277 | + | |
278 | + private void updateProfileThresholds(TenantId tenantId, ApiUsageStateId id, | |
279 | + TenantProfileConfiguration oldData, TenantProfileConfiguration newData) { | |
280 | + long ts = System.currentTimeMillis(); | |
281 | + List<TsKvEntry> profileThresholds = new ArrayList<>(); | |
282 | + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { | |
283 | + long newProfileThreshold = newData.getProfileThreshold(key); | |
284 | + if (oldData == null || oldData.getProfileThreshold(key) != newProfileThreshold) { | |
285 | + log.info("[{}] Updating profile threshold [{}]:[{}]", tenantId, key, newProfileThreshold); | |
286 | + profileThresholds.add(new BasicTsKvEntry(ts, new LongDataEntry(key.getApiLimitKey(), newProfileThreshold))); | |
287 | + } | |
288 | + } | |
289 | + if (!profileThresholds.isEmpty()) { | |
290 | + tsWsService.saveAndNotifyInternal(tenantId, id, profileThresholds, VOID_CALLBACK); | |
291 | + } | |
292 | + } | |
293 | + | |
294 | + private void persistAndNotify(TenantApiUsageState state, Map<ApiFeature, ApiUsageStateValue> result) { | |
295 | + log.info("[{}] Detected update of the API state: {}", state.getTenantId(), result); | |
296 | + apiUsageStateService.update(state.getApiUsageState()); | |
297 | + clusterService.onApiStateChange(state.getApiUsageState(), null); | |
298 | + long ts = System.currentTimeMillis(); | |
299 | + List<TsKvEntry> stateTelemetry = new ArrayList<>(); | |
300 | + result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))))); | |
301 | + tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); | |
302 | + | |
303 | + String email = tenantService.findTenantById(state.getTenantId()).getEmail(); | |
304 | + | |
305 | + if (StringUtils.isNotEmpty(email)) { | |
306 | + result.forEach((apiFeature, stateValue) -> { | |
307 | + mailExecutor.submit(() -> { | |
308 | + try { | |
309 | + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage(state, apiFeature, stateValue)); | |
310 | + } catch (ThingsboardException e) { | |
311 | + log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); | |
312 | + } | |
313 | + }); | |
314 | + }); | |
315 | + } else { | |
316 | + log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId()); | |
317 | + } | |
318 | + } | |
319 | + | |
320 | + private ApiUsageStateMailMessage createStateMailMessage(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) { | |
321 | + StateChecker checker = getStateChecker(stateValue); | |
322 | + for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) { | |
323 | + long threshold = state.getProfileThreshold(apiUsageRecordKey); | |
324 | + long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey); | |
325 | + long value = state.get(apiUsageRecordKey); | |
326 | + if (checker.check(threshold, warnThreshold, value)) { | |
327 | + return new ApiUsageStateMailMessage(apiUsageRecordKey, threshold, value); | |
328 | + } | |
329 | + } | |
330 | + return null; | |
331 | + } | |
332 | + | |
333 | + private StateChecker getStateChecker(ApiUsageStateValue stateValue) { | |
334 | + if (ApiUsageStateValue.ENABLED.equals(stateValue)) { | |
335 | + return (t, wt, v) -> true; | |
336 | + } else if (ApiUsageStateValue.WARNING.equals(stateValue)) { | |
337 | + return (t, wt, v) -> v < t && v >= wt; | |
338 | + } else { | |
339 | + return (t, wt, v) -> v >= t; | |
340 | + } | |
341 | + } | |
342 | + | |
343 | + private interface StateChecker { | |
344 | + boolean check(long threshold, long warnThreshold, long value); | |
345 | + } | |
346 | + | |
347 | + private void checkStartOfNextCycle() { | |
348 | + updateLock.lock(); | |
349 | + try { | |
350 | + long now = System.currentTimeMillis(); | |
351 | + myTenantStates.values().forEach(state -> { | |
352 | + if ((state.getNextCycleTs() < now) && (now - state.getNextCycleTs() < TimeUnit.HOURS.toMillis(1))) { | |
353 | + TenantId tenantId = state.getTenantId(); | |
354 | + state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); | |
355 | + saveNewCounts(state, Arrays.asList(ApiUsageRecordKey.values())); | |
356 | + updateTenantState(state, tenantProfileCache.get(tenantId)); | |
357 | + } | |
358 | + }); | |
359 | + } finally { | |
360 | + updateLock.unlock(); | |
361 | + } | |
362 | + } | |
363 | + | |
364 | + private void saveNewCounts(TenantApiUsageState state, List<ApiUsageRecordKey> keys) { | |
365 | + List<TsKvEntry> counts = keys.stream() | |
366 | + .map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L))) | |
367 | + .collect(Collectors.toList()); | |
368 | + | |
369 | + tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK); | |
370 | + } | |
371 | + | |
372 | + private TenantApiUsageState getOrFetchState(TenantId tenantId) { | |
373 | + TenantApiUsageState tenantState = myTenantStates.get(tenantId); | |
374 | + if (tenantState == null) { | |
375 | + ApiUsageState dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); | |
376 | + if (dbStateEntity == null) { | |
377 | + try { | |
378 | + dbStateEntity = apiUsageStateService.createDefaultApiUsageState(tenantId); | |
379 | + } catch (Exception e) { | |
380 | + dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); | |
381 | + } | |
382 | + } | |
383 | + TenantProfile tenantProfile = tenantProfileCache.get(tenantId); | |
384 | + tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity); | |
385 | + List<ApiUsageRecordKey> newCounts = new ArrayList<>(); | |
386 | + try { | |
387 | + List<TsKvEntry> dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get(); | |
388 | + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { | |
389 | + boolean cycleEntryFound = false; | |
390 | + boolean hourlyEntryFound = false; | |
391 | + for (TsKvEntry tsKvEntry : dbValues) { | |
392 | + if (tsKvEntry.getKey().equals(key.getApiCountKey())) { | |
393 | + cycleEntryFound = true; | |
394 | + | |
395 | + boolean oldCount = tsKvEntry.getTs() == tenantState.getCurrentCycleTs(); | |
396 | + tenantState.put(key, oldCount ? tsKvEntry.getLongValue().get() : 0L); | |
397 | + | |
398 | + if (!oldCount) { | |
399 | + newCounts.add(key); | |
400 | + } | |
401 | + } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) { | |
402 | + hourlyEntryFound = true; | |
403 | + tenantState.putHourly(key, tsKvEntry.getTs() == tenantState.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L); | |
404 | + } | |
405 | + if (cycleEntryFound && hourlyEntryFound) { | |
406 | + break; | |
407 | + } | |
408 | + } | |
409 | + } | |
410 | + log.debug("[{}] Initialized state: {}", tenantId, dbStateEntity); | |
411 | + myTenantStates.put(tenantId, tenantState); | |
412 | + saveNewCounts(tenantState, newCounts); | |
413 | + } catch (InterruptedException | ExecutionException e) { | |
414 | + log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); | |
415 | + } | |
416 | + } | |
417 | + return tenantState; | |
418 | + } | |
419 | + | |
420 | + private void initStatesFromDataBase() { | |
421 | + try { | |
422 | + log.info("Initializing tenant states."); | |
423 | + PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); | |
424 | + for (Tenant tenant : tenantIterator) { | |
425 | + if (!myTenantStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) { | |
426 | + log.debug("[{}] Initializing tenant state.", tenant.getId()); | |
427 | + updateLock.lock(); | |
428 | + try { | |
429 | + updateTenantState(getOrFetchState(tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); | |
430 | + log.debug("[{}] Initialized tenant state.", tenant.getId()); | |
431 | + } catch (Exception e) { | |
432 | + log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e); | |
433 | + } finally { | |
434 | + updateLock.unlock(); | |
435 | + } | |
436 | + } | |
437 | + } | |
438 | + log.info("Initialized tenant states."); | |
439 | + } catch (Exception e) { | |
440 | + log.warn("Unknown failure", e); | |
441 | + } | |
442 | + } | |
443 | + | |
444 | + @PreDestroy | |
445 | + private void destroy() { | |
446 | + if (mailExecutor != null) { | |
447 | + mailExecutor.shutdownNow(); | |
448 | + } | |
449 | + } | |
450 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.apiusage; | |
17 | + | |
18 | +import org.springframework.context.ApplicationListener; | |
19 | +import org.thingsboard.server.common.data.ApiUsageState; | |
20 | +import org.thingsboard.server.common.data.id.TenantId; | |
21 | +import org.thingsboard.server.common.data.id.TenantProfileId; | |
22 | +import org.thingsboard.server.common.msg.queue.TbCallback; | |
23 | +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; | |
24 | +import org.thingsboard.server.queue.common.TbProtoQueueMsg; | |
25 | +import org.thingsboard.server.queue.discovery.PartitionChangeEvent; | |
26 | + | |
27 | +public interface TbApiUsageStateService extends ApplicationListener<PartitionChangeEvent> { | |
28 | + | |
29 | + void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback); | |
30 | + | |
31 | + ApiUsageState getApiUsageState(TenantId tenantId); | |
32 | + | |
33 | + void onTenantProfileUpdate(TenantProfileId tenantProfileId); | |
34 | + | |
35 | + void onTenantUpdate(TenantId tenantId); | |
36 | + | |
37 | + void onApiUsageStateUpdate(TenantId tenantId); | |
38 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.apiusage; | |
17 | + | |
18 | +import lombok.Getter; | |
19 | +import lombok.Setter; | |
20 | +import org.springframework.data.util.Pair; | |
21 | +import org.thingsboard.server.common.data.ApiFeature; | |
22 | +import org.thingsboard.server.common.data.ApiUsageRecordKey; | |
23 | +import org.thingsboard.server.common.data.ApiUsageState; | |
24 | +import org.thingsboard.server.common.data.ApiUsageStateValue; | |
25 | +import org.thingsboard.server.common.data.TenantProfile; | |
26 | +import org.thingsboard.server.common.data.id.TenantId; | |
27 | +import org.thingsboard.server.common.data.id.TenantProfileId; | |
28 | +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; | |
29 | +import org.thingsboard.server.common.msg.tools.SchedulerUtils; | |
30 | + | |
31 | +import java.util.Arrays; | |
32 | +import java.util.HashMap; | |
33 | +import java.util.HashSet; | |
34 | +import java.util.Map; | |
35 | +import java.util.Set; | |
36 | +import java.util.concurrent.ConcurrentHashMap; | |
37 | + | |
38 | +public class TenantApiUsageState { | |
39 | + | |
40 | + private final Map<ApiUsageRecordKey, Long> currentCycleValues = new ConcurrentHashMap<>(); | |
41 | + private final Map<ApiUsageRecordKey, Long> currentHourValues = new ConcurrentHashMap<>(); | |
42 | + | |
43 | + @Getter | |
44 | + @Setter | |
45 | + private TenantProfileId tenantProfileId; | |
46 | + @Getter | |
47 | + @Setter | |
48 | + private TenantProfileData tenantProfileData; | |
49 | + @Getter | |
50 | + private final ApiUsageState apiUsageState; | |
51 | + @Getter | |
52 | + private volatile long currentCycleTs; | |
53 | + @Getter | |
54 | + private volatile long nextCycleTs; | |
55 | + @Getter | |
56 | + private volatile long currentHourTs; | |
57 | + | |
58 | + public TenantApiUsageState(TenantProfile tenantProfile, ApiUsageState apiUsageState) { | |
59 | + this.tenantProfileId = tenantProfile.getId(); | |
60 | + this.tenantProfileData = tenantProfile.getProfileData(); | |
61 | + this.apiUsageState = apiUsageState; | |
62 | + this.currentCycleTs = SchedulerUtils.getStartOfCurrentMonth(); | |
63 | + this.nextCycleTs = SchedulerUtils.getStartOfNextMonth(); | |
64 | + this.currentHourTs = SchedulerUtils.getStartOfCurrentHour(); | |
65 | + } | |
66 | + | |
67 | + public void put(ApiUsageRecordKey key, Long value) { | |
68 | + currentCycleValues.put(key, value); | |
69 | + } | |
70 | + | |
71 | + public void putHourly(ApiUsageRecordKey key, Long value) { | |
72 | + currentHourValues.put(key, value); | |
73 | + } | |
74 | + | |
75 | + public long add(ApiUsageRecordKey key, long value) { | |
76 | + long result = currentCycleValues.getOrDefault(key, 0L) + value; | |
77 | + currentCycleValues.put(key, result); | |
78 | + return result; | |
79 | + } | |
80 | + | |
81 | + public long get(ApiUsageRecordKey key) { | |
82 | + return currentCycleValues.getOrDefault(key, 0L); | |
83 | + } | |
84 | + | |
85 | + public long addToHourly(ApiUsageRecordKey key, long value) { | |
86 | + long result = currentHourValues.getOrDefault(key, 0L) + value; | |
87 | + currentHourValues.put(key, result); | |
88 | + return result; | |
89 | + } | |
90 | + | |
91 | + public void setHour(long currentHourTs) { | |
92 | + this.currentHourTs = currentHourTs; | |
93 | + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { | |
94 | + currentHourValues.put(key, 0L); | |
95 | + } | |
96 | + } | |
97 | + | |
98 | + public void setCycles(long currentCycleTs, long nextCycleTs) { | |
99 | + this.currentCycleTs = currentCycleTs; | |
100 | + this.nextCycleTs = nextCycleTs; | |
101 | + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { | |
102 | + currentCycleValues.put(key, 0L); | |
103 | + } | |
104 | + } | |
105 | + | |
106 | + public long getProfileThreshold(ApiUsageRecordKey key) { | |
107 | + return tenantProfileData.getConfiguration().getProfileThreshold(key); | |
108 | + } | |
109 | + | |
110 | + public long getProfileWarnThreshold(ApiUsageRecordKey key) { | |
111 | + return tenantProfileData.getConfiguration().getWarnThreshold(key); | |
112 | + } | |
113 | + | |
114 | + public TenantId getTenantId() { | |
115 | + return apiUsageState.getTenantId(); | |
116 | + } | |
117 | + | |
118 | + public ApiUsageStateValue getFeatureValue(ApiFeature feature) { | |
119 | + switch (feature) { | |
120 | + case TRANSPORT: | |
121 | + return apiUsageState.getTransportState(); | |
122 | + case RE: | |
123 | + return apiUsageState.getReExecState(); | |
124 | + case DB: | |
125 | + return apiUsageState.getDbStorageState(); | |
126 | + case JS: | |
127 | + return apiUsageState.getJsExecState(); | |
128 | + case EMAIL: | |
129 | + return apiUsageState.getEmailExecState(); | |
130 | + case SMS: | |
131 | + return apiUsageState.getSmsExecState(); | |
132 | + default: | |
133 | + return ApiUsageStateValue.ENABLED; | |
134 | + } | |
135 | + } | |
136 | + | |
137 | + public boolean setFeatureValue(ApiFeature feature, ApiUsageStateValue value) { | |
138 | + ApiUsageStateValue currentValue = getFeatureValue(feature); | |
139 | + switch (feature) { | |
140 | + case TRANSPORT: | |
141 | + apiUsageState.setTransportState(value); | |
142 | + break; | |
143 | + case RE: | |
144 | + apiUsageState.setReExecState(value); | |
145 | + break; | |
146 | + case DB: | |
147 | + apiUsageState.setDbStorageState(value); | |
148 | + break; | |
149 | + case JS: | |
150 | + apiUsageState.setJsExecState(value); | |
151 | + break; | |
152 | + case EMAIL: | |
153 | + apiUsageState.setEmailExecState(value); | |
154 | + break; | |
155 | + case SMS: | |
156 | + apiUsageState.setSmsExecState(value); | |
157 | + break; | |
158 | + } | |
159 | + return !currentValue.equals(value); | |
160 | + } | |
161 | + | |
162 | + public Map<ApiFeature, ApiUsageStateValue> checkStateUpdatedDueToThresholds() { | |
163 | + return checkStateUpdatedDueToThreshold(new HashSet<>(Arrays.asList(ApiFeature.values()))); | |
164 | + } | |
165 | + | |
166 | + public Map<ApiFeature, ApiUsageStateValue> checkStateUpdatedDueToThreshold(Set<ApiFeature> features) { | |
167 | + Map<ApiFeature, ApiUsageStateValue> result = new HashMap<>(); | |
168 | + for (ApiFeature feature : features) { | |
169 | + Pair<ApiFeature, ApiUsageStateValue> tmp = checkStateUpdatedDueToThreshold(feature); | |
170 | + if (tmp != null) { | |
171 | + result.put(tmp.getFirst(), tmp.getSecond()); | |
172 | + } | |
173 | + } | |
174 | + return result; | |
175 | + } | |
176 | + | |
177 | + public Pair<ApiFeature, ApiUsageStateValue> checkStateUpdatedDueToThreshold(ApiFeature feature) { | |
178 | + ApiUsageStateValue featureValue = ApiUsageStateValue.ENABLED; | |
179 | + for (ApiUsageRecordKey recordKey : ApiUsageRecordKey.getKeys(feature)) { | |
180 | + long value = get(recordKey); | |
181 | + long threshold = getProfileThreshold(recordKey); | |
182 | + long warnThreshold = getProfileWarnThreshold(recordKey); | |
183 | + ApiUsageStateValue tmpValue; | |
184 | + if (threshold == 0 || value < warnThreshold) { | |
185 | + tmpValue = ApiUsageStateValue.ENABLED; | |
186 | + } else if (value < threshold) { | |
187 | + tmpValue = ApiUsageStateValue.WARNING; | |
188 | + } else { | |
189 | + tmpValue = ApiUsageStateValue.DISABLED; | |
190 | + } | |
191 | + featureValue = ApiUsageStateValue.toMoreRestricted(featureValue, tmpValue); | |
192 | + } | |
193 | + return setFeatureValue(feature, featureValue) ? Pair.of(feature, featureValue) : null; | |
194 | + } | |
195 | + | |
196 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.device; | |
17 | + | |
18 | +import com.fasterxml.jackson.core.JsonProcessingException; | |
19 | +import com.fasterxml.jackson.databind.JsonNode; | |
20 | +import com.fasterxml.jackson.databind.node.ObjectNode; | |
21 | +import com.google.common.util.concurrent.ListenableFuture; | |
22 | +import lombok.extern.slf4j.Slf4j; | |
23 | +import org.apache.commons.lang.RandomStringUtils; | |
24 | +import org.springframework.beans.factory.annotation.Autowired; | |
25 | +import org.springframework.stereotype.Service; | |
26 | +import org.springframework.util.StringUtils; | |
27 | +import org.thingsboard.server.common.data.DataConstants; | |
28 | +import org.thingsboard.server.common.data.Device; | |
29 | +import org.thingsboard.server.common.data.DeviceProfile; | |
30 | +import org.thingsboard.server.common.data.audit.ActionType; | |
31 | +import org.thingsboard.server.common.data.id.CustomerId; | |
32 | +import org.thingsboard.server.common.data.id.TenantId; | |
33 | +import org.thingsboard.server.common.data.id.UserId; | |
34 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
35 | +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; | |
36 | +import org.thingsboard.server.common.data.kv.StringDataEntry; | |
37 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | |
38 | +import org.thingsboard.server.common.msg.TbMsg; | |
39 | +import org.thingsboard.server.common.msg.TbMsgMetaData; | |
40 | +import org.thingsboard.server.common.msg.queue.ServiceType; | |
41 | +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; | |
42 | +import org.thingsboard.server.dao.attributes.AttributesService; | |
43 | +import org.thingsboard.server.dao.audit.AuditLogService; | |
44 | +import org.thingsboard.server.dao.device.DeviceCredentialsService; | |
45 | +import org.thingsboard.server.dao.device.DeviceDao; | |
46 | +import org.thingsboard.server.dao.device.DeviceProfileDao; | |
47 | +import org.thingsboard.server.dao.device.DeviceProvisionService; | |
48 | +import org.thingsboard.server.dao.device.DeviceService; | |
49 | +import org.thingsboard.server.dao.device.provision.ProvisionFailedException; | |
50 | +import org.thingsboard.server.dao.device.provision.ProvisionRequest; | |
51 | +import org.thingsboard.server.dao.device.provision.ProvisionResponse; | |
52 | +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; | |
53 | +import org.thingsboard.server.dao.util.mapping.JacksonUtil; | |
54 | +import org.thingsboard.server.gen.transport.TransportProtos; | |
55 | +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; | |
56 | +import org.thingsboard.server.queue.TbQueueCallback; | |
57 | +import org.thingsboard.server.queue.TbQueueProducer; | |
58 | +import org.thingsboard.server.queue.common.TbProtoQueueMsg; | |
59 | +import org.thingsboard.server.queue.discovery.PartitionService; | |
60 | +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; | |
61 | +import org.thingsboard.server.queue.util.TbCoreComponent; | |
62 | +import org.thingsboard.server.service.state.DeviceStateService; | |
63 | + | |
64 | +import java.util.Collections; | |
65 | +import java.util.List; | |
66 | +import java.util.Optional; | |
67 | +import java.util.concurrent.ExecutionException; | |
68 | +import java.util.concurrent.locks.ReentrantLock; | |
69 | + | |
70 | + | |
71 | +@Service | |
72 | +@Slf4j | |
73 | +@TbCoreComponent | |
74 | +public class DeviceProvisionServiceImpl implements DeviceProvisionService { | |
75 | + | |
76 | + protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer; | |
77 | + | |
78 | + private static final String DEVICE_PROVISION_STATE = "provisionState"; | |
79 | + private static final String PROVISIONED_STATE = "provisioned"; | |
80 | + | |
81 | + private final ReentrantLock deviceCreationLock = new ReentrantLock(); | |
82 | + | |
83 | + @Autowired | |
84 | + DeviceDao deviceDao; | |
85 | + | |
86 | + @Autowired | |
87 | + DeviceProfileDao deviceProfileDao; | |
88 | + | |
89 | + @Autowired | |
90 | + DeviceService deviceService; | |
91 | + | |
92 | + @Autowired | |
93 | + DeviceCredentialsService deviceCredentialsService; | |
94 | + | |
95 | + @Autowired | |
96 | + AttributesService attributesService; | |
97 | + | |
98 | + @Autowired | |
99 | + DeviceStateService deviceStateService; | |
100 | + | |
101 | + @Autowired | |
102 | + AuditLogService auditLogService; | |
103 | + | |
104 | + @Autowired | |
105 | + PartitionService partitionService; | |
106 | + | |
107 | + public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider) { | |
108 | + ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer(); | |
109 | + } | |
110 | + | |
111 | + @Override | |
112 | + public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { | |
113 | + String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); | |
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 | + } | |
122 | + | |
123 | + if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) { | |
124 | + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); | |
125 | + } | |
126 | + | |
127 | + DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey); | |
128 | + | |
129 | + if (targetProfile == null || targetProfile.getProfileData().getProvisionConfiguration() == null || | |
130 | + targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() == null) { | |
131 | + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); | |
132 | + } | |
133 | + | |
134 | + Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null); | |
135 | + | |
136 | + switch (targetProfile.getProvisionType()) { | |
137 | + case ALLOW_CREATE_NEW_DEVICES: | |
138 | + if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { | |
139 | + if (targetDevice != null) { | |
140 | + log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); | |
141 | + notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false); | |
142 | + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); | |
143 | + } else { | |
144 | + return createDevice(provisionRequest, targetProfile); | |
145 | + } | |
146 | + } | |
147 | + break; | |
148 | + case CHECK_PRE_PROVISIONED_DEVICES: | |
149 | + if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { | |
150 | + if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { | |
151 | + return processProvision(targetDevice, provisionRequest); | |
152 | + } else { | |
153 | + log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); | |
154 | + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); | |
155 | + } | |
156 | + } | |
157 | + break; | |
158 | + } | |
159 | + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); | |
160 | + } | |
161 | + | |
162 | + private ProvisionResponse processProvision(Device device, ProvisionRequest provisionRequest) { | |
163 | + try { | |
164 | + Optional<AttributeKvEntry> provisionState = attributesService.find(device.getTenantId(), device.getId(), | |
165 | + DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); | |
166 | + if (provisionState != null && provisionState.isPresent() && !provisionState.get().getValueAsString().equals(PROVISIONED_STATE)) { | |
167 | + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); | |
168 | + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); | |
169 | + } else { | |
170 | + saveProvisionStateAttribute(device).get(); | |
171 | + notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); | |
172 | + } | |
173 | + } catch (InterruptedException | ExecutionException e) { | |
174 | + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); | |
175 | + } | |
176 | + return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS); | |
177 | + } | |
178 | + | |
179 | + private ProvisionResponse createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { | |
180 | + deviceCreationLock.lock(); | |
181 | + try { | |
182 | + return processCreateDevice(provisionRequest, profile); | |
183 | + } finally { | |
184 | + deviceCreationLock.unlock(); | |
185 | + } | |
186 | + } | |
187 | + | |
188 | + private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) { | |
189 | + pushProvisionEventToRuleEngine(provisionRequest, device, type); | |
190 | + logAction(device.getTenantId(), device.getCustomerId(), device, success, provisionRequest); | |
191 | + } | |
192 | + | |
193 | + private ProvisionResponse processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { | |
194 | + Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); | |
195 | + try { | |
196 | + if (device == null) { | |
197 | + if (StringUtils.isEmpty(provisionRequest.getDeviceName())) { | |
198 | + String newDeviceName = RandomStringUtils.randomAlphanumeric(20); | |
199 | + log.info("Device name not found in provision request. Generated name is: {}", newDeviceName); | |
200 | + provisionRequest.setDeviceName(newDeviceName); | |
201 | + } | |
202 | + Device savedDevice = deviceService.saveDevice(provisionRequest, profile); | |
203 | + | |
204 | + deviceStateService.onDeviceAdded(savedDevice); | |
205 | + saveProvisionStateAttribute(savedDevice).get(); | |
206 | + pushDeviceCreatedEventToRuleEngine(savedDevice); | |
207 | + notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); | |
208 | + | |
209 | + return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS); | |
210 | + } else { | |
211 | + log.warn("[{}] The device is already provisioned!", device.getName()); | |
212 | + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); | |
213 | + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); | |
214 | + } | |
215 | + } catch (InterruptedException | ExecutionException e) { | |
216 | + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); | |
217 | + } | |
218 | + } | |
219 | + | |
220 | + private ListenableFuture<List<Void>> saveProvisionStateAttribute(Device device) { | |
221 | + return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, | |
222 | + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE), | |
223 | + System.currentTimeMillis()))); | |
224 | + } | |
225 | + | |
226 | + private DeviceCredentials getDeviceCredentials(Device device) { | |
227 | + return deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); | |
228 | + } | |
229 | + | |
230 | + private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { | |
231 | + try { | |
232 | + JsonNode entityNode = JacksonUtil.valueToTree(request); | |
233 | + TbMsg msg = TbMsg.newMsg(type, device.getId(), createTbMsgMetaData(device), JacksonUtil.toString(entityNode)); | |
234 | + sendToRuleEngine(device.getTenantId(), msg, null); | |
235 | + } catch (IllegalArgumentException e) { | |
236 | + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e); | |
237 | + } | |
238 | + } | |
239 | + | |
240 | + private void pushDeviceCreatedEventToRuleEngine(Device device) { | |
241 | + try { | |
242 | + ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); | |
243 | + TbMsg msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); | |
244 | + sendToRuleEngine(device.getTenantId(), msg, null); | |
245 | + } catch (JsonProcessingException | IllegalArgumentException e) { | |
246 | + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e); | |
247 | + } | |
248 | + } | |
249 | + | |
250 | + protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { | |
251 | + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator()); | |
252 | + TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg)) | |
253 | + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) | |
254 | + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build(); | |
255 | + ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), callback); | |
256 | + } | |
257 | + | |
258 | + private TbMsgMetaData createTbMsgMetaData(Device device) { | |
259 | + TbMsgMetaData metaData = new TbMsgMetaData(); | |
260 | + metaData.putValue("tenantId", device.getTenantId().toString()); | |
261 | + return metaData; | |
262 | + } | |
263 | + | |
264 | + private void logAction(TenantId tenantId, CustomerId customerId, Device device, boolean success, ProvisionRequest provisionRequest) { | |
265 | + ActionType actionType = success ? ActionType.PROVISION_SUCCESS : ActionType.PROVISION_FAILURE; | |
266 | + auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest); | |
267 | + } | |
268 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
... | ... | @@ -28,11 +28,21 @@ import org.thingsboard.server.common.data.Customer; |
28 | 28 | import org.thingsboard.server.common.data.DataConstants; |
29 | 29 | import org.thingsboard.server.common.data.Device; |
30 | 30 | import org.thingsboard.server.common.data.DeviceProfile; |
31 | +import org.thingsboard.server.common.data.DeviceProfileProvisionType; | |
32 | +import org.thingsboard.server.common.data.DeviceProfileType; | |
33 | +import org.thingsboard.server.common.data.DeviceTransportType; | |
31 | 34 | import org.thingsboard.server.common.data.Tenant; |
32 | 35 | import org.thingsboard.server.common.data.TenantProfile; |
33 | -import org.thingsboard.server.common.data.TenantProfileData; | |
34 | 36 | import org.thingsboard.server.common.data.User; |
35 | -import org.thingsboard.server.common.data.asset.Asset; | |
37 | +import org.thingsboard.server.common.data.alarm.AlarmSeverity; | |
38 | +import org.thingsboard.server.common.data.device.profile.AlarmCondition; | |
39 | +import org.thingsboard.server.common.data.device.profile.AlarmRule; | |
40 | +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; | |
41 | +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; | |
42 | +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; | |
43 | +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; | |
44 | +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; | |
45 | +import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; | |
36 | 46 | import org.thingsboard.server.common.data.id.CustomerId; |
37 | 47 | import org.thingsboard.server.common.data.id.DeviceId; |
38 | 48 | import org.thingsboard.server.common.data.id.DeviceProfileId; |
... | ... | @@ -41,19 +51,30 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
41 | 51 | import org.thingsboard.server.common.data.kv.BooleanDataEntry; |
42 | 52 | import org.thingsboard.server.common.data.kv.DoubleDataEntry; |
43 | 53 | import org.thingsboard.server.common.data.kv.LongDataEntry; |
44 | -import org.thingsboard.server.common.data.relation.EntityRelation; | |
54 | +import org.thingsboard.server.common.data.page.PageLink; | |
55 | +import org.thingsboard.server.common.data.query.BooleanFilterPredicate; | |
56 | +import org.thingsboard.server.common.data.query.DynamicValue; | |
57 | +import org.thingsboard.server.common.data.query.DynamicValueSourceType; | |
58 | +import org.thingsboard.server.common.data.query.EntityKey; | |
59 | +import org.thingsboard.server.common.data.query.EntityKeyType; | |
60 | +import org.thingsboard.server.common.data.query.EntityKeyValueType; | |
61 | +import org.thingsboard.server.common.data.query.FilterPredicateValue; | |
62 | +import org.thingsboard.server.common.data.query.KeyFilter; | |
63 | +import org.thingsboard.server.common.data.query.NumericFilterPredicate; | |
64 | +import org.thingsboard.server.common.data.rule.RuleChainType; | |
45 | 65 | import org.thingsboard.server.common.data.security.Authority; |
46 | 66 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
47 | 67 | import org.thingsboard.server.common.data.security.UserCredentials; |
68 | +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; | |
69 | +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; | |
48 | 70 | import org.thingsboard.server.common.data.widget.WidgetsBundle; |
49 | -import org.thingsboard.server.dao.asset.AssetService; | |
50 | 71 | import org.thingsboard.server.dao.attributes.AttributesService; |
51 | 72 | import org.thingsboard.server.dao.customer.CustomerService; |
52 | 73 | import org.thingsboard.server.dao.device.DeviceCredentialsService; |
53 | 74 | import org.thingsboard.server.dao.device.DeviceProfileService; |
54 | 75 | import org.thingsboard.server.dao.device.DeviceService; |
55 | 76 | import org.thingsboard.server.dao.exception.DataValidationException; |
56 | -import org.thingsboard.server.dao.relation.RelationService; | |
77 | +import org.thingsboard.server.dao.rule.RuleChainService; | |
57 | 78 | import org.thingsboard.server.dao.settings.AdminSettingsService; |
58 | 79 | import org.thingsboard.server.dao.tenant.TenantProfileService; |
59 | 80 | import org.thingsboard.server.dao.tenant.TenantService; |
... | ... | @@ -61,6 +82,8 @@ import org.thingsboard.server.dao.user.UserService; |
61 | 82 | import org.thingsboard.server.dao.widget.WidgetsBundleService; |
62 | 83 | |
63 | 84 | import java.util.Arrays; |
85 | +import java.util.Collections; | |
86 | +import java.util.TreeMap; | |
64 | 87 | |
65 | 88 | @Service |
66 | 89 | @Profile("install") |
... | ... | @@ -96,12 +119,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
96 | 119 | private CustomerService customerService; |
97 | 120 | |
98 | 121 | @Autowired |
99 | - private RelationService relationService; | |
100 | - | |
101 | - @Autowired | |
102 | - private AssetService assetService; | |
103 | - | |
104 | - @Autowired | |
105 | 122 | private DeviceService deviceService; |
106 | 123 | |
107 | 124 | @Autowired |
... | ... | @@ -113,6 +130,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
113 | 130 | @Autowired |
114 | 131 | private DeviceCredentialsService deviceCredentialsService; |
115 | 132 | |
133 | + @Autowired | |
134 | + private RuleChainService ruleChainService; | |
135 | + | |
116 | 136 | @Bean |
117 | 137 | protected BCryptPasswordEncoder passwordEncoder() { |
118 | 138 | return new BCryptPasswordEncoder(); |
... | ... | @@ -127,13 +147,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
127 | 147 | public void createDefaultTenantProfiles() throws Exception { |
128 | 148 | tenantProfileService.findOrCreateDefaultTenantProfile(TenantId.SYS_TENANT_ID); |
129 | 149 | |
150 | + TenantProfileData tenantProfileData = new TenantProfileData(); | |
151 | + tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration()); | |
152 | + | |
130 | 153 | TenantProfile isolatedTbCoreProfile = new TenantProfile(); |
131 | 154 | isolatedTbCoreProfile.setDefault(false); |
132 | 155 | isolatedTbCoreProfile.setName("Isolated TB Core"); |
133 | - isolatedTbCoreProfile.setProfileData(new TenantProfileData()); | |
134 | 156 | isolatedTbCoreProfile.setDescription("Isolated TB Core tenant profile"); |
135 | 157 | isolatedTbCoreProfile.setIsolatedTbCore(true); |
136 | 158 | isolatedTbCoreProfile.setIsolatedTbRuleEngine(false); |
159 | + isolatedTbCoreProfile.setProfileData(tenantProfileData); | |
137 | 160 | try { |
138 | 161 | tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreProfile); |
139 | 162 | } catch (DataValidationException e) { |
... | ... | @@ -143,10 +166,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
143 | 166 | TenantProfile isolatedTbRuleEngineProfile = new TenantProfile(); |
144 | 167 | isolatedTbRuleEngineProfile.setDefault(false); |
145 | 168 | isolatedTbRuleEngineProfile.setName("Isolated TB Rule Engine"); |
146 | - isolatedTbRuleEngineProfile.setProfileData(new TenantProfileData()); | |
147 | 169 | isolatedTbRuleEngineProfile.setDescription("Isolated TB Rule Engine tenant profile"); |
148 | 170 | isolatedTbRuleEngineProfile.setIsolatedTbCore(false); |
149 | 171 | isolatedTbRuleEngineProfile.setIsolatedTbRuleEngine(true); |
172 | + isolatedTbRuleEngineProfile.setProfileData(tenantProfileData); | |
173 | + | |
150 | 174 | try { |
151 | 175 | tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbRuleEngineProfile); |
152 | 176 | } catch (DataValidationException e) { |
... | ... | @@ -156,10 +180,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
156 | 180 | TenantProfile isolatedTbCoreAndTbRuleEngineProfile = new TenantProfile(); |
157 | 181 | isolatedTbCoreAndTbRuleEngineProfile.setDefault(false); |
158 | 182 | isolatedTbCoreAndTbRuleEngineProfile.setName("Isolated TB Core and TB Rule Engine"); |
159 | - isolatedTbCoreAndTbRuleEngineProfile.setProfileData(new TenantProfileData()); | |
160 | 183 | isolatedTbCoreAndTbRuleEngineProfile.setDescription("Isolated TB Core and TB Rule Engine tenant profile"); |
161 | 184 | isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbCore(true); |
162 | 185 | isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbRuleEngine(true); |
186 | + isolatedTbCoreAndTbRuleEngineProfile.setProfileData(tenantProfileData); | |
187 | + | |
163 | 188 | try { |
164 | 189 | tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreAndTbRuleEngineProfile); |
165 | 190 | } catch (DataValidationException e) { |
... | ... | @@ -173,6 +198,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
173 | 198 | generalSettings.setKey("general"); |
174 | 199 | ObjectNode node = objectMapper.createObjectNode(); |
175 | 200 | node.put("baseUrl", "http://localhost:8080"); |
201 | + node.put("prohibitDifferentUrl", true); | |
176 | 202 | generalSettings.setJsonValue(node); |
177 | 203 | adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, generalSettings); |
178 | 204 | |
... | ... | @@ -194,6 +220,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
194 | 220 | } |
195 | 221 | |
196 | 222 | @Override |
223 | + public void createOAuth2Templates() throws Exception { | |
224 | + installScripts.createOAuth2Templates(); | |
225 | + } | |
226 | + | |
227 | + @Override | |
197 | 228 | public void loadDemoData() throws Exception { |
198 | 229 | Tenant demoTenant = new Tenant(); |
199 | 230 | demoTenant.setRegion("Global"); |
... | ... | @@ -233,35 +264,149 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
233 | 264 | createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " + |
234 | 265 | "Raspberry Pi GPIO control sample application"); |
235 | 266 | |
236 | - Asset thermostatAlarms = new Asset(); | |
237 | - thermostatAlarms.setTenantId(demoTenant.getId()); | |
238 | - thermostatAlarms.setName("Thermostat Alarms"); | |
239 | - thermostatAlarms.setType("AlarmPropagationAsset"); | |
240 | - thermostatAlarms = assetService.saveAsset(thermostatAlarms); | |
241 | - | |
242 | - DeviceProfile thermostatDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), "thermostat"); | |
243 | - | |
244 | - DeviceId t1Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); | |
245 | - DeviceId t2Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); | |
246 | - | |
247 | - relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t1Id, "ToAlarmPropagationAsset")); | |
248 | - relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t2Id, "ToAlarmPropagationAsset")); | |
267 | + DeviceProfile thermostatDeviceProfile = new DeviceProfile(); | |
268 | + thermostatDeviceProfile.setTenantId(demoTenant.getId()); | |
269 | + thermostatDeviceProfile.setDefault(false); | |
270 | + thermostatDeviceProfile.setName("thermostat"); | |
271 | + thermostatDeviceProfile.setType(DeviceProfileType.DEFAULT); | |
272 | + thermostatDeviceProfile.setTransportType(DeviceTransportType.DEFAULT); | |
273 | + thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); | |
274 | + thermostatDeviceProfile.setDescription("Thermostat device profile"); | |
275 | + thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChainsByType( | |
276 | + demoTenant.getId(), RuleChainType.CORE, new PageLink(1, 0, "Thermostat")).getData().get(0).getId()); | |
277 | + | |
278 | + DeviceProfileData deviceProfileData = new DeviceProfileData(); | |
279 | + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); | |
280 | + DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration(); | |
281 | + DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null); | |
282 | + deviceProfileData.setConfiguration(configuration); | |
283 | + deviceProfileData.setTransportConfiguration(transportConfiguration); | |
284 | + deviceProfileData.setProvisionConfiguration(provisionConfiguration); | |
285 | + thermostatDeviceProfile.setProfileData(deviceProfileData); | |
286 | + | |
287 | + DeviceProfileAlarm highTemperature = new DeviceProfileAlarm(); | |
288 | + highTemperature.setId("highTemperatureAlarmID"); | |
289 | + highTemperature.setAlarmType("High Temperature"); | |
290 | + AlarmRule temperatureRule = new AlarmRule(); | |
291 | + AlarmCondition temperatureCondition = new AlarmCondition(); | |
292 | + temperatureCondition.setSpec(new SimpleAlarmConditionSpec()); | |
293 | + | |
294 | + KeyFilter temperatureAlarmFlagAttributeFilter = new KeyFilter(); | |
295 | + temperatureAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperatureAlarmFlag")); | |
296 | + temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); | |
297 | + BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate(); | |
298 | + temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); | |
299 | + temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); | |
300 | + temperatureAlarmFlagAttributeFilter.setPredicate(temperatureAlarmFlagAttributePredicate); | |
301 | + | |
302 | + KeyFilter temperatureTimeseriesFilter = new KeyFilter(); | |
303 | + temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); | |
304 | + temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); | |
305 | + NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); | |
306 | + temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); | |
307 | + FilterPredicateValue<Double> temperatureTimeseriesPredicateValue = | |
308 | + new FilterPredicateValue<>(25.0, null, | |
309 | + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold")); | |
310 | + temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue); | |
311 | + temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate); | |
312 | + temperatureCondition.setCondition(Arrays.asList(temperatureAlarmFlagAttributeFilter, temperatureTimeseriesFilter)); | |
313 | + temperatureRule.setAlarmDetails("Current temperature = ${temperature}"); | |
314 | + temperatureRule.setCondition(temperatureCondition); | |
315 | + highTemperature.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule))); | |
316 | + | |
317 | + AlarmRule clearTemperatureRule = new AlarmRule(); | |
318 | + AlarmCondition clearTemperatureCondition = new AlarmCondition(); | |
319 | + clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec()); | |
320 | + | |
321 | + KeyFilter clearTemperatureTimeseriesFilter = new KeyFilter(); | |
322 | + clearTemperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); | |
323 | + clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); | |
324 | + NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); | |
325 | + clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL); | |
326 | + FilterPredicateValue<Double> clearTemperatureTimeseriesPredicateValue = | |
327 | + new FilterPredicateValue<>(25.0, null, | |
328 | + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold")); | |
329 | + | |
330 | + clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue); | |
331 | + clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate); | |
332 | + clearTemperatureCondition.setCondition(Collections.singletonList(clearTemperatureTimeseriesFilter)); | |
333 | + clearTemperatureRule.setCondition(clearTemperatureCondition); | |
334 | + clearTemperatureRule.setAlarmDetails("Current temperature = ${temperature}"); | |
335 | + highTemperature.setClearRule(clearTemperatureRule); | |
336 | + | |
337 | + DeviceProfileAlarm lowHumidity = new DeviceProfileAlarm(); | |
338 | + lowHumidity.setId("lowHumidityAlarmID"); | |
339 | + lowHumidity.setAlarmType("Low Humidity"); | |
340 | + AlarmRule humidityRule = new AlarmRule(); | |
341 | + AlarmCondition humidityCondition = new AlarmCondition(); | |
342 | + humidityCondition.setSpec(new SimpleAlarmConditionSpec()); | |
343 | + | |
344 | + KeyFilter humidityAlarmFlagAttributeFilter = new KeyFilter(); | |
345 | + humidityAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "humidityAlarmFlag")); | |
346 | + humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); | |
347 | + BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate(); | |
348 | + humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); | |
349 | + humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); | |
350 | + humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagAttributePredicate); | |
351 | + | |
352 | + KeyFilter humidityTimeseriesFilter = new KeyFilter(); | |
353 | + humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); | |
354 | + humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); | |
355 | + NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate(); | |
356 | + humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); | |
357 | + FilterPredicateValue<Double> humidityTimeseriesPredicateValue = | |
358 | + new FilterPredicateValue<>(60.0, null, | |
359 | + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold")); | |
360 | + humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue); | |
361 | + humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate); | |
362 | + humidityCondition.setCondition(Arrays.asList(humidityAlarmFlagAttributeFilter, humidityTimeseriesFilter)); | |
363 | + | |
364 | + humidityRule.setCondition(humidityCondition); | |
365 | + humidityRule.setAlarmDetails("Current humidity = ${humidity}"); | |
366 | + lowHumidity.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule))); | |
367 | + | |
368 | + AlarmRule clearHumidityRule = new AlarmRule(); | |
369 | + AlarmCondition clearHumidityCondition = new AlarmCondition(); | |
370 | + clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec()); | |
371 | + | |
372 | + KeyFilter clearHumidityTimeseriesFilter = new KeyFilter(); | |
373 | + clearHumidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); | |
374 | + clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); | |
375 | + NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate(); | |
376 | + clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL); | |
377 | + FilterPredicateValue<Double> clearHumidityTimeseriesPredicateValue = | |
378 | + new FilterPredicateValue<>(60.0, null, | |
379 | + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold")); | |
380 | + | |
381 | + clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue); | |
382 | + clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate); | |
383 | + clearHumidityCondition.setCondition(Collections.singletonList(clearHumidityTimeseriesFilter)); | |
384 | + clearHumidityRule.setCondition(clearHumidityCondition); | |
385 | + clearHumidityRule.setAlarmDetails("Current humidity = ${humidity}"); | |
386 | + lowHumidity.setClearRule(clearHumidityRule); | |
387 | + | |
388 | + deviceProfileData.setAlarms(Arrays.asList(highTemperature, lowHumidity)); | |
389 | + | |
390 | + DeviceProfile savedThermostatDeviceProfile = deviceProfileService.saveDeviceProfile(thermostatDeviceProfile); | |
391 | + | |
392 | + DeviceId t1Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); | |
393 | + DeviceId t2Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); | |
249 | 394 | |
250 | 395 | attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE, |
251 | 396 | Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)), |
252 | 397 | new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -122.1503)), |
253 | - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)), | |
254 | - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)), | |
255 | - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 20)), | |
256 | - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 50)))); | |
398 | + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)), | |
399 | + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("humidityAlarmFlag", true)), | |
400 | + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperatureAlarmThreshold", (long) 20)), | |
401 | + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 50)))); | |
257 | 402 | |
258 | 403 | attributesService.save(demoTenant.getId(), t2Id, DataConstants.SERVER_SCOPE, |
259 | 404 | Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.493801)), |
260 | 405 | new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -121.948769)), |
261 | - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)), | |
262 | - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)), | |
263 | - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 25)), | |
264 | - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 30)))); | |
406 | + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)), | |
407 | + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("humidityAlarmFlag", true)), | |
408 | + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperatureAlarmThreshold", (long) 25)), | |
409 | + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 30)))); | |
265 | 410 | |
266 | 411 | installScripts.loadDashboards(demoTenant.getId(), null); |
267 | 412 | } | ... | ... |
... | ... | @@ -26,11 +26,13 @@ import org.thingsboard.server.common.data.id.CustomerId; |
26 | 26 | import org.thingsboard.server.common.data.id.EntityId; |
27 | 27 | import org.thingsboard.server.common.data.id.RuleChainId; |
28 | 28 | import org.thingsboard.server.common.data.id.TenantId; |
29 | +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; | |
29 | 30 | import org.thingsboard.server.common.data.rule.RuleChain; |
30 | 31 | import org.thingsboard.server.common.data.rule.RuleChainMetaData; |
31 | 32 | import org.thingsboard.server.common.data.widget.WidgetType; |
32 | 33 | import org.thingsboard.server.common.data.widget.WidgetsBundle; |
33 | 34 | import org.thingsboard.server.dao.dashboard.DashboardService; |
35 | +import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; | |
34 | 36 | import org.thingsboard.server.dao.rule.RuleChainService; |
35 | 37 | import org.thingsboard.server.dao.widget.WidgetTypeService; |
36 | 38 | import org.thingsboard.server.dao.widget.WidgetsBundleService; |
... | ... | @@ -40,6 +42,7 @@ import java.nio.file.DirectoryStream; |
40 | 42 | import java.nio.file.Files; |
41 | 43 | import java.nio.file.Path; |
42 | 44 | import java.nio.file.Paths; |
45 | +import java.util.Optional; | |
43 | 46 | |
44 | 47 | import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper; |
45 | 48 | |
... | ... | @@ -61,6 +64,7 @@ public class InstallScripts { |
61 | 64 | public static final String DEMO_DIR = "demo"; |
62 | 65 | public static final String RULE_CHAINS_DIR = "rule_chains"; |
63 | 66 | public static final String WIDGET_BUNDLES_DIR = "widget_bundles"; |
67 | + public static final String OAUTH2_CONFIG_TEMPLATES_DIR = "oauth2_config_templates"; | |
64 | 68 | public static final String DASHBOARDS_DIR = "dashboards"; |
65 | 69 | |
66 | 70 | public static final String JSON_EXT = ".json"; |
... | ... | @@ -80,6 +84,9 @@ public class InstallScripts { |
80 | 84 | @Autowired |
81 | 85 | private WidgetsBundleService widgetsBundleService; |
82 | 86 | |
87 | + @Autowired | |
88 | + private OAuth2ConfigTemplateService oAuth2TemplateService; | |
89 | + | |
83 | 90 | public Path getTenantRuleChainsDir() { |
84 | 91 | return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR); |
85 | 92 | } |
... | ... | @@ -146,12 +153,6 @@ public class InstallScripts { |
146 | 153 | return ruleChain; |
147 | 154 | } |
148 | 155 | |
149 | - | |
150 | - public void createDefaultEdgeRuleChains(TenantId tenantId) { | |
151 | - Path tenantChainsDir = getTenantRuleChainsDir(); | |
152 | - loadRuleChainFromFile(tenantId, tenantChainsDir.resolve("edge_root_rule_chain.json")); | |
153 | - } | |
154 | - | |
155 | 156 | public void loadSystemWidgets() throws Exception { |
156 | 157 | Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR); |
157 | 158 | try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) { |
... | ... | @@ -206,50 +207,42 @@ public class InstallScripts { |
206 | 207 | } |
207 | 208 | } |
208 | 209 | |
209 | - | |
210 | 210 | public void loadDemoRuleChains(TenantId tenantId) throws Exception { |
211 | - Path ruleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, RULE_CHAINS_DIR); | |
212 | 211 | try { |
213 | - JsonNode ruleChainJson = objectMapper.readTree(ruleChainsDir.resolve("thermostat_alarms.json").toFile()); | |
214 | - RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class); | |
215 | - RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class); | |
216 | - ruleChain.setTenantId(tenantId); | |
217 | - ruleChain = ruleChainService.saveRuleChain(ruleChain); | |
218 | - ruleChainMetaData.setRuleChainId(ruleChain.getId()); | |
219 | - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData); | |
220 | - | |
221 | - JsonNode rootChainJson = objectMapper.readTree(ruleChainsDir.resolve("root_rule_chain.json").toFile()); | |
222 | - RuleChain rootChain = objectMapper.treeToValue(rootChainJson.get("ruleChain"), RuleChain.class); | |
223 | - RuleChainMetaData rootChainMetaData = objectMapper.treeToValue(rootChainJson.get("metadata"), RuleChainMetaData.class); | |
224 | - | |
225 | - RuleChainId thermostatsRuleChainId = ruleChain.getId(); | |
226 | - rootChainMetaData.getRuleChainConnections().forEach(connection -> connection.setTargetRuleChainId(thermostatsRuleChainId)); | |
227 | - rootChain.setTenantId(tenantId); | |
228 | - rootChain = ruleChainService.saveRuleChain(rootChain); | |
229 | - rootChainMetaData.setRuleChainId(rootChain.getId()); | |
230 | - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), rootChainMetaData); | |
231 | - | |
232 | - loadRuleChainFromFile(tenantId, ruleChainsDir.resolve("edge_root_rule_chain.json")); | |
212 | + createDefaultRuleChains(tenantId); | |
213 | + createDefaultRuleChain(tenantId, "Thermostat"); | |
214 | + createDefaultEdgeRuleChains(tenantId); | |
233 | 215 | } catch (Exception e) { |
234 | 216 | log.error("Unable to load dashboard from json", e); |
235 | 217 | throw new RuntimeException("Unable to load dashboard from json", e); |
236 | 218 | } |
237 | 219 | } |
238 | 220 | |
239 | - private void loadRuleChainFromFile(TenantId tenantId, Path ruleChainPath) { | |
240 | - try { | |
241 | - JsonNode ruleChainJson = objectMapper.readTree(ruleChainPath.toFile()); | |
242 | - RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class); | |
243 | - RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class); | |
244 | - | |
245 | - ruleChain.setTenantId(tenantId); | |
246 | - ruleChain = ruleChainService.saveRuleChain(ruleChain); | |
221 | + public void createDefaultEdgeRuleChains(TenantId tenantId) throws IOException { | |
222 | + Path tenantChainsDir = getTenantRuleChainsDir(); | |
223 | + createRuleChainFromFile(tenantId, tenantChainsDir.resolve("edge_root_rule_chain.json"), null); | |
224 | + } | |
247 | 225 | |
248 | - ruleChainMetaData.setRuleChainId(ruleChain.getId()); | |
249 | - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData); | |
250 | - } catch (Exception e) { | |
251 | - log.error("Unable to load rule chain from json: [{}]", ruleChainPath.toString()); | |
252 | - throw new RuntimeException("Unable to load rule chain from json", e); | |
226 | + public void createOAuth2Templates() throws Exception { | |
227 | + Path oauth2ConfigTemplatesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, OAUTH2_CONFIG_TEMPLATES_DIR); | |
228 | + try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(oauth2ConfigTemplatesDir, path -> path.toString().endsWith(JSON_EXT))) { | |
229 | + dirStream.forEach( | |
230 | + path -> { | |
231 | + try { | |
232 | + JsonNode oauth2ConfigTemplateJson = objectMapper.readTree(path.toFile()); | |
233 | + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = objectMapper.treeToValue(oauth2ConfigTemplateJson, OAuth2ClientRegistrationTemplate.class); | |
234 | + Optional<OAuth2ClientRegistrationTemplate> existingClientRegistrationTemplate = | |
235 | + oAuth2TemplateService.findClientRegistrationTemplateByProviderId(clientRegistrationTemplate.getProviderId()); | |
236 | + if (existingClientRegistrationTemplate.isPresent()) { | |
237 | + clientRegistrationTemplate.setId(existingClientRegistrationTemplate.get().getId()); | |
238 | + } | |
239 | + oAuth2TemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); | |
240 | + } catch (Exception e) { | |
241 | + log.error("Unable to load oauth2 config templates from json: [{}]", path.toString()); | |
242 | + throw new RuntimeException("Unable to load oauth2 config templates from json", e); | |
243 | + } | |
244 | + } | |
245 | + ); | |
253 | 246 | } |
254 | 247 | } |
255 | 248 | } | ... | ... |
... | ... | @@ -28,6 +28,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService; |
28 | 28 | import org.thingsboard.server.dao.device.DeviceProfileService; |
29 | 29 | import org.thingsboard.server.dao.device.DeviceService; |
30 | 30 | import org.thingsboard.server.dao.tenant.TenantService; |
31 | +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; | |
31 | 32 | import org.thingsboard.server.service.install.sql.SqlDbHelper; |
32 | 33 | |
33 | 34 | import java.nio.charset.Charset; |
... | ... | @@ -96,6 +97,9 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService |
96 | 97 | @Autowired |
97 | 98 | private DeviceProfileService deviceProfileService; |
98 | 99 | |
100 | + @Autowired | |
101 | + private ApiUsageStateService apiUsageStateService; | |
102 | + | |
99 | 103 | |
100 | 104 | @Override |
101 | 105 | public void upgradeDatabase(String fromVersion) throws Exception { |
... | ... | @@ -364,6 +368,24 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService |
364 | 368 | } catch (Exception e) { |
365 | 369 | } |
366 | 370 | |
371 | + try { | |
372 | + conn.createStatement().execute("CREATE TABLE IF NOT EXISTS api_usage_state (" + | |
373 | + " id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY," + | |
374 | + " created_time bigint NOT NULL," + | |
375 | + " tenant_id uuid," + | |
376 | + " entity_type varchar(32)," + | |
377 | + " entity_id uuid," + | |
378 | + " transport varchar(32)," + | |
379 | + " db_storage varchar(32)," + | |
380 | + " re_exec varchar(32)," + | |
381 | + " js_exec varchar(32)," + | |
382 | + " email_exec varchar(32)," + | |
383 | + " sms_exec varchar(32)," + | |
384 | + " CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)\n" + | |
385 | + ");"); | |
386 | + } catch (Exception e) { | |
387 | + } | |
388 | + | |
367 | 389 | schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update_before.sql"); |
368 | 390 | loadSql(schemaUpdateFile, conn); |
369 | 391 | |
... | ... | @@ -379,6 +401,10 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService |
379 | 401 | do { |
380 | 402 | pageData = tenantService.findTenants(pageLink); |
381 | 403 | for (Tenant tenant : pageData.getData()) { |
404 | + try { | |
405 | + apiUsageStateService.createDefaultApiUsageState(tenant.getId()); | |
406 | + } catch (Exception e) { | |
407 | + } | |
382 | 408 | List<EntitySubtype> deviceTypes = deviceService.findDeviceTypesByTenantId(tenant.getId()).get(); |
383 | 409 | try { |
384 | 410 | deviceProfileService.createDefaultDeviceProfile(tenant.getId()); | ... | ... |